mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -718,6 +718,7 @@ Periodic heartbeat runs.
|
||||
target: "last", // last | whatsapp | telegram | discord | ... | none
|
||||
prompt: "Read HEARTBEAT.md if it exists...",
|
||||
ackMaxChars: 300,
|
||||
suppressToolErrorWarnings: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -725,6 +726,7 @@ Periodic heartbeat runs.
|
||||
```
|
||||
|
||||
- `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.
|
||||
- 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.
|
||||
- `prompt`: overrides the default prompt body (not merged).
|
||||
- `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`.
|
||||
- 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.
|
||||
|
||||
@@ -922,6 +922,7 @@ export async function runEmbeddedPiAgent(
|
||||
verboseLevel: params.verboseLevel,
|
||||
reasoningLevel: params.reasoningLevel,
|
||||
toolResultFormat: resolvedToolResultFormat,
|
||||
suppressToolErrorWarnings: params.suppressToolErrorWarnings,
|
||||
inlineToolResultsAllowed: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ export type RunEmbeddedPiAgentParams = {
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
toolResultFormat?: ToolResultFormat;
|
||||
/** If true, suppress tool error warning payloads for this run (including mutating tools). */
|
||||
suppressToolErrorWarnings?: boolean;
|
||||
execOverrides?: Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
||||
bashElevated?: ExecElevatedDefaults;
|
||||
timeoutMs: number;
|
||||
|
||||
@@ -252,6 +252,15 @@ describe("buildEmbeddedRunPayloads", () => {
|
||||
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", () => {
|
||||
const payloads = buildPayloads({
|
||||
lastToolError: { toolName: "message", meta: "reply", error: "text required" },
|
||||
|
||||
@@ -48,7 +48,11 @@ function shouldShowToolErrorWarning(params: {
|
||||
lastToolError: LastToolError;
|
||||
hasUserFacingReply: boolean;
|
||||
suppressToolErrors: boolean;
|
||||
suppressToolErrorWarnings?: boolean;
|
||||
}): boolean {
|
||||
if (params.suppressToolErrorWarnings) {
|
||||
return false;
|
||||
}
|
||||
const isMutatingToolError =
|
||||
params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName);
|
||||
if (isMutatingToolError) {
|
||||
@@ -71,6 +75,7 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
toolResultFormat?: ToolResultFormat;
|
||||
suppressToolErrorWarnings?: boolean;
|
||||
inlineToolResultsAllowed: boolean;
|
||||
}): Array<{
|
||||
text?: string;
|
||||
@@ -247,6 +252,7 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
lastToolError: params.lastToolError,
|
||||
hasUserFacingReply: hasUserFacingAssistantReply,
|
||||
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.
|
||||
|
||||
@@ -312,6 +312,7 @@ export async function runAgentTurnWithFallback(params: {
|
||||
}
|
||||
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
||||
})(),
|
||||
suppressToolErrorWarnings: params.opts?.suppressToolErrorWarnings,
|
||||
bashElevated: params.followupRun.run.bashElevated,
|
||||
timeoutMs: params.followupRun.run.timeoutMs,
|
||||
runId,
|
||||
|
||||
@@ -166,6 +166,7 @@ export function createFollowupRunner(params: {
|
||||
thinkLevel: queued.run.thinkLevel,
|
||||
verboseLevel: queued.run.verboseLevel,
|
||||
reasoningLevel: queued.run.reasoningLevel,
|
||||
suppressToolErrorWarnings: opts?.suppressToolErrorWarnings,
|
||||
execOverrides: queued.run.execOverrides,
|
||||
bashElevated: queued.run.bashElevated,
|
||||
timeoutMs: queued.run.timeoutMs,
|
||||
|
||||
@@ -29,6 +29,8 @@ export type GetReplyOptions = {
|
||||
isHeartbeat?: boolean;
|
||||
/** Resolved heartbeat model override (provider/model string from merged per-agent config). */
|
||||
heartbeatModelOverride?: string;
|
||||
/** If true, suppress tool error warning payloads for this run. */
|
||||
suppressToolErrorWarnings?: boolean;
|
||||
onPartialReply?: (payload: ReplyPayload) => Promise<void> | void;
|
||||
onReasoningStream?: (payload: ReplyPayload) => Promise<void> | void;
|
||||
/** 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).",
|
||||
"agents.list[].identity.avatar":
|
||||
"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":
|
||||
'mDNS broadcast mode ("minimal" default, "full" includes cliPath/sshPort, "off" disables mDNS).',
|
||||
"gateway.auth.token":
|
||||
|
||||
@@ -220,6 +220,8 @@ export type AgentDefaultsConfig = {
|
||||
prompt?: string;
|
||||
/** Max chars allowed after HEARTBEAT_OK before delivery (default: 30). */
|
||||
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)
|
||||
* as a separate message prefixed with `Reasoning:` (same as `/reasoning on`).
|
||||
|
||||
@@ -29,6 +29,7 @@ export const HeartbeatSchema = z
|
||||
accountId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
ackMaxChars: z.number().int().nonnegative().optional(),
|
||||
suppressToolErrorWarnings: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((val, ctx) => {
|
||||
|
||||
@@ -540,9 +540,10 @@ export async function runHeartbeatOnce(opts: {
|
||||
|
||||
try {
|
||||
const heartbeatModelOverride = heartbeat?.model?.trim() || undefined;
|
||||
const suppressToolErrorWarnings = heartbeat?.suppressToolErrorWarnings === true;
|
||||
const replyOpts = heartbeatModelOverride
|
||||
? { isHeartbeat: true, heartbeatModelOverride }
|
||||
: { isHeartbeat: true };
|
||||
? { isHeartbeat: true, heartbeatModelOverride, suppressToolErrorWarnings }
|
||||
: { isHeartbeat: true, suppressToolErrorWarnings };
|
||||
const replyResult = await getReplyFromConfig(ctx, replyOpts, cfg);
|
||||
const replyPayload = resolveHeartbeatReplyPayload(replyResult);
|
||||
const includeReasoning = heartbeat?.includeReasoning === true;
|
||||
|
||||
Reference in New Issue
Block a user