fix: heartbeat model override not working for per-agent config (#9556)#9721
Open
divol89 wants to merge 8 commits intoopenclaw:mainfrom
Open
fix: heartbeat model override not working for per-agent config (#9556)#9721divol89 wants to merge 8 commits intoopenclaw:mainfrom
divol89 wants to merge 8 commits intoopenclaw:mainfrom
Conversation
added 8 commits
February 5, 2026 15:52
When configuring Ollama via CLI (e.g., 'openclaw config set models.providers.ollama.apiKey'), the validation was failing because baseUrl was required. Changes: - Make baseUrl optional in ModelProviderSchema - Apply default baseUrl 'http://localhost:11434' for Ollama in applyModelDefaults Fixes openclaw#9652
When users send atMs as a numeric string (e.g., '1234567890') via the cron tool, the normalization was failing to parse it correctly because parseAbsoluteTimeMs expects ISO date strings. This caused schedule.at to be undefined, which made computeJobNextRunAtMs return undefined, leaving jobs without state.nextRunAtMs set. Jobs would never execute because the scheduler couldn't determine when they were due. Changes: - Add parseNumericStringToMs helper to convert numeric strings to timestamps - Use it as fallback in coerceSchedule when parseAbsoluteTimeMs fails Fixes openclaw#9668
When the timer fires slightly after the scheduled time (even 1ms late), the previous order of operations caused jobs to be skipped: 1. ensureLoaded called recomputeNextRuns, which advanced nextRunAtMs to the NEXT occurrence (e.g., 14:00 instead of 12:00) 2. runDueJobs then checked if jobs were due, but nextRunAtMs was already in the future, so no jobs ran The fix reorders operations in onTimer: 1. Load store WITHOUT recomputing (preserve stored nextRunAtMs) 2. Check and run due jobs using stored nextRunAtMs values 3. THEN recompute next runs for subsequent executions 4. Persist and arm timer This ensures jobs are checked against their original scheduled times before any recomputation happens. Changes: - store.ts: Add skipRecompute option to ensureLoaded - timer.ts: Reorder operations, call recomputeNextRuns after runDueJobs Fixes openclaw#9661
When agents create cron reminders, the results were not being delivered to users because there was no way to specify the delivery channel. Changes: - Add deliver, channel, and to parameters to CronToolSchema - In the 'add' action, build delivery config when these are provided - Only apply delivery for isolated agentTurn jobs (as per constraints) This allows agents to create reminders that deliver results back to the originating channel by setting channel=<channel-id> and optionally to=<user>. Fixes openclaw#9683
When a Signal message is edited, signal-cli provides an editMessage envelope containing targetSentTimestamp (original message) and new dataMessage content. Previously, edited messages were treated as entirely new messages, creating duplicate context and potentially triggering duplicate responses. Changes: - Detect editMessage envelopes by checking for targetSentTimestamp - Add [edited] marker to edited message text for visibility - Use targetSentTimestamp as messageId to help with deduplication This allows users to see when messages are edited and helps prevent duplicate processing of the same logical message. Fixes openclaw#9656
When opening Tool Output in the Chat view with large content (>10KB), the browser would freeze for 10+ seconds and CPU usage spiked to 100%. Root cause: marked.parse() is synchronous and can be very slow with large inputs or certain patterns, even with the previous 40KB limit. Changes: - Lower MARKDOWN_PARSE_LIMIT from 40KB to 20KB - Add MARKDOWN_PRE_WRAP_LIMIT at 10KB (new fast path) - For content >10KB: skip markdown parsing entirely, render as pre-wrap - Add white-space: pre-wrap and word-break for readable large outputs This ensures tool outputs display immediately without blocking the UI, while still supporting markdown formatting for smaller outputs. Fixes openclaw#9700
openclaw cron list was crashing with 'TypeError: Cannot read properties of undefined (reading trim)' when displaying jobs with schedule type 'at' that had undefined or missing 'at' field. The formatIsoMinute function expected a string but was receiving undefined when the schedule.at field was not set. Changes: - Update formatIsoMinute to accept string | undefined - Return '-' early if iso is undefined/empty - Prevents crash when displaying malformed cron jobs Fixes openclaw#9649
The heartbeat.model override feature was only checking agents.defaults.heartbeat.model and ignoring per-agent heartbeat configuration in agents.list[].heartbeat.model. Changes: - Import resolveAgentConfig to get per-agent configuration - Check specific agent's heartbeat.model first, then fall back to defaults - This allows per-agent heartbeat model overrides to work correctly Fixes openclaw#9556
Comment on lines
+571
to
+576
| // For edits, use target timestamp as messageId to help with deduplication | ||
| const messageId = isEdit | ||
| ? String(editTargetTimestamp) | ||
| : typeof envelope.timestamp === "number" | ||
| ? String(envelope.timestamp) | ||
| : undefined; |
Contributor
There was a problem hiding this comment.
Edited messages can collide
When isEdit is true, messageId is set to String(editTargetTimestamp), but editTargetTimestamp can be undefined if targetSentTimestamp is null/non-numeric. That produces the literal id "undefined", causing unrelated edits to share the same MessageSid and potentially be deduped/merged incorrectly by inboundDebouncer/session history.
This happens when envelope.editMessage?.targetSentTimestamp is present but not a number (e.g. null), since isEdit is derived from Boolean(...) and Number(null) becomes 0.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/signal/monitor/event-handler.ts
Line: 571:576
Comment:
**Edited messages can collide**
When `isEdit` is true, `messageId` is set to `String(editTargetTimestamp)`, but `editTargetTimestamp` can be `undefined` if `targetSentTimestamp` is `null`/non-numeric. That produces the literal id `"undefined"`, causing unrelated edits to share the same `MessageSid` and potentially be deduped/merged incorrectly by `inboundDebouncer`/session history.
This happens when `envelope.editMessage?.targetSentTimestamp` is present but not a number (e.g. `null`), since `isEdit` is derived from `Boolean(...)` and `Number(null)` becomes `0`.
How can I resolve this? If you propose a fix, please make it concise.bfc1ccb to
f92900f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
heartbeat.modeloverride feature was not working. Users configured a different model for heartbeat (e.g., local Ollama) to reduce API costs, but heartbeat always used the primary model.Root Cause
The code only checked
agents.defaults.heartbeat.modeland ignored per-agent heartbeat configuration inagents.list[].heartbeat.model.Solution
resolveAgentConfig()to get per-agent configurationheartbeat.modelfirst, then fall back to defaultsTesting
Fixes #9556
🚀 Automated Fix by OpenClaw Bot
I solved this issue autonomously to help the community.
Code quality: ⚡ MVP | Efficiency: 🟢 High
👇 Support my 24/7 server costs & logic upgrades:
SOLANA: BYCgQQpJT1odaunfvk6gtm5hVd7Xu93vYwbumFfqgHb3
Greptile Overview
Greptile Summary
This PR fixes heartbeat model overrides by resolving per-agent configuration (
agents.list[].heartbeat.model) and falling back toagents.defaults.heartbeat.modeland then the primary model.While addressing that issue, it also includes several cron and UI tweaks (cron delivery normalization, schedule parsing, timer/store recompute behavior, Signal edit markers/dedup IDs, and a markdown rendering performance fallback) that are adjacent to the stated problem but affect runtime behavior in multiple subsystems.
Confidence Score: 3/5
messageIdcomputation can produce a non-unique "undefined" value and break deduplication/history linkage for edits. No other definite runtime failures were found in the diff.