diff --git a/CHANGELOG.md b/CHANGELOG.md index 4565563e290..2576dbbfaba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Plugins/ACPX: accept an optional `args` array in `agents.` config so paths and flag values containing spaces stay intact when spawning ACP agent processes. Thanks @TheArchitectit and @BunsDev. +- Agents: inject the current provider/model identity into system prompts, including configured prompt overrides and CLI hook prompt replacements, so agents can answer model-identity questions from the actual runtime selection. - Plugins/CLI: add the optional bundled `oc-path` plugin, providing `openclaw path` for surgical `oc://` access to markdown, JSONC, and JSONL workspace files. - Plugins/SDK: add unified model catalog registration for text, image, video, and music providers, including `providerCatalogEntry` manifests, shared media list help, live catalog caching, and per-model video capability overlays. - CLI: make parser, startup, config, guardrail, channel, agent, task, session, and MCP failures explain what happened and point to the next recovery command. diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index f797471efea..c64a8d3d62c 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -273,7 +273,9 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { }); expect(context.params.prompt).toBe("history:2\n\nlatest ask"); - expect(context.systemPrompt).toBe("prepend system\n\nhook system\n\nappend system"); + expect(context.systemPrompt).toBe( + "prepend system\n\nhook system\n\nappend system\n\nCurrent model identity: test-cli/test-model. If asked what model you are, answer with this value for the current run.", + ); expect(hookRunner.runBeforePromptBuild).toHaveBeenCalledWith( { prompt: "latest ask", @@ -441,7 +443,7 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { expect(context.params.prompt).toBe("prompt prepend\n\nlegacy prepend\n\nlatest ask"); expect(context.systemPrompt).toBe( - "prompt prepend system\n\nlegacy prepend system\n\nprompt system\n\nprompt append system\n\nlegacy append system", + "prompt prepend system\n\nlegacy prepend system\n\nprompt system\n\nprompt append system\n\nlegacy append system\n\nCurrent model identity: test-cli/test-model. If asked what model you are, answer with this value for the current run.", ); expect(hookRunner.runBeforePromptBuild).toHaveBeenCalledOnce(); expect(hookRunner.runBeforeAgentStart).toHaveBeenCalledOnce(); @@ -475,7 +477,9 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { }); expect(context.params.prompt).toBe("latest ask"); - expect(context.systemPrompt).toBe("base extra system"); + expect(context.systemPrompt).toBe( + "base extra system\n\nCurrent model identity: test-cli/test-model. If asked what model you are, answer with this value for the current run.", + ); expect(context.systemPrompt).not.toContain("hook exploded"); expect(hookRunner.runBeforePromptBuild).toHaveBeenCalledOnce(); } finally { @@ -570,7 +574,9 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { config: createCliBackendConfig(), }); - expect(context.systemPrompt).toBe("active video task\n\nhook prepend system\n\nhook system"); + expect(context.systemPrompt).toBe( + "active video task\n\nhook prepend system\n\nhook system\n\nCurrent model identity: test-cli/test-model. If asked what model you are, answer with this value for the current run.", + ); expect(mockBuildActiveVideoGenerationTaskPromptContextForSession).toHaveBeenCalledWith( "agent:main:test", ); diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 730a4266572..31680557c07 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -43,6 +43,7 @@ import { applyPluginTextReplacements } from "../plugin-text-transforms.js"; import { resolveSkillsPromptForRun } from "../skills.js"; import { resolveSystemPromptOverride } from "../system-prompt-override.js"; import { buildSystemPromptReport } from "../system-prompt-report.js"; +import { appendModelIdentitySystemPrompt } from "../system-prompt.js"; import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js"; import { prepareCliBundleMcpConfig } from "./bundle-mcp.js"; import { buildSystemPrompt, normalizeCliModel } from "./helpers.js"; @@ -417,7 +418,10 @@ export async function prepareCliRunContext( }), prompt: preparedPrompt, }); - systemPrompt = applyPluginTextReplacements(systemPrompt, backendResolved.textTransforms?.input); + systemPrompt = appendModelIdentitySystemPrompt({ + systemPrompt: applyPluginTextReplacements(systemPrompt, backendResolved.textTransforms?.input), + model: modelDisplay, + }); const systemPromptReport = buildSystemPromptReport({ source: "run", generatedAt: Date.now(), diff --git a/src/agents/pi-embedded-runner/run/attempt-system-prompt.test.ts b/src/agents/pi-embedded-runner/run/attempt-system-prompt.test.ts index a77721eaea4..8ccff5f6b32 100644 --- a/src/agents/pi-embedded-runner/run/attempt-system-prompt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt-system-prompt.test.ts @@ -58,6 +58,7 @@ describe("buildAttemptSystemPrompt", () => { }); expect(result.systemPrompt).toContain("Custom override prompt."); + expect(result.systemPrompt).toContain("Current model identity: openai/gpt-5.5."); expect(result.systemPrompt).toContain("## Bootstrap Pending"); expect(result.systemPrompt).toContain("BOOTSTRAP.md is included below in Project Context"); expect(result.systemPrompt).toContain("## Bootstrap Context Notice"); @@ -96,6 +97,7 @@ describe("buildAttemptSystemPrompt", () => { }); expect(result.systemPrompt).toContain("Custom override prompt."); + expect(result.systemPrompt).toContain("Current model identity: openai/gpt-5.5."); expect(result.systemPrompt).toContain("## Subagent Context"); expect(result.systemPrompt).toContain("RUN_MODE_TASK_77950"); }); diff --git a/src/agents/pi-embedded-runner/run/attempt-system-prompt.ts b/src/agents/pi-embedded-runner/run/attempt-system-prompt.ts index 9398e5f2478..fbd2656bdfe 100644 --- a/src/agents/pi-embedded-runner/run/attempt-system-prompt.ts +++ b/src/agents/pi-embedded-runner/run/attempt-system-prompt.ts @@ -1,6 +1,9 @@ import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import type { ProviderTransformSystemPromptContext } from "../../../plugins/types.js"; -import { appendAgentBootstrapSystemPromptSupplement } from "../../system-prompt.js"; +import { + appendAgentBootstrapSystemPromptSupplement, + appendModelIdentitySystemPrompt, +} from "../../system-prompt.js"; import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js"; type EmbeddedSystemPromptParams = Parameters[0]; @@ -48,15 +51,18 @@ export function buildAttemptSystemPrompt( params: BuildAttemptSystemPromptParams, ): AttemptSystemPrompt { const baseSystemPrompt = params.systemPromptOverrideText - ? appendRuntimeExtraSystemPrompt({ - systemPrompt: appendAgentBootstrapSystemPromptSupplement({ - systemPrompt: params.systemPromptOverrideText, - bootstrapMode: params.embeddedSystemPrompt.bootstrapMode, - bootstrapTruncationNotice: params.embeddedSystemPrompt.bootstrapTruncationNotice, - contextFiles: params.embeddedSystemPrompt.contextFiles, + ? appendModelIdentitySystemPrompt({ + systemPrompt: appendRuntimeExtraSystemPrompt({ + systemPrompt: appendAgentBootstrapSystemPromptSupplement({ + systemPrompt: params.systemPromptOverrideText, + bootstrapMode: params.embeddedSystemPrompt.bootstrapMode, + bootstrapTruncationNotice: params.embeddedSystemPrompt.bootstrapTruncationNotice, + contextFiles: params.embeddedSystemPrompt.contextFiles, + }), + extraSystemPrompt: params.embeddedSystemPrompt.extraSystemPrompt, + promptMode: params.embeddedSystemPrompt.promptMode, }), - extraSystemPrompt: params.embeddedSystemPrompt.extraSystemPrompt, - promptMode: params.embeddedSystemPrompt.promptMode, + model: params.embeddedSystemPrompt.runtimeInfo.model, }) : buildEmbeddedSystemPrompt(params.embeddedSystemPrompt); diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index a6a9664e9f5..4fd7eb3cecc 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -165,6 +165,7 @@ import { import { resolveSystemPromptOverride } from "../../system-prompt-override.js"; import { buildSystemPromptParams } from "../../system-prompt-params.js"; import { buildSystemPromptReport } from "../../system-prompt-report.js"; +import { appendModelIdentitySystemPrompt } from "../../system-prompt.js"; import { resolveAgentTimeoutMs } from "../../timeout.js"; import { buildEmptyExplicitToolAllowlistError, @@ -2717,6 +2718,14 @@ export async function runEmbeddedAttempt( ); } } + const modelAwareSystemPrompt = appendModelIdentitySystemPrompt({ + systemPrompt: systemPromptText, + model: runtimeInfo.model, + }); + if (modelAwareSystemPrompt !== systemPromptText) { + applySystemPromptOverrideToSession(activeSession, modelAwareSystemPrompt); + systemPromptText = modelAwareSystemPrompt; + } if (cacheObservabilityEnabled) { const cacheObservation = beginPromptCacheObservation({ diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index 5e6b6738ad0..dee34af702d 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -101,6 +101,20 @@ describe("buildAgentSystemPrompt", () => { expect(tokenA).not.toBe(tokenB); }); + it("injects the current model identity into the runtime prompt", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + runtimeInfo: { + agentId: "main", + model: "openai/gpt-5.5", + }, + }); + + expect(prompt).toContain( + "Current model identity: openai/gpt-5.5. If asked what model you are, answer with this value for the current run.", + ); + }); + it("omits extended sections in minimal prompt mode", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/openclaw", diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 05e19e13f0e..0ddbeb66ac6 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -550,6 +550,51 @@ function formatFullAccessBlockedReason(reason?: EmbeddedFullAccessBlockedReason) } return "runtime constraints"; } + +const MODEL_IDENTITY_PREFIX = "Current model identity:"; + +export function buildModelIdentityPromptLine(model?: string): string | undefined { + const trimmed = model?.trim(); + if (!trimmed) { + return undefined; + } + return `${MODEL_IDENTITY_PREFIX} ${trimmed}. If asked what model you are, answer with this value for the current run.`; +} + +export function appendModelIdentitySystemPrompt(params: { + systemPrompt: string; + model?: string; +}): string { + const line = buildModelIdentityPromptLine(params.model); + if (!line) { + return params.systemPrompt; + } + + let replaced = false; + const nextLines = params.systemPrompt + .split(/\r?\n/u) + .filter((candidate) => { + if (!candidate.trimStart().startsWith(MODEL_IDENTITY_PREFIX)) { + return true; + } + if (replaced) { + return false; + } + replaced = true; + return true; + }) + .map((candidate) => + candidate.trimStart().startsWith(MODEL_IDENTITY_PREFIX) ? line : candidate, + ); + + if (replaced) { + return nextLines.join("\n"); + } + + const base = params.systemPrompt.trimEnd(); + return base ? `${base}\n\n${line}` : line; +} + export function buildAgentSystemPrompt(params: { workspaceDir: string; defaultThinkLevel?: ThinkLevel; @@ -757,6 +802,7 @@ export function buildAgentSystemPrompt(params: { const skillsPrompt = params.skillsPrompt?.trim(); const heartbeatPrompt = params.heartbeatPrompt?.trim(); const runtimeInfo = params.runtimeInfo; + const modelIdentityLine = buildModelIdentityPromptLine(runtimeInfo?.model); const runtimeChannel = normalizeOptionalLowercaseString(runtimeInfo?.channel); const runtimeCapabilities = runtimeInfo?.capabilities ?? []; const runtimeCapabilitiesLower = new Set( @@ -816,7 +862,9 @@ export function buildAgentSystemPrompt(params: { // For "none" mode, return just the basic identity line if (promptMode === "none") { - return "You are a personal assistant running inside OpenClaw."; + return ["You are a personal assistant running inside OpenClaw.", modelIdentityLine] + .filter(Boolean) + .join("\n"); } const contextFiles = params.contextFiles ?? []; @@ -1174,6 +1222,7 @@ export function buildAgentSystemPrompt(params: { lines.push( "## Runtime", buildRuntimeLine(runtimeInfo, runtimeChannel, runtimeCapabilities, params.defaultThinkLevel), + ...(modelIdentityLine ? [modelIdentityLine] : []), `Reasoning: ${reasoningLevel} (hidden unless on/stream). Toggle /reasoning; /status shows Reasoning when enabled.`, );