@koda-sl/baker-bridge 0.105.0 → 0.107.0-dev.a5e33d211
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 +22 -27
- package/dist/hono/agent-session.test.js +6 -6
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +1 -4
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +9 -9
- package/dist/hono/avatar-casting.js +2 -2
- package/dist/hono/avatar-casting.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +2 -14
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +54 -117
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +39 -146
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/convex.d.ts +0 -1
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +0 -4
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/convex.test.js +3 -3
- package/dist/hono/mcp-servers.d.ts +2 -0
- package/dist/hono/mcp-servers.d.ts.map +1 -1
- package/dist/hono/mcp-servers.js +17 -16
- package/dist/hono/mcp-servers.js.map +1 -1
- package/dist/hono/mcp-servers.test.js +10 -38
- package/dist/hono/mcp-servers.test.js.map +1 -1
- package/dist/hono/model-capabilities.d.ts.map +1 -1
- package/dist/hono/model-capabilities.js +28 -19
- package/dist/hono/model-capabilities.js.map +1 -1
- package/dist/hono/model-capabilities.test.js +37 -15
- package/dist/hono/model-capabilities.test.js.map +1 -1
- package/dist/hono/relay.d.ts +1 -2
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +0 -3
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +6 -22
- package/dist/hono/relay.test.js.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +4 -4
- package/dist/hono/tag-fields.js +1 -1
- package/dist/hono/tag-fields.js.map +1 -1
- package/dist/hono/tag-input.js +1 -1
- package/dist/hono/tag-input.js.map +1 -1
- package/dist/hono/turn-events.d.ts +1 -1
- package/dist/hono/turn-events.d.ts.map +1 -1
- package/dist/hono/turn-events.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ vi.mock("./runtime-files.ts", async (importOriginal) => {
|
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
18
|
import { AVATAR_CASTING_TOOL_NAME } from "./avatar-casting.js";
|
|
19
|
-
import { BridgeJobExecutor, createStallDetector, isContextOverflowRefusal, isControlChannelDeny, parsePlanDecision, remapAnswersToQuestionText, } from "./bridge-job.js";
|
|
19
|
+
import { BridgeJobExecutor, createStallDetector, isContextOverflowRefusal, isControlChannelDeny, OUTPUT_STYLE, parsePlanDecision, remapAnswersToQuestionText, } from "./bridge-job.js";
|
|
20
20
|
import { CONNECTION_INPUT_TOOL_NAME } from "./connection-input.js";
|
|
21
21
|
import { FLOW_INPUT_TOOL_NAME } from "./flow-input.js";
|
|
22
22
|
import { bridgeRuntimeFiles } from "./runtime-files.js";
|
|
@@ -63,7 +63,6 @@ function recordingRelay() {
|
|
|
63
63
|
const agentActivityUpdates = [];
|
|
64
64
|
const compactionUpdates = [];
|
|
65
65
|
const reviewDigests = [];
|
|
66
|
-
const retractedEvents = [];
|
|
67
66
|
const contextUsageUpdates = [];
|
|
68
67
|
return {
|
|
69
68
|
assistantEvents,
|
|
@@ -72,7 +71,6 @@ function recordingRelay() {
|
|
|
72
71
|
compactionUpdates,
|
|
73
72
|
contextUsageUpdates,
|
|
74
73
|
reviewDigests,
|
|
75
|
-
retractedEvents,
|
|
76
74
|
startPreview: () => undefined,
|
|
77
75
|
previewText: () => undefined,
|
|
78
76
|
previewTools: () => undefined,
|
|
@@ -88,10 +86,6 @@ function recordingRelay() {
|
|
|
88
86
|
assistantEvents.push({ target, event, model });
|
|
89
87
|
return Promise.resolve();
|
|
90
88
|
},
|
|
91
|
-
retractEvents: (target, eventIds) => {
|
|
92
|
-
retractedEvents.push({ target, eventIds });
|
|
93
|
-
return Promise.resolve();
|
|
94
|
-
},
|
|
95
89
|
complete: () => Promise.resolve(),
|
|
96
90
|
inputRequest: () => Promise.resolve(true),
|
|
97
91
|
inputResolved: () => Promise.resolve(),
|
|
@@ -314,8 +308,8 @@ describe("isControlChannelDeny", () => {
|
|
|
314
308
|
})).toBe(false);
|
|
315
309
|
});
|
|
316
310
|
it("does not match a PreToolUse hook block that quotes the envelope in its explanation", () => {
|
|
317
|
-
// Live-run finding: the scaffold's guard-background-commands hook
|
|
318
|
-
//
|
|
311
|
+
// Live-run finding: the scaffold's guard-background-commands hook, when it
|
|
312
|
+
// still blocked backgrounded subagents, explained why by quoting the failure.
|
|
319
313
|
// Its block arrives as an is_error tool_result, so an envelope-substring
|
|
320
314
|
// match counted it as a deny — three blocked spawns in one turn would have
|
|
321
315
|
// tripped the breaker and errored a perfectly healthy turn.
|
|
@@ -751,13 +745,13 @@ describe("BridgeJobExecutor", () => {
|
|
|
751
745
|
const relay = recordingRelay();
|
|
752
746
|
const executor = createExecutor({
|
|
753
747
|
relay,
|
|
754
|
-
model: "claude-opus-
|
|
748
|
+
model: "claude-opus-4-6",
|
|
755
749
|
queryResults: [
|
|
756
750
|
scriptedMessages(assistantMessage("assistant_1", "Done.", "session_next", {
|
|
757
751
|
usage: { input_tokens: 4000, cache_read_input_tokens: 96000 },
|
|
758
752
|
}),
|
|
759
753
|
// The runtime reports Opus's catalog window as 200k through the
|
|
760
|
-
// OpenRouter passthrough — wrong. Opus
|
|
754
|
+
// OpenRouter passthrough — wrong. Opus 4.6 is 1M on OpenRouter.
|
|
761
755
|
resultMessage(0.02, "session_next", {
|
|
762
756
|
"claude-main": {
|
|
763
757
|
inputTokens: 4000,
|
|
@@ -1169,8 +1163,7 @@ describe("BridgeJobExecutor", () => {
|
|
|
1169
1163
|
// Read off a session's own `init` tool list, the CLI offers these to every
|
|
1170
1164
|
// Session. Each one reaches past what a marketer's chat is for: the worktree
|
|
1171
1165
|
// pair rearranges the branches Publish owns, `Workflow` fans out a fleet on
|
|
1172
|
-
// the organization's OpenRouter key
|
|
1173
|
-
// single-session Runtime does not have.
|
|
1166
|
+
// the organization's OpenRouter key.
|
|
1174
1167
|
it("removes the tools that reach outside what a Session is", async () => {
|
|
1175
1168
|
const queryInputs = [];
|
|
1176
1169
|
const executor = createExecutor({
|
|
@@ -1179,10 +1172,12 @@ describe("BridgeJobExecutor", () => {
|
|
|
1179
1172
|
});
|
|
1180
1173
|
await executor.execute({ threadId: "thread_reach", turnId: "turn_reach", prompt: "Build a landing", history: history([]) }, activeControls());
|
|
1181
1174
|
const disallowed = queryInputs[0]?.options.disallowedTools ?? [];
|
|
1182
|
-
expect(disallowed).toEqual(expect.arrayContaining(["EnterWorktree", "ExitWorktree", "Workflow"
|
|
1175
|
+
expect(disallowed).toEqual(expect.arrayContaining(["EnterWorktree", "ExitWorktree", "Workflow"]));
|
|
1183
1176
|
// `Task` is the subagent spawn itself — the scaffold's own fan-out workers
|
|
1184
|
-
// ride it, so blocking multi-agent orchestration must not catch it.
|
|
1177
|
+
// ride it, so blocking multi-agent orchestration must not catch it. And
|
|
1178
|
+
// `SendMessage` is how the agent follows up with a subagent it already ran.
|
|
1185
1179
|
expect(disallowed).not.toContain("Task");
|
|
1180
|
+
expect(disallowed).not.toContain("SendMessage");
|
|
1186
1181
|
});
|
|
1187
1182
|
it("allowlists the workspace's own skills so the CLI's built-in developer skills stay out of the session", async () => {
|
|
1188
1183
|
const workspace = await mkdtemp(join(tmpdir(), "baker-job-skills-"));
|
|
@@ -1370,31 +1365,42 @@ describe("BridgeJobExecutor", () => {
|
|
|
1370
1365
|
await executor.execute({ threadId: "thread_uw", turnId: "turn_uw", prompt: "Hi", history: history([]) }, activeControls());
|
|
1371
1366
|
expect((queryInputs[0]?.options.settings ?? {})).not.toHaveProperty("autoCompactWindow");
|
|
1372
1367
|
});
|
|
1373
|
-
//
|
|
1374
|
-
//
|
|
1375
|
-
//
|
|
1376
|
-
it("sends
|
|
1368
|
+
// The output style is pinned on every turn, so the settings tier now always has
|
|
1369
|
+
// something in it — even for a model whose window we cannot resolve and with no
|
|
1370
|
+
// chat id to enable the memory directory, which used to leave it empty.
|
|
1371
|
+
it("always sends the settings tier, because the output style is pinned every turn", async () => {
|
|
1372
|
+
vi.stubEnv("BAKER_CHAT_ID", "");
|
|
1377
1373
|
const queryInputs = [];
|
|
1378
1374
|
const executor = createExecutor({
|
|
1379
1375
|
queryInputs,
|
|
1380
|
-
|
|
1376
|
+
model: "claude-unknown",
|
|
1377
|
+
queryResults: [scriptedMessages(resultMessage(0, "session_no_settings"))],
|
|
1381
1378
|
});
|
|
1382
|
-
await executor.execute({ threadId: "
|
|
1383
|
-
expect(
|
|
1379
|
+
await executor.execute({ threadId: "thread_ns", turnId: "turn_ns", prompt: "Hi", history: history([]) }, activeControls());
|
|
1380
|
+
expect(queryInputs[0]?.options.settings).toEqual({ outputStyle: OUTPUT_STYLE });
|
|
1384
1381
|
});
|
|
1385
|
-
//
|
|
1386
|
-
//
|
|
1387
|
-
//
|
|
1388
|
-
|
|
1389
|
-
|
|
1382
|
+
// The CLI accepts an unrecognized output-style name silently — a misspelling
|
|
1383
|
+
// returns a clean 200 on the default voice, with nothing at runtime to notice
|
|
1384
|
+
// it. Verified against the CLI: `{"outputStyle":"NotARealStyle"}` succeeded.
|
|
1385
|
+
// So the literal is pinned here rather than left to review.
|
|
1386
|
+
it("names the built-in Concise style exactly", () => {
|
|
1387
|
+
expect(OUTPUT_STYLE).toBe("Concise");
|
|
1388
|
+
});
|
|
1389
|
+
it("runs every turn under the pinned output style", async () => {
|
|
1390
1390
|
const queryInputs = [];
|
|
1391
1391
|
const executor = createExecutor({
|
|
1392
1392
|
queryInputs,
|
|
1393
|
-
|
|
1394
|
-
|
|
1393
|
+
queryResults: [
|
|
1394
|
+
scriptedMessages(resultMessage(0, "session_style")),
|
|
1395
|
+
scriptedMessages(resultMessage(0, "session_style")),
|
|
1396
|
+
],
|
|
1395
1397
|
});
|
|
1396
|
-
await executor.execute({ threadId: "
|
|
1397
|
-
|
|
1398
|
+
await executor.execute({ threadId: "thread_style", turnId: "turn_style_1", prompt: "Hi", history: history([]) }, activeControls());
|
|
1399
|
+
await executor.execute({ threadId: "thread_style", turnId: "turn_style_2", prompt: "Again", history: history([]) }, activeControls());
|
|
1400
|
+
expect(queryInputs[0]?.options.settings).toMatchObject({ outputStyle: "Concise" });
|
|
1401
|
+
// Constant across turns — the style is part of the system prompt, so a
|
|
1402
|
+
// per-turn value would re-write the cache prefix on every request.
|
|
1403
|
+
expect(queryInputs[1]?.options.settings).toMatchObject({ outputStyle: "Concise" });
|
|
1398
1404
|
});
|
|
1399
1405
|
it("runs exact publish commands with the prompt passed through verbatim", async () => {
|
|
1400
1406
|
const queryInputs = [];
|
|
@@ -1923,8 +1929,8 @@ describe("BridgeJobExecutor", () => {
|
|
|
1923
1929
|
expect(castingRequests[0]).toMatchObject({ avatarHandle: "elena" });
|
|
1924
1930
|
});
|
|
1925
1931
|
it("hands the agent's own casting call the face the user picked, instead of a second picker", async () => {
|
|
1926
|
-
//
|
|
1927
|
-
//
|
|
1932
|
+
// The tool tells the agent to call it IMMEDIATELY after `baker avatars create`, so
|
|
1933
|
+
// this is the documented path, not an edge. The auto-cast owed for that create used
|
|
1928
1934
|
// to run under its own tool-use id and then let the explicit call through unhandled —
|
|
1929
1935
|
// which returns the tool's `{ status: "declined" }` default. The agent was told the
|
|
1930
1936
|
// user had rejected all four faces moments after they picked one, and the remedy in
|
|
@@ -3372,117 +3378,4 @@ describe("BridgeJobExecutor review mode", () => {
|
|
|
3372
3378
|
expect(outcome).toMatchObject({ kind: "completed", structuredOutput: findings });
|
|
3373
3379
|
});
|
|
3374
3380
|
});
|
|
3375
|
-
describe("BridgeJobExecutor model refusals", () => {
|
|
3376
|
-
function refusalNoFallback() {
|
|
3377
|
-
return {
|
|
3378
|
-
type: "system",
|
|
3379
|
-
subtype: "model_refusal_no_fallback",
|
|
3380
|
-
original_model: "claude-opus-5-5",
|
|
3381
|
-
request_id: "req_1",
|
|
3382
|
-
api_refusal_category: "cyber",
|
|
3383
|
-
content: "Claude Code is unable to respond to this request.",
|
|
3384
|
-
uuid: "refusal_notice",
|
|
3385
|
-
session_id: "session_refused",
|
|
3386
|
-
};
|
|
3387
|
-
}
|
|
3388
|
-
it("ends a refused turn with a product-language error instead of a success", async () => {
|
|
3389
|
-
const executor = createExecutor({
|
|
3390
|
-
queryResults: [scriptedMessages(refusalNoFallback(), resultMessage(0.01, "session_refused"))],
|
|
3391
|
-
});
|
|
3392
|
-
const outcome = await executor.execute({ threadId: "thread_refused", turnId: "turn_refused", prompt: "Hi", history: history([]) }, activeControls());
|
|
3393
|
-
expect(outcome).toMatchObject({
|
|
3394
|
-
kind: "errored",
|
|
3395
|
-
errors: ["I couldn't help with that request. Try rephrasing what you need."],
|
|
3396
|
-
});
|
|
3397
|
-
});
|
|
3398
|
-
it("records the refusal category for whoever reads the turn afterwards", async () => {
|
|
3399
|
-
const turnEvents = [];
|
|
3400
|
-
const executor = createExecutor({
|
|
3401
|
-
turnEvents,
|
|
3402
|
-
queryResults: [scriptedMessages(refusalNoFallback(), resultMessage(0.01, "session_refused"))],
|
|
3403
|
-
});
|
|
3404
|
-
await executor.execute({ threadId: "thread_refused", turnId: "turn_refused", prompt: "Hi", history: history([]) }, activeControls());
|
|
3405
|
-
expect(turnEvents).toContainEqual(expect.objectContaining({ event: "model_refusal", detail: expect.objectContaining({ category: "cyber" }) }));
|
|
3406
|
-
});
|
|
3407
|
-
// The CLI renders the refusal as an assistant error frame written for a
|
|
3408
|
-
// terminal user. The turn already ends on a product-language error, so that
|
|
3409
|
-
// frame never reaches the chat.
|
|
3410
|
-
it("does not relay the CLI's refusal frame to the chat", async () => {
|
|
3411
|
-
const relay = recordingRelay();
|
|
3412
|
-
const refusalFrame = {
|
|
3413
|
-
type: "assistant",
|
|
3414
|
-
uuid: "refusal_frame",
|
|
3415
|
-
message: {
|
|
3416
|
-
role: "assistant",
|
|
3417
|
-
content: [{ type: "text", text: "API Error: Claude Code is unable to respond to this request." }],
|
|
3418
|
-
stop_reason: "refusal",
|
|
3419
|
-
},
|
|
3420
|
-
};
|
|
3421
|
-
const executor = createExecutor({
|
|
3422
|
-
relay,
|
|
3423
|
-
queryResults: [scriptedMessages(refusalFrame, refusalNoFallback(), resultMessage(0.01, "session_refused"))],
|
|
3424
|
-
});
|
|
3425
|
-
await executor.execute({ threadId: "thread_refused", turnId: "turn_refused", prompt: "Hi", history: history([]) }, activeControls());
|
|
3426
|
-
expect(relay.assistantEvents).toEqual([]);
|
|
3427
|
-
});
|
|
3428
|
-
// A refused subagent is reported the same way, but the main agent gets the
|
|
3429
|
-
// failed task back and carries on — the turn is still its to finish.
|
|
3430
|
-
it("lets the turn complete when the main conversation carries on after a refusal", async () => {
|
|
3431
|
-
const executor = createExecutor({
|
|
3432
|
-
queryResults: [
|
|
3433
|
-
scriptedMessages(refusalNoFallback(), assistantMessage("assistant_after", "Done another way.", "session_refused"), resultMessage(0.01, "session_refused")),
|
|
3434
|
-
],
|
|
3435
|
-
});
|
|
3436
|
-
const outcome = await executor.execute({ threadId: "thread_refused", turnId: "turn_refused", prompt: "Hi", history: history([]) }, activeControls());
|
|
3437
|
-
expect(outcome).toMatchObject({ kind: "completed" });
|
|
3438
|
-
});
|
|
3439
|
-
it("keeps the product-language error when the refused turn's result is an error", async () => {
|
|
3440
|
-
const erroredResult = {
|
|
3441
|
-
type: "result",
|
|
3442
|
-
subtype: "error_during_execution",
|
|
3443
|
-
is_error: true,
|
|
3444
|
-
errors: ["API Error: Claude Code is unable to respond to this request."],
|
|
3445
|
-
total_cost_usd: 0.01,
|
|
3446
|
-
session_id: "session_refused",
|
|
3447
|
-
};
|
|
3448
|
-
const executor = createExecutor({ queryResults: [scriptedMessages(refusalNoFallback(), erroredResult)] });
|
|
3449
|
-
const outcome = await executor.execute({ threadId: "thread_refused", turnId: "turn_refused", prompt: "Hi", history: history([]) }, activeControls());
|
|
3450
|
-
expect(outcome).toMatchObject({
|
|
3451
|
-
kind: "errored",
|
|
3452
|
-
errors: ["I couldn't help with that request. Try rephrasing what you need."],
|
|
3453
|
-
});
|
|
3454
|
-
});
|
|
3455
|
-
// A fallback model retried the refused request. What the refused leg already
|
|
3456
|
-
// posted is superseded, so it leaves the chat instead of sitting above the
|
|
3457
|
-
// answer that replaced it.
|
|
3458
|
-
it("retracts the messages a refusal fallback superseded", async () => {
|
|
3459
|
-
const relay = recordingRelay();
|
|
3460
|
-
const fallbackNotice = {
|
|
3461
|
-
type: "system",
|
|
3462
|
-
subtype: "model_refusal_fallback",
|
|
3463
|
-
trigger: "refusal",
|
|
3464
|
-
direction: "retry",
|
|
3465
|
-
scope: "session",
|
|
3466
|
-
original_model: "claude-opus-5-5",
|
|
3467
|
-
fallback_model: "claude-sonnet-5",
|
|
3468
|
-
request_id: "req_1",
|
|
3469
|
-
api_refusal_category: "cyber",
|
|
3470
|
-
retracted_message_uuids: ["refused_partial_1", "refused_partial_2"],
|
|
3471
|
-
content: "Retried on a different model.",
|
|
3472
|
-
uuid: "fallback_notice",
|
|
3473
|
-
session_id: "session_fallback",
|
|
3474
|
-
};
|
|
3475
|
-
const executor = createExecutor({
|
|
3476
|
-
relay,
|
|
3477
|
-
queryResults: [
|
|
3478
|
-
scriptedMessages(assistantMessage("refused_partial_1", "Sure, here is", "session_fallback"), fallbackNotice, assistantMessage("fallback_answer", "Here is the plan.", "session_fallback"), resultMessage(0.01, "session_fallback")),
|
|
3479
|
-
],
|
|
3480
|
-
});
|
|
3481
|
-
const outcome = await executor.execute({ threadId: "thread_fb", turnId: "turn_fb", prompt: "Hi", history: history([]) }, activeControls());
|
|
3482
|
-
expect(relay.retractedEvents).toEqual([
|
|
3483
|
-
{ target: { threadId: "thread_fb", turnId: "turn_fb" }, eventIds: ["refused_partial_1", "refused_partial_2"] },
|
|
3484
|
-
]);
|
|
3485
|
-
expect(outcome).toMatchObject({ kind: "completed" });
|
|
3486
|
-
});
|
|
3487
|
-
});
|
|
3488
3381
|
//# sourceMappingURL=bridge-job.test.js.map
|