


























@@ -11,6 +11,7 @@ import {
1111} from "../../context-engine/registry.js";
1212import type { ContextEngine } from "../../context-engine/types.js";
1313import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
14+import { clearMemoryPluginState, registerMemoryPromptSection } from "../../plugins/memory-state.js";
1415import { testing as cliBackendsTesting } from "../cli-backends.js";
1516import { hashCliSessionText } from "../cli-session.js";
1617import { buildActiveImageGenerationTaskPromptContextForSession } from "../image-generation-task-status.js";
@@ -197,6 +198,7 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
197198getActiveMcpLoopbackRuntime: vi.fn(() => undefined),
198199ensureMcpLoopbackServer: vi.fn(createTestMcpLoopbackServer),
199200createMcpLoopbackServerConfig: vi.fn(createTestMcpLoopbackServerConfig),
201+resolveMcpLoopbackScopedTools: vi.fn(() => ({ agentId: "main", tools: [] })),
200202resolveOpenClawReferencePaths: vi.fn(async () => ({ docsPath: null, sourcePath: null })),
201203});
202204mockGetGlobalHookRunner.mockReturnValue(null);
@@ -213,6 +215,7 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
213215mockBuildActiveImageGenerationTaskPromptContextForSession.mockReset();
214216mockBuildActiveVideoGenerationTaskPromptContextForSession.mockReset();
215217mockBuildActiveMusicGenerationTaskPromptContextForSession.mockReset();
218+clearMemoryPluginState();
216219vi.unstubAllEnvs();
217220});
218221@@ -987,6 +990,178 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
987990}
988991});
989992993+it("uses loopback-scoped tools when building bundled MCP CLI prompts", async () => {
994+const { dir, sessionFile } = createSessionFile();
995+try {
996+registerMemoryPromptSection(({ availableTools }) =>
997+availableTools.has("memory_search")
998+ ? ["## Memory Recall", `tools=${[...availableTools].toSorted().join(",")}`, ""]
999+ : [],
1000+);
1001+const getActiveMcpLoopbackRuntime = vi.fn(() => ({
1002+port: 31783,
1003+ownerToken: "owner-token",
1004+nonOwnerToken: "non-owner-token",
1005+}));
1006+const ensureMcpLoopbackServer = vi.fn(createTestMcpLoopbackServer);
1007+const createMcpLoopbackServerConfig = vi.fn(createTestMcpLoopbackServerConfig);
1008+const resolveMcpLoopbackScopedTools = vi.fn(() => ({
1009+agentId: "main",
1010+tools: [
1011+{
1012+name: "memory_search",
1013+label: "Memory Search",
1014+description: "Search memory",
1015+parameters: { type: "object", properties: {} },
1016+execute: vi.fn(),
1017+},
1018+],
1019+}));
1020+setCliRunnerPrepareTestDeps({
1021+ getActiveMcpLoopbackRuntime,
1022+ ensureMcpLoopbackServer,
1023+ createMcpLoopbackServerConfig,
1024+ resolveMcpLoopbackScopedTools,
1025+});
1026+cliBackendsTesting.setDepsForTest({
1027+resolvePluginSetupCliBackend: () => undefined,
1028+resolveRuntimeCliBackends: () => [
1029+{
1030+id: "native-cli",
1031+pluginId: "native-plugin",
1032+bundleMcp: true,
1033+bundleMcpMode: "claude-config-file",
1034+config: {
1035+command: "native-cli",
1036+args: ["--print"],
1037+systemPromptArg: "--system-prompt",
1038+systemPromptWhen: "first",
1039+output: "text",
1040+input: "arg",
1041+sessionMode: "existing",
1042+},
1043+},
1044+],
1045+});
1046+1047+const context = await prepareCliRunContext({
1048+sessionId: "session-test",
1049+sessionKey: "agent:main:test",
1050+ sessionFile,
1051+workspaceDir: dir,
1052+prompt: "latest ask",
1053+provider: "native-cli",
1054+model: "test-model",
1055+timeoutMs: 1_000,
1056+runId: "run-test-loopback-prompt-tools",
1057+config: createCliBackendConfig({ bundleMcp: true, systemPromptOverride: null }),
1058+cliSessionBinding: {
1059+sessionId: "cli-session",
1060+promptToolNamesHash: "old-tool-surface",
1061+},
1062+});
1063+1064+expect(resolveMcpLoopbackScopedTools).toHaveBeenCalledWith({
1065+cfg: expect.any(Object),
1066+sessionKey: "agent:main:test",
1067+messageProvider: undefined,
1068+accountId: undefined,
1069+inboundEventKind: undefined,
1070+senderIsOwner: undefined,
1071+});
1072+expect(context.systemPrompt).toContain("## Memory Recall");
1073+expect(context.systemPrompt).toContain("tools=memory_search");
1074+expect(context.systemPromptReport.tools.entries.map((entry) => entry.name)).toEqual([
1075+"memory_search",
1076+]);
1077+expect(context.promptToolNamesHash).toBe(
1078+hashCliSessionText(JSON.stringify(["memory_search"])),
1079+);
1080+expect(context.reusableCliSession).toEqual({ invalidatedReason: "system-prompt" });
1081+} finally {
1082+fs.rmSync(dir, { recursive: true, force: true });
1083+}
1084+});
1085+1086+it("does not advertise loopback prompt tools when the runtime is unavailable", async () => {
1087+const { dir, sessionFile } = createSessionFile();
1088+try {
1089+registerMemoryPromptSection(({ availableTools }) =>
1090+availableTools.has("memory_search")
1091+ ? ["## Memory Recall", `tools=${[...availableTools].toSorted().join(",")}`, ""]
1092+ : [],
1093+);
1094+const getActiveMcpLoopbackRuntime = vi.fn(() => undefined);
1095+const ensureMcpLoopbackServer = vi.fn(async () => {
1096+throw new Error("loopback unavailable");
1097+});
1098+const createMcpLoopbackServerConfig = vi.fn(createTestMcpLoopbackServerConfig);
1099+const resolveMcpLoopbackScopedTools = vi.fn(() => ({
1100+agentId: "main",
1101+tools: [
1102+{
1103+name: "memory_search",
1104+label: "Memory Search",
1105+description: "Search memory",
1106+parameters: { type: "object", properties: {} },
1107+execute: vi.fn(),
1108+},
1109+],
1110+}));
1111+setCliRunnerPrepareTestDeps({
1112+ getActiveMcpLoopbackRuntime,
1113+ ensureMcpLoopbackServer,
1114+ createMcpLoopbackServerConfig,
1115+ resolveMcpLoopbackScopedTools,
1116+});
1117+cliBackendsTesting.setDepsForTest({
1118+resolvePluginSetupCliBackend: () => undefined,
1119+resolveRuntimeCliBackends: () => [
1120+{
1121+id: "native-cli",
1122+pluginId: "native-plugin",
1123+bundleMcp: true,
1124+bundleMcpMode: "claude-config-file",
1125+config: {
1126+command: "native-cli",
1127+args: ["--print"],
1128+systemPromptArg: "--system-prompt",
1129+systemPromptWhen: "first",
1130+output: "text",
1131+input: "arg",
1132+sessionMode: "existing",
1133+},
1134+},
1135+],
1136+});
1137+1138+const context = await prepareCliRunContext({
1139+sessionId: "session-test",
1140+sessionKey: "agent:main:test",
1141+ sessionFile,
1142+workspaceDir: dir,
1143+prompt: "latest ask",
1144+provider: "native-cli",
1145+model: "test-model",
1146+timeoutMs: 1_000,
1147+runId: "run-test-loopback-prompt-tools-fallback",
1148+config: createCliBackendConfig({ bundleMcp: true, systemPromptOverride: null }),
1149+});
1150+1151+expect(ensureMcpLoopbackServer).toHaveBeenCalledTimes(1);
1152+expect(getActiveMcpLoopbackRuntime).toHaveBeenCalledTimes(2);
1153+expect(createMcpLoopbackServerConfig).not.toHaveBeenCalled();
1154+expect(resolveMcpLoopbackScopedTools).not.toHaveBeenCalled();
1155+expect(context.systemPrompt).not.toContain("## Memory Recall");
1156+expect(context.systemPrompt).not.toContain("memory_search");
1157+expect(context.systemPromptReport.tools.entries).toEqual([]);
1158+expect(context.promptToolNamesHash).toBeUndefined();
1159+expect(context.preparedBackend.env).toBeUndefined();
1160+} finally {
1161+fs.rmSync(dir, { recursive: true, force: true });
1162+}
1163+});
1164+9901165it("passes current turn kind into bundle MCP loopback env", async () => {
9911166const { dir, sessionFile } = createSessionFile();
9921167try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。