add prependSystemContext and appendSystemContext to before_prompt_build (fixes #35131) (#35177)

Merged via squash.

Prepared head SHA: d9a2869ad6
Co-authored-by: maweibin <18023423+maweibin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
maweibin
2026-03-06 02:06:59 +08:00
committed by GitHub
parent 174eeea76c
commit 09c68f8f0e
11 changed files with 265 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import {
composeSystemPromptWithHookContext,
isOllamaCompatProvider,
resolveAttemptFsWorkspaceOnly,
resolveOllamaBaseUrlForRun,
@@ -54,6 +55,8 @@ describe("resolvePromptBuildHookResult", () => {
expect(result).toEqual({
prependContext: "from-cache",
systemPrompt: "legacy-system",
prependSystemContext: undefined,
appendSystemContext: undefined,
});
});
@@ -71,6 +74,58 @@ describe("resolvePromptBuildHookResult", () => {
expect(hookRunner.runBeforeAgentStart).toHaveBeenCalledWith({ prompt: "hello", messages }, {});
expect(result.prependContext).toBe("from-hook");
});
it("merges prompt-build and legacy context fields in deterministic order", async () => {
const hookRunner = {
hasHooks: vi.fn(() => true),
runBeforePromptBuild: vi.fn(async () => ({
prependContext: "prompt context",
prependSystemContext: "prompt prepend",
appendSystemContext: "prompt append",
})),
runBeforeAgentStart: vi.fn(async () => ({
prependContext: "legacy context",
prependSystemContext: "legacy prepend",
appendSystemContext: "legacy append",
})),
};
const result = await resolvePromptBuildHookResult({
prompt: "hello",
messages: [],
hookCtx: {},
hookRunner,
});
expect(result.prependContext).toBe("prompt context\n\nlegacy context");
expect(result.prependSystemContext).toBe("prompt prepend\n\nlegacy prepend");
expect(result.appendSystemContext).toBe("prompt append\n\nlegacy append");
});
});
describe("composeSystemPromptWithHookContext", () => {
it("returns undefined when no hook system context is provided", () => {
expect(composeSystemPromptWithHookContext({ baseSystemPrompt: "base" })).toBeUndefined();
});
it("builds prepend/base/append system prompt order", () => {
expect(
composeSystemPromptWithHookContext({
baseSystemPrompt: " base system ",
prependSystemContext: " prepend ",
appendSystemContext: " append ",
}),
).toBe("prepend\n\nbase system\n\nappend");
});
it("avoids blank separators when base system prompt is empty", () => {
expect(
composeSystemPromptWithHookContext({
baseSystemPrompt: " ",
appendSystemContext: " append only ",
}),
).toBe("append only");
});
});
describe("resolvePromptModeForSession", () => {

View File

@@ -19,6 +19,7 @@ import type {
PluginHookBeforePromptBuildResult,
} from "../../../plugins/types.js";
import { isSubagentSessionKey } from "../../../routing/session-key.js";
import { joinPresentTextSegments } from "../../../shared/text/join-segments.js";
import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
@@ -567,12 +568,37 @@ export async function resolvePromptBuildHookResult(params: {
: undefined);
return {
systemPrompt: promptBuildResult?.systemPrompt ?? legacyResult?.systemPrompt,
prependContext: [promptBuildResult?.prependContext, legacyResult?.prependContext]
.filter((value): value is string => Boolean(value))
.join("\n\n"),
prependContext: joinPresentTextSegments([
promptBuildResult?.prependContext,
legacyResult?.prependContext,
]),
prependSystemContext: joinPresentTextSegments([
promptBuildResult?.prependSystemContext,
legacyResult?.prependSystemContext,
]),
appendSystemContext: joinPresentTextSegments([
promptBuildResult?.appendSystemContext,
legacyResult?.appendSystemContext,
]),
};
}
export function composeSystemPromptWithHookContext(params: {
baseSystemPrompt?: string;
prependSystemContext?: string;
appendSystemContext?: string;
}): string | undefined {
const prependSystem = params.prependSystemContext?.trim();
const appendSystem = params.appendSystemContext?.trim();
if (!prependSystem && !appendSystem) {
return undefined;
}
return joinPresentTextSegments(
[params.prependSystemContext, params.baseSystemPrompt, params.appendSystemContext],
{ trim: true },
);
}
export function resolvePromptModeForSession(sessionKey?: string): "minimal" | "full" {
if (!sessionKey) {
return "full";
@@ -1522,6 +1548,20 @@ export async function runEmbeddedAttempt(
systemPromptText = legacySystemPrompt;
log.debug(`hooks: applied systemPrompt override (${legacySystemPrompt.length} chars)`);
}
const prependedOrAppendedSystemPrompt = composeSystemPromptWithHookContext({
baseSystemPrompt: systemPromptText,
prependSystemContext: hookResult?.prependSystemContext,
appendSystemContext: hookResult?.appendSystemContext,
});
if (prependedOrAppendedSystemPrompt) {
const prependSystemLen = hookResult?.prependSystemContext?.trim().length ?? 0;
const appendSystemLen = hookResult?.appendSystemContext?.trim().length ?? 0;
applySystemPromptOverrideToSession(activeSession, prependedOrAppendedSystemPrompt);
systemPromptText = prependedOrAppendedSystemPrompt;
log.debug(
`hooks: applied prependSystemContext/appendSystemContext (${prependSystemLen}+${appendSystemLen} chars)`,
);
}
}
log.debug(`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`);