fix: restore unbound message channel prompt options

This commit is contained in:
Peter Steinberger
2026-05-10 14:36:51 +01:00
parent d273ae73c0
commit 5f6eb671c2
2 changed files with 41 additions and 1 deletions

View File

@@ -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",

View File

@@ -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<string>;
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,
}),