Skip to content

feat(ui): single-line tool indicator on Feishu/Telegram, heartbeat on WeChat#268

Merged
floodsung merged 1 commit into
mainfrom
feat/compact-tool-indicator
May 14, 2026
Merged

feat(ui): single-line tool indicator on Feishu/Telegram, heartbeat on WeChat#268
floodsung merged 1 commit into
mainfrom
feat/compact-tool-indicator

Conversation

@floodsung

Copy link
Copy Markdown
Contributor

Summary

Users repeatedly told us the per-tool list inside the running card is noise — they read the final response, not the intermediate Read/Edit/Bash trail. This PR replaces the ever-growing tool list with one summary line and hides the section entirely once the turn finishes.

What changes

  • Feishu v1 + v2 cards — during `thinking` / `running` / `waiting_for_input`, render exactly one line: `⏳ · N tools`. On `complete` / `error`, the tool section is gone; the card shows only the user prompt, the final response, and the grey footer.
  • Telegram — same single-line treatment, same hide-on-complete.
  • WeChat — per-batch progress text was the noisiest surface (WeChat can't edit messages, so each tool batch was a fresh chat message every 5 s). Replaced with a single-line heartbeat throttled to 30 s: `🔧 运行中: · N tools`. Long runs still show signs of life without flooding the thread.
  • Web UI — untouched. It already collapses tool calls into a chevron-expandable section (`web/src/components/chat/AssistantMessage.tsx:15-46`), which is the right escape hatch for users who want detail.
  • No `CardState` schema changes. The renderers derive the indicator from `state.toolCalls[last]` + `.length`, so the existing stream-processor and web UI keep working without churn.
  • Drops a pre-existing unused-import warning in `wechat-sender.ts` that I created by simplifying `renderProgressMessage` → `renderHeartbeatMessage`.

Why now

User asked for it after consistent feedback that "现在已经没人看中间的工具调用了". The hung-run signal still has a home: while the turn is in flight, the one-line indicator keeps updating, so users can tell whether the card is alive or frozen.

Test plan

  • `npm run build` clean
  • `npx vitest run` → 288 / 288 (added two new tests asserting single-line render + zero tool elements on complete)
  • `npm run lint` → 0 errors, 2 pre-existing warnings (untouched)
  • Manual: send a multi-tool prompt on Feishu, confirm the card shows one line `⏳ · N tools` during run, then drops the section on complete
  • Manual: same on Telegram
  • Manual: same on WeChat — confirm at most one progress message per 30 s and only the final result lands as a chat message

🤖 Generated with Claude Code

… WeChat

Users repeatedly told us the running tool-call list is noise — they only
care about the final answer. Replace the ever-growing per-tool list with
one summary line, and hide the section entirely once the turn finishes.

Feishu card builders (v1 + v2)
  - One line during in-flight states: `⏳ <latest tool> · N tools`.
  - Section disappears on `complete` / `error`; card shows only the user
    prompt, the final response, and the grey footer stats.
  - No new `CardState` fields — derive the indicator from
    `state.toolCalls[last]` + `.length`, so web UI's collapsible per-tool
    view (already the preferred surface for users who want detail)
    keeps working unchanged.

Telegram sender
  - Same single-line treatment + hide-on-complete; matches Feishu cards.

WeChat sender
  - Per-batch progress text is the noisiest surface (WeChat can't edit
    messages, so each tool batch lands as a new chat message every 5s).
    Replaced with a single-line heartbeat throttled to 30s:
    `🔧 运行中:<tool> · N tools`. Long runs still show signs of life
    without flooding the thread.
  - Drops the unused `CardStatus` import while we're here.

Tests
  - card-builder + card-builder-v2: assert single-line render during
    `running`, and zero tool-related element on `complete`.

288 / 288 vitest pass. Lint clean (2 pre-existing warnings, untouched).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@floodsung floodsung merged commit d6291a8 into main May 14, 2026
3 checks passed
@floodsung floodsung deleted the feat/compact-tool-indicator branch May 14, 2026 00:37
floodsung added a commit that referenced this pull request May 14, 2026
…se, show only final result (#269)

PR #268 hid the running tool list from the main card. The "Agent activity
between turns" spontaneous card had the same problem one layer deeper —
it accumulated every assistant message (including bare `tool_use` blocks
that rendered as `🔧 Bash`/`🔧 Read` lines) into a numbered list, exactly
the play-by-play we just hid from the main card.

Apply the same UX bet here:

- `extractSpontaneousSnippet`: tool_use-only assistant messages now
  return null. Text snippets (the agent's actual conclusion) still
  survive. Mixed messages still surface the text and ignore the
  adjacent tool_use, same as before.
- `formatSpontaneousCardBody`: render only the LATEST snippet (the
  conclusion of the burst), not a numbered list of all snippets. When
  N>1 were coalesced, append a small `_(N events coalesced; showing
  latest)_` footer so the user knows there was more activity if they
  want to dig into pm2 logs or the web UI.

Side effects:

- A between-turn burst that produced ONLY tool calls (no agent text)
  no longer triggers a card at all — the buffer stays empty, the
  30 s flush is a no-op. This is the correct outcome: nothing
  user-meaningful to report.
- The continuation-turn render path (handleContinuationTurn) was
  already covered by PR #268 since it goes through the same
  card-builder.ts surface. No change needed there.

Tests:
- extractSpontaneousSnippet: assert tool_use-only returns null,
  text+tool_use still returns the text, multi-tool_use returns null.
- formatSpontaneousCardBody: assert single-snippet renders without a
  numbered prefix; N>1 renders the latest snippet plus the
  coalesced-count footer, and the earlier snippets are NOT in the
  body.

290/290 vitest pass. Build + lint clean (2 pre-existing warnings).

Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
SimonYeyi pushed a commit to SimonYeyi/metabot that referenced this pull request May 26, 2026
… WeChat (xvirobotics#268)

Users repeatedly told us the running tool-call list is noise — they only
care about the final answer. Replace the ever-growing per-tool list with
one summary line, and hide the section entirely once the turn finishes.

Feishu card builders (v1 + v2)
  - One line during in-flight states: `⏳ <latest tool> · N tools`.
  - Section disappears on `complete` / `error`; card shows only the user
    prompt, the final response, and the grey footer stats.
  - No new `CardState` fields — derive the indicator from
    `state.toolCalls[last]` + `.length`, so web UI's collapsible per-tool
    view (already the preferred surface for users who want detail)
    keeps working unchanged.

Telegram sender
  - Same single-line treatment + hide-on-complete; matches Feishu cards.

WeChat sender
  - Per-batch progress text is the noisiest surface (WeChat can't edit
    messages, so each tool batch lands as a new chat message every 5s).
    Replaced with a single-line heartbeat throttled to 30s:
    `🔧 运行中:<tool> · N tools`. Long runs still show signs of life
    without flooding the thread.
  - Drops the unused `CardStatus` import while we're here.

Tests
  - card-builder + card-builder-v2: assert single-line render during
    `running`, and zero tool-related element on `complete`.

288 / 288 vitest pass. Lint clean (2 pre-existing warnings, untouched).

Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
SimonYeyi pushed a commit to SimonYeyi/metabot that referenced this pull request May 26, 2026
…se, show only final result (xvirobotics#269)

PR xvirobotics#268 hid the running tool list from the main card. The "Agent activity
between turns" spontaneous card had the same problem one layer deeper —
it accumulated every assistant message (including bare `tool_use` blocks
that rendered as `🔧 Bash`/`🔧 Read` lines) into a numbered list, exactly
the play-by-play we just hid from the main card.

Apply the same UX bet here:

- `extractSpontaneousSnippet`: tool_use-only assistant messages now
  return null. Text snippets (the agent's actual conclusion) still
  survive. Mixed messages still surface the text and ignore the
  adjacent tool_use, same as before.
- `formatSpontaneousCardBody`: render only the LATEST snippet (the
  conclusion of the burst), not a numbered list of all snippets. When
  N>1 were coalesced, append a small `_(N events coalesced; showing
  latest)_` footer so the user knows there was more activity if they
  want to dig into pm2 logs or the web UI.

Side effects:

- A between-turn burst that produced ONLY tool calls (no agent text)
  no longer triggers a card at all — the buffer stays empty, the
  30 s flush is a no-op. This is the correct outcome: nothing
  user-meaningful to report.
- The continuation-turn render path (handleContinuationTurn) was
  already covered by PR xvirobotics#268 since it goes through the same
  card-builder.ts surface. No change needed there.

Tests:
- extractSpontaneousSnippet: assert tool_use-only returns null,
  text+tool_use still returns the text, multi-tool_use returns null.
- formatSpontaneousCardBody: assert single-snippet renders without a
  numbered prefix; N>1 renders the latest snippet plus the
  coalesced-count footer, and the earlier snippets are NOT in the
  body.

290/290 vitest pass. Build + lint clean (2 pre-existing warnings).

Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant