Skip to content

Comments

fix(ui): prevent CPU spike when opening large tool outputs (#9700)#9710

Open
divol89 wants to merge 6 commits intoopenclaw:mainfrom
divol89:fix/9700-tool-output-performance
Open

fix(ui): prevent CPU spike when opening large tool outputs (#9700)#9710
divol89 wants to merge 6 commits intoopenclaw:mainfrom
divol89:fix/9700-tool-output-performance

Conversation

@divol89
Copy link

@divol89 divol89 commented Feb 5, 2026

Problem

When opening Tool Output in the Chat view with large content, 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.

Solution

Add a fast path for large outputs that skips markdown parsing entirely.

Changes

  • Lower MARKDOWN_PARSE_LIMIT from 40KB to 20KB
  • Add MARKDOWN_PRE_WRAP_LIMIT at 10KB (new fast path)
  • For content >10KB: render as pre-wrap instead of parsing markdown
  • Add white-space: pre-wrap and word-break for readable formatting

Performance Impact

  • Tool outputs >10KB now display immediately without blocking UI
  • Smaller outputs still get full markdown rendering

Fixes #9700


🚀 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 improves UI responsiveness when rendering large tool outputs by adding a fast path in ui/src/ui/markdown.ts that skips synchronous marked.parse() and instead renders large content in a sanitized <pre> with pre-wrap formatting. It also includes several backend changes (cron delivery options, cron next-run recomputation behavior, config schema/default updates, cron schedule normalization, and Signal edit handling).

Key thing to double-check before merge: the markdown size thresholds are now inconsistent with the described behavior (the parse limit constant was lowered but is no longer used), and Signal edit deduplication can produce invalid IDs if the target timestamp isn’t a finite number.

Confidence Score: 3/5

  • This PR is likely safe to merge after fixing a couple of correctness issues in new logic.
  • UI change is straightforward, but there is at least one definite behavioral bug (unused parse-limit constant makes part of the change ineffective) and a concrete edge case in Signal edit message IDs that can break deduplication if the timestamp is not finite.
  • ui/src/ui/markdown.ts, src/signal/monitor/event-handler.ts

Shadow added 6 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-barnacle openclaw-barnacle bot added channel: signal Channel integration: signal app: web-ui App: web-ui agents Agent runtime and tooling labels Feb 5, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 5, 2026

Additional Comments (2)

ui/src/ui/markdown.ts
Dead config constant

MARKDOWN_PARSE_LIMIT is still defined but no longer used after switching the large-content fast path to MARKDOWN_PRE_WRAP_LIMIT. This makes the intended “20KB parse limit” change a no-op and risks future confusion about which threshold actually controls markdown parsing.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/markdown.ts
Line: 25:27

Comment:
**Dead config constant**

`MARKDOWN_PARSE_LIMIT` is still defined but no longer used after switching the large-content fast path to `MARKDOWN_PRE_WRAP_LIMIT`. This makes the intended “20KB parse limit” change a no-op and risks future confusion about which threshold actually controls markdown parsing.

How can I resolve this? If you propose a fix, please make it concise.

src/signal/monitor/event-handler.ts
Invalid edit messageId

For edits, messageId becomes String(editTargetTimestamp), but editTargetTimestamp is computed via Number(...) without validating it’s finite. If targetSentTimestamp is present but non-numeric (or too large), this produces "NaN"/"Infinity" message IDs, breaking dedup semantics.

const editTargetTimestamp = isEdit ? Number(envelope.editMessage.targetSentTimestamp) : undefined;
const messageId = isEdit
  ? (Number.isFinite(editTargetTimestamp) ? String(editTargetTimestamp) : undefined)
  : typeof envelope.timestamp === "number"
    ? String(envelope.timestamp)
    : undefined;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/signal/monitor/event-handler.ts
Line: 563:566

Comment:
**Invalid edit messageId**

For edits, `messageId` becomes `String(editTargetTimestamp)`, but `editTargetTimestamp` is computed via `Number(...)` without validating it’s finite. If `targetSentTimestamp` is present but non-numeric (or too large), this produces `"NaN"`/`"Infinity"` message IDs, breaking dedup semantics.

```suggestion
const editTargetTimestamp = isEdit ? Number(envelope.editMessage.targetSentTimestamp) : undefined;
const messageId = isEdit
  ? (Number.isFinite(editTargetTimestamp) ? String(editTargetTimestamp) : undefined)
  : typeof envelope.timestamp === "number"
    ? String(envelope.timestamp)
    : undefined;
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: signal Channel integration: signal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: when open the Tool Output in the Chat view, the browser stuck and cannot click

1 participant