diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 59827b2af..2ec3d9bd4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixed +- Fixed a redundant system prompt update (tools, rules) when an extension set the same active tools in a different order, for example a reversed `pi.setActiveTools()` list. Tools that stay active now keep their order and newly activated tools are appended ([#11495](https://github.com/earendil-works/pi/issues/11495)) - Fixed `pi install` and `pi remove` adding ever deeper nested entries to the npm `package-lock.json` when the agent directory is reached through a symlink, such as `~/.pi` linked into a dotfiles repo. pi now passes the resolved real path to npm ([#11358](https://github.com/earendil-works/pi/issues/11358)) - MCP sign-in now rejects authorization endpoints and other OAuth metadata URLs that are not `http:` or `https:`. Before, a malicious MCP server could supply a `file:` or custom protocol URL, which pi passed to the platform URL handler. - Automatic `fd` and `rg` downloads no longer fetch x86-64 binaries on ARM32 and other architectures without a prebuilt release. pi now warns and recommends installing the tool with the system package manager ([#11209](https://github.com/earendil-works/pi/issues/11209)) diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 3a9174cf6..c1529f019 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -1591,9 +1591,14 @@ export class AgentSession { * the registered, non-hidden ones; they are declared to the model. Active tools with a * `prepareLoadout` hook can change the declared descriptions and hide declarations from * requests (see {@link _installHiddenDeclarationsProjection}). + * + * The active tools are a set: tools that stay active keep their current order and newly active + * ones are appended, so reordering the same names does not change the prompt (#11495). */ private _applyToolLoadout(toolNames: string[]): AgentTool[] { - const tools = [...new Set(toolNames)].flatMap((name) => { + const requested = new Set(toolNames); + const kept = this.getActiveToolNames().filter((name) => requested.has(name)); + const tools = [...new Set([...kept, ...requested])].flatMap((name) => { const tool = this._toolRegistry.get(name); return tool && this._getToolExposure(name) !== "hidden" && this._isActivatable(name) ? [tool] : []; }); diff --git a/packages/coding-agent/test/system-prompt-updates.test.ts b/packages/coding-agent/test/system-prompt-updates.test.ts index a55c493d9..bbe5fb9f0 100644 --- a/packages/coding-agent/test/system-prompt-updates.test.ts +++ b/packages/coding-agent/test/system-prompt-updates.test.ts @@ -53,6 +53,23 @@ describe("system prompt updates", () => { } }); + // #11495 + test("does not patch the prompt when the active tools are only reordered", async () => { + const harness = await createHarness(); + try { + harness.setResponses([fauxAssistantMessage("first"), fauxAssistantMessage("second")]); + await harness.session.prompt("one"); + const before = harness.session.getActiveToolNames(); + harness.session.setActiveToolsByName([...before].reverse()); + expect(harness.session.getActiveToolNames()).toEqual(before); + await harness.session.prompt("two"); + const systemMessages = harness.session.messages.filter((message) => message.role === "system"); + expect(systemMessages).toHaveLength(1); + } finally { + harness.cleanup(); + } + }); + test("opens a transcript without a system message and declares the prompt on the first request", async () => { const tempDir = mkdtempSync(join(tmpdir(), "pi-system-prompt-migration-")); try {