@koda-sl/baker-bridge 0.172.0-dev.b570aa3f2 → 0.172.0-dev.f1dc1ec56
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 +51 -67
- package/dist/builder/builder-turn.d.ts +28 -0
- package/dist/builder/builder-turn.d.ts.map +1 -0
- package/dist/builder/builder-turn.js +17 -0
- package/dist/builder/builder-turn.js.map +1 -0
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +4 -3
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +4 -3
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +5 -6
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +91 -177
- package/dist/hono/agent.test.js.map +1 -1
- package/dist/hono/attachments.d.ts.map +1 -1
- package/dist/hono/attachments.js +9 -1
- package/dist/hono/attachments.js.map +1 -1
- package/dist/hono/attachments.test.js +22 -1
- package/dist/hono/attachments.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +16 -28
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +60 -189
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +99 -305
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/flow-input-card-fields.test.js +1 -1
- package/dist/hono/flow-input.d.ts +1 -1
- package/dist/hono/flow-input.js +1 -1
- package/dist/hono/main-sync.d.ts +22 -0
- package/dist/hono/main-sync.d.ts.map +1 -1
- package/dist/hono/main-sync.js +110 -2
- package/dist/hono/main-sync.js.map +1 -1
- package/dist/hono/main-sync.test.js +127 -1
- package/dist/hono/main-sync.test.js.map +1 -1
- package/dist/hono/model-capabilities.d.ts +0 -1
- package/dist/hono/model-capabilities.d.ts.map +1 -1
- package/dist/hono/model-capabilities.js +0 -4
- package/dist/hono/model-capabilities.js.map +1 -1
- package/dist/hono/openrouter-session.d.ts +13 -19
- package/dist/hono/openrouter-session.d.ts.map +1 -1
- package/dist/hono/openrouter-session.js +13 -19
- package/dist/hono/openrouter-session.js.map +1 -1
- package/dist/hono/publish-handoff.d.ts +0 -1
- package/dist/hono/publish-handoff.d.ts.map +1 -1
- package/dist/hono/publish-handoff.js +0 -53
- package/dist/hono/publish-handoff.js.map +1 -1
- package/dist/hono/publish-handoff.test.js +0 -48
- package/dist/hono/publish-handoff.test.js.map +1 -1
- package/dist/hono/relay.d.ts +0 -8
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +0 -2
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +0 -30
- package/dist/hono/relay.test.js.map +1 -1
- package/dist/hono/runtime-files.d.ts +0 -1
- package/dist/hono/runtime-files.d.ts.map +1 -1
- package/dist/hono/runtime-files.js +0 -3
- package/dist/hono/runtime-files.js.map +1 -1
- package/dist/hono/served-model.d.ts +0 -8
- package/dist/hono/served-model.d.ts.map +1 -1
- package/dist/hono/served-model.js +0 -41
- package/dist/hono/served-model.js.map +1 -1
- package/dist/hono/served-model.test.js +1 -19
- package/dist/hono/served-model.test.js.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +0 -2
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/types.d.ts +7 -12
- package/dist/hono/types.d.ts.map +1 -1
- package/dist/review/digest.d.ts +0 -13
- 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/dist/review/structured-output-gate.d.ts +10 -0
- package/dist/review/structured-output-gate.d.ts.map +1 -1
- package/dist/review/structured-output-gate.js +15 -5
- package/dist/review/structured-output-gate.js.map +1 -1
- package/package.json +1 -2
- package/dist/hono/subagent-route.d.ts +0 -64
- package/dist/hono/subagent-route.d.ts.map +0 -1
- package/dist/hono/subagent-route.js +0 -147
- package/dist/hono/subagent-route.js.map +0 -1
- package/dist/hono/subagent-route.test.d.ts +0 -2
- package/dist/hono/subagent-route.test.d.ts.map +0 -1
- package/dist/hono/subagent-route.test.js +0 -68
- package/dist/hono/subagent-route.test.js.map +0 -1
- package/dist/hono/tool-result-ledger.d.ts +0 -32
- package/dist/hono/tool-result-ledger.d.ts.map +0 -1
- package/dist/hono/tool-result-ledger.js +0 -67
- package/dist/hono/tool-result-ledger.js.map +0 -1
- package/dist/hono/tool-result-ledger.test.d.ts +0 -2
- package/dist/hono/tool-result-ledger.test.d.ts.map +0 -1
- package/dist/hono/tool-result-ledger.test.js +0 -61
- package/dist/hono/tool-result-ledger.test.js.map +0 -1
- package/dist/hono/turn-route.d.ts +0 -84
- package/dist/hono/turn-route.d.ts.map +0 -1
- package/dist/hono/turn-route.js +0 -121
- package/dist/hono/turn-route.js.map +0 -1
- package/dist/hono/turn-route.test.d.ts +0 -2
- package/dist/hono/turn-route.test.d.ts.map +0 -1
- package/dist/hono/turn-route.test.js +0 -134
- package/dist/hono/turn-route.test.js.map +0 -1
- package/dist/hono/workspace-instructions.d.ts +0 -43
- package/dist/hono/workspace-instructions.d.ts.map +0 -1
- package/dist/hono/workspace-instructions.js +0 -68
- package/dist/hono/workspace-instructions.js.map +0 -1
- package/dist/hono/workspace-instructions.test.d.ts +0 -2
- package/dist/hono/workspace-instructions.test.d.ts.map +0 -1
- package/dist/hono/workspace-instructions.test.js +0 -68
- package/dist/hono/workspace-instructions.test.js.map +0 -1
package/dist/hono/bridge-job.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { BUILDER_DISALLOWED_TOOLS, BUILDER_SYSTEM_PROMPT, builderOutputHooks, } from "../builder/builder-turn.js";
|
|
4
5
|
import { capDigestSections } from "../review/digest-bundle.js";
|
|
5
6
|
import { buildReviewDigest, digestPaths } from "../review/evidence.js";
|
|
6
7
|
import { renderEvidenceBlock } from "../review/evidence-block.js";
|
|
@@ -23,24 +24,20 @@ import { composioScopesToInvalidate, failedCustomServers, reportMcpInvalid, serv
|
|
|
23
24
|
import { agentModelOverride, autoCompactWindowForModel, betasForModel, contextWindowForModel, } from "./model-capabilities.js";
|
|
24
25
|
import { agentShellPrefixEnv, killFirstWrapper } from "./oom-priority.js";
|
|
25
26
|
import { anthropicCustomHeaders, runtimeOpenRouterTags } from "./openrouter-attribution.js";
|
|
26
|
-
import { buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV,
|
|
27
|
+
import { BEDROCK_SERVABLE_CAPABILITIES, buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, } from "./openrouter-session.js";
|
|
27
28
|
import { credentialReminder, currentReferenceReminder, kickoffBriefReminder } from "./prompt-context.js";
|
|
28
29
|
import { publishLanguageReminder, publishRunReminder, publishStandingReminder } from "./publish-handoff.js";
|
|
29
30
|
import { bridgeRuntimeFiles } from "./runtime-files.js";
|
|
30
|
-
import { alignServedModels
|
|
31
|
+
import { alignServedModels } from "./served-model.js";
|
|
31
32
|
import { applyCurrentSenderAttribution, buildCancelBoundary, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, interruptedResumeAttempt, isCliResumeRefusal, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
|
|
32
33
|
import { readSessionSkills } from "./session-skills.js";
|
|
33
34
|
import { ownQueryUsage, readUsageBaseline } from "./session-usage-baseline.js";
|
|
34
35
|
import { skillLanguageHooks } from "./skill-language.js";
|
|
35
36
|
import { BridgeSlashCommands, leadingSkillCommand, verbatimSlashCommand } from "./slash-commands.js";
|
|
36
|
-
import { readAgentDefinitions, subagentHooks } from "./subagent-route.js";
|
|
37
37
|
import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TAG_INPUT_TOOL_NAME, unknownPrefillMessage, } from "./tag-input.js";
|
|
38
38
|
import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
|
|
39
|
-
import { ToolResultLedger } from "./tool-result-ledger.js";
|
|
40
39
|
import { recordTurnEvent } from "./turn-events.js";
|
|
41
40
|
import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
|
|
42
|
-
import { askTurnSignals, needsSignals, routeTurn, } from "./turn-route.js";
|
|
43
|
-
import { prepareSharedInstructions } from "./workspace-instructions.js";
|
|
44
41
|
import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
|
|
45
42
|
export function parsePlanDecision(value) {
|
|
46
43
|
if (!value || typeof value !== "object") {
|
|
@@ -221,13 +218,6 @@ const BASE_DISALLOWED_TOOLS = [
|
|
|
221
218
|
// not asked to fund a research fleet. Ordinary fan-out is unaffected:
|
|
222
219
|
// `Task` stays, so the scaffold's subagents still run.
|
|
223
220
|
"Workflow",
|
|
224
|
-
// Developer tools: design-system sync, code-review findings, Jupyter edits.
|
|
225
|
-
// None was called once in 358 production chats (Oct 2026), and each
|
|
226
|
-
// definition rides every request whenever the CLI does not defer it —
|
|
227
|
-
// DesignSync's alone is ~9k characters.
|
|
228
|
-
"DesignSync",
|
|
229
|
-
"ReportFindings",
|
|
230
|
-
"NotebookEdit",
|
|
231
221
|
// NOT listed: `SendMessage`. It is how the model follows up with a
|
|
232
222
|
// subagent it already ran, and the CLI tells it to on every Agent result.
|
|
233
223
|
// The continued subagent runs in the background, but it announces itself
|
|
@@ -245,7 +235,12 @@ function disallowedToolsFor(job) {
|
|
|
245
235
|
// calls go through `reviewBashPolicy` — because each fails differently: a
|
|
246
236
|
// route could be mis-scoped, a policy could miss a spelling, and a tool the
|
|
247
237
|
// model cannot see cannot be reached at all.
|
|
248
|
-
|
|
238
|
+
if (job.review)
|
|
239
|
+
return [...BASE_DISALLOWED_TOOLS, ...REVIEW_DISALLOWED_TOOLS];
|
|
240
|
+
// A task has nobody to answer a question: it decides, or returns the question.
|
|
241
|
+
if (job.builder)
|
|
242
|
+
return [...BASE_DISALLOWED_TOOLS, ...BUILDER_DISALLOWED_TOOLS];
|
|
243
|
+
return [...BASE_DISALLOWED_TOOLS];
|
|
249
244
|
}
|
|
250
245
|
/**
|
|
251
246
|
* A review is pinned to one model in the backend's constants, not to whatever
|
|
@@ -256,7 +251,7 @@ function disallowedToolsFor(job) {
|
|
|
256
251
|
function modelForJob(job, sessionModel) {
|
|
257
252
|
return job.review?.model ?? sessionModel;
|
|
258
253
|
}
|
|
259
|
-
export const REVIEW_DISALLOWED_TOOLS = ["Edit", "Write", "MultiEdit", "ExitPlanMode", "Skill"];
|
|
254
|
+
export const REVIEW_DISALLOWED_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit", "ExitPlanMode", "Skill"];
|
|
260
255
|
/**
|
|
261
256
|
* The findings contract, enforced by the SDK rather than parsed back out of
|
|
262
257
|
* prose. `structured_output` on the result message is what reaches Convex, and
|
|
@@ -267,10 +262,29 @@ export function reviewQueryOptions(job) {
|
|
|
267
262
|
? { outputFormat: { type: "json_schema", schema: job.review.outputSchema }, hooks: reviewOutputHooks() }
|
|
268
263
|
: {};
|
|
269
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* A task's result contract, enforced by the SDK like the review's findings, under the task's own system
|
|
267
|
+
* prompt (`../builder/builder-turn.ts`). An ordinary turn keeps the empty one the SDK sends by default.
|
|
268
|
+
*/
|
|
269
|
+
export function builderQueryOptions(job) {
|
|
270
|
+
return job.builder
|
|
271
|
+
? {
|
|
272
|
+
systemPrompt: BUILDER_SYSTEM_PROMPT,
|
|
273
|
+
outputFormat: { type: "json_schema", schema: job.builder.outputSchema },
|
|
274
|
+
hooks: builderOutputHooks(),
|
|
275
|
+
}
|
|
276
|
+
: {};
|
|
277
|
+
}
|
|
278
|
+
/** What shapes a turn's answer: a task's contract and prompt, the review's findings, or nothing. */
|
|
279
|
+
function answerOptions(job) {
|
|
280
|
+
return job.builder ? builderQueryOptions(job) : reviewQueryOptions(job);
|
|
281
|
+
}
|
|
270
282
|
/** What the agent's model calls are for, as OpenRouter is told (`openrouter-attribution.ts`). */
|
|
271
283
|
export function agentFeature(job) {
|
|
272
284
|
if (job.review)
|
|
273
285
|
return "chatReview.session";
|
|
286
|
+
if (job.builder)
|
|
287
|
+
return "builder.task";
|
|
274
288
|
return job.planMode ? "agent.plan" : "agent.turn";
|
|
275
289
|
}
|
|
276
290
|
/**
|
|
@@ -443,19 +457,6 @@ function createBridgeJobState() {
|
|
|
443
457
|
detachedSession: false,
|
|
444
458
|
modelUsageTotals: new Map(),
|
|
445
459
|
queryModelUsage: new Map(),
|
|
446
|
-
toolResults: new ToolResultLedger(),
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
/** What a finished turn reports about its spend: per-model usage and per-tool result sizes. */
|
|
450
|
-
function turnUsage(state) {
|
|
451
|
-
const toolResults = state.toolResults.summary();
|
|
452
|
-
return {
|
|
453
|
-
modelUsage: turnModelUsageList(state.modelUsageTotals),
|
|
454
|
-
...(toolResults.length > 0 ? { toolResults } : {}),
|
|
455
|
-
// Left off when nothing was decided: Jev never answered and the model is the Runtime's.
|
|
456
|
-
...(state.turnRoute && !(state.turnRoute.reason === "unjudged" && state.turnRoute.modelReason === "fixed")
|
|
457
|
-
? { turnRoute: state.turnRoute }
|
|
458
|
-
: {}),
|
|
459
460
|
};
|
|
460
461
|
}
|
|
461
462
|
function errorMessage(error) {
|
|
@@ -532,79 +533,6 @@ function hookSettingsForJob(job) {
|
|
|
532
533
|
}
|
|
533
534
|
return {};
|
|
534
535
|
}
|
|
535
|
-
/**
|
|
536
|
-
* Which instruction files a job loads as memory. A Session review loads none:
|
|
537
|
-
* its brief has it read the rulebook as the Session had it (`git show
|
|
538
|
-
* <sha>:CLAUDE.md`), since Publish may already have moved the working tree past
|
|
539
|
-
* it, and the auto-loaded copy is ~24k tokens of the marketer's instructions
|
|
540
|
-
* written cold and re-read on every review request. The client's agent loads
|
|
541
|
-
* the workspace's `CLAUDE.md` itself only when it could not be split into the
|
|
542
|
-
* shared system prompt (`prepareSharedInstructions`).
|
|
543
|
-
*/
|
|
544
|
-
/** A Session review reads no CLAUDE.md, so it has nothing to share. */
|
|
545
|
-
async function sharedInstructionsFor(job) {
|
|
546
|
-
if (job.review) {
|
|
547
|
-
return null;
|
|
548
|
-
}
|
|
549
|
-
return await prepareSharedInstructions({
|
|
550
|
-
projectRoot: bridgeRuntimeFiles.projectRoot(),
|
|
551
|
-
contextDir: bridgeRuntimeFiles.instructionsDir(),
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
function sharedInstructionOptions(sharedInstructions) {
|
|
555
|
-
if (!sharedInstructions) {
|
|
556
|
-
return {};
|
|
557
|
-
}
|
|
558
|
-
return {
|
|
559
|
-
systemPrompt: {
|
|
560
|
-
type: "preset",
|
|
561
|
-
preset: "claude_code",
|
|
562
|
-
append: sharedInstructions.shared,
|
|
563
|
-
// Recorded once per conversation, so a scaffold sync mid-chat cannot
|
|
564
|
-
// move the cached prefix under it; it lands at the next compaction.
|
|
565
|
-
snapshot: true,
|
|
566
|
-
},
|
|
567
|
-
additionalDirectories: [bridgeRuntimeFiles.instructionsDir()],
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
function bridgeJudge(feature) {
|
|
571
|
-
return jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature }));
|
|
572
|
-
}
|
|
573
|
-
function judgesOf(dependencies) {
|
|
574
|
-
return {
|
|
575
|
-
judge: dependencies.judge ?? bridgeJudge("bridge.toolCallGuard"),
|
|
576
|
-
routeJudge: dependencies.routeJudge ?? bridgeJudge("bridge.turnRoute"),
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
function sharedInstructionEnv(sharedInstructions) {
|
|
580
|
-
return sharedInstructions ? { CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1" } : {};
|
|
581
|
-
}
|
|
582
|
-
/** Appends `extra`'s hook matchers after the ones `options` already carries. */
|
|
583
|
-
function withSubagentHooks(options, extra) {
|
|
584
|
-
const hooks = { ...(options.hooks ?? {}) };
|
|
585
|
-
for (const [event, matchers] of Object.entries(extra)) {
|
|
586
|
-
hooks[event] = [...(hooks[event] ?? []), ...matchers];
|
|
587
|
-
}
|
|
588
|
-
return { ...options, hooks };
|
|
589
|
-
}
|
|
590
|
-
function recordSubagentRoute(state, agentType, route) {
|
|
591
|
-
if (state.turnRoute) {
|
|
592
|
-
state.turnRoute.subagents = [...(state.turnRoute.subagents ?? []), { agentType, ...route }];
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function turnEffortOf(state) {
|
|
596
|
-
return state.turnRoute?.effort ?? "medium";
|
|
597
|
-
}
|
|
598
|
-
/** The agent's last message in the chat, empty on its first turn. */
|
|
599
|
-
function previousReplyOf(job) {
|
|
600
|
-
return (job.history?.entries ?? []).filter((entry) => entry.role === "assistant").at(-1)?.text ?? "";
|
|
601
|
-
}
|
|
602
|
-
function instructionSettingsForJob(job, sharedInstructions) {
|
|
603
|
-
if (job.review) {
|
|
604
|
-
return { claudeMdExcludes: ["**/CLAUDE.md"] };
|
|
605
|
-
}
|
|
606
|
-
return sharedInstructions ? { claudeMdExcludes: [join(bridgeRuntimeFiles.projectRoot(), "CLAUDE.md")] } : {};
|
|
607
|
-
}
|
|
608
536
|
// Carry a Runtime-wide model override down to the fan-out workers.
|
|
609
537
|
//
|
|
610
538
|
// The shared subagents pin themselves to Sonnet 5.5 in their own frontmatter (see
|
|
@@ -1264,10 +1192,9 @@ export class BridgeJobExecutor {
|
|
|
1264
1192
|
settleWorkspace;
|
|
1265
1193
|
catchUpWithMain;
|
|
1266
1194
|
holdsNoWork;
|
|
1195
|
+
settleAfterPublish;
|
|
1267
1196
|
keepCliInStep;
|
|
1268
1197
|
judge;
|
|
1269
|
-
routeJudge;
|
|
1270
|
-
routeModels;
|
|
1271
1198
|
constructor(dependencies) {
|
|
1272
1199
|
this.sdk = dependencies.sdk;
|
|
1273
1200
|
this.relay = dependencies.relay;
|
|
@@ -1290,11 +1217,13 @@ export class BridgeJobExecutor {
|
|
|
1290
1217
|
this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
|
|
1291
1218
|
this.catchUpWithMain = dependencies.catchUpWithMain;
|
|
1292
1219
|
this.holdsNoWork = dependencies.holdsNoWork;
|
|
1220
|
+
this.settleAfterPublish = dependencies.settleAfterPublish;
|
|
1293
1221
|
this.keepCliInStep = dependencies.keepCliInStep;
|
|
1294
1222
|
this.readFlowTree =
|
|
1295
1223
|
dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
|
|
1296
|
-
|
|
1297
|
-
|
|
1224
|
+
this.judge =
|
|
1225
|
+
dependencies.judge ??
|
|
1226
|
+
jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature: "bridge.toolCallGuard" }));
|
|
1298
1227
|
}
|
|
1299
1228
|
async execute(job, controls) {
|
|
1300
1229
|
try {
|
|
@@ -1306,58 +1235,6 @@ export class BridgeJobExecutor {
|
|
|
1306
1235
|
await this.inFlightTurns.clear(job.threadId, job.turnId).catch(() => undefined);
|
|
1307
1236
|
}
|
|
1308
1237
|
}
|
|
1309
|
-
/**
|
|
1310
|
-
* What Jev reads in this turn, asked while the boundary loads (~0.3 s). Null
|
|
1311
|
-
* for a Session review, which keeps its own model and effort, and for a turn
|
|
1312
|
-
* that decides without it. Never throws: an unanswered judge is no signal.
|
|
1313
|
-
*/
|
|
1314
|
-
async askSignals(job, request) {
|
|
1315
|
-
if (job.review || !needsSignals(this.turnFacts(job, request))) {
|
|
1316
|
-
return null;
|
|
1317
|
-
}
|
|
1318
|
-
return await askTurnSignals({
|
|
1319
|
-
message: job.prompt,
|
|
1320
|
-
previousReply: previousReplyOf(job),
|
|
1321
|
-
judge: this.routeJudge,
|
|
1322
|
-
}).catch(() => null);
|
|
1323
|
-
}
|
|
1324
|
-
/** This turn's model and effort (see `turn-route.ts`); undefined for a Session review. */
|
|
1325
|
-
async routeFor(job, request, attempt, signals) {
|
|
1326
|
-
if (job.review) {
|
|
1327
|
-
return undefined;
|
|
1328
|
-
}
|
|
1329
|
-
const resumed = attempt.resumeOptions.resume;
|
|
1330
|
-
const sessionModel = resumed ? await readConversationModel(resumed, claudeConfigDir()) : undefined;
|
|
1331
|
-
return routeTurn({
|
|
1332
|
-
...this.turnFacts(job, request),
|
|
1333
|
-
signals: await signals,
|
|
1334
|
-
defaultModel: this.model,
|
|
1335
|
-
routeModels: this.routeModels,
|
|
1336
|
-
...(sessionModel ? { sessionModel } : {}),
|
|
1337
|
-
startedChat: previousReplyOf(job) === "",
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
/** What this turn's subagents are given (see `subagent-route.ts`); nothing for a Session review. */
|
|
1341
|
-
async subagentHooksFor(job, state, sharedInstructions, model) {
|
|
1342
|
-
if (job.review) {
|
|
1343
|
-
return {};
|
|
1344
|
-
}
|
|
1345
|
-
return subagentHooks({
|
|
1346
|
-
instructions: sharedInstructions?.shared ?? null,
|
|
1347
|
-
definitions: await readAgentDefinitions(bridgeRuntimeFiles.projectRoot()),
|
|
1348
|
-
judge: this.routeModels ? this.routeJudge : null,
|
|
1349
|
-
parentModel: model,
|
|
1350
|
-
onRoute: (agentType, route) => recordSubagentRoute(state, agentType, route),
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
turnFacts(job, request) {
|
|
1354
|
-
return {
|
|
1355
|
-
planMode: job.planMode === true,
|
|
1356
|
-
leadingCommand: request.leadingCommand,
|
|
1357
|
-
unattended: job.unattended === true,
|
|
1358
|
-
hasAttachments: (job.attachments?.length ?? 0) > 0,
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
1238
|
async executeTurn(job, controls) {
|
|
1362
1239
|
const state = createBridgeJobState();
|
|
1363
1240
|
let result = { costUsd: 0, attemptedRehydration: false };
|
|
@@ -1369,8 +1246,6 @@ export class BridgeJobExecutor {
|
|
|
1369
1246
|
if (!request?.prompt || !controls.isActive()) {
|
|
1370
1247
|
return { kind: "cancelled", costUsd: 0, model: state.model };
|
|
1371
1248
|
}
|
|
1372
|
-
// Asked while the boundary loads, so the answer (~0.3 s) costs the turn no time.
|
|
1373
|
-
const signals = this.askSignals(job, request);
|
|
1374
1249
|
const loaded = await this.loadTrustedBoundary(job, controls);
|
|
1375
1250
|
if (loaded?.kind !== "clean") {
|
|
1376
1251
|
request = await this.withContinuedAttachments(job, request);
|
|
@@ -1389,7 +1264,6 @@ export class BridgeJobExecutor {
|
|
|
1389
1264
|
state.detachedSession = attempt.detachedSession === true;
|
|
1390
1265
|
result.attemptedRehydration = attempt.attemptedRehydration;
|
|
1391
1266
|
this.recordAttemptContextSource(job, attempt);
|
|
1392
|
-
state.turnRoute = await this.routeFor(job, request, attempt, signals);
|
|
1393
1267
|
result = await this.runAttempt(job, controls, state, attempt);
|
|
1394
1268
|
return await this.finishSuccessfulAttempt(job, controls, state, result.costUsd);
|
|
1395
1269
|
}
|
|
@@ -1398,8 +1272,6 @@ export class BridgeJobExecutor {
|
|
|
1398
1272
|
if (repaired && controls.isActive()) {
|
|
1399
1273
|
const repairState = createBridgeJobState();
|
|
1400
1274
|
repairState.detachedSession = repaired.detachedSession === true;
|
|
1401
|
-
// The same turn, so the same model and level: a change would re-write the cache.
|
|
1402
|
-
repairState.turnRoute = state.turnRoute;
|
|
1403
1275
|
let repairResult = { costUsd: 0, attemptedRehydration: repaired.attemptedRehydration };
|
|
1404
1276
|
try {
|
|
1405
1277
|
repairResult = await this.runAttempt(job, controls, repairState, repaired);
|
|
@@ -1549,7 +1421,7 @@ export class BridgeJobExecutor {
|
|
|
1549
1421
|
return `${block}\n\n${job.prompt}`;
|
|
1550
1422
|
}
|
|
1551
1423
|
async runAttempt(job, controls, state, attempt) {
|
|
1552
|
-
state.model =
|
|
1424
|
+
state.model = this.model;
|
|
1553
1425
|
// A stalled stream closes the wedged query and resumes the CLI session in
|
|
1554
1426
|
// place — the turn survives a hung upstream request instead of dying at the
|
|
1555
1427
|
// 30-minute session watchdog with all mid-turn work stranded.
|
|
@@ -1557,7 +1429,7 @@ export class BridgeJobExecutor {
|
|
|
1557
1429
|
let current = attempt;
|
|
1558
1430
|
for (let stallRetries = 0;;) {
|
|
1559
1431
|
try {
|
|
1560
|
-
costUsd += await this.runQueryOnce(job, controls, state, current,
|
|
1432
|
+
costUsd += await this.runQueryOnce(job, controls, state, current, this.model);
|
|
1561
1433
|
break;
|
|
1562
1434
|
}
|
|
1563
1435
|
catch (error) {
|
|
@@ -1602,22 +1474,22 @@ export class BridgeJobExecutor {
|
|
|
1602
1474
|
return { costUsd, attemptedRehydration: attempt.attemptedRehydration };
|
|
1603
1475
|
}
|
|
1604
1476
|
async finishSuccessfulAttempt(job, controls, state, costUsd) {
|
|
1605
|
-
const
|
|
1477
|
+
const modelUsage = turnModelUsageList(state.modelUsageTotals);
|
|
1606
1478
|
if (!controls.isActive()) {
|
|
1607
1479
|
await this.clearIfDirty(job, controls, state, "abort");
|
|
1608
|
-
return { kind: "cancelled", costUsd, model: state.model,
|
|
1480
|
+
return { kind: "cancelled", costUsd, model: state.model, modelUsage };
|
|
1609
1481
|
}
|
|
1610
1482
|
// No boundary is handed back, so the refused exchange never becomes part of
|
|
1611
1483
|
// the conversation the next message resumes.
|
|
1612
1484
|
if (state.refusedWithoutFallback) {
|
|
1613
|
-
return { kind: "errored", costUsd, model: state.model,
|
|
1485
|
+
return { kind: "errored", costUsd, model: state.model, modelUsage, errors: [MODEL_REFUSAL_MESSAGE] };
|
|
1614
1486
|
}
|
|
1615
1487
|
if (!state.postedAssistantEvent && state.turnText.length > 0) {
|
|
1616
1488
|
await this.postSyntheticAssistantEvent(job, state);
|
|
1617
1489
|
}
|
|
1618
1490
|
if (!controls.isActive()) {
|
|
1619
1491
|
await this.clearIfDirty(job, controls, state, "abort");
|
|
1620
|
-
return { kind: "cancelled", costUsd, model: state.model,
|
|
1492
|
+
return { kind: "cancelled", costUsd, model: state.model, modelUsage };
|
|
1621
1493
|
}
|
|
1622
1494
|
// A detached turn (publish) ran in a session of its own. Handing that session
|
|
1623
1495
|
// back as the thread's boundary would make the next chat message resume a
|
|
@@ -1630,11 +1502,17 @@ export class BridgeJobExecutor {
|
|
|
1630
1502
|
// `closeRunThatChangedNothing`), and only the Runtime can vouch for its files.
|
|
1631
1503
|
// Never after a publish turn: that one settles the chat itself.
|
|
1632
1504
|
const workspaceUnchanged = !state.detachedSession && !job.review && this.holdsNoWork !== undefined && (await this.holdsNoWork());
|
|
1505
|
+
// A publish turn's push is settled before the chat goes on: its next turn starts from what went live.
|
|
1506
|
+
if (state.detachedSession) {
|
|
1507
|
+
await this.settleAfterPublish?.().catch((error) => {
|
|
1508
|
+
console.error(`[BridgeJob] could not settle the publish for ${job.threadId}`, error);
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1633
1511
|
return {
|
|
1634
1512
|
kind: "completed",
|
|
1635
1513
|
costUsd,
|
|
1636
1514
|
model: state.model,
|
|
1637
|
-
|
|
1515
|
+
modelUsage,
|
|
1638
1516
|
...(workspaceUnchanged ? { workspaceUnchanged: true } : {}),
|
|
1639
1517
|
cleanBoundary,
|
|
1640
1518
|
...(state.structuredOutput === undefined ? {} : { structuredOutput: state.structuredOutput }),
|
|
@@ -1642,7 +1520,7 @@ export class BridgeJobExecutor {
|
|
|
1642
1520
|
}
|
|
1643
1521
|
errorOutcome(job, controls, state, result, error) {
|
|
1644
1522
|
const model = state.model;
|
|
1645
|
-
const
|
|
1523
|
+
const modelUsage = turnModelUsageList(state.modelUsageTotals);
|
|
1646
1524
|
if (result.attemptedRehydration) {
|
|
1647
1525
|
void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydration_failed", {
|
|
1648
1526
|
historyEntryCount: job.history?.entries.length ?? 0,
|
|
@@ -1652,7 +1530,7 @@ export class BridgeJobExecutor {
|
|
|
1652
1530
|
});
|
|
1653
1531
|
}
|
|
1654
1532
|
if (!controls.isActive()) {
|
|
1655
|
-
return { kind: "cancelled", costUsd: result.costUsd, model,
|
|
1533
|
+
return { kind: "cancelled", costUsd: result.costUsd, model, modelUsage };
|
|
1656
1534
|
}
|
|
1657
1535
|
const shown = userFacingTurnError(error);
|
|
1658
1536
|
if (shown !== errorMessage(error)) {
|
|
@@ -1662,7 +1540,7 @@ export class BridgeJobExecutor {
|
|
|
1662
1540
|
kind: "errored",
|
|
1663
1541
|
costUsd: result.costUsd,
|
|
1664
1542
|
model,
|
|
1665
|
-
|
|
1543
|
+
modelUsage,
|
|
1666
1544
|
errors: [shown],
|
|
1667
1545
|
};
|
|
1668
1546
|
}
|
|
@@ -1937,7 +1815,6 @@ export class BridgeJobExecutor {
|
|
|
1937
1815
|
await this.alignResumedTranscript(attempt);
|
|
1938
1816
|
const mcpServers = await this.resolveMcpServers(job);
|
|
1939
1817
|
const skills = await readSessionSkills(bridgeRuntimeFiles.projectRoot());
|
|
1940
|
-
const sharedInstructions = await sharedInstructionsFor(job);
|
|
1941
1818
|
const autoCompactWindow = autoCompactWindowForModel(model);
|
|
1942
1819
|
const betas = betasForModel(model);
|
|
1943
1820
|
// No `outputStyle`: the built-in `Concise` style was pinned here against Opus 5's
|
|
@@ -1973,7 +1850,6 @@ export class BridgeJobExecutor {
|
|
|
1973
1850
|
}
|
|
1974
1851
|
: {}),
|
|
1975
1852
|
...hookSettingsForJob(job),
|
|
1976
|
-
...instructionSettingsForJob(job, sharedInstructions),
|
|
1977
1853
|
};
|
|
1978
1854
|
const agentTags = runtimeOpenRouterTags(process.env, {
|
|
1979
1855
|
service: "agent",
|
|
@@ -1993,17 +1869,15 @@ export class BridgeJobExecutor {
|
|
|
1993
1869
|
// more per level than Opus 5, so compare `low` on real Sessions before
|
|
1994
1870
|
// treating this as tuned.
|
|
1995
1871
|
//
|
|
1996
|
-
//
|
|
1997
|
-
//
|
|
1998
|
-
//
|
|
1999
|
-
//
|
|
2000
|
-
effort
|
|
1872
|
+
// MUST stay a single constant. The resolved effort is rendered into the
|
|
1873
|
+
// prompt, so changing it between requests invalidates every cache
|
|
1874
|
+
// breakpoint above it — on a session carrying hundreds of thousands of
|
|
1875
|
+
// tokens that re-write costs far more than any per-turn tuning saves.
|
|
1876
|
+
// Vary it per message only once the route accepts per-message effort
|
|
1877
|
+
// (see `BEDROCK_SERVABLE_CAPABILITIES`); prose is no substitute.
|
|
1878
|
+
effort: "medium",
|
|
2001
1879
|
cwd: process.cwd(),
|
|
2002
1880
|
settingSources: ["project"],
|
|
2003
|
-
// The scaffold's CLAUDE.md, minus its `@COMPANY.md` import, as the system
|
|
2004
|
-
// prompt, so every chat reads one cached copy instead of writing its own;
|
|
2005
|
-
// the import loads from `instructionsDir` (see workspace-instructions.ts).
|
|
2006
|
-
...sharedInstructionOptions(sharedInstructions),
|
|
2007
1881
|
// `baker_ui` hosts in-process interactive dashboard tools (secure tag
|
|
2008
1882
|
// form). Always present; HTTP (Composio) servers merge alongside it.
|
|
2009
1883
|
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
|
|
@@ -2016,7 +1890,7 @@ export class BridgeJobExecutor {
|
|
|
2016
1890
|
// the workspace could not be read; omit rather than allowlist nothing.
|
|
2017
1891
|
...(skills ? { skills } : {}),
|
|
2018
1892
|
disallowedTools: disallowedToolsFor(job),
|
|
2019
|
-
...
|
|
1893
|
+
...withCredentialScrub(answerOptions(job), job),
|
|
2020
1894
|
// Never omitted: since SDK 0.3.286 an omitted mode starts a third-party-provider
|
|
2021
1895
|
// session (OpenRouter) in auto mode, where a classifier rather than `canUseTool`
|
|
2022
1896
|
// decides tool calls.
|
|
@@ -2055,8 +1929,6 @@ export class BridgeJobExecutor {
|
|
|
2055
1929
|
// they are never TTY-gated, so every help read the agent does inside
|
|
2056
1930
|
// the sandbox pays ~5-10% of its tokens on colour codes it can't see.
|
|
2057
1931
|
NO_COLOR: "1",
|
|
2058
|
-
// Loads the `CLAUDE.md` in `instructionsDir`, which imports COMPANY.md.
|
|
2059
|
-
...sharedInstructionEnv(sharedInstructions),
|
|
2060
1932
|
// Pin the conversation to one OpenRouter provider instance so the
|
|
2061
1933
|
// instance-scoped prompt cache is actually re-read across the turn's
|
|
2062
1934
|
// many tool-call requests. Merged into every model request body by the
|
|
@@ -2068,8 +1940,8 @@ export class BridgeJobExecutor {
|
|
|
2068
1940
|
ANTHROPIC_CUSTOM_HEADERS: [process.env.ANTHROPIC_CUSTOM_HEADERS, anthropicCustomHeaders(agentTags.headers)]
|
|
2069
1941
|
.filter(Boolean)
|
|
2070
1942
|
.join("\n"),
|
|
2071
|
-
//
|
|
2072
|
-
[MODEL_CAPABILITIES_ENV]:
|
|
1943
|
+
// Without it no Opus 5.5 request can reach Bedrock; see openrouter-session.ts.
|
|
1944
|
+
[MODEL_CAPABILITIES_ENV]: BEDROCK_SERVABLE_CAPABILITIES,
|
|
2073
1945
|
// The CLI names each session with a second, structured-output request on the
|
|
2074
1946
|
// turn's model. OpenRouter's Bedrock cannot serve that, so it was billed to
|
|
2075
1947
|
// OpenRouter credits on Azure or Vertex, and nothing in Baker reads the name.
|
|
@@ -2151,7 +2023,6 @@ export class BridgeJobExecutor {
|
|
|
2151
2023
|
for await (const msg of query) {
|
|
2152
2024
|
stallDetector.observeMessage(msg);
|
|
2153
2025
|
turnEnd.observeMessage(msg);
|
|
2154
|
-
state.toolResults.observe(msg);
|
|
2155
2026
|
controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
|
|
2156
2027
|
try {
|
|
2157
2028
|
costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);
|