@koda-sl/baker-bridge 0.168.0-dev.d78500250 → 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 +27 -18
- 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 +17 -11
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +97 -49
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +67 -7
- 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 +3 -0
- package/dist/hono/openrouter-session.d.ts.map +1 -1
- package/dist/hono/openrouter-session.js +3 -0
- package/dist/hono/openrouter-session.js.map +1 -1
- package/dist/hono/relay.d.ts +4 -4
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +1 -1
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +10 -4
- package/dist/hono/relay.test.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 +2 -0
- package/dist/hono/tool-result-ledger.d.ts.map +1 -1
- package/dist/hono/tool-result-ledger.js +12 -8
- package/dist/hono/tool-result-ledger.js.map +1 -1
- 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/package.json +1 -1
- package/dist/hono/effort-route.d.ts +0 -43
- package/dist/hono/effort-route.d.ts.map +0 -1
- package/dist/hono/effort-route.js +0 -36
- package/dist/hono/effort-route.js.map +0 -1
- package/dist/hono/effort-route.test.d.ts +0 -2
- package/dist/hono/effort-route.test.d.ts.map +0 -1
- package/dist/hono/effort-route.test.js +0 -54
- package/dist/hono/effort-route.test.js.map +0 -1
package/dist/hono/bridge-job.js
CHANGED
|
@@ -15,7 +15,6 @@ import { CONNECTION_INPUT_TOOL_NAME, CONNECTION_PLATFORMS, connectedButNotLoaded
|
|
|
15
15
|
import { messageContextTokens, pickContextWindow, streamEventContextUsage, turnContextUsage, } from "./context-usage.js";
|
|
16
16
|
import { CREDENTIAL_INPUT_TOOL_NAME, normalizeCredentialInputToolInput, } from "./credential-input.js";
|
|
17
17
|
import { credentialScrubHooks } from "./credential-scrub.js";
|
|
18
|
-
import { DEFAULT_TURN_EFFORT, decideTurnEffort } from "./effort-route.js";
|
|
19
18
|
import { agentStatedFlowMessage, FLOW_INPUT_TOOL_NAME, nodeTargetRefusals, normalizeFlowInputToolInput, readRawFlowTree, sanitizeFlowRequests, unknownFlowPrefillMessage, } from "./flow-input.js";
|
|
20
19
|
import { writesInventedLogo } from "./invented-logo.js";
|
|
21
20
|
import { jevJudge } from "./jev.js";
|
|
@@ -28,7 +27,7 @@ import { buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, PER_TURN_E
|
|
|
28
27
|
import { credentialReminder, currentReferenceReminder, kickoffBriefReminder } from "./prompt-context.js";
|
|
29
28
|
import { publishLanguageReminder, publishRunReminder, publishStandingReminder } from "./publish-handoff.js";
|
|
30
29
|
import { bridgeRuntimeFiles } from "./runtime-files.js";
|
|
31
|
-
import { alignServedModels } from "./served-model.js";
|
|
30
|
+
import { alignServedModels, readConversationModel } from "./served-model.js";
|
|
32
31
|
import { applyCurrentSenderAttribution, buildCancelBoundary, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, interruptedResumeAttempt, isCliResumeRefusal, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
|
|
33
32
|
import { readSessionSkills } from "./session-skills.js";
|
|
34
33
|
import { ownQueryUsage, readUsageBaseline } from "./session-usage-baseline.js";
|
|
@@ -39,6 +38,7 @@ import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
|
|
|
39
38
|
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";
|
|
41
|
+
import { askTurnSignals, needsSignals, routeTurn, } from "./turn-route.js";
|
|
42
42
|
import { prepareSharedInstructions } from "./workspace-instructions.js";
|
|
43
43
|
import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
|
|
44
44
|
export function parsePlanDecision(value) {
|
|
@@ -451,9 +451,9 @@ function turnUsage(state) {
|
|
|
451
451
|
return {
|
|
452
452
|
modelUsage: turnModelUsageList(state.modelUsageTotals),
|
|
453
453
|
...(toolResults.length > 0 ? { toolResults } : {}),
|
|
454
|
-
//
|
|
455
|
-
...(state.
|
|
456
|
-
? {
|
|
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
457
|
: {}),
|
|
458
458
|
};
|
|
459
459
|
}
|
|
@@ -540,6 +540,51 @@ function hookSettingsForJob(job) {
|
|
|
540
540
|
* the workspace's `CLAUDE.md` itself only when it could not be split into the
|
|
541
541
|
* shared system prompt (`prepareSharedInstructions`).
|
|
542
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
|
+
}
|
|
543
588
|
function instructionSettingsForJob(job, sharedInstructions) {
|
|
544
589
|
if (job.review) {
|
|
545
590
|
return { claudeMdExcludes: ["**/CLAUDE.md"] };
|
|
@@ -1207,7 +1252,8 @@ export class BridgeJobExecutor {
|
|
|
1207
1252
|
holdsNoWork;
|
|
1208
1253
|
keepCliInStep;
|
|
1209
1254
|
judge;
|
|
1210
|
-
|
|
1255
|
+
routeJudge;
|
|
1256
|
+
routeModels;
|
|
1211
1257
|
constructor(dependencies) {
|
|
1212
1258
|
this.sdk = dependencies.sdk;
|
|
1213
1259
|
this.relay = dependencies.relay;
|
|
@@ -1233,12 +1279,8 @@ export class BridgeJobExecutor {
|
|
|
1233
1279
|
this.keepCliInStep = dependencies.keepCliInStep;
|
|
1234
1280
|
this.readFlowTree =
|
|
1235
1281
|
dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
|
|
1236
|
-
this.judge =
|
|
1237
|
-
|
|
1238
|
-
jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature: "bridge.toolCallGuard" }));
|
|
1239
|
-
this.effortJudge =
|
|
1240
|
-
dependencies.effortJudge ??
|
|
1241
|
-
jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature: "bridge.effortRoute" }));
|
|
1282
|
+
({ judge: this.judge, routeJudge: this.routeJudge } = judgesOf(dependencies));
|
|
1283
|
+
this.routeModels = dependencies.routeModels === true;
|
|
1242
1284
|
}
|
|
1243
1285
|
async execute(job, controls) {
|
|
1244
1286
|
try {
|
|
@@ -1251,24 +1293,43 @@ export class BridgeJobExecutor {
|
|
|
1251
1293
|
}
|
|
1252
1294
|
}
|
|
1253
1295
|
/**
|
|
1254
|
-
*
|
|
1255
|
-
* Session review, which keeps
|
|
1256
|
-
*
|
|
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.
|
|
1257
1299
|
*/
|
|
1258
|
-
async
|
|
1259
|
-
if (job.review) {
|
|
1300
|
+
async askSignals(job, request) {
|
|
1301
|
+
if (job.review || !needsSignals(this.turnFacts(job, request))) {
|
|
1260
1302
|
return null;
|
|
1261
1303
|
}
|
|
1262
|
-
|
|
1263
|
-
return await decideTurnEffort({
|
|
1304
|
+
return await askTurnSignals({
|
|
1264
1305
|
message: job.prompt,
|
|
1265
|
-
previousReply:
|
|
1266
|
-
judge: this.
|
|
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 {
|
|
1267
1328
|
planMode: job.planMode === true,
|
|
1268
1329
|
leadingCommand: request.leadingCommand,
|
|
1269
1330
|
unattended: job.unattended === true,
|
|
1270
1331
|
hasAttachments: (job.attachments?.length ?? 0) > 0,
|
|
1271
|
-
}
|
|
1332
|
+
};
|
|
1272
1333
|
}
|
|
1273
1334
|
async executeTurn(job, controls) {
|
|
1274
1335
|
const state = createBridgeJobState();
|
|
@@ -1282,7 +1343,7 @@ export class BridgeJobExecutor {
|
|
|
1282
1343
|
return { kind: "cancelled", costUsd: 0, model: state.model };
|
|
1283
1344
|
}
|
|
1284
1345
|
// Asked while the boundary loads, so the answer (~0.3 s) costs the turn no time.
|
|
1285
|
-
const
|
|
1346
|
+
const signals = this.askSignals(job, request);
|
|
1286
1347
|
const loaded = await this.loadTrustedBoundary(job, controls);
|
|
1287
1348
|
if (loaded?.kind !== "clean") {
|
|
1288
1349
|
request = await this.withContinuedAttachments(job, request);
|
|
@@ -1301,7 +1362,7 @@ export class BridgeJobExecutor {
|
|
|
1301
1362
|
state.detachedSession = attempt.detachedSession === true;
|
|
1302
1363
|
result.attemptedRehydration = attempt.attemptedRehydration;
|
|
1303
1364
|
this.recordAttemptContextSource(job, attempt);
|
|
1304
|
-
state.
|
|
1365
|
+
state.turnRoute = await this.routeFor(job, request, attempt, signals);
|
|
1305
1366
|
result = await this.runAttempt(job, controls, state, attempt);
|
|
1306
1367
|
return await this.finishSuccessfulAttempt(job, controls, state, result.costUsd);
|
|
1307
1368
|
}
|
|
@@ -1310,8 +1371,8 @@ export class BridgeJobExecutor {
|
|
|
1310
1371
|
if (repaired && controls.isActive()) {
|
|
1311
1372
|
const repairState = createBridgeJobState();
|
|
1312
1373
|
repairState.detachedSession = repaired.detachedSession === true;
|
|
1313
|
-
// The same turn, so the same level: a
|
|
1314
|
-
repairState.
|
|
1374
|
+
// The same turn, so the same model and level: a change would re-write the cache.
|
|
1375
|
+
repairState.turnRoute = state.turnRoute;
|
|
1315
1376
|
let repairResult = { costUsd: 0, attemptedRehydration: repaired.attemptedRehydration };
|
|
1316
1377
|
try {
|
|
1317
1378
|
repairResult = await this.runAttempt(job, controls, repairState, repaired);
|
|
@@ -1461,7 +1522,7 @@ export class BridgeJobExecutor {
|
|
|
1461
1522
|
return `${block}\n\n${job.prompt}`;
|
|
1462
1523
|
}
|
|
1463
1524
|
async runAttempt(job, controls, state, attempt) {
|
|
1464
|
-
state.model = this.model;
|
|
1525
|
+
state.model = state.turnRoute?.model ?? this.model;
|
|
1465
1526
|
// A stalled stream closes the wedged query and resumes the CLI session in
|
|
1466
1527
|
// place — the turn survives a hung upstream request instead of dying at the
|
|
1467
1528
|
// 30-minute session watchdog with all mid-turn work stranded.
|
|
@@ -1469,7 +1530,7 @@ export class BridgeJobExecutor {
|
|
|
1469
1530
|
let current = attempt;
|
|
1470
1531
|
for (let stallRetries = 0;;) {
|
|
1471
1532
|
try {
|
|
1472
|
-
costUsd += await this.runQueryOnce(job, controls, state, current,
|
|
1533
|
+
costUsd += await this.runQueryOnce(job, controls, state, current, state.model);
|
|
1473
1534
|
break;
|
|
1474
1535
|
}
|
|
1475
1536
|
catch (error) {
|
|
@@ -1849,12 +1910,7 @@ export class BridgeJobExecutor {
|
|
|
1849
1910
|
await this.alignResumedTranscript(attempt);
|
|
1850
1911
|
const mcpServers = await this.resolveMcpServers(job);
|
|
1851
1912
|
const skills = await readSessionSkills(bridgeRuntimeFiles.projectRoot());
|
|
1852
|
-
const sharedInstructions = job
|
|
1853
|
-
? null
|
|
1854
|
-
: await prepareSharedInstructions({
|
|
1855
|
-
projectRoot: bridgeRuntimeFiles.projectRoot(),
|
|
1856
|
-
contextDir: bridgeRuntimeFiles.instructionsDir(),
|
|
1857
|
-
});
|
|
1913
|
+
const sharedInstructions = await sharedInstructionsFor(job);
|
|
1858
1914
|
const autoCompactWindow = autoCompactWindowForModel(model);
|
|
1859
1915
|
const betas = betasForModel(model);
|
|
1860
1916
|
// No `outputStyle`: the built-in `Concise` style was pinned here against Opus 5's
|
|
@@ -1914,25 +1970,13 @@ export class BridgeJobExecutor {
|
|
|
1914
1970
|
// its first tool exchange onward. It may differ between turns only because
|
|
1915
1971
|
// the CLI also restates it per message (`PER_TURN_EFFORT_CAPABILITIES`),
|
|
1916
1972
|
// which keeps the cache; prose is no substitute.
|
|
1917
|
-
effort: state
|
|
1973
|
+
effort: turnEffortOf(state),
|
|
1918
1974
|
cwd: process.cwd(),
|
|
1919
1975
|
settingSources: ["project"],
|
|
1920
1976
|
// The scaffold's CLAUDE.md, minus its `@COMPANY.md` import, as the system
|
|
1921
1977
|
// prompt, so every chat reads one cached copy instead of writing its own;
|
|
1922
1978
|
// the import loads from `instructionsDir` (see workspace-instructions.ts).
|
|
1923
|
-
...(sharedInstructions
|
|
1924
|
-
? {
|
|
1925
|
-
systemPrompt: {
|
|
1926
|
-
type: "preset",
|
|
1927
|
-
preset: "claude_code",
|
|
1928
|
-
append: sharedInstructions.shared,
|
|
1929
|
-
// Recorded once per conversation, so a scaffold sync mid-chat cannot
|
|
1930
|
-
// move the cached prefix under it; it lands at the next compaction.
|
|
1931
|
-
snapshot: true,
|
|
1932
|
-
},
|
|
1933
|
-
additionalDirectories: [bridgeRuntimeFiles.instructionsDir()],
|
|
1934
|
-
}
|
|
1935
|
-
: {}),
|
|
1979
|
+
...sharedInstructionOptions(sharedInstructions),
|
|
1936
1980
|
// `baker_ui` hosts in-process interactive dashboard tools (secure tag
|
|
1937
1981
|
// form). Always present; HTTP (Composio) servers merge alongside it.
|
|
1938
1982
|
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
|
|
@@ -1985,7 +2029,7 @@ export class BridgeJobExecutor {
|
|
|
1985
2029
|
// the sandbox pays ~5-10% of its tokens on colour codes it can't see.
|
|
1986
2030
|
NO_COLOR: "1",
|
|
1987
2031
|
// Loads the `CLAUDE.md` in `instructionsDir`, which imports COMPANY.md.
|
|
1988
|
-
...(sharedInstructions
|
|
2032
|
+
...sharedInstructionEnv(sharedInstructions),
|
|
1989
2033
|
// Pin the conversation to one OpenRouter provider instance so the
|
|
1990
2034
|
// instance-scoped prompt cache is actually re-read across the turn's
|
|
1991
2035
|
// many tool-call requests. Merged into every model request body by the
|
|
@@ -1999,6 +2043,10 @@ export class BridgeJobExecutor {
|
|
|
1999
2043
|
.join("\n"),
|
|
2000
2044
|
// Lets a turn change effort without losing the prompt cache; see openrouter-session.ts.
|
|
2001
2045
|
[MODEL_CAPABILITIES_ENV]: PER_TURN_EFFORT_CAPABILITIES,
|
|
2046
|
+
// The CLI names each session with a second, structured-output request on the
|
|
2047
|
+
// turn's model. OpenRouter's Bedrock cannot serve that, so it was billed to
|
|
2048
|
+
// OpenRouter credits on Azure or Vertex, and nothing in Baker reads the name.
|
|
2049
|
+
CLAUDE_CODE_DISABLE_TERMINAL_TITLE: "1",
|
|
2002
2050
|
// The turn's message sender, so in-sandbox `baker` calls can scope to the acting user.
|
|
2003
2051
|
...(job.currentSender?.userId ? { BAKER_ACTING_USER_ID: job.currentSender.userId } : {}),
|
|
2004
2052
|
...subagentModelEnv(),
|