diff --git a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts index 7a635befa..ec258ebaa 100644 --- a/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts +++ b/plugins/provider-claude-code/src/bridge/__tests__/bridge.test.ts @@ -1,3 +1,5 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { chmodSync, mkdirSync, @@ -706,6 +708,42 @@ describe("bridge", () => { } }); + it("issue 3625 retains bridge tools across compact and same-session resume", async () => { + const bridge = createBridgeJsonRpcTestHarness(handleLine); + const queries: ControlledClaudeQuery[] = []; + queryMock.mockImplementation(() => { + const query = createControlledClaudeQuery(); + queries.push(query); + return query; + }); + const threadId = "test-3625"; + const dynamicTools = [{ name: "probe", description: "Test probe", inputSchema: { type: "object" } }]; + const params = { threadId, cwd: "/tmp", instructionMode: "append", options: canonicalOptions({}), dynamicTools }; + const client = new Client({ name: "probe-client", version: "1.0.0" }); + try { + bridge.sendRequest(1, "thread/start", params); + const response = await bridge.waitForResponse(1); + const providerThreadId = getProviderThreadIdFromResult(response); + const options = queryMock.mock.calls[0][0].options; + expect(options.allowedTools).toContain("mcp__bb-bridge__probe"); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await options.mcpServers["bb-bridge"].instance.connect(serverTransport); + await client.connect(clientTransport); + expect((await client.listTools()).tools.map((tool) => tool.name)).toEqual(["probe"]); + queries[0].emit({ type: "system", subtype: "compact_boundary", uuid: "00000000-0000-4000-8000-000000000001", session_id: providerThreadId, compact_metadata: { trigger: "manual", pre_tokens: 450, post_tokens: 100 } }); + await vi.waitFor(() => expect(queries[0].getContextUsage).toHaveBeenCalled()); + expect((await client.listTools()).tools.map((tool) => tool.name)).toEqual(["probe"]); + bridge.sendRequest(3, "thread/resume", { ...params, providerThreadId }); + await bridge.waitForResponse(3); + expect(queryMock).toHaveBeenCalledTimes(1); + expect((await client.listTools()).tools.map((tool) => tool.name)).toEqual(["probe"]); + } finally { + await client.close(); + await stopBridgeThread({ bridge, queries, threadId }); + bridge.restore(); + } + }); + it("publishes a context snapshot after a turn and invalidates it at compaction", async () => { const bridge = createBridgeJsonRpcTestHarness(handleLine); const queries: ControlledClaudeQuery[] = [];