From 5f6eb671c2ca0070cd6ad8b0fd5c02ed9cdf3263 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 14:36:51 +0100 Subject: [PATCH] fix: restore unbound message channel prompt options --- src/agents/system-prompt.test.ts | 21 +++++++++++++++++++++ src/agents/system-prompt.ts | 21 ++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index 0d255db5a80..fbee5bf4193 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; import { typedCases } from "../test-utils/typed-cases.js"; +import { listDeliverableMessageChannels } from "../utils/message-channel.js"; import { buildSubagentSystemPrompt } from "./subagent-system-prompt.js"; import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./system-prompt-cache-boundary.js"; import { @@ -747,14 +748,34 @@ describe("buildAgentSystemPrompt", () => { workspaceDir: "/tmp/openclaw", toolNames: ["message"], }); + const channelOptions = listDeliverableMessageChannels().join("|"); expect(prompt).toContain("message: Send messages and channel actions"); expect(prompt).toContain("### message tool"); expect(prompt).toContain("Use `message` for proactive sends + channel actions"); expect(prompt).toContain("For `action=send`, include `target` and `message`."); + expect(prompt).toContain( + `No current/default source channel: include \`channel\` for proactive sends; valid ids: ${channelOptions}.`, + ); expect(prompt).toContain(`respond with ONLY: ${SILENT_REPLY_TOKEN}`); }); + it("keeps channel choice guidance lean when message sends have a source channel", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/openclaw", + toolNames: ["message"], + runtimeInfo: { + channel: "telegram", + }, + }); + + expect(prompt).toContain( + "Pass `channel` only when sending outside the current/default source channel.", + ); + expect(prompt).not.toContain("No current/default source channel"); + expect(prompt).not.toContain("valid ids:"); + }); + it("gates sub-agent orchestration guidance on available tools", () => { const messagingPrompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/openclaw", diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index ba112e6d19b..cb07cfd2d8a 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -13,6 +13,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, } from "../shared/string-coerce.js"; +import { listDeliverableMessageChannels } from "../utils/message-channel.js"; import type { ActiveProcessSessionReference } from "./bash-process-references.js"; import type { BootstrapMode } from "./bootstrap-mode.js"; import { @@ -461,6 +462,7 @@ function buildMessagingSection(params: { availableTools: Set; inlineButtonsEnabled: boolean; runtimeChannel?: string; + messageChannelOptions?: string; messageToolHints?: string[]; sourceReplyDeliveryMode?: SourceReplyDeliveryMode; }) { @@ -499,7 +501,9 @@ function buildMessagingSection(params: { messageToolOnly ? "- For `action=send`, include `message`. The target defaults to the current source channel; include `target` only when sending somewhere else." : "- For `action=send`, include `target` and `message`.", - "- Pass `channel` only when sending outside the current/default source channel.", + params.messageChannelOptions + ? `- No current/default source channel: include \`channel\` for proactive sends; valid ids: ${params.messageChannelOptions}.` + : "- Pass `channel` only when sending outside the current/default source channel.", messageToolOnly ? "- If you use `message` (`action=send`) to deliver visible output, do not repeat that visible content in your final answer; final answers are private in this mode." : `- If you use \`message\` (\`action=send\`) to deliver your user-visible reply, respond with ONLY: ${SILENT_REPLY_TOKEN} (avoid duplicate replies).`, @@ -519,6 +523,17 @@ function buildMessagingSection(params: { ]; } +function buildMessageChannelOptions(runtimeChannel?: string): string | undefined { + const deliverableChannels: readonly string[] = listDeliverableMessageChannels(); + if (deliverableChannels.length <= 1) { + return undefined; + } + if (runtimeChannel && deliverableChannels.includes(runtimeChannel)) { + return undefined; + } + return deliverableChannels.join("|"); +} + function buildVoiceSection(params: { isMinimal: boolean; ttsHint?: string }) { if (params.isMinimal) { return []; @@ -842,6 +857,9 @@ export function buildAgentSystemPrompt(params: { const isMinimal = promptMode === "minimal" || promptMode === "none"; const subagentDelegationMode = normalizeSubagentDelegationMode(params.subagentDelegationMode); const sourceMessageToolOnly = params.sourceReplyDeliveryMode === "message_tool_only"; + const messageChannelOptions = availableTools.has("message") + ? buildMessageChannelOptions(runtimeChannel) + : undefined; const silentReplyPromptMode = sourceMessageToolOnly ? "none" : (params.silentReplyPromptMode ?? "generic"); @@ -1206,6 +1224,7 @@ export function buildAgentSystemPrompt(params: { availableTools, inlineButtonsEnabled, runtimeChannel, + messageChannelOptions, messageToolHints: params.messageToolHints, sourceReplyDeliveryMode: params.sourceReplyDeliveryMode, }),