Skip to content

feat(ui): compact 'Agent activity between turns' card — drop tool noise, show only final result#269

Merged
floodsung merged 1 commit into
mainfrom
feat/compact-spontaneous-card
May 14, 2026
Merged

feat(ui): compact 'Agent activity between turns' card — drop tool noise, show only final result#269
floodsung merged 1 commit into
mainfrom
feat/compact-spontaneous-card

Conversation

@floodsung

Copy link
Copy Markdown
Contributor

Summary

Apply the PR #268 UX bet ("hide intermediate tool noise, show only the final result") one layer deeper — to the spontaneous "Agent activity between turns" card that coalesces teammate / /goal / background-task pings every 30 s.

Before

When 5 between-turn events fired during a quiet window, the card listed every one as a numbered item:

Agent activity between turns (background task return, teammate ping, or /goal evaluator):
1. 🔧 Bash
2. Looking at the PR comments…
3. 🔧 Read
4. Found 3 things to address.
5. Pushed commit abc1234 to the branch.

`🔧 Bash` / `🔧 Read` are exactly the per-tool play-by-play we just hid from the live card — surfacing them between turns put it right back.

After

Agent activity between turns (…)

Pushed commit abc1234 to the branch.

(3 events coalesced; showing latest)

Only the latest text snippet (the agent's conclusion of the burst) makes it into the card. The footer tells users how many events were coalesced if they want to dig into `pm2 logs metabot` or the web UI for the play-by-play.

Changes

  • `extractSpontaneousSnippet` — tool_use blocks now return `null`. Mixed text + tool_use messages still surface the text; pure tool_use messages drop. Pure-tool bursts now produce no card (buffer stays empty, 30 s flush is a no-op), which is correct — nothing user-meaningful happened.
  • `formatSpontaneousCardBody` — show only the last snippet, no numbered list, with a coalesced-count footer when N>1.
  • continuation-turn render path (`handleContinuationTurn`) is unchanged — it already routes through `card-builder.ts` and inherits PR feat(ui): single-line tool indicator on Feishu/Telegram, heartbeat on WeChat #268's single-line tool indicator + hide-on-complete.

Test plan

  • `npm run build` — clean
  • `npx vitest run` — 290 / 290 pass (3 reworded + 2 new assertions)
  • `npm run lint` — 0 errors, 2 pre-existing warnings (untouched)
  • Manual: trigger an Agent Teams burst that includes several tool calls + a final text reply, confirm only the final text lands in the spontaneous card with the new footer
  • Manual: trigger a tool-only between-turn burst (e.g. a teammate just running tools with no text response), confirm no spontaneous card is sent

🤖 Generated with Claude Code

…se, show only final result

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: Claude Opus 4.7 <noreply@anthropic.com>
@floodsung floodsung merged commit 27da25b into main May 14, 2026
3 checks passed
@floodsung floodsung deleted the feat/compact-spontaneous-card branch May 14, 2026 00:53
floodsung added a commit that referenced this pull request May 14, 2026
…on (#271)

The spontaneous between-turn card was leading every body with an italic
`Agent activity between turns (background task return, teammate ping, or
/goal evaluator):` caption. Users called it out as visual clutter — and
the underlying signal (this is a between-turn burst, not a regular turn)
should come from card chrome anyway, not from a long preamble.

Drop the caption; carry the signal in the card header instead.

- New `CardStatus = 'agent_activity'` — rendered as a blue header with
  the title "Agent activity" + 🔵 icon. Distinct from `running` /
  `thinking` (also blue but titled "Running…"/"Thinking…") and from
  `complete` (green) so users can tell at a glance whether the card is
  a live turn, a finished reply, or a between-turn burst.
- `flushSpontaneous` now uses the new status; `formatSpontaneousCardBody`
  returns just the latest snippet (and the coalesced-count footer for
  N>1 bursts) — no italic caption.
- Add a guard in `flushSpontaneous`: if the buffer somehow has zero
  snippets (e.g. tool-only burst that got fully filtered by the
  text-only extractor in #269), skip the card entirely. The empty card
  case is now unreachable but the guard is cheap insurance.
- Mirror the new status into v1 + v2 card builders, the Telegram
  sender's STATUS_EMOJI/STATUS_LABEL maps, and the web frontend
  CardStatus union. Drop the now-unused SPONTANEOUS_CARD_HEADER
  export and the regression tests that pinned its phrasing.

Tests
  - card-builder + card-builder-v2: add an agent_activity render test
    asserting blue header + "Agent activity" title + no body caption.
  - message-bridge: rewrite formatSpontaneousCardBody tests to assert
    the body is exactly the latest snippet, with no italic caption and
    no "between turns" / "long-running" substrings.

291 / 291 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
…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>
SimonYeyi pushed a commit to SimonYeyi/metabot that referenced this pull request May 26, 2026
…on (xvirobotics#271)

The spontaneous between-turn card was leading every body with an italic
`Agent activity between turns (background task return, teammate ping, or
/goal evaluator):` caption. Users called it out as visual clutter — and
the underlying signal (this is a between-turn burst, not a regular turn)
should come from card chrome anyway, not from a long preamble.

Drop the caption; carry the signal in the card header instead.

- New `CardStatus = 'agent_activity'` — rendered as a blue header with
  the title "Agent activity" + 🔵 icon. Distinct from `running` /
  `thinking` (also blue but titled "Running…"/"Thinking…") and from
  `complete` (green) so users can tell at a glance whether the card is
  a live turn, a finished reply, or a between-turn burst.
- `flushSpontaneous` now uses the new status; `formatSpontaneousCardBody`
  returns just the latest snippet (and the coalesced-count footer for
  N>1 bursts) — no italic caption.
- Add a guard in `flushSpontaneous`: if the buffer somehow has zero
  snippets (e.g. tool-only burst that got fully filtered by the
  text-only extractor in xvirobotics#269), skip the card entirely. The empty card
  case is now unreachable but the guard is cheap insurance.
- Mirror the new status into v1 + v2 card builders, the Telegram
  sender's STATUS_EMOJI/STATUS_LABEL maps, and the web frontend
  CardStatus union. Drop the now-unused SPONTANEOUS_CARD_HEADER
  export and the regression tests that pinned its phrasing.

Tests
  - card-builder + card-builder-v2: add an agent_activity render test
    asserting blue header + "Agent activity" title + no body caption.
  - message-bridge: rewrite formatSpontaneousCardBody tests to assert
    the body is exactly the latest snippet, with no italic caption and
    no "between turns" / "long-running" substrings.

291 / 291 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