diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3b5d367eb..76b61a5d849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -183,6 +183,7 @@ Docs: https://docs.openclaw.ai - Agents/Transcripts: validate assistant tool-call names (syntax/length + registered tool allowlist) before transcript persistence and during replay sanitization so malformed failover tool names no longer poison sessions with repeated provider HTTP 400 errors. (#23324) Thanks @johnsantry. - Agents/Mistral: sanitize tool-call IDs in the embedded agent loop and generate strict provider-safe pending tool-call IDs, preventing Mistral strict9 `HTTP 400` failures on tool continuations. (#23698) Thanks @echoVic. - Agents/Compaction: strip stale assistant usage snapshots from pre-compaction turns when replaying history after a compaction summary so context-token estimation no longer reuses pre-compaction totals and immediately re-triggers destructive follow-up compactions. (#19127) Thanks @tedwatson. +- Agents/Hooks: honor legacy `before_agent_start` `systemPrompt` values in the embedded prompt-build path so plugin-provided system-prompt overrides are applied instead of being silently ignored. (#13475, #14583) Thanks @yinghaosang and @mushuiyu422. - Agents/Replies: emit a default completion acknowledgement (`✅ Done.`) only for direct/private tool-only completions with no final assistant text, while suppressing synthetic acknowledgements for channel/group sessions and runs that already delivered output via messaging tools. (#22834) Thanks @Oldshue. - Agents/Subagents: honor `tools.subagents.tools.alsoAllow` and explicit subagent `allow` entries when resolving built-in subagent deny defaults, so explicitly granted tools (for example `sessions_send`) are no longer blocked unless re-denied in `tools.subagents.tools.deny`. (#23359) Thanks @goren-beehero. - Agents/Subagents: make announce call timeouts configurable via `agents.defaults.subagents.announceTimeoutMs` and restore a 60s default to prevent false timeout failures on slower announce paths. (#22719) Thanks @Valadon. diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index ae364723a20..e98b3607b30 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -560,7 +560,7 @@ export async function runEmbeddedAttempt( tools, }); const systemPromptOverride = createSystemPromptOverride(appendPrompt); - const systemPromptText = systemPromptOverride(); + let systemPromptText = systemPromptOverride(); const sessionLock = await acquireSessionWriteLock({ sessionFile: params.sessionFile, @@ -1038,6 +1038,13 @@ export async function runEmbeddedAttempt( `hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`, ); } + const legacySystemPrompt = + typeof hookResult?.systemPrompt === "string" ? hookResult.systemPrompt.trim() : ""; + if (legacySystemPrompt) { + applySystemPromptOverrideToSession(activeSession, legacySystemPrompt); + systemPromptText = legacySystemPrompt; + log.debug(`hooks: applied systemPrompt override (${legacySystemPrompt.length} chars)`); + } } log.debug(`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`); diff --git a/src/agents/pi-embedded-runner/system-prompt.test.ts b/src/agents/pi-embedded-runner/system-prompt.test.ts new file mode 100644 index 00000000000..355b2c67ae9 --- /dev/null +++ b/src/agents/pi-embedded-runner/system-prompt.test.ts @@ -0,0 +1,51 @@ +import type { AgentSession } from "@mariozechner/pi-coding-agent"; +import { describe, expect, it, vi } from "vitest"; +import { applySystemPromptOverrideToSession, createSystemPromptOverride } from "./system-prompt.js"; + +function createMockSession() { + const setSystemPrompt = vi.fn(); + const session = { + agent: { setSystemPrompt }, + } as unknown as AgentSession; + return { session, setSystemPrompt }; +} + +describe("applySystemPromptOverrideToSession", () => { + it("applies a string override to the session system prompt", () => { + const { session, setSystemPrompt } = createMockSession(); + const prompt = "You are a helpful assistant with custom context."; + + applySystemPromptOverrideToSession(session, prompt); + + expect(setSystemPrompt).toHaveBeenCalledWith(prompt); + const mutable = session as unknown as { _baseSystemPrompt?: string }; + expect(mutable._baseSystemPrompt).toBe(prompt); + }); + + it("trims whitespace from string overrides", () => { + const { session, setSystemPrompt } = createMockSession(); + + applySystemPromptOverrideToSession(session, " padded prompt "); + + expect(setSystemPrompt).toHaveBeenCalledWith("padded prompt"); + }); + + it("applies a function override to the session system prompt", () => { + const { session, setSystemPrompt } = createMockSession(); + const override = createSystemPromptOverride("function-based prompt"); + + applySystemPromptOverrideToSession(session, override); + + expect(setSystemPrompt).toHaveBeenCalledWith("function-based prompt"); + }); + + it("sets _rebuildSystemPrompt that returns the override", () => { + const { session } = createMockSession(); + applySystemPromptOverrideToSession(session, "rebuild test"); + + const mutable = session as unknown as { + _rebuildSystemPrompt?: (toolNames: string[]) => string; + }; + expect(mutable._rebuildSystemPrompt?.(["tool1"])).toBe("rebuild test"); + }); +});