
























1+// Integration proof for tools.effective global sessions scoped to non-default agents.
2+import path from "node:path";
3+import { beforeEach, describe, expect, it, vi } from "vitest";
4+import { installGatewayTestHooks, testState, writeSessionStore } from "../test-helpers.js";
5+import { getGatewayConfigModule, sessionStoreEntry } from "../test/server-sessions.test-helpers.js";
6+import { testing, toolsEffectiveHandlers } from "./tools-effective.js";
7+8+const inventoryMocks = vi.hoisted(() => ({
9+resolveEffectiveToolInventory: vi.fn(
10+(params: { agentId: string; modelProvider?: string; modelId?: string }) => ({
11+agentId: params.agentId,
12+profile: "coding",
13+groups: [
14+{
15+id: "core",
16+label: "Built-in tools",
17+source: "core",
18+tools: [
19+{
20+id: "exec",
21+label: "Exec",
22+description: "Run shell commands",
23+source: "core",
24+},
25+],
26+},
27+],
28+modelProvider: params.modelProvider,
29+modelId: params.modelId,
30+}),
31+),
32+resolveEffectiveToolInventoryRuntimeModelContext: vi.fn(() => ({
33+modelApi: "openai-responses",
34+runtimeModel: {
35+id: "work-model",
36+name: "Work model",
37+provider: "openai",
38+api: "openai-responses",
39+baseUrl: "https://api.openai.com/v1",
40+},
41+})),
42+}));
43+44+vi.mock("./tools-effective.runtime.js", async (importOriginal) => {
45+const actual = await importOriginal<typeof import("./tools-effective.runtime.js")>();
46+return {
47+ ...actual,
48+resolveEffectiveToolInventory: inventoryMocks.resolveEffectiveToolInventory,
49+resolveEffectiveToolInventoryRuntimeModelContext:
50+inventoryMocks.resolveEffectiveToolInventoryRuntimeModelContext,
51+peekSessionMcpRuntime: vi.fn(() => undefined),
52+resolveSessionMcpConfigSummary: vi.fn(() => ({ fingerprint: "mcp:0", serverNames: [] })),
53+buildBundleMcpToolsFromCatalog: vi.fn(() => []),
54+applyFinalEffectiveToolPolicy: vi.fn(
55+(params: { bundledTools: unknown[] }) => params.bundledTools,
56+),
57+getActivePluginRegistryVersion: vi.fn(() => 1),
58+getActivePluginChannelRegistryVersion: vi.fn(() => 1),
59+};
60+});
61+62+installGatewayTestHooks();
63+64+describe("tools.effective global agent integration", () => {
65+let mainStorePath = "";
66+let workStorePath = "";
67+let getRuntimeConfig: Awaited<ReturnType<typeof getGatewayConfigModule>>["getRuntimeConfig"];
68+69+async function seedSelectedGlobalStores() {
70+const stateDir = process.env.OPENCLAW_STATE_DIR;
71+if (!stateDir) {
72+throw new Error("OPENCLAW_STATE_DIR is required");
73+}
74+const dir = path.join(stateDir, "session-stores", `tools-effective-${Date.now()}`);
75+const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
76+testState.sessionStorePath = storeTemplate;
77+testState.sessionConfig = { scope: "global" };
78+testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
79+mainStorePath = storeTemplate.replace("{agentId}", "main");
80+workStorePath = storeTemplate.replace("{agentId}", "work");
81+const configModule = await getGatewayConfigModule();
82+configModule.clearRuntimeConfigSnapshot();
83+configModule.clearConfigCache();
84+getRuntimeConfig = configModule.getRuntimeConfig;
85+}
86+87+beforeEach(async () => {
88+testing.resetToolsEffectiveCacheForTest();
89+vi.clearAllMocks();
90+await seedSelectedGlobalStores();
91+});
92+93+it("resolves tools.effective for global session scoped to a non-default agent store", async () => {
94+await writeSessionStore({
95+storePath: mainStorePath,
96+entries: {
97+global: sessionStoreEntry("sess-main-global", {
98+modelProvider: "openai",
99+model: "main-model",
100+}),
101+},
102+});
103+await writeSessionStore({
104+storePath: workStorePath,
105+agentId: "work",
106+entries: {
107+global: sessionStoreEntry("sess-work-global", {
108+modelProvider: "openai",
109+model: "work-model",
110+}),
111+},
112+});
113+114+const respond = vi.fn();
115+await toolsEffectiveHandlers["tools.effective"]({
116+params: { sessionKey: "global", agentId: "work" },
117+respond: respond as never,
118+context: { getRuntimeConfig } as never,
119+client: null,
120+req: { type: "req", id: "req-tools-effective-global", method: "tools.effective" },
121+isWebchatConnect: () => false,
122+});
123+124+const call = respond.mock.calls[0] as [boolean, { agentId?: string }?, unknown?] | undefined;
125+expect(call?.[0]).toBe(true);
126+expect(call?.[1]?.agentId).toBe("work");
127+expect(inventoryMocks.resolveEffectiveToolInventory).toHaveBeenCalledWith(
128+expect.objectContaining({
129+agentId: "work",
130+sessionKey: "global",
131+modelProvider: "openai",
132+modelId: "work-model",
133+}),
134+);
135+});
136+137+// Negative control on the real session-resolution path: a non-global key owned
138+// by `main` must keep rejecting a mismatched configured agent. Before the
139+// ownership-narrowing fix the requested agent overrode session-agent resolution
140+// here, so this request would have succeeded under `work`.
141+it("rejects a mismatched configured agent for a non-global session key", async () => {
142+await seedNonGlobalMainStore();
143+144+await writeSessionStore({
145+storePath: mainStorePath,
146+entries: {
147+"agent:main:abc": sessionStoreEntry("sess-main-agent", {
148+modelProvider: "openai",
149+model: "main-model",
150+}),
151+},
152+});
153+154+const respond = vi.fn();
155+await toolsEffectiveHandlers["tools.effective"]({
156+params: { sessionKey: "agent:main:abc", agentId: "work" },
157+respond: respond as never,
158+context: { getRuntimeConfig } as never,
159+client: null,
160+req: { type: "req", id: "req-tools-effective-mismatch", method: "tools.effective" },
161+isWebchatConnect: () => false,
162+});
163+164+const call = respond.mock.calls[0] as
165+| [boolean, unknown?, { code: number; message: string }?]
166+| undefined;
167+expect(call?.[0]).toBe(false);
168+expect(call?.[2]?.message).toBe('agent id "work" does not match session agent "main"');
169+expect(inventoryMocks.resolveEffectiveToolInventory).not.toHaveBeenCalled();
170+});
171+172+async function seedNonGlobalMainStore() {
173+const stateDir = process.env.OPENCLAW_STATE_DIR;
174+if (!stateDir) {
175+throw new Error("OPENCLAW_STATE_DIR is required");
176+}
177+const dir = path.join(stateDir, "session-stores", `tools-effective-nonglobal-${Date.now()}`);
178+const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
179+testState.sessionStorePath = storeTemplate;
180+testState.sessionConfig = undefined;
181+testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
182+mainStorePath = storeTemplate.replace("{agentId}", "main");
183+const configModule = await getGatewayConfigModule();
184+configModule.clearRuntimeConfigSnapshot();
185+configModule.clearConfigCache();
186+getRuntimeConfig = configModule.getRuntimeConfig;
187+}
188+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。