mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 09:08:38 +00:00
Heartbeat: allow suppressing tool warnings (#18497)
* Heartbeat: allow suppressing tool warnings * Changelog: note heartbeat tool-warning suppression
This commit is contained in:
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Telegram: prevent streaming final replies from being overwritten by later final/error payloads, and suppress fallback tool-error warnings when a recovered assistant answer already exists after tool calls. (#17883) Thanks @Marvae and @obviyus.
|
- Telegram: prevent streaming final replies from being overwritten by later final/error payloads, and suppress fallback tool-error warnings when a recovered assistant answer already exists after tool calls. (#17883) Thanks @Marvae and @obviyus.
|
||||||
- Memory/QMD: scope managed collection names per agent and precreate glob-backed collection directories before registration, preventing cross-agent collection clobbering and startup ENOENT failures in fresh workspaces. (#17194) Thanks @jonathanadams96.
|
- Memory/QMD: scope managed collection names per agent and precreate glob-backed collection directories before registration, preventing cross-agent collection clobbering and startup ENOENT failures in fresh workspaces. (#17194) Thanks @jonathanadams96.
|
||||||
- Auto-reply/TTS: keep tool-result media delivery enabled in group chats and native command sessions (while still suppressing tool summary text) so `NO_REPLY` follow-ups do not drop successful TTS audio. (#17991) Thanks @zerone0x.
|
- Auto-reply/TTS: keep tool-result media delivery enabled in group chats and native command sessions (while still suppressing tool summary text) so `NO_REPLY` follow-ups do not drop successful TTS audio. (#17991) Thanks @zerone0x.
|
||||||
|
- Heartbeat: allow suppressing tool error warning payloads during heartbeat runs via a new heartbeat config flag. (#18497) Thanks @thewilloftheshadow.
|
||||||
- Cron: preserve per-job schedule-error isolation in post-run maintenance recompute so malformed sibling jobs no longer abort persistence of successful runs. (#17852) Thanks @pierreeurope.
|
- Cron: preserve per-job schedule-error isolation in post-run maintenance recompute so malformed sibling jobs no longer abort persistence of successful runs. (#17852) Thanks @pierreeurope.
|
||||||
- CLI/Pairing: make `openclaw qr --remote` prefer `gateway.remote.url` over tailscale/public URL resolution and register the `openclaw clawbot qr` legacy alias path. (#18091)
|
- CLI/Pairing: make `openclaw qr --remote` prefer `gateway.remote.url` over tailscale/public URL resolution and register the `openclaw clawbot qr` legacy alias path. (#18091)
|
||||||
- CLI/QR: restore fail-fast validation for `openclaw qr --remote` when neither `gateway.remote.url` nor tailscale `serve`/`funnel` is configured, preventing unusable remote pairing QR flows. (#18166) Thanks @mbelinky.
|
- CLI/QR: restore fail-fast validation for `openclaw qr --remote` when neither `gateway.remote.url` nor tailscale `serve`/`funnel` is configured, preventing unusable remote pairing QR flows. (#18166) Thanks @mbelinky.
|
||||||
|
|||||||
@@ -718,6 +718,7 @@ Periodic heartbeat runs.
|
|||||||
target: "last", // last | whatsapp | telegram | discord | ... | none
|
target: "last", // last | whatsapp | telegram | discord | ... | none
|
||||||
prompt: "Read HEARTBEAT.md if it exists...",
|
prompt: "Read HEARTBEAT.md if it exists...",
|
||||||
ackMaxChars: 300,
|
ackMaxChars: 300,
|
||||||
|
suppressToolErrorWarnings: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -725,6 +726,7 @@ Periodic heartbeat runs.
|
|||||||
```
|
```
|
||||||
|
|
||||||
- `every`: duration string (ms/s/m/h). Default: `30m`.
|
- `every`: duration string (ms/s/m/h). Default: `30m`.
|
||||||
|
- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
|
||||||
- Per-agent: set `agents.list[].heartbeat`. When any agent defines `heartbeat`, **only those agents** run heartbeats.
|
- Per-agent: set `agents.list[].heartbeat`. When any agent defines `heartbeat`, **only those agents** run heartbeats.
|
||||||
- Heartbeats run full agent turns — shorter intervals burn more tokens.
|
- Heartbeats run full agent turns — shorter intervals burn more tokens.
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele
|
|||||||
- `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
|
- `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
|
||||||
- `prompt`: overrides the default prompt body (not merged).
|
- `prompt`: overrides the default prompt body (not merged).
|
||||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
||||||
|
- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
|
||||||
- `activeHours`: restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.
|
- `activeHours`: restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.
|
||||||
- Omitted or `"user"`: uses your `agents.defaults.userTimezone` if set, otherwise falls back to the host system timezone.
|
- Omitted or `"user"`: uses your `agents.defaults.userTimezone` if set, otherwise falls back to the host system timezone.
|
||||||
- `"local"`: always uses the host system timezone.
|
- `"local"`: always uses the host system timezone.
|
||||||
|
|||||||
@@ -922,6 +922,7 @@ export async function runEmbeddedPiAgent(
|
|||||||
verboseLevel: params.verboseLevel,
|
verboseLevel: params.verboseLevel,
|
||||||
reasoningLevel: params.reasoningLevel,
|
reasoningLevel: params.reasoningLevel,
|
||||||
toolResultFormat: resolvedToolResultFormat,
|
toolResultFormat: resolvedToolResultFormat,
|
||||||
|
suppressToolErrorWarnings: params.suppressToolErrorWarnings,
|
||||||
inlineToolResultsAllowed: false,
|
inlineToolResultsAllowed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ export type RunEmbeddedPiAgentParams = {
|
|||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningLevel?: ReasoningLevel;
|
reasoningLevel?: ReasoningLevel;
|
||||||
toolResultFormat?: ToolResultFormat;
|
toolResultFormat?: ToolResultFormat;
|
||||||
|
/** If true, suppress tool error warning payloads for this run (including mutating tools). */
|
||||||
|
suppressToolErrorWarnings?: boolean;
|
||||||
execOverrides?: Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
execOverrides?: Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
||||||
bashElevated?: ExecElevatedDefaults;
|
bashElevated?: ExecElevatedDefaults;
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
|
|||||||
@@ -252,6 +252,15 @@ describe("buildEmbeddedRunPayloads", () => {
|
|||||||
expect(payloads[0]?.text).toContain("connection timeout");
|
expect(payloads[0]?.text).toContain("connection timeout");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("suppresses mutating tool errors when suppressToolErrorWarnings is enabled", () => {
|
||||||
|
const payloads = buildPayloads({
|
||||||
|
lastToolError: { toolName: "exec", error: "command not found" },
|
||||||
|
suppressToolErrorWarnings: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payloads).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("shows recoverable tool errors for mutating tools", () => {
|
it("shows recoverable tool errors for mutating tools", () => {
|
||||||
const payloads = buildPayloads({
|
const payloads = buildPayloads({
|
||||||
lastToolError: { toolName: "message", meta: "reply", error: "text required" },
|
lastToolError: { toolName: "message", meta: "reply", error: "text required" },
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ function shouldShowToolErrorWarning(params: {
|
|||||||
lastToolError: LastToolError;
|
lastToolError: LastToolError;
|
||||||
hasUserFacingReply: boolean;
|
hasUserFacingReply: boolean;
|
||||||
suppressToolErrors: boolean;
|
suppressToolErrors: boolean;
|
||||||
|
suppressToolErrorWarnings?: boolean;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
|
if (params.suppressToolErrorWarnings) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const isMutatingToolError =
|
const isMutatingToolError =
|
||||||
params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName);
|
params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName);
|
||||||
if (isMutatingToolError) {
|
if (isMutatingToolError) {
|
||||||
@@ -71,6 +75,7 @@ export function buildEmbeddedRunPayloads(params: {
|
|||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningLevel?: ReasoningLevel;
|
reasoningLevel?: ReasoningLevel;
|
||||||
toolResultFormat?: ToolResultFormat;
|
toolResultFormat?: ToolResultFormat;
|
||||||
|
suppressToolErrorWarnings?: boolean;
|
||||||
inlineToolResultsAllowed: boolean;
|
inlineToolResultsAllowed: boolean;
|
||||||
}): Array<{
|
}): Array<{
|
||||||
text?: string;
|
text?: string;
|
||||||
@@ -247,6 +252,7 @@ export function buildEmbeddedRunPayloads(params: {
|
|||||||
lastToolError: params.lastToolError,
|
lastToolError: params.lastToolError,
|
||||||
hasUserFacingReply: hasUserFacingAssistantReply,
|
hasUserFacingReply: hasUserFacingAssistantReply,
|
||||||
suppressToolErrors: Boolean(params.config?.messages?.suppressToolErrors),
|
suppressToolErrors: Boolean(params.config?.messages?.suppressToolErrors),
|
||||||
|
suppressToolErrorWarnings: params.suppressToolErrorWarnings,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Always surface mutating tool failures so we do not silently confirm actions that did not happen.
|
// Always surface mutating tool failures so we do not silently confirm actions that did not happen.
|
||||||
|
|||||||
@@ -312,6 +312,7 @@ export async function runAgentTurnWithFallback(params: {
|
|||||||
}
|
}
|
||||||
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
||||||
})(),
|
})(),
|
||||||
|
suppressToolErrorWarnings: params.opts?.suppressToolErrorWarnings,
|
||||||
bashElevated: params.followupRun.run.bashElevated,
|
bashElevated: params.followupRun.run.bashElevated,
|
||||||
timeoutMs: params.followupRun.run.timeoutMs,
|
timeoutMs: params.followupRun.run.timeoutMs,
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ export function createFollowupRunner(params: {
|
|||||||
thinkLevel: queued.run.thinkLevel,
|
thinkLevel: queued.run.thinkLevel,
|
||||||
verboseLevel: queued.run.verboseLevel,
|
verboseLevel: queued.run.verboseLevel,
|
||||||
reasoningLevel: queued.run.reasoningLevel,
|
reasoningLevel: queued.run.reasoningLevel,
|
||||||
|
suppressToolErrorWarnings: opts?.suppressToolErrorWarnings,
|
||||||
execOverrides: queued.run.execOverrides,
|
execOverrides: queued.run.execOverrides,
|
||||||
bashElevated: queued.run.bashElevated,
|
bashElevated: queued.run.bashElevated,
|
||||||
timeoutMs: queued.run.timeoutMs,
|
timeoutMs: queued.run.timeoutMs,
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type GetReplyOptions = {
|
|||||||
isHeartbeat?: boolean;
|
isHeartbeat?: boolean;
|
||||||
/** Resolved heartbeat model override (provider/model string from merged per-agent config). */
|
/** Resolved heartbeat model override (provider/model string from merged per-agent config). */
|
||||||
heartbeatModelOverride?: string;
|
heartbeatModelOverride?: string;
|
||||||
|
/** If true, suppress tool error warning payloads for this run. */
|
||||||
|
suppressToolErrorWarnings?: boolean;
|
||||||
onPartialReply?: (payload: ReplyPayload) => Promise<void> | void;
|
onPartialReply?: (payload: ReplyPayload) => Promise<void> | void;
|
||||||
onReasoningStream?: (payload: ReplyPayload) => Promise<void> | void;
|
onReasoningStream?: (payload: ReplyPayload) => Promise<void> | void;
|
||||||
/** Called when a thinking/reasoning block ends. */
|
/** Called when a thinking/reasoning block ends. */
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const FIELD_HELP: Record<string, string> = {
|
|||||||
"Optional allowlist of skills for this agent (omit = all skills; empty = no skills).",
|
"Optional allowlist of skills for this agent (omit = all skills; empty = no skills).",
|
||||||
"agents.list[].identity.avatar":
|
"agents.list[].identity.avatar":
|
||||||
"Avatar image path (relative to the agent workspace only) or a remote URL/data URL.",
|
"Avatar image path (relative to the agent workspace only) or a remote URL/data URL.",
|
||||||
|
"agents.defaults.heartbeat.suppressToolErrorWarnings":
|
||||||
|
"Suppress tool error warning payloads during heartbeat runs.",
|
||||||
|
"agents.list[].heartbeat.suppressToolErrorWarnings":
|
||||||
|
"Suppress tool error warning payloads during heartbeat runs.",
|
||||||
"discovery.mdns.mode":
|
"discovery.mdns.mode":
|
||||||
'mDNS broadcast mode ("minimal" default, "full" includes cliPath/sshPort, "off" disables mDNS).',
|
'mDNS broadcast mode ("minimal" default, "full" includes cliPath/sshPort, "off" disables mDNS).',
|
||||||
"gateway.auth.token":
|
"gateway.auth.token":
|
||||||
|
|||||||
@@ -220,6 +220,8 @@ export type AgentDefaultsConfig = {
|
|||||||
prompt?: string;
|
prompt?: string;
|
||||||
/** Max chars allowed after HEARTBEAT_OK before delivery (default: 30). */
|
/** Max chars allowed after HEARTBEAT_OK before delivery (default: 30). */
|
||||||
ackMaxChars?: number;
|
ackMaxChars?: number;
|
||||||
|
/** Suppress tool error warning payloads during heartbeat runs. */
|
||||||
|
suppressToolErrorWarnings?: boolean;
|
||||||
/**
|
/**
|
||||||
* When enabled, deliver the model's reasoning payload for heartbeat runs (when available)
|
* When enabled, deliver the model's reasoning payload for heartbeat runs (when available)
|
||||||
* as a separate message prefixed with `Reasoning:` (same as `/reasoning on`).
|
* as a separate message prefixed with `Reasoning:` (same as `/reasoning on`).
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const HeartbeatSchema = z
|
|||||||
accountId: z.string().optional(),
|
accountId: z.string().optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
ackMaxChars: z.number().int().nonnegative().optional(),
|
ackMaxChars: z.number().int().nonnegative().optional(),
|
||||||
|
suppressToolErrorWarnings: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.superRefine((val, ctx) => {
|
.superRefine((val, ctx) => {
|
||||||
|
|||||||
@@ -540,9 +540,10 @@ export async function runHeartbeatOnce(opts: {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const heartbeatModelOverride = heartbeat?.model?.trim() || undefined;
|
const heartbeatModelOverride = heartbeat?.model?.trim() || undefined;
|
||||||
|
const suppressToolErrorWarnings = heartbeat?.suppressToolErrorWarnings === true;
|
||||||
const replyOpts = heartbeatModelOverride
|
const replyOpts = heartbeatModelOverride
|
||||||
? { isHeartbeat: true, heartbeatModelOverride }
|
? { isHeartbeat: true, heartbeatModelOverride, suppressToolErrorWarnings }
|
||||||
: { isHeartbeat: true };
|
: { isHeartbeat: true, suppressToolErrorWarnings };
|
||||||
const replyResult = await getReplyFromConfig(ctx, replyOpts, cfg);
|
const replyResult = await getReplyFromConfig(ctx, replyOpts, cfg);
|
||||||
const replyPayload = resolveHeartbeatReplyPayload(replyResult);
|
const replyPayload = resolveHeartbeatReplyPayload(replyResult);
|
||||||
const includeReasoning = heartbeat?.includeReasoning === true;
|
const includeReasoning = heartbeat?.includeReasoning === true;
|
||||||
|
|||||||
Reference in New Issue
Block a user