From 1bf083dd04d03833f7f1247d9ddcd842bbf5425e Mon Sep 17 00:00:00 2001 From: kartik-mem0 Date: Thu, 10 Sep 2026 21:03:19 +0530 Subject: [PATCH] build(plugins): pin retained runtime and cover handoff error paths --- .../build/handoff-runtime.json | 2 +- .../core/handoff-runtime.json | 2 +- .../core/handoff-runtime.json | 2 +- .../codex-plugin/core/handoff-runtime.json | 2 +- .../cursor-plugin/core/handoff-runtime.json | 2 +- .../kimi-plugin/core/handoff-runtime.json | 2 +- .../core/handoff-runtime.json | 2 +- integrations/openclaw/skill-loader.test.ts | 4 ++ integrations/openclaw/tests/handoff.test.ts | 61 ++++++++++++++++++- 9 files changed, 69 insertions(+), 10 deletions(-) diff --git a/integrations/agent-plugin-core/build/handoff-runtime.json b/integrations/agent-plugin-core/build/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/agent-plugin-core/build/handoff-runtime.json +++ b/integrations/agent-plugin-core/build/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/antigravity-plugin/core/handoff-runtime.json b/integrations/antigravity-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/antigravity-plugin/core/handoff-runtime.json +++ b/integrations/antigravity-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/claude-code-plugin/core/handoff-runtime.json b/integrations/claude-code-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/claude-code-plugin/core/handoff-runtime.json +++ b/integrations/claude-code-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/codex-plugin/core/handoff-runtime.json b/integrations/codex-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/codex-plugin/core/handoff-runtime.json +++ b/integrations/codex-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/cursor-plugin/core/handoff-runtime.json b/integrations/cursor-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/cursor-plugin/core/handoff-runtime.json +++ b/integrations/cursor-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/kimi-plugin/core/handoff-runtime.json b/integrations/kimi-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/kimi-plugin/core/handoff-runtime.json +++ b/integrations/kimi-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/mem0-agent-plugin/core/handoff-runtime.json b/integrations/mem0-agent-plugin/core/handoff-runtime.json index 8ae267e01..1c1587d63 100644 --- a/integrations/mem0-agent-plugin/core/handoff-runtime.json +++ b/integrations/mem0-agent-plugin/core/handoff-runtime.json @@ -1,5 +1,5 @@ { - "revision": "d33b6604a4099c951207af9633d0bf0ddd20ed28", + "revision": "59939c003f6b3eb8add709e5897f7bfbe3e9f4d8", "files": { "handoff_engine.py": "5786e4f24e1145ce26867d18c78fafc8e3de4097b5494815073a2e09df15ea11", "handoff_sources.py": "dee34ca5a6cd591e2468b10108233adde3de0f7f2858e0b864f88ae6bed5e5f9" diff --git a/integrations/openclaw/skill-loader.test.ts b/integrations/openclaw/skill-loader.test.ts index 91a7b1ce7..0b22f1663 100644 --- a/integrations/openclaw/skill-loader.test.ts +++ b/integrations/openclaw/skill-loader.test.ts @@ -131,6 +131,10 @@ describe("loadCompactTriagePrompt", () => { }); expect(prompt).toContain("No automatic recall happens in manual mode."); + const fullPrompt = loadTriagePrompt({recall: {strategy: "manual"}}); + expect(fullPrompt).toContain("No automatic recall happens in manual mode."); + expect(fullPrompt).toContain(SEARCH_GUIDANCE); + expect(fullPrompt).not.toContain("ALWAYS rewrite"); }); it("includes short config summaries and truncates oversized custom rules", () => { diff --git a/integrations/openclaw/tests/handoff.test.ts b/integrations/openclaw/tests/handoff.test.ts index e1356550f..6e3303e1f 100644 --- a/integrations/openclaw/tests/handoff.test.ts +++ b/integrations/openclaw/tests/handoff.test.ts @@ -6,14 +6,14 @@ import {runNativeSession, runHandoffAction} from "../../agent-plugin-core/typesc import {registerHandoffCommand} from "../tools/handoff.ts"; vi.mock("../../agent-plugin-core/typescript/src/handoff.ts", async original => ({...await original(), runNativeSession: vi.fn(), runHandoffAction: vi.fn()})); let dir: string; -beforeEach(async () => {vi.clearAllMocks(); dir = await mkdtemp(join(tmpdir(), "openclaw-handoff-"));}); +beforeEach(async () => {vi.resetAllMocks(); dir = await mkdtemp(join(tmpdir(), "openclaw-handoff-"));}); afterEach(async () => {await rm(dir, {recursive: true, force: true});}); -function setup() { +function setup(context: {workspaceDir?: string} = {workspaceDir: "/tmp/native-project"}) { const registerCommand = vi.fn(); const registerTool = vi.fn(); const hooks = new Map(); registerHandoffCommand({registerCommand, registerTool, on: (name: string, handler: any) => hooks.set(name, handler)} as any); - return {cmd: registerCommand.mock.calls[0][0], tool: registerTool.mock.calls[0][0]({workspaceDir: "/tmp/native-project"}), hooks}; + return {cmd: registerCommand.mock.calls[0][0], tool: registerTool.mock.calls[0][0](context), hooks}; } it("saves the trusted current OpenClaw transcript from a user-only command", async () => { const {cmd} = setup(); @@ -61,3 +61,58 @@ it("fails explicitly on unavailable context or resource errors", async () => { expect((await cmd.handler({sessionFile: "/tmp/native.jsonl"})).text).toContain("invalid native transcript"); expect((await cmd.handler({args: "resume /tmp/shared.json", sessionFile: "/tmp/native.jsonl"})).text).toContain("identity is unavailable"); }); + +it("lists resources from the transcript project without queuing prompt context", async () => { + const {cmd, hooks} = setup(); + const sessionFile = join(dir, "native.jsonl"); + await writeFile(sessionFile, JSON.stringify({type: "session", cwd: dir}) + "\n"); + vi.mocked(runHandoffAction).mockResolvedValue("Available shared resource"); + expect(await cmd.handler({args: "list", sessionFile, sessionId: "native"})).toEqual({text: "Available shared resource"}); + expect(runHandoffAction).toHaveBeenCalledWith(expect.any(URL), "list", dir, undefined); + expect(hooks.get("before_prompt_build")({}, {sessionId: "native"})).toBeUndefined(); +}); +it.each([ + ["empty transcript", ""], + ["non-session header", JSON.stringify({type: "message", cwd: "/tmp"})], + ["missing cwd", JSON.stringify({type: "session"})], + ["relative cwd", JSON.stringify({type: "session", cwd: "relative/project"})], +])("rejects %s before accessing shared resources", async (_description, content) => { + const {cmd, hooks} = setup(); + const sessionFile = join(dir, "invalid.jsonl"); + await writeFile(sessionFile, content); + const result = await cmd.handler({args: "resume /tmp/shared.json", sessionFile, sessionId: "native"}); + expect(result.text).toContain("project directory is unavailable"); + expect(runHandoffAction).not.toHaveBeenCalled(); + expect(hooks.get("before_prompt_build")({}, {sessionId: "native"})).toBeUndefined(); +}); +it("reports a missing transcript rather than guessing a project", async () => { + const {cmd} = setup(); + expect((await cmd.handler({args: "list", sessionFile: join(dir, "missing.jsonl")})).text).toContain("ENOENT"); + expect(runHandoffAction).not.toHaveBeenCalled(); +}); +it("rejects tools without a native workspace and unsupported actions", async () => { + const missing = setup({}).tool; + expect(await missing.execute("call", {action: "list"})).toMatchObject({isError: true, content: [{text: expect.stringContaining("project directory is unavailable")}]}); + const {tool} = setup(); + expect(await tool.execute("call", {action: "save"})).toMatchObject({isError: true, content: [{text: expect.stringContaining("Choose list or resume")}]}); + expect(runHandoffAction).not.toHaveBeenCalled(); +}); +it("lists through the tool without requiring a resource path", async () => { + const {tool} = setup(); + vi.mocked(runHandoffAction).mockResolvedValue("No shared resources"); + expect(await tool.execute("call", {action: "list"})).toEqual({content: [{type: "text", text: "No shared resources"}]}); + expect(runHandoffAction).toHaveBeenCalledWith(expect.any(URL), "list", "/tmp/native-project", undefined); +}); +it.each([new Error("Resource cannot be read"), "Resource cannot be read"])("surfaces resource failures as tool errors: %s", async (error) => { + const {tool} = setup(); + vi.mocked(runHandoffAction).mockRejectedValue(error); + expect(await tool.execute("call", {action: "resume", resource: "/tmp/shared.json"})).toEqual({isError: true, content: [{type: "text", text: "Session handoff failed: Resource cannot be read"}]}); +}); +it("reports a non-Error command failure without injecting context", async () => { + const {cmd, hooks} = setup(); + const sessionFile = join(dir, "native.jsonl"); + await writeFile(sessionFile, JSON.stringify({type: "session", cwd: dir}) + "\n"); + vi.mocked(runHandoffAction).mockRejectedValue("Resource cannot be read"); + expect(await cmd.handler({args: "resume /tmp/shared.json", sessionFile, sessionId: "native"})).toEqual({text: "Session handoff failed: Resource cannot be read"}); + expect(hooks.get("before_prompt_build")({}, {sessionId: "native"})).toBeUndefined(); +});