






















@@ -1,6 +1,6 @@
11import { beforeEach, describe, expect, it, vi } from "vitest";
22import { ErrorCodes } from "../protocol/index.js";
3-import { toolsEffectiveHandlers } from "./tools-effective.js";
3+import { __testing, toolsEffectiveHandlers } from "./tools-effective.js";
4455const runtimeMocks = vi.hoisted(() => ({
66deliveryContextFromSession: vi.fn(() => ({
@@ -29,6 +29,7 @@ const runtimeMocks = vi.hoisted(() => ({
2929model: "gpt-4.1",
3030},
3131})),
32+getActivePluginRegistryVersion: vi.fn(() => 1),
3233resolveEffectiveToolInventory: vi.fn(() => ({
3334agentId: "main",
3435profile: "coding",
@@ -77,6 +78,9 @@ function createInvokeParams(params: Record<string, unknown>) {
7778describe("tools.effective handler", () => {
7879beforeEach(() => {
7980vi.clearAllMocks();
81+__testing.resetToolsEffectiveCacheForTest();
82+__testing.resetToolsEffectiveNowForTest();
83+runtimeMocks.getActivePluginRegistryVersion.mockReturnValue(1);
8084});
81858286it("rejects invalid params", async () => {
@@ -167,6 +171,93 @@ describe("tools.effective handler", () => {
167171);
168172});
169173174+it("serves repeated requests from the fresh inventory cache", async () => {
175+const first = createInvokeParams({ sessionKey: "main:abc" });
176+await first.invoke();
177+const second = createInvokeParams({ sessionKey: "main:abc" });
178+await second.invoke();
179+180+expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);
181+expect((first.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);
182+expect((second.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);
183+});
184+185+it("coalesces identical cache misses while inventory resolution is pending", async () => {
186+const first = createInvokeParams({ sessionKey: "main:abc" });
187+const second = createInvokeParams({ sessionKey: "main:abc" });
188+189+await Promise.all([first.invoke(), second.invoke()]);
190+191+expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);
192+expect((first.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);
193+expect((second.respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);
194+});
195+196+it("returns stale cached inventory immediately while refreshing in the background", async () => {
197+let now = 1_000;
198+__testing.setToolsEffectiveNowForTest(() => now);
199+const stalePayload = {
200+agentId: "main",
201+profile: "coding",
202+groups: [
203+{
204+id: "core",
205+label: "Built-in tools",
206+source: "core",
207+tools: [
208+{
209+id: "read",
210+label: "Read",
211+description: "Read files",
212+rawDescription: "Read files",
213+source: "core",
214+},
215+],
216+},
217+],
218+};
219+const refreshedPayload = {
220+agentId: "main",
221+profile: "coding",
222+groups: [
223+{
224+id: "core",
225+label: "Built-in tools",
226+source: "core",
227+tools: [
228+{
229+id: "exec",
230+label: "Exec",
231+description: "Run shell commands",
232+rawDescription: "Run shell commands",
233+source: "core",
234+},
235+],
236+},
237+],
238+};
239+runtimeMocks.resolveEffectiveToolInventory
240+.mockReturnValueOnce(stalePayload)
241+.mockReturnValueOnce(refreshedPayload);
242+243+const initial = createInvokeParams({ sessionKey: "main:abc" });
244+await initial.invoke();
245+now += 11_000;
246+247+const stale = createInvokeParams({ sessionKey: "main:abc" });
248+await stale.invoke();
249+250+expect((stale.respond.mock.calls[0] as RespondCall | undefined)?.[1]).toBe(stalePayload);
251+expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(1);
252+253+await new Promise<void>((resolve) => setImmediate(resolve));
254+expect(runtimeMocks.resolveEffectiveToolInventory).toHaveBeenCalledTimes(2);
255+256+const fresh = createInvokeParams({ sessionKey: "main:abc" });
257+await fresh.invoke();
258+expect((fresh.respond.mock.calls[0] as RespondCall | undefined)?.[1]).toBe(refreshedPayload);
259+});
260+170261it("falls back to origin.threadId when delivery context omits thread metadata", async () => {
171262runtimeMocks.loadSessionEntry.mockReturnValueOnce({
172263cfg: {},
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。