@koda-sl/baker-bridge 0.148.0-dev.a87640a51 → 0.152.0-dev.1a19b1876

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.
@@ -241,6 +241,8 @@ function createExecutor(options) {
241
241
  stalledCliExitMs: 0,
242
242
  prepareAttachments: options.prepareAttachments,
243
243
  ...(options.inFlightTurns ? { inFlightTurns: options.inFlightTurns } : {}),
244
+ ...(options.catchUpWithMain ? { catchUpWithMain: options.catchUpWithMain } : {}),
245
+ ...(options.keepCliInStep ? { keepCliInStep: options.keepCliInStep } : {}),
244
246
  });
245
247
  }
246
248
  function memoryInFlightTurns(initial) {
@@ -260,24 +262,6 @@ function memoryInFlightTurns(initial) {
260
262
  },
261
263
  };
262
264
  }
263
- // The CLI's own report of a failed model request, written as an assistant
264
- // message — the shape production stores (`model: "<synthetic>"`, `error`).
265
- function apiErrorMessage(uuid, text, error) {
266
- return {
267
- type: "assistant",
268
- uuid,
269
- session_id: "session_api_error",
270
- parent_tool_use_id: null,
271
- error,
272
- is_api_error_message: true,
273
- message: {
274
- role: "assistant",
275
- model: "<synthetic>",
276
- content: [{ type: "text", text }],
277
- stop_reason: "stop_sequence",
278
- },
279
- };
280
- }
281
265
  function resultMessage(costUsd, sessionId, modelUsage) {
282
266
  return {
283
267
  type: "result",
@@ -1345,6 +1329,38 @@ describe("BridgeJobExecutor", () => {
1345
1329
  }, activeControls());
1346
1330
  expect(queryInputs[0]?.prompt).toBe("/audit-google-ads-account 3865083115");
1347
1331
  });
1332
+ it("keeps a skill command leading when catching up with main is what brought the skill in", async () => {
1333
+ const workspace = await mkdtemp(join(tmpdir(), "baker-job-skill-caught-up-"));
1334
+ await mkdir(join(workspace, ".claude", "skills", "landing"), { recursive: true });
1335
+ projectRootMock.value = workspace;
1336
+ const queryInputs = [];
1337
+ let told = 0;
1338
+ const executor = createExecutor({
1339
+ queryInputs,
1340
+ queryResults: [scriptedMessages(resultMessage(0, "session_skill_caught_up"))],
1341
+ catchUpWithMain: async () => {
1342
+ await mkdir(join(workspace, ".claude", "skills", "audit-google-ads-account"), { recursive: true });
1343
+ return {
1344
+ inWorkspace: true,
1345
+ changed: [".claude/skills/audit-google-ads-account/SKILL.md"],
1346
+ told: () => {
1347
+ told += 1;
1348
+ return Promise.resolve();
1349
+ },
1350
+ };
1351
+ },
1352
+ });
1353
+ await executor.execute({
1354
+ threadId: "thread_skill_caught_up",
1355
+ turnId: "turn_skill_caught_up",
1356
+ prompt: "/audit-google-ads-account 3865083115",
1357
+ history: history([]),
1358
+ currentSender: { name: "Dana", userId: "user_dana" },
1359
+ }, activeControls());
1360
+ expect(queryInputs[0]?.prompt).toBe("/audit-google-ads-account 3865083115");
1361
+ expect(queryInputs[0]?.reminder).toBeUndefined();
1362
+ expect(told).toBe(0);
1363
+ });
1348
1364
  it("passes per-turn MCP servers into Agent SDK query options with the acting user", async () => {
1349
1365
  const queryInputs = [];
1350
1366
  const calls = [];
@@ -1668,6 +1684,110 @@ describe("BridgeJobExecutor", () => {
1668
1684
  expect(queryInputs[0]?.prompt).toBe("/publish");
1669
1685
  expect(queryInputs[0]?.reminder).toBeUndefined();
1670
1686
  });
1687
+ it("tells the agent which files another chat's publish just brought into its workspace", async () => {
1688
+ const queryInputs = [];
1689
+ let told = 0;
1690
+ const executor = createExecutor({
1691
+ queryInputs,
1692
+ catchUpWithMain: async () => ({
1693
+ inWorkspace: true,
1694
+ changed: ["src/pages/home/hero.md"],
1695
+ told: () => {
1696
+ told += 1;
1697
+ return Promise.resolve();
1698
+ },
1699
+ }),
1700
+ });
1701
+ await executor.execute({
1702
+ threadId: "thread_caught_up",
1703
+ turnId: "turn_caught_up",
1704
+ prompt: "Cambia el subtítulo del hero a «Sin entrada, paga en 24 meses».",
1705
+ history: history([
1706
+ { role: "user", text: "¿Qué pone ahora el hero de la home?", createdAt: 1 },
1707
+ {
1708
+ role: "assistant",
1709
+ text: "El titular dice «Paneles solares para tu casa» y el subtítulo «Instala hoy y paga en 12 meses».",
1710
+ createdAt: 2,
1711
+ },
1712
+ ]),
1713
+ }, activeControls());
1714
+ expect(queryInputs[0]?.prompt).toContain("Sin entrada, paga en 24 meses");
1715
+ expect(queryInputs[0]?.reminder).toContain("Read again any of them you work on");
1716
+ expect(queryInputs[0]?.reminder).toContain("- src/pages/home/hero.md");
1717
+ expect(told).toBe(1);
1718
+ });
1719
+ it("keeps the note for the next turn when the user stops this one while the workspace catches up", async () => {
1720
+ const queryInputs = [];
1721
+ const controls = activeControls();
1722
+ let told = 0;
1723
+ const executor = createExecutor({
1724
+ queryInputs,
1725
+ catchUpWithMain: () => {
1726
+ controls.deactivate();
1727
+ return Promise.resolve({
1728
+ inWorkspace: false,
1729
+ changed: ["src/pages/home/hero.md"],
1730
+ told: () => {
1731
+ told += 1;
1732
+ return Promise.resolve();
1733
+ },
1734
+ });
1735
+ },
1736
+ });
1737
+ const outcome = await executor.execute({ threadId: "thread_stopped", turnId: "turn_stopped", prompt: "Cambia el hero", history: history([]) }, controls);
1738
+ expect(outcome.kind).toBe("cancelled");
1739
+ expect(queryInputs).toHaveLength(0);
1740
+ expect(told).toBe(0);
1741
+ });
1742
+ it("leaves the workspace of a finished Session alone while it is reviewed", async () => {
1743
+ let caughtUp = false;
1744
+ const executor = createExecutor({
1745
+ catchUpWithMain: () => {
1746
+ caughtUp = true;
1747
+ return Promise.resolve(undefined);
1748
+ },
1749
+ });
1750
+ await executor.execute({
1751
+ threadId: "thread_review_sync",
1752
+ turnId: "turn_review_sync",
1753
+ prompt: "review",
1754
+ history: history([]),
1755
+ review: { threadIds: [], chatFacts: { chatId: "chat_1" }, outputRepair: true },
1756
+ }, activeControls());
1757
+ expect(caughtUp).toBe(false);
1758
+ });
1759
+ it("brings the CLI to the release Convex sent before the turn starts", async () => {
1760
+ const queryInputs = [];
1761
+ const requested = [];
1762
+ const executor = createExecutor({
1763
+ queryInputs,
1764
+ keepCliInStep: (wanted) => {
1765
+ requested.push({ wanted, turnStarted: queryInputs.length > 0 });
1766
+ return Promise.resolve();
1767
+ },
1768
+ });
1769
+ await executor.execute({ threadId: "thread_cli", turnId: "turn_cli", prompt: "Hola", cliVersion: "0.412.0", history: history([]) }, activeControls());
1770
+ expect(requested).toEqual([{ wanted: "0.412.0", turnStarted: false }]);
1771
+ expect(queryInputs).toHaveLength(1);
1772
+ });
1773
+ it("keeps the Session's CLI while it is reviewed", async () => {
1774
+ const wanted = [];
1775
+ const executor = createExecutor({
1776
+ keepCliInStep: (version) => {
1777
+ wanted.push(version);
1778
+ return Promise.resolve();
1779
+ },
1780
+ });
1781
+ await executor.execute({
1782
+ threadId: "thread_review_cli",
1783
+ turnId: "turn_review_cli",
1784
+ prompt: "review",
1785
+ cliVersion: "0.412.0",
1786
+ history: history([]),
1787
+ review: { threadIds: [], chatFacts: { chatId: "chat_1" }, outputRepair: true },
1788
+ }, activeControls());
1789
+ expect(wanted).toEqual([]);
1790
+ });
1671
1791
  it("keeps the viewing-reference context beside the message when a stale session falls back to rehydration", async () => {
1672
1792
  const queryInputs = [];
1673
1793
  const executor = createExecutor({
@@ -2079,7 +2199,7 @@ describe("BridgeJobExecutor", () => {
2079
2199
  expect(savedBoundaries).toEqual([]);
2080
2200
  expect(clearCalls).toContainEqual({ threadId: "thread_result_error", reason: "result_error" });
2081
2201
  });
2082
- it("reports a provider's server error in product language, not its status line", async () => {
2202
+ it("preserves provider status details when an SDK result error omits errors", async () => {
2083
2203
  const clearCalls = [];
2084
2204
  const executor = createExecutor({
2085
2205
  clearCalls,
@@ -2105,69 +2225,10 @@ describe("BridgeJobExecutor", () => {
2105
2225
  kind: "errored",
2106
2226
  costUsd: 0.17,
2107
2227
  model: "claude-test",
2108
- errors: [expect.stringContaining("on our side")],
2228
+ errors: ["Claude Code returned an error result: API Error: 500 Internal Server Error; apiErrorStatus 500"],
2109
2229
  });
2110
2230
  expect(clearCalls).toContainEqual({ threadId: "thread_provider_result_error", reason: "result_error" });
2111
2231
  });
2112
- it("never shows the marketer a model provider's billing error, as a reply or as the turn's error", async () => {
2113
- const providerError = "API Error: 402 Insufficient credits. Add more using https://openrouter.ai/settings/credits";
2114
- const relay = recordingRelay();
2115
- const executor = createExecutor({
2116
- relay,
2117
- queryResults: [
2118
- scriptedMessages(apiErrorMessage("assistant_402", providerError, "unknown"), {
2119
- type: "result",
2120
- subtype: "success",
2121
- is_error: true,
2122
- result: providerError,
2123
- api_error_status: 402,
2124
- total_cost_usd: 0,
2125
- session_id: "session_402",
2126
- }),
2127
- ],
2128
- });
2129
- const outcome = await executor.execute({ threadId: "thread_402", turnId: "turn_402", prompt: "Build the creatives", history: history([]) }, activeControls());
2130
- const shown = JSON.stringify([relay.assistantEvents, outcome.kind === "errored" ? outcome.errors : []]);
2131
- expect(outcome.kind).toBe("errored");
2132
- expect(shown).not.toMatch(/API Error|openrouter|402/i);
2133
- });
2134
- it("tells the marketer how to fix an image the provider could not read, not to send the same message again", async () => {
2135
- const imageError = 'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"}}';
2136
- const executor = createExecutor({
2137
- queryResults: [
2138
- scriptedMessages({
2139
- type: "result",
2140
- subtype: "success",
2141
- is_error: true,
2142
- result: imageError,
2143
- api_error_status: 400,
2144
- total_cost_usd: 0,
2145
- session_id: "session_bad_image",
2146
- }),
2147
- ],
2148
- });
2149
- const outcome = await executor.execute({ threadId: "thread_bad_image", turnId: "turn_bad_image", prompt: "Use this photo", history: history([]) }, activeControls());
2150
- expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([
2151
- expect.stringContaining("extension lies about its format"),
2152
- ]);
2153
- });
2154
- it("keeps a server error the turn recovered from out of the chat, and in the turn events", async () => {
2155
- const providerError = "API Error: Server error mid-response. The response above may be incomplete.";
2156
- const relay = recordingRelay();
2157
- const turnEvents = [];
2158
- const executor = createExecutor({
2159
- relay,
2160
- turnEvents,
2161
- queryResults: [
2162
- scriptedMessages(apiErrorMessage("assistant_server_error", providerError, "server_error"), assistantMessage("assistant_recovered", "Your three headlines are ready.", "session_recovered"), resultMessage(0.3, "session_recovered")),
2163
- ],
2164
- });
2165
- const outcome = await executor.execute({ threadId: "thread_recovered", turnId: "turn_recovered", prompt: "Write headlines", history: history([]) }, activeControls());
2166
- expect(outcome.kind).toBe("completed");
2167
- expect(JSON.stringify(relay.assistantEvents)).not.toContain("API Error");
2168
- expect(JSON.stringify(relay.assistantEvents)).toContain("Your three headlines are ready.");
2169
- expect(turnEvents).toContainEqual(expect.objectContaining({ event: "api_error", detail: { error: "server_error", text: providerError } }));
2170
- });
2171
2232
  it("closes SDK queries on error and inactive exits but not on natural clean completion", async () => {
2172
2233
  const naturalCloseCalls = [];
2173
2234
  const naturalExecutor = createExecutor({