mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
fix(doctor): persist group visible reply default (#76513)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user