@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.
Files changed (45) hide show
  1. package/README.md +22 -27
  2. package/dist/hono/agent-session.test.js +6 -6
  3. package/dist/hono/agent.d.ts.map +1 -1
  4. package/dist/hono/agent.js +1 -4
  5. package/dist/hono/agent.js.map +1 -1
  6. package/dist/hono/agent.test.js +9 -9
  7. package/dist/hono/avatar-casting.js +2 -2
  8. package/dist/hono/avatar-casting.js.map +1 -1
  9. package/dist/hono/bridge-job.d.ts +2 -14
  10. package/dist/hono/bridge-job.d.ts.map +1 -1
  11. package/dist/hono/bridge-job.js +54 -117
  12. package/dist/hono/bridge-job.js.map +1 -1
  13. package/dist/hono/bridge-job.test.js +39 -146
  14. package/dist/hono/bridge-job.test.js.map +1 -1
  15. package/dist/hono/convex.d.ts +0 -1
  16. package/dist/hono/convex.d.ts.map +1 -1
  17. package/dist/hono/convex.js +0 -4
  18. package/dist/hono/convex.js.map +1 -1
  19. package/dist/hono/convex.test.js +3 -3
  20. package/dist/hono/mcp-servers.d.ts +2 -0
  21. package/dist/hono/mcp-servers.d.ts.map +1 -1
  22. package/dist/hono/mcp-servers.js +17 -16
  23. package/dist/hono/mcp-servers.js.map +1 -1
  24. package/dist/hono/mcp-servers.test.js +10 -38
  25. package/dist/hono/mcp-servers.test.js.map +1 -1
  26. package/dist/hono/model-capabilities.d.ts.map +1 -1
  27. package/dist/hono/model-capabilities.js +28 -19
  28. package/dist/hono/model-capabilities.js.map +1 -1
  29. package/dist/hono/model-capabilities.test.js +37 -15
  30. package/dist/hono/model-capabilities.test.js.map +1 -1
  31. package/dist/hono/relay.d.ts +1 -2
  32. package/dist/hono/relay.d.ts.map +1 -1
  33. package/dist/hono/relay.js +0 -3
  34. package/dist/hono/relay.js.map +1 -1
  35. package/dist/hono/relay.test.js +6 -22
  36. package/dist/hono/relay.test.js.map +1 -1
  37. package/dist/hono/session-continuity.integration.test.js +4 -4
  38. package/dist/hono/tag-fields.js +1 -1
  39. package/dist/hono/tag-fields.js.map +1 -1
  40. package/dist/hono/tag-input.js +1 -1
  41. package/dist/hono/tag-input.js.map +1 -1
  42. package/dist/hono/turn-events.d.ts +1 -1
  43. package/dist/hono/turn-events.d.ts.map +1 -1
  44. package/dist/hono/turn-events.js.map +1 -1
  45. 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 explains
318
- // WHY it blocks a backgrounded subagent by quoting the failure it prevents.
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-5-5",
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 5.5 is 1M on OpenRouter.
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, and `SendMessage` addresses peers a
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", "SendMessage"]));
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
- // No output style is sent. `Concise` was pinned against Opus 5's preamble; on
1374
- // Opus 5.5 it suppressed the one-line progress notes a marketer reads while a
1375
- // long turn works. The voice rules live in the scaffold CLAUDE.md instead.
1376
- it("sends no output style, so the default voice and its progress notes survive", async () => {
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
- queryResults: [scriptedMessages(resultMessage(0, "session_style"))],
1376
+ model: "claude-unknown",
1377
+ queryResults: [scriptedMessages(resultMessage(0, "session_no_settings"))],
1381
1378
  });
1382
- await executor.execute({ threadId: "thread_style", turnId: "turn_style_1", prompt: "Hi", history: history([]) }, activeControls());
1383
- expect((queryInputs[0]?.options.settings ?? {})).not.toHaveProperty("outputStyle");
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
- // Nothing to put in the settings tier (unknown model window, no chat id for the
1386
- // memory directory) means no `settings` key at all: the SDK forwards even `{}`
1387
- // to the CLI as a `--settings` flag tier.
1388
- it("omits the settings tier when nothing in it applies", async () => {
1389
- vi.stubEnv("BAKER_CHAT_ID", "");
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
- model: "claude-unknown",
1394
- queryResults: [scriptedMessages(resultMessage(0, "session_no_settings"))],
1393
+ queryResults: [
1394
+ scriptedMessages(resultMessage(0, "session_style")),
1395
+ scriptedMessages(resultMessage(0, "session_style")),
1396
+ ],
1395
1397
  });
1396
- await executor.execute({ threadId: "thread_ns", turnId: "turn_ns", prompt: "Hi", history: history([]) }, activeControls());
1397
- expect(queryInputs[0]?.options).not.toHaveProperty("settings");
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
- // An agent may still call it right after `baker avatars create` (the tool now says the
1927
- // picker opens by itself, but the call is tolerated). The auto-cast owed for that create used
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