Skip to content

fix(ui): avoid false stale chat send guard - #2034

Merged
EItanya merged 12 commits into
kagent-dev:mainfrom
erauner12:fix/ui-send-guard-streaming-messages
Jun 25, 2026
Merged

EItanya merged 12 commits into
kagent-dev:mainfrom
erauner12:fix/ui-send-guard-streaming-messages

Conversation

@erauner12

@erauner12 erauner12 commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes a false-positive chat send guard that can show New messages loaded — please review before sending during normal same-tab usage after an agent response has already completed and is visible.

The send guard now compares backend-derived messages against all locally visible comparable messages instead of only persisted/stored messages. This preserves the intended cross-tab stale-message warning while allowing the next same-tab send to proceed.

Fixes #2033.

Changes

  • Count all locally visible comparable chat messages when deciding whether the backend has unseen messages.
  • Apply the same comparable-message filtering to backend-derived messages before comparing counts.
  • Add mounted regression tests covering:
    • same-tab completed stream does not block the next send
    • true cross-tab/unseen messages still block
    • cross-tab/unseen messages still block after a same-tab stream

Validation

Automated:

cd ui
npm test -- --runTestsByPath src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx --runInBand

Result:

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

Previously also run successfully during branch validation:

src/lib/__tests__/messageHandlers.test.ts: 27 passed
Full UI Jest suite: 268 passed

Manual:

  • Deployed/tested locally against kind-kagent.
  • Verified the next same-tab message sends without the false New messages loaded toast after a completed agent response.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 17, 2026
@erauner12
erauner12 marked this pull request as ready for review June 17, 2026 01:17
@erauner12
erauner12 requested a review from peterj as a code owner June 17, 2026 01:17
Copilot AI review requested due to automatic review settings June 17, 2026 01:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the cross-tab “send guard” logic in ChatInterface to avoid falsely blocking sends when same-tab streaming messages are already visible, and adds Jest coverage for the updated behavior.

Changes:

  • Count comparable messages using both stored + streaming messages (and exclude specific ADK originalTypes) to prevent false “stale messages” blocks.
  • Reuse the same comparable-message counting logic for both local and DB message comparisons.
  • Add a dedicated test suite validating send-guard behavior across same-tab streaming and cross-tab updates.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
ui/src/components/chat/ChatInterface.tsx Refines send-guard message counting and centralizes exclusion logic to reduce false blocks.
ui/src/components/chat/tests/ChatInterface.sendGuard.test.tsx Adds regression tests covering same-tab streaming vs cross-tab message scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +461 to +462

export function countSendGuardComparableMessages(messages: Message[]): number {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: you could do this with a oneliner :) (typescript can sometimes feel like python :D)

messages.filter(isSendGuardComparableMessage).length;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed :)

}

function getSendGuardMessageContentSignature(message: Message): string {
const meta = message.metadata as ADKMetadata | undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit -- can you pull out the \u0001 and \u0000 as consts within the function, so it doesn't look like random strings.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

actually, pull them out of the function, since they are using in other functions too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed

@erauner12
erauner12 requested a review from peterj June 23, 2026 14:24
@EItanya
EItanya merged commit e1422ab into kagent-dev:main Jun 25, 2026
24 checks passed
dimetron added a commit to dimetron/kagent that referenced this pull request Jun 26, 2026
Resolve send-guard conflict in ui/src/components/chat/ChatInterface.tsx by
adopting upstream's localMessages/comparable-message approach (kagent-dev#2034) over the
branch's earlier high-water-mark implementation. Also picks up configurable
stream timeout (kagent-dev#1973), go default declarative runtime (kagent-dev#2083), and controller
service annotations (kagent-dev#2088).
iplay88keys pushed a commit that referenced this pull request Jun 30, 2026
Fixes a false-positive chat send guard that can show `New messages
loaded — please review before sending` during normal same-tab usage
after an agent response has already completed and is visible.

The send guard now compares backend-derived messages against all locally
visible comparable messages instead of only persisted/stored messages.
This preserves the intended cross-tab stale-message warning while
allowing the next same-tab send to proceed.

Fixes #2033.

## Changes

- Count all locally visible comparable chat messages when deciding
whether the backend has unseen messages.
- Apply the same comparable-message filtering to backend-derived
messages before comparing counts.
- Add mounted regression tests covering:
  - same-tab completed stream does not block the next send
  - true cross-tab/unseen messages still block
  - cross-tab/unseen messages still block after a same-tab stream

## Validation

Automated:

```bash
cd ui
npm test -- --runTestsByPath src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx --runInBand
```

Result:

```text
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
```

Previously also run successfully during branch validation:

```text
src/lib/__tests__/messageHandlers.test.ts: 27 passed
Full UI Jest suite: 268 passed
```

Manual:

- Deployed/tested locally against `kind-kagent`.
- Verified the next same-tab message sends without the false `New
messages loaded` toast after a completed agent response.

---------

Signed-off-by: Evan Rauner <[email protected]>
Co-authored-by: Eitan Yarmush <[email protected]>
(cherry picked from commit e1422ab)
Signed-off-by: Jeremy Alvis <[email protected]>
EItanya added a commit that referenced this pull request Jul 1, 2026
… release/v0.9.x (#2116)

## Summary

Backport of the chat send-guard fix to `release/v0.9.x`. The send guard
falsely blocked the next message with "New messages loaded — please
review before sending" on essentially every turn after the first —
sending twice was needed each time.

This backport is **two cherry-picks**, in order:

- `#2034` (`e1422ab4`) — `fix(ui): avoid false stale chat send guard`.
Prerequisite.
- `#2115` (`df828c0d`) — `Stop false stale-send guard after tool-call
turns`. The actual fix.

## Why #2034 is included (not just #2115)

`release/v0.9.x` shipped an older, **count-based** send guard: it blocks
when `extractMessagesFromTasks(tasks).length > localMessageCount`,
comparing raw message counts. On that guard, the just-completed turn
lives in `streamingMessages` and is not counted in `localMessageCount`
(which counts only `storedMessages`) until a block triggers a reload —
so every send after the first blocks once, reloads, and succeeds on
retry. That is the "send twice every message" symptom.

`#2115` makes streamed and persisted messages key identically
(`contextId`/`taskId`), which only matters for the **key-based** guard
introduced by `#2034`. Applied alone to 0.9.x it would be a no-op
(backfilling ids does not change a count). So `#2034` is cherry-picked
first to bring the key-based guard, then `#2115` fixes it.

Note for reviewers: `#2034` is a behavioral change to the guard
(count-based → key-based comparison of comparable messages), not only a
bugfix. Both cherry-picks applied with no conflicts (0.9.x's
`ChatInterface.tsx`/`messageHandlers.ts` were identical to `#2034`'s
parent).

## What #2115 fixes (on top of #2034)

Persisted agent messages omit `contextId`/`taskId` (A2A optional fields;
the task is the canonical carrier). The locally-streamed copies carry
the task's real ids, so the key-based guard keys them as `["task", …]`
while the backend-extracted copies fall back to `["message", <id>]`
(regenerated for tool messages). They never match, the backend looks
ahead of the local view, and the send blocks on every turn with an agent
response.

The fix carries the ids on agent messages so the copies key identically:

- **UI** (`ui/src/lib/messageHandlers.ts`): when flattening
`task.history`, backfill `contextId`/`taskId` from the task for every
extracted agent message (text and tool), treating `""` as absent.
Repairs already-persisted sessions regardless of producer.
- **Runtime** (source): stamp the ids at emission time in both producers
— Python `kagent-adk` (`convert_event_to_a2a_message`) and the Go ADK
executor (`go/adk/pkg/a2a/executor.go`).

---------

Signed-off-by: Evan Rauner <[email protected]>
Signed-off-by: Jeremy Alvis <[email protected]>
Co-authored-by: erauner12 <[email protected]>
Co-authored-by: Eitan Yarmush <[email protected]>
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
kagent 0.10.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre><!-- Release notes generated using configuration in .github/release.yml at v0.10.0 -->

## What's Changed
### Features
* feat(streaming): configurable nginx, controller, and UI inactivity ti… by @yanivmn in kagent-dev/kagent#1973
* feat(helm): add controller service annotations by @anthonyhaussman in kagent-dev/kagent#2088
* feat: enable sharing of chat sessions by @onematchfox in kagent-dev/kagent#1935
* feat: substrate support for BYO and python runtimes for SandboxAgent CR by @jmhbh in kagent-dev/kagent#2043
* feat(substrate): bump agent substrate to 0.0.7 by @supreme-gg-gg in kagent-dev/kagent#2109
* feat(substrate): bump agent substrate to 0.0.8 by @jjamroga in kagent-dev/kagent#2140
* feat(helm/kagent): support deployment annotations by @anthonyhaussman in kagent-dev/kagent#2143
* feat: add nodeSelector support to agent helm charts by @the-mentor in kagent-dev/kagent#2145
* feat: expose icon, provider, documentation URL and version on agent A2A card by @QuentinBisson in kagent-dev/kagent#2188
* feat: add claude ACP sandbox target by @the-mentor in kagent-dev/kagent#2189
* feat(sandbox-agents): Store session state in `durableDir` volume for declarative sandbox agents by @jmhbh in kagent-dev/kagent#2171
* feat(helm): add UI HTTPRoute by @younsl in kagent-dev/kagent#2165
* feat(helm): bump bundled oauth2-proxy subchart to ~10.7.0 by @elihai-risk in kagent-dev/kagent#2254
* feat: default nodeSelector for agent deployments (--default-agent-node-selector) by @elihai-risk in kagent-dev/kagent#2259
* feat(helm): add podLabels for controller and UI pod templates by @elihai-risk in kagent-dev/kagent#2237
* feat(helm): add extraObjects for arbitrary manifests by @younsl in kagent-dev/kagent#2174
* feat(ui-test): Playwright ui tests foundation stage 0 by @cjlg-soloio in kagent-dev/kagent#2262
* feat: allow read-only shares to read A2A tasks by @QuentinBisson in kagent-dev/kagent#2191
* feat(controller): make Go (ADK) runtime agent image independently configurable by @QuentinBisson in kagent-dev/kagent#2021
* feat(helm): allow custom annotations on the default ModelConfig by @marians in kagent-dev/kagent#2201
* feat(ui-test): Playwright UI E2E suite — helpers + feature flows (stage 2) by @cjlg-soloio in kagent-dev/kagent#2264
* feat: serve A2A ListTasks from the task store by @QuentinBisson in kagent-dev/kagent#2187
* feat(models): add maxCompletionTokens for OpenAI by @anthonytwh in kagent-dev/kagent#2175
* feat(models): wire maxOutputTokens for native Gemini and Vertex by @anthonytwh in kagent-dev/kagent#2147
* feat: add envFrom support to agent deployment spec by @uncycler in kagent-dev/kagent#2321
* feat: add native AWS Bedrock Guardrails support to ModelConfig by @sanskar-singh-2403 in kagent-dev/kagent#2323
* feat(bedrock): configurable Bedrock read/connect timeout by @go-faustino in kagent-dev/kagent#2223
* feat: add deploymentAnnotations to agent deployment spec by @anthonyhaussman in kagent-dev/kagent#2359
* feat(helm/kagent): allow disabling default modelconfig by @anthonyhaussman in kagent-dev/kagent#2142
* feat(sts): send RFC 8707 resource and audience on token exchange by @QuentinBisson in kagent-dev/kagent#2106
* feat(sts): send RFC 8707 resource and audience on token exchange (Python) by @QuentinBisson in kagent-dev/kagent#2107
* Backport of 2407 (pg connection) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2399
* Backport 2409 (db session cleanup) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2403
* Backport of 2408 (s3 skills) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2405
* feat: configurable refresh interval for tool server controllers by @supreme-gg-gg in kagent-dev/kagent#2480
* feat: support file upload in agent chat by @supreme-gg-gg in kagent-dev/kagent#2429
### Bug Fixes
* fix(ui): chat interface overflows viewport causing input to be hidden below fold by @onematchfox in kagent-dev/kagent#2081
* fix(ui): avoid false stale chat send guard by @erauner12 in kagent-dev/kagent#2034
* fix(go-adk): extract system prompt from genai.GenerateContentConfig in Ollama and Bedrock model providers by @supreme-gg-gg in kagent-dev/kagent#2118
* fix(helm): support omitting image.registry in grafana-mcp chart by @michaelspinks in kagent-dev/kagent#2125
* fix(helm): support omitting image.registry in querydoc chart by @michaelspinks in kagent-dev/kagent#2124
* fix(security): patch CRITICAL/HIGH CVEs in golang-adk-full and app images by @jsonmp-k8 in kagent-dev/kagent#2133
* fix(go-adk): openai embeddings generation by @supreme-gg-gg in kagent-dev/kagent#2132
* fix(helm): omit empty repository segment in bundled image path by @michaelspinks in kagent-dev/kagent#2120
* fix: intermittent Postgres deadlock for concurrent memory searches by @supreme-gg-gg in kagent-dev/kagent#2146
* fix(deps): bump google-adk to 1.32.0 for Anthropic thinking blocks by @anxkhn in kagent-dev/kagent#2172
* fix: fix typos in part_converter comments by @mesutoezdil in kagent-dev/kagent#2195
* fix(adk): annotate serialize_metadata_value as str | dict by @anxkhn in kagent-dev/kagent#2173
* fix(ui): fix model name editing on the model config form by @Valyrian-Code in kagent-dev/kagent#1979
* fix: correct package comment in v1alpha2 groupversion_info by @mesutoezdil in kagent-dev/kagent#2183
* fix(crewai): unwrap Part before DataPart check so structured inputs are used by @anxkhn in kagent-dev/kagent#2231
* fix(helm): expose nodeSelector and tolerations for kagent-tools subchart by @the-mentor in kagent-dev/kagent#2213
* fix(helm): make the cluster DNS domain configurable by @shekhargit1912 in kagent-dev/kagent#2209
* fix: parse migration filenames the same way golang-migrate does by @mesutoezdil in kagent-dev/kagent#2194
* fix: build sandbox runtime outside QEMU emulation by @peterj in kagent-dev/kagent#2299
* fix: set kgateway.dev/a2a as appProtoco for BYO agents by @supreme-gg-gg in kagent-dev/kagent#2305
* fix(db): normalize agent name in memory vector search by @mesutoezdil in kagent-dev/kagent#2307
* fix(adk/bedrock): coerce nil tool-call args to empty object by @go-faustino in kagent-dev/kagent#2222
* fix: correct webhook typo in CLI help text by @tziyon31 in kagent-dev/kagent#2203
* fix(build): use AZURE_OPENAI_API_KEY env var name consistently by @mesutoezdil in kagent-dev/kagent#2310
* fix(security): scope task get, create, and delete to the owner by @mesutoezdil in kagent-dev/kagent#2265
* fix(adk): keep token refresh loop alive after a failed read by @mesutoezdil in kagent-dev/kagent#2330
* fix(acp-shim): drain child stdout during terminate to avoid teardown deadlock by @tomsen02 in kagent-dev/kagent#2338
* fix(adk): keep full session state when num_recent_events is set by @mesutoezdil in kagent-dev/kagent#2329
* fix(controller): wire up SessionSharesHandler in NewHandlers (#2363) by @onematchfox in kagent-dev/kagent#2420
* fix: ensure API key passthrough works for OpenAI embedding models by @onematchfox in kagent-dev/kagent#2494
* fix(adk): validate ask_user questions by @erauner12 in kagent-dev/kagent#2486
* Backport fix(golang-adk): resolve user identity from A2A call context to 0.10.x by @buravc in kagent-dev/kagent#2507
* fix(a2a): serve exact task reads from the persistent store by @erauner12 in kagent-dev/kagent#2484
* fix(ui): preserve post-login redirect target through `oauth2-proxy` sign-in by @onematchfox in kagent-dev/kagent#2532
### Documentation
* docs: fix duplicate descriptions and broken link in README by @mesutoezdil in kagent-dev/kagent#2068
* docs: fix typos and errors in component READMEs by @mesutoezdil in kagent-dev/kagent#2070
* docs: fix typos and list formatting in contributor docs by @mesutoezdil in kagent-dev/kagent#2069
* docs: replace outdated AutoGen reference in SECURITY.md by @mesutoezdil in kagent-dev/kagent#2110
* docs(python): fix kagent-langgraph README import to kagent.langgraph by @anxkhn in kagent-dev/kagent#2167
* docs(python): fix KAgentSession import path in kagent-openai README by @anxkhn in kagent-dev/kagent#2163
* docs: fix stale references and 

See merge request: Harmonybrew/homebrew-core!18335
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI chat: "New messages loaded" notification can incorrectly block sending the next message

4 participants