Skip to content

fix: collapse tool calls during streaming to prevent truncation hiding agent text#253

Merged
thepagent merged 2 commits intoopenabdev:mainfrom
wangyuyan-agent:fix/streaming-truncation-242
Apr 15, 2026
Merged

fix: collapse tool calls during streaming to prevent truncation hiding agent text#253
thepagent merged 2 commits intoopenabdev:mainfrom
wangyuyan-agent:fix/streaming-truncation-242

Conversation

@wangyuyan-agent
Copy link
Copy Markdown
Contributor

@wangyuyan-agent wangyuyan-agent commented Apr 12, 2026

Summary

Fixes #242 — streaming truncation hides agent interaction prompts, causing thread deadlock.

┌──────────────────────────────────────────────────────────────────┐
│ BEFORE — streaming phase (bug)                                   │
│                                                                  │
│  compose_display() output:                                       │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │ ✅ `Spawning agent crew`                                   │  │
│  │ ✅ `Searching for '(?i)openclaw'`                          │  │
│  │ ✅ `Running: find / -maxdepth 4 -iname "*openclaw*"`      │  │
│  │ ✅ `Running: curl -s "https://api.github.com/search/...`  │  │
│  │ ✅ `Running: curl -s "https://api.github.com/repos/...`   │  │
│  │ ✅ `Running: curl -s "https://api.github.com/repos/...`   │  │
│  │ ✅ `Running: echo "=== TOP CONTRIBUTORS ===" ; curl...`   │  │
│  │ ✅ `Running: echo "=== Cosmos/IBC ===" ; curl -s ...`     │  │
│  │ ✅ `Reading listing claw-info, listing openclaw`           │  │
│  │ ✅ `Reading README.md:1, LICENSE:1-30`                     │  │
│  │ ✅ `Running: node -e "const p = require(...)"`             │  │
│  │ ✅ `Reading tool_d7d3ac0c...:1-50`                        │  │
│  │ ✅ `Reading 2026-04-02.md:1-60, gateway-lifecycle...`     │  │
│  │ ✅ `Summarizing`                                           │  │
│  │ ✅ `Summarizing`                                           │  │
│  │                                                            │  │
│  │  "I found issues in config-7. Should I fix it?"            │  │
│  └────────────────────────────────────────────────────────────┘  │
│       ▲                                                          │
│       │  truncate_chars(&content, 1900) keeps FIRST 1900 chars   │
│       │  15 tool lines ≈ 1200+ chars → agent text GONE           │
│       │                                                          │
│  User sees: 15 lines of ✅ + "…"                                 │
│  Agent waits for reply it will never get → DEADLOCK              │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ AFTER — streaming phase (fix)                                    │
│                                                                  │
│  compose_display(..., streaming=true):                            │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │ ✅ 15 tool(s) completed                    (~30 chars)     │  │
│  │                                                            │  │
│  │ I found issues in config-7. Should I fix it?               │  │
│  │                                            ← VISIBLE ✅    │  │
│  │                                                            │  │
│  │           (1800+ chars remaining for text)                 │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
│  Fallback: if STILL over 1900 → tail-priority truncation         │
│            keeps LAST 1900 chars → latest text always visible    │
└──────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│ AFTER — final message (unchanged)                                │
│                                                                  │
│  compose_display(..., streaming=false):                           │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │ ✅ `Spawning agent crew`                                   │  │
│  │ ✅ `Searching for '(?i)openclaw'`                          │  │
│  │ ✅ `Running: find / ...`                                   │  │
│  │ ... (all tools fully expanded)                             │  │
│  │ ✅ `Summarizing`                                           │  │
│  │                                                            │  │
│  │ I found issues in config-7. Should I fix it?               │  │
│  └────────────────────────────────────────────────────────────┘  │
│  split_message() handles >2000 chars → multiple Discord msgs     │
└──────────────────────────────────────────────────────────────────┘

Collapsing rules (streaming only):
  finished ≤ 3  →  individual lines (no change)
  finished > 3  →  "✅ N tool(s) completed"
  with failures →  "✅ 3 · ❌ 2 tool(s) completed"
  running       →  always individual (guarded at same threshold)

Changes

Single file: src/discord.rs + removal of unused truncate_chars in src/format.rs

1. Threshold-based tool collapsing in compose_display()

Added a streaming mode that collapses finished tool entries when count exceeds TOOL_COLLAPSE_THRESHOLD (3):

  • ≤ 3 finished tools → shown individually (no UX regression)
  • > 3 finished tools✅ N tool(s) completed (one line)
  • Failed tools → counted separately: ✅ 3 · ❌ 2 tool(s) completed
  • Running tools → always shown individually, with same threshold guard for parallel edge case (🔧 N more running)

Why 3? A tool line averages 40–80 chars. 3 lines ≈ 120–240 chars = 6–13% of the 1900-char budget, leaving 1660+ for agent text. Also the practical "glanceable" limit.

2. Tail-priority truncation (defense-in-depth)

Streaming edit now keeps the last 1900 chars instead of the first, with …(truncated) indicator. Even if collapsing isn't enough, the user always sees the most recent agent output.

3. Final message unchanged

Final edit uses full expansion (streaming: false) — split_message() handles multi-message delivery, so truncation isn't a risk. Collapsing is only for the streaming phase.

Test results

7 new unit tests added (33 total, all passing):

  • Threshold boundary (3 vs 4 tools)
  • Mixed completed + failed count accuracy
  • Running tools alongside collapsed finished tools
  • Parallel running tools guard
  • Non-streaming full expansion
  • Multi-byte character safety (CJK, emoji)

Screenshots

2 tools — individual display (below threshold):

2 tools

15 tools — collapsed + agent text visible:

15 tools

18 tools — collapsed:

18 tools

…g agent text

- Add streaming mode to compose_display() that collapses finished tool
  entries into a summary line when count exceeds TOOL_COLLAPSE_THRESHOLD (3).
  Threshold justified: 3 lines ≈ 120-240 chars (6-13% of 1900-char budget),
  also the practical 'glanceable' limit.
- Running tools shown individually; guarded with same threshold for
  parallel tool call edge case (shows last N + '🔧 X more running').
- Tail-priority truncation as defense-in-depth: streaming edit keeps
  last 1900 chars instead of first, with '…(truncated)' indicator.
- Final message uses full expansion (non-streaming) since split_message()
  handles multi-message delivery without truncation risk.
- Added 7 unit tests covering threshold boundary, mixed completed/failed
  counts, parallel running guard, non-streaming mode, and multi-byte
  character safety.

Fixes openabdev#242
@wangyuyan-agent wangyuyan-agent force-pushed the fix/streaming-truncation-242 branch from 14e8bec to d083496 Compare April 12, 2026 06:52
@thepagent thepagent merged commit 73797ce into openabdev:main Apr 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: streaming truncation hides agent interaction prompts, causing thread deadlock

3 participants