fix(doctor): persist group visible reply default (#76513)

This commit is contained in:
scoootscooob
2026-05-03 02:32:41 -07:00
committed by GitHub
parent de88b379c9
commit 85e4ec1fb1
9 changed files with 109 additions and 6 deletions

View File

@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
- Plugins/install: keep managed npm-root security scans from treating earlier plugin `openclaw` peer links as failures, so one external plugin install cannot poison later official npm installs. Thanks @vincentkoc.
- Memory LanceDB: allow installed-but-unconfigured plugin metadata to load so onboarding and setup flows can prompt for embedding config instead of failing the plugin registry first. Thanks @vincentkoc.
- CLI/plugins: keep `plugins enable` and `plugins disable` from creating unconfigured channel config sections, so channel plugins with required setup fields no longer fail validation during lifecycle probes. Thanks @vincentkoc.
- Doctor/config: set `messages.groupChat.visibleReplies: "message_tool"` during compatibility repair for configured-channel configs that omit a visible-reply policy, so upgrades can persist the intended tool-only group/channel reply default. Thanks @kagura-agent.
- Agents/sessions: keep delayed `sessions_send` A2A replies alive after soft wait-window timeouts, while preserving terminal run timeouts and avoiding stale target replies in requester sessions. Fixes #76443. Thanks @ryswork1993 and @vincentkoc.
- CLI/sessions: keep intentional empty agent replies silent after tool-delivered channel output, instead of surfacing a misleading "No reply from agent." fallback. Thanks @vincentkoc.
- Config/doctor: cap `.clobbered.*` forensic snapshots per config path and serialize snapshot writes so repeated `doctor --fix` recovery loops cannot flood the config directory. Fixes #76454; carries forward #65649. Thanks @JUSTICEESSIELP, @rsnow, and @vincentkoc.

View File

@@ -1,4 +1,4 @@
b9831b7dafd0a7d6d1256ee531b30c0b75c64bf0f494fcc9e68bf2255fdb560a config-baseline.json
b6ebb672410bd1ff148ee6d25fba1a359032686959e28d7b8f0313323f94debf config-baseline.core.json
d9dbaace82aff4445be6ed11e52e69b4548239e3a4e659538f96dfb3ed3c57ac config-baseline.json
9d4d4ab553dadca237d837f71dc7fc13e4ea65d33a564c2ea6775180c413e86a config-baseline.core.json
f2a1aad257c570b497865680c331568a6775369528749826dfa35c1f644483fc config-baseline.channel.json
fffe0e74eab92a88c3c57952a70bc932438ce3a7f5f9982688437f2cdaee0bcb config-baseline.plugin.json
858f82733d9828b28bf88bc226e155d8417c494215eb3f808f15799daa42a7d7 config-baseline.plugin.json

View File

@@ -41,6 +41,7 @@ otherwise -> reply
## Visible replies
For group/channel rooms, OpenClaw defaults to `messages.groupChat.visibleReplies: "message_tool"`.
`openclaw doctor --fix` writes this default into configured-channel configs that omit it.
That means the agent still processes the turn and can update memory/session state, but its normal final answer is not automatically posted back into the room. To speak visibly, the agent uses `message(action=send)`.
If the message tool is unavailable under the active tool policy, OpenClaw falls

View File

@@ -189,6 +189,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- `routing.groupChat.requireMention` → `channels.whatsapp/telegram/imessage.groups."*".requireMention`
- `routing.groupChat.historyLimit` → `messages.groupChat.historyLimit`
- `routing.groupChat.mentionPatterns` → `messages.groupChat.mentionPatterns`
- configured-channel configs missing visible reply policy → `messages.groupChat.visibleReplies: "message_tool"`
- `routing.queue` → `messages.queue`
- `routing.bindings` → top-level `bindings`
- `routing.agents`/`routing.defaultAgentId` → `agents.list` + `agents.list[].default`

View File

@@ -146,6 +146,63 @@ describe("normalizeCompatibilityConfigValues", () => {
fs.rmSync(tempOauthDir, { recursive: true, force: true });
});
it("sets the group visible reply default for configured channels", () => {
const res = normalizeCompatibilityConfigValues({
channels: {
discord: {},
},
messages: {
groupChat: {
mentionPatterns: ["@openclaw"],
},
},
});
expect(res.config.messages?.groupChat).toEqual({
mentionPatterns: ["@openclaw"],
visibleReplies: "message_tool",
});
expect(res.changes).toContain(
'Set messages.groupChat.visibleReplies to "message_tool" so group/channel replies use the message tool by default.',
);
});
it("does not set group visible replies without channels or when already explicit", () => {
expect(
normalizeCompatibilityConfigValues({
messages: {
groupChat: {
mentionPatterns: ["@openclaw"],
},
},
}).changes,
).toEqual([]);
expect(
normalizeCompatibilityConfigValues({
channels: {
discord: {},
},
messages: {
visibleReplies: "automatic",
},
}).config.messages?.groupChat?.visibleReplies,
).toBeUndefined();
expect(
normalizeCompatibilityConfigValues({
channels: {
discord: {},
},
messages: {
groupChat: {
visibleReplies: "automatic",
},
},
}).config.messages?.groupChat?.visibleReplies,
).toBe("automatic");
});
it("does not add whatsapp config when missing and no auth exists", () => {
const res = normalizeCompatibilityConfigValues({
messages: { ackReaction: "👀" },
@@ -216,6 +273,11 @@ describe("normalizeCompatibilityConfigValues", () => {
it("leaves invalid legacy secretref-env markers for validation to reject", () => {
const res = normalizeCompatibilityConfigValues({
messages: {
groupChat: {
visibleReplies: "message_tool",
},
},
channels: {
discord: {
token: "secretref-env:not-valid",

View File

@@ -8,6 +8,7 @@ import {
normalizeLegacyRuntimeModelRefs,
normalizeLegacyNanoBananaSkill,
normalizeLegacyTalkConfig,
normalizeMissingGroupVisibleRepliesDefault,
seedMissingDefaultAccountsFromSingleAccountBase,
} from "./legacy-config-core-normalizers.js";
import { migrateLegacyWebFetchConfig } from "./legacy-web-fetch-migrate.js";
@@ -41,6 +42,7 @@ export function normalizeBaseCompatibilityConfigValues(
next = normalizeLegacyOpenAIModelProviderApi(next, changes);
next = normalizeLegacyRuntimeModelRefs(next, changes);
next = normalizeLegacyCrossContextMessageConfig(next, changes);
next = normalizeMissingGroupVisibleRepliesDefault(next, changes);
next = normalizeLegacyMediaProviderOptions(next, changes);
return normalizeLegacyMistralModelMaxTokens(next, changes);
}

View File

@@ -14,6 +14,42 @@ import { isRecord } from "./legacy-config-record-shared.js";
import { isLegacyModelsAddCodexMetadataModel } from "./legacy-models-add-metadata.js";
export { normalizeLegacyTalkConfig } from "./legacy-talk-config-normalizer.js";
function hasConfiguredChannels(cfg: OpenClawConfig): boolean {
const channels = cfg.channels;
if (!isRecord(channels)) {
return false;
}
return Object.keys(channels).some((channelId) => channelId !== "defaults");
}
export function normalizeMissingGroupVisibleRepliesDefault(
cfg: OpenClawConfig,
changes: string[],
): OpenClawConfig {
const messages = cfg.messages;
if (
!hasConfiguredChannels(cfg) ||
messages?.visibleReplies !== undefined ||
messages?.groupChat?.visibleReplies !== undefined
) {
return cfg;
}
const nextMessages = messages ? { ...messages } : {};
nextMessages.groupChat = {
...messages?.groupChat,
visibleReplies: "message_tool",
};
changes.push(
'Set messages.groupChat.visibleReplies to "message_tool" so group/channel replies use the message tool by default.',
);
return {
...cfg,
messages: nextMessages,
};
}
export function normalizeLegacyCommandsConfig(
cfg: OpenClawConfig,
changes: string[],

View File

@@ -19056,7 +19056,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
enum: ["automatic", "message_tool"],
title: "Group Visible Replies",
description:
'Overrides visible source replies for group/channel conversations. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
'Overrides visible source replies for group/channel conversations. Defaults to "message_tool" when no global visible reply policy is set. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
},
},
additionalProperties: false,
@@ -28529,7 +28529,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
},
"messages.groupChat.visibleReplies": {
label: "Group Visible Replies",
help: 'Overrides visible source replies for group/channel conversations. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
help: 'Overrides visible source replies for group/channel conversations. Defaults to "message_tool" when no global visible reply policy is set. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
tags: ["advanced"],
},
"messages.queue": {

View File

@@ -1642,7 +1642,7 @@ export const FIELD_HELP: Record<string, string> = {
"messages.groupChat.historyLimit":
"Maximum number of prior group messages loaded as context per turn for group sessions. Use higher values for richer continuity, or lower values for faster and cheaper responses.",
"messages.groupChat.visibleReplies":
'Overrides visible source replies for group/channel conversations. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
'Overrides visible source replies for group/channel conversations. Defaults to "message_tool" when no global visible reply policy is set. "message_tool" keeps normal final replies private and requires message(action=send) for room output; "automatic" posts normal replies as before.',
"messages.queue":
"Inbound message queue strategy for messages that arrive while a session run is active. Default mode is steer, with followup fallback when steering is unavailable.",
"messages.queue.mode":