diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ca35e42c1..caafbcef16a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai - Plugins/before_prompt_build system-context fields: add `prependSystemContext` and `appendSystemContext` so static plugin guidance can be placed in system prompt space for provider caching and lower repeated prompt token cost. (#35177) thanks @maweibin. - Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails. (#35094) Thanks @joshavant. - Plugins/hook policy: add `plugins.entries..hooks.allowPromptInjection`, validate unknown typed hook names at runtime, and preserve legacy `before_agent_start` model/provider overrides while stripping prompt-mutating fields when prompt injection is disabled. (#36567) thanks @gumadeiras. +- Tools/Diffs guidance: restore a short system-prompt hint for enabled diffs while keeping the detailed instructions in the companion skill, so diffs usage guidance stays out of user-prompt space. Thanks @gumadeiras. ### Breaking diff --git a/docs/tools/diffs.md b/docs/tools/diffs.md index eb9706338f8..6207366034e 100644 --- a/docs/tools/diffs.md +++ b/docs/tools/diffs.md @@ -10,7 +10,7 @@ read_when: # Diffs -`diffs` is an optional plugin tool and companion skill that turns change content into a read-only diff artifact for agents. +`diffs` is an optional plugin tool with short built-in system guidance and a companion skill that turns change content into a read-only diff artifact for agents. It accepts either: @@ -23,6 +23,8 @@ It can return: - a rendered file path (PNG or PDF) for message delivery - both outputs in one call +When enabled, the plugin prepends concise usage guidance into system-prompt space and also exposes a detailed skill for cases where the agent needs fuller instructions. + ## Quick start 1. Enable the plugin. @@ -44,6 +46,29 @@ It can return: } ``` +## Disable built-in system guidance + +If you want to keep the `diffs` tool enabled but disable its built-in system-prompt guidance, set `plugins.entries.diffs.hooks.allowPromptInjection` to `false`: + +```json5 +{ + plugins: { + entries: { + diffs: { + enabled: true, + hooks: { + allowPromptInjection: false, + }, + }, + }, + }, +} +``` + +This blocks the diffs plugin's `before_prompt_build` hook while keeping the plugin, tool, and companion skill available. + +If you want to disable both the guidance and the tool, disable the plugin instead. + ## Typical agent workflow 1. Agent calls `diffs`. diff --git a/extensions/diffs/README.md b/extensions/diffs/README.md index 028835cf561..f1af1792cb8 100644 --- a/extensions/diffs/README.md +++ b/extensions/diffs/README.md @@ -16,7 +16,7 @@ The tool can return: - `details.filePath`: a local rendered artifact path when file rendering is requested - `details.fileFormat`: the rendered file format (`png` or `pdf`) -When the plugin is enabled, it also ships a companion skill from `skills/` that guides when to use `diffs`. This guidance is delivered through normal skill loading, not unconditional prompt-hook injection on every turn. +When the plugin is enabled, it also ships a companion skill from `skills/` and prepends stable tool-usage guidance into system-prompt space via `before_prompt_build`. The hook uses `prependSystemContext`, so the guidance stays out of user-prompt space while still being available every turn. This means an agent can: diff --git a/extensions/diffs/index.test.ts b/extensions/diffs/index.test.ts index 6c7e2555b58..84ce5d9fe87 100644 --- a/extensions/diffs/index.test.ts +++ b/extensions/diffs/index.test.ts @@ -4,7 +4,7 @@ import { createMockServerResponse } from "../../src/test-utils/mock-http-respons import plugin from "./index.js"; describe("diffs plugin registration", () => { - it("registers the tool and http route", () => { + it("registers the tool, http route, and system-prompt guidance hook", async () => { const registerTool = vi.fn(); const registerHttpRoute = vi.fn(); const on = vi.fn(); @@ -43,7 +43,14 @@ describe("diffs plugin registration", () => { auth: "plugin", match: "prefix", }); - expect(on).not.toHaveBeenCalled(); + expect(on).toHaveBeenCalledTimes(1); + expect(on.mock.calls[0]?.[0]).toBe("before_prompt_build"); + const beforePromptBuild = on.mock.calls[0]?.[1]; + const result = await beforePromptBuild?.({}, {}); + expect(result).toMatchObject({ + prependSystemContext: expect.stringContaining("prefer the `diffs` tool"), + }); + expect(result?.prependContext).toBeUndefined(); }); it("applies plugin-config defaults through registered tool and viewer handler", async () => { diff --git a/extensions/diffs/index.ts b/extensions/diffs/index.ts index 8b038b42fcc..b1547b1087d 100644 --- a/extensions/diffs/index.ts +++ b/extensions/diffs/index.ts @@ -7,6 +7,7 @@ import { resolveDiffsPluginSecurity, } from "./src/config.js"; import { createDiffsHttpHandler } from "./src/http.js"; +import { DIFFS_AGENT_GUIDANCE } from "./src/prompt-guidance.js"; import { DiffArtifactStore } from "./src/store.js"; import { createDiffsTool } from "./src/tool.js"; @@ -34,6 +35,9 @@ const plugin = { allowRemoteViewer: security.allowRemoteViewer, }), }); + api.on("before_prompt_build", async () => ({ + prependSystemContext: DIFFS_AGENT_GUIDANCE, + })); }, }; diff --git a/extensions/diffs/src/prompt-guidance.ts b/extensions/diffs/src/prompt-guidance.ts new file mode 100644 index 00000000000..37cbd501261 --- /dev/null +++ b/extensions/diffs/src/prompt-guidance.ts @@ -0,0 +1,7 @@ +export const DIFFS_AGENT_GUIDANCE = [ + "When you need to show edits as a real diff, prefer the `diffs` tool instead of writing a manual summary.", + "It accepts either `before` + `after` text or a unified `patch`.", + "`mode=view` returns `details.viewerUrl` for canvas use; `mode=file` returns `details.filePath`; `mode=both` returns both.", + "If you need to send the rendered file, use the `message` tool with `path` or `filePath`.", + "Include `path` when you know the filename, and omit presentation overrides unless needed.", +].join("\n");