Skip to content

Polish agentic chat and move agent to Vertex#490

Open
dtrn2048 wants to merge 8 commits intomainfrom
feat/agentic-chat-main
Open

Polish agentic chat and move agent to Vertex#490
dtrn2048 wants to merge 8 commits intomainfrom
feat/agentic-chat-main

Conversation

@dtrn2048
Copy link
Copy Markdown
Contributor

@dtrn2048 dtrn2048 commented Mar 19, 2026

Summary

  • move the agent service from Google AI Studio usage to Google Vertex and align the agentic flow with the existing GCP setup
  • bring agentic chat UI behavior in line with the main chat experience, including titles, copy actions, timestamps, transcript deep links, loading states, and compact tool activity
  • include the locale updates needed for the new agentic chat copy, including Dutch translations

Validation

  • cd echo/server && uv run mypy dembrane/ --ignore-missing-imports
  • cd echo/frontend && /Users/dattran/Development/echo/echo/frontend/node_modules/.bin/biome lint src/components/chat/AgenticChatPanel.tsx src/components/chat/ChatHistoryMessage.tsx src/components/chat/agenticToolActivity.ts src/components/common/Markdown.tsx --diagnostic-level=error
  • cd echo/frontend && /Users/dattran/Development/echo/echo/frontend/node_modules/.bin/tsc --noEmit

Summary by CodeRabbit

  • New Features

    • Vertex AI auth options added; default LLM set to Claude Opus 4.6 and default location europe-west1.
    • Language support for agent runs and automatic chat title generation.
    • Transcript chunk links now deep-link, auto-scroll and briefly highlight target chunks.
  • New Features (UI)

    • Agentic Chat enhancements: rebuilt timeline/timestamps/sorting, improved tool activity rows, raw-data toggle, live-run indicator, autosizing input, copy-to-transcript, and scroll-to-bottom behavior.
  • Tests

    • Added unit and E2E tests for agentic flows, credential/project resolution, transcript matching, and title-generation.
  • Chores

    • Documentation, dependency, config and i18n catalog updates; local test/env config added.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Migrates LLM wiring from Gemini to Vertex/Anthropic with Vertex/GCP credential and project resolution; adds per-conversation transcript match metadata and server-side snippet extraction; threads optional message language and async chat-title generation into agentic endpoints; implements a large frontend Agentic Chat UI refactor (timeline, tools, transcript linking/highlighting), tests, deps, and i18n updates.

Changes

Cohort / File(s) Summary
Agent config & deps
echo/agent/settings.py, echo/agent/pyproject.toml, echo/agent/README.md
Remove Gemini API key, change default LLM_MODEL to claude-opus-4-6, add VERTEX_*/GCP_SA_JSON settings with JSON parsing validator, swap langchain dependency to Vertex variant and add anthropic[vertex], and update README local run auth to Vertex/ADC options.
LLM wiring & client types
echo/agent/agent.py, echo/agent/echo_client.py
Replace Gemini ChatGoogleGenerativeAI usage with dynamic Vertex/Anthropic ChatAnthropicVertex import and explicit/ADC credential resolution; add optional matches to AgentProjectConversation TypedDict.
Agent unit & settings tests
echo/agent/tests/test_agent_graph.py, echo/agent/tests/test_agent_tools.py, echo/agent/tests/test_settings.py
Add tests for _build_llm credential/project resolution and ADC behavior; update transcript-snippet tests to consume API-provided matches; adapt settings tests to new Vertex/GCP env parsing.
Backend agentic API & tests
echo/server/dembrane/api/agentic.py, echo/server/tests/api/test_agentic_api.py
Add language to create/append schemas; implement transcript-query snippet/match assembly (matches per conversation); add async chat title generation flow invoked after persisting user messages; update tests for title generation and matches.
Frontend Agentic UI overhaul
echo/frontend/src/components/chat/AgenticChatPanel.tsx, echo/frontend/src/components/chat/agenticToolActivity.ts, echo/frontend/src/components/chat/ChatHistoryMessage.tsx, echo/frontend/src/components/common/Markdown.tsx
Large UI refactor: assemble/sort timeline by sortSeq/timestamps, enrich transcript chunk links, replace tool UI with Mantine Collapse/ActionIcon, change tool activity pairing and shape (sortSeq, timestamp), override markdown anchors for agentic transcript links, add scroll/hydration behavior, and adjust message rendering.
Transcript view & chunk highlighting
echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx, echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx
Support URL-hash #chunk-... targeting with auto-paging until found, smooth scroll and temporary highlight of target chunk, add highlighted prop, and simplify transcript/error rendering logic.
Frontend API & E2E
echo/frontend/src/lib/api.ts, echo/cypress/cypress.env.json, echo/cypress/e2e/suites/36-agentic-chat-local.cy.js, echo/cypress/cypress.config.js
createAgenticRun/appendAgenticRunMessage accept optional language; add local Cypress env and a comprehensive local agentic-chat E2E; cypress config improvements for env/auth resolution and Node core imports.
i18n catalogs
echo/frontend/src/locales/{en-US,de-DE,es-ES,fr-FR,it-IT,nl-NL}.po
Add many Agentic Chat UI msgids (statuses, transcript/tool affordances, raw-data controls), mark legacy Agentic Chat obsolete, adjust source references and clear/add translations.
Ruff / server tooling
echo/server/pyproject.toml
Adjust ruff lint config layout, rename typing rule id, and tweak ignored/unfixable rule lists.
Cypress env cleanup
echo/cypress/cypress.env.json
Remove stored auth from staging/prod/testing entries and add local environment with local dashboard/portal URLs (ensure trailing newline).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Polish agentic chat and move agent to Vertex' directly reflects the two main objectives: polishing the agentic chat UI and migrating from Gemini API to Vertex AI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agentic-chat-main

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
echo/agent/echo_client.py (1)

34-41: 🧹 Nitpick | 🔵 Trivial

Type matches as a real schema.

list[dict[str, Optional[str]]] drops the contract we now rely on and turns downstream access into unchecked string lookups. A dedicated TypedDict keeps mypy useful here.

♻️ Proposed typing
+class AgentProjectConversationMatch(TypedDict, total=False):
+    chunk_id: Optional[str]
+    timestamp: Optional[str]
+    snippet: Optional[str]
+
+
 class AgentProjectConversation(TypedDict, total=False):
     conversation_id: str
     participant_name: Optional[str]
     status: str
     summary: Optional[str]
     started_at: Optional[str]
     last_chunk_at: Optional[str]
-    matches: list[dict[str, Optional[str]]]
+    matches: list[AgentProjectConversationMatch]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/agent/echo_client.py` around lines 34 - 41, The matches field on
AgentProjectConversation is currently typed as list[dict[str, Optional[str]]],
which loses structure and breaks mypy—define a concrete TypedDict (e.g.,
ConversationMatch or AgentProjectConversationMatch) that lists the expected keys
(for example match_id: str, score: Optional[float], snippet: Optional[str],
source: Optional[str] or whatever contract the code uses) and replace matches:
list[dict[str, Optional[str]]] with matches: list[ConversationMatch]; update
imports (TypedDict, Optional) and any code that constructs or reads matches to
use the new TypedDict keys so static typing is preserved (reference
AgentProjectConversation and the matches field).
echo/frontend/src/components/chat/AgenticChatPanel.tsx (1)

630-633: ⚠️ Potential issue | 🟠 Major

Clear the prompt after the write succeeds, not before.

setInput("") happens before either backend write. If createAgenticRun or appendAgenticRunMessage rejects, the user loses the draft for a transient failure.

💾 Proposed fix
		setError(null);
		setIsSubmitting(true);
-		setInput("");

		try {
			let targetRunId = runId;
			const nextLanguage = iso639_1 ?? "en";

			if (!targetRunId) {
				const created = await createAgenticRun({
					language: nextLanguage,
					message,
					project_chat_id: chatId,
					project_id: projectId,
				});
+				setInput("");
				targetRunId = created.id;
				setRunId(targetRunId);
				setRunStatus(created.status);
				window.localStorage.setItem(storageKeyForChat(chatId), targetRunId);
				invalidateChatQueries();
				const payload = await refreshEvents(targetRunId, 0);
				if (!isTerminalStatus(payload.status)) {
					void startStream(targetRunId, payload.next_seq);
				}
			} else {
				const updated = await appendAgenticRunMessage(targetRunId, {
					language: nextLanguage,
					message,
				});
+				setInput("");
				setRunStatus(updated.status);
				invalidateChatQueries();

Also applies to: 638-658

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 630 -
633, The code currently clears the user's draft via setInput("") before the
backend calls (createAgenticRun / appendAgenticRunMessage) complete; change the
flow so setInput("") is only called after the async write succeeds (i.e., after
await createAgenticRun(...) or await appendAgenticRunMessage(...) resolves) and
keep setIsSubmitting(true) and setError(null) as-is; additionally ensure both
code paths that handle creating a new run (createAgenticRun) and appending to an
existing run (appendAgenticRunMessage) follow the same pattern so failures leave
the draft intact and errors are set via setError, and move the clearing logic
from the early branch near setInput to the success-handling block used for lines
covering the create/append sections.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@echo/cypress/cypress.env.json`:
- Around line 40-46: Remove the plaintext test credential block (the local.auth
object containing "email" and "password") from cypress.env.json; keep only
non-sensitive settings like "dashboardUrl" and "portalUrl" in that tracked file
and change test code to read credentials from environment variables or a
gitignored override file (e.g., process.env or Cypress.env for EMAIL/PASSWORD)
instead of the "local.auth" keys so no PII or passwords are committed.

In `@echo/cypress/e2e/suites/36-agentic-chat-local.cy.js`:
- Around line 74-90: The transcript-match fixture used in the
output.kwargs.content JSON payload is missing the timestamp field in the matches
entry, causing tests to miss timestamp-related regressions; update the object
created where JSON.stringify is called (the block referencing
transcriptConversationId, transcriptChunkId, and query) to include a timestamp
property alongside chunk_id and snippet (e.g., timestamp: <appropriate ISO or
epoch value>) so the stub matches the backend shape.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 451-457: The current logic only switches to polling when
streamFailureCount >= 2, so a single stream drop leaves the UI wedged; update
the state update in setStreamFailureCount (and the analogous block around the
lines 527-529) to trigger a retry/reaming attempt on the first failure and only
fall back to polling after the second failure: when incrementing
streamFailureCount in the setStreamFailureCount callback, if next === 1 schedule
or call the routine that re-arms the live stream (e.g., setIsStreaming(true) or
invoke the existing start/rehydrate streaming function) so a reconnect is
attempted, and if next >= 2 setError and allow the polling effect (which checks
streamFailureCount >= 2) to run. Ensure you reference and update the same
variables/functions: setStreamFailureCount, streamFailureCount, isStreaming (or
startStreaming/rehydrate function) so both sites behave identically.
- Around line 572-575: The effect that auto-scrolls (useEffect watching
timeline.length and calling scrollToBottom) should first check visibility and
the user's current scroll position so it doesn't force-scroll while the user is
reading older content: update the effect to only call scrollToBottom when
isVisible is true and when the panel is already at-or-near the bottom (use or
add a helper/flag like isScrolledToBottom or compute via the scroll container's
scrollTop/scrollHeight/clientHeight), otherwise do nothing (and let your "new
messages" button appear); reference useEffect, timeline.length, scrollToBottom,
and isVisible when implementing the guard.
- Around line 335-339: Replace the hardcoded speaker labels in
computedChatForCopy so exported transcripts use translations and the assistant
label is lowercase; call the translation helper (either useTranslation's t
template literal or <Trans>) instead of "User"/"Dembrane" and pass the
translated values into formatMessage (e.g., formatMessage(message, t`User`,
t`dembrane`)), and add the necessary import/useTranslation hook to obtain t;
ensure the assistant label remains "dembrane" lowercase in the translated
string.
- Around line 224-243: TOOL_STATUS_META currently hardcodes Tailwind palette
classes (dotClass/textClass) which ties the UI to one color theme; define CSS
variables for each status color (e.g., --tool-status-completed-bg,
--tool-status-completed-text, --tool-status-running-bg, etc.) in your theme or
component root and replace the hardcoded classes in TOOL_STATUS_META (dotClass,
textClass) with class names that reference those vars (or use style attributes
that read the CSS vars) so the colors follow the app palette; update the other
similar occurrences called out (lines ~787-803, 833-937, 965-978) to use the
same CSS vars for consistency.
- Around line 1002-1007: The Enter key handler in the onKeyDown for
AgenticChatPanel submits even during IME composition; modify the handler used
around onKeyDown to check event.nativeEvent?.isComposing (or
event.nativeEvent.isComposing === true) and skip submission when composing.
Specifically, inside the arrow function that currently checks event.key ===
"Enter" && !event.shiftKey and calls handleSubmit, add a guard to return early
if isComposing is true so event.preventDefault() and handleSubmit() only run
when not composing.

In `@echo/frontend/src/components/chat/agenticToolActivity.ts`:
- Around line 176-231: The current pairing in extractTopLevelToolActivity uses
openToolIndexes keyed only by parsed.toolName, causing overlapping calls to the
same tool to be mispaired; change the logic to key openToolIndexes by a stable
per-call identifier from the event (e.g., parsed.callId or parsed.id) instead of
just toolName, update takeLatestOpenIndex to accept and look up by that
call-level key (or maintain a separate Map<callId, index>), and ensure created
ids (currently `tool-${parsed.toolName}-${parsed.seq}`) and parseToolEvent usage
remain consistent so each start/running event is matched to its corresponding
end/error using the call id.

In `@echo/frontend/src/locales/de-DE.po`:
- Around line 507-515: The German .po entries for the AgenticChatPanel strings
are empty so the UI falls back to English; fill each msgstr for the msgid values
("Agent is working...", "Agent run failed", and all other listed msgids from the
comment ranges) with proper German translations in the de-DE.po file so the
supported de-DE locale is complete; locate the strings originating from
AgenticChatPanel (msgid contexts shown) and update their corresponding msgstr
entries (and similarly for the other ranges referenced: 735-737, 1527-1529,
2051-2057, 2314-2317, 2411-2413, 2582-2619, 2655-2657, 3063-3067, 3131-3133,
3891-3893, 3940-3978, 4201-4204, 4775-4778, 4811-4827) with accurate German copy
so no de-DE entries remain blank.

In `@echo/frontend/src/locales/fr-FR.po`:
- Around line 522-529: The French .po entries for the new agentic feature are
missing translations so fr-FR will fall back to English; update the fr-FR
translation file by providing French msgstr values for the listed msgid strings
(e.g., "Agent is working..." and "Agent run failed") introduced for the
AgenticChatPanel component (AgenticChatPanel.tsx) and propagate equivalent
translations for the other referenced msgids across the same locale set (en-US,
nl-NL, de-DE, fr-FR, es-ES, it-IT) following the normal localization flow so all
.po files in locales cover the supported languages.

In
`@echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx`:
- Around line 93-104: The effect that auto-scrolls to targetChunkId (the
useEffect watching allChunks and targetChunkId) runs repeatedly as allChunks
grows; add a one-shot guard using a ref or state (e.g., autoScrollDoneRef or
hasAutoScrolled) so the effect returns early if already performed for the
current targetChunkId, and set that flag immediately after calling
scrollIntoView and setHighlightedChunkId; keep targetChunkId in the dependency
list so a new target re-arms the behavior.
- Around line 62-64: The current computation of targetChunkId decodes
location.hash unguarded and will throw on malformed percent-encodings; update
the logic in ProjectConversationTranscript (where targetChunkId is computed) to
defensively decode by trying decodeURIComponent inside a try/catch (or using a
small safeDecode helper) and returning null on error or if the hash doesn't
start with "#chunk-"; ensure the variable name targetChunkId and the
location.hash check remain but replace the direct decodeURIComponent call with
the safe/try-catch approach so render cannot be broken by bad hashes.

In `@echo/server/dembrane/api/agentic.py`:
- Around line 461-489: This read-then-write race must be turned into an atomic
conditional update: change the call site to use an atomic "set only if still
empty" operation (e.g. add/use chat_service.set_chat_name_if_empty or
chat_service.update_name_if_empty) instead of calling chat_service.set_chat_name
after get_by_id_or_raise; implement the conditional update inside the
chat_service / storage layer so it does a single compare-and-set (or DB
conditional update) and returns whether it succeeded, and update the caller (the
code using run_in_thread_pool, chat_service.get_by_id_or_raise, generate_title
and chat_service.set_chat_name) to call that new conditional method and handle
the returned boolean (no-op if false).
- Around line 648-649: The call to _maybe_generate_chat_title is currently
awaited in the send-message critical path (e.g., the invocation at await
_maybe_generate_chat_title(body.project_chat_id, body.message, body.language)
and the similar block at lines 678-682), causing extra lookup/model latency;
change these to fire-and-forget background tasks instead of awaiting them: after
the run/message append completes, schedule _maybe_generate_chat_title via an
async background runner (e.g., asyncio.create_task or your app's task queue),
ensure any exceptions from the background task are caught/logged (wrap call in a
short wrapper that logs but does not propagate), and apply the same non-blocking
pattern to the other instance(s) referenced (the 678-682 block) so title
generation is best-effort and does not delay response.

In `@echo/server/tests/api/test_agentic_api.py`:
- Around line 309-317: The fixture data for _FakeChatService does not match
ChatService.get_by_id_or_raise's expected shape because ownership is nested
under the project object; update the fake chat entries (used in tests
referencing _FakeChatService) to set project_id as an object that includes both
id and directus_user_id (e.g., project_id: {"id": "project-1",
"directus_user_id": "user-1"}) instead of placing directus_user_id at the top
level; apply this change to every occurrence of the fake chat fixture in the
test file so tests exercise the real shape that ChatService.get_by_id_or_raise
expects.

---

Outside diff comments:
In `@echo/agent/echo_client.py`:
- Around line 34-41: The matches field on AgentProjectConversation is currently
typed as list[dict[str, Optional[str]]], which loses structure and breaks
mypy—define a concrete TypedDict (e.g., ConversationMatch or
AgentProjectConversationMatch) that lists the expected keys (for example
match_id: str, score: Optional[float], snippet: Optional[str], source:
Optional[str] or whatever contract the code uses) and replace matches:
list[dict[str, Optional[str]]] with matches: list[ConversationMatch]; update
imports (TypedDict, Optional) and any code that constructs or reads matches to
use the new TypedDict keys so static typing is preserved (reference
AgentProjectConversation and the matches field).

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 630-633: The code currently clears the user's draft via
setInput("") before the backend calls (createAgenticRun /
appendAgenticRunMessage) complete; change the flow so setInput("") is only
called after the async write succeeds (i.e., after await createAgenticRun(...)
or await appendAgenticRunMessage(...) resolves) and keep setIsSubmitting(true)
and setError(null) as-is; additionally ensure both code paths that handle
creating a new run (createAgenticRun) and appending to an existing run
(appendAgenticRunMessage) follow the same pattern so failures leave the draft
intact and errors are set via setError, and move the clearing logic from the
early branch near setInput to the success-handling block used for lines covering
the create/append sections.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d92e86d6-6f74-410b-a083-5b8b75297e89

📥 Commits

Reviewing files that changed from the base of the PR and between 91e5945 and cd07159.

⛔ Files ignored due to path filters (1)
  • echo/agent/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • echo/agent/README.md
  • echo/agent/agent.py
  • echo/agent/echo_client.py
  • echo/agent/pyproject.toml
  • echo/agent/settings.py
  • echo/agent/tests/test_agent_graph.py
  • echo/agent/tests/test_agent_tools.py
  • echo/agent/tests/test_settings.py
  • echo/cypress/cypress.env.json
  • echo/cypress/e2e/suites/36-agentic-chat-local.cy.js
  • echo/frontend/src/components/chat/AgenticChatPanel.tsx
  • echo/frontend/src/components/chat/ChatHistoryMessage.tsx
  • echo/frontend/src/components/chat/agenticToolActivity.ts
  • echo/frontend/src/components/common/Markdown.tsx
  • echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx
  • echo/frontend/src/lib/api.ts
  • echo/frontend/src/locales/de-DE.po
  • echo/frontend/src/locales/de-DE.ts
  • echo/frontend/src/locales/en-US.po
  • echo/frontend/src/locales/en-US.ts
  • echo/frontend/src/locales/es-ES.po
  • echo/frontend/src/locales/es-ES.ts
  • echo/frontend/src/locales/fr-FR.po
  • echo/frontend/src/locales/fr-FR.ts
  • echo/frontend/src/locales/it-IT.po
  • echo/frontend/src/locales/it-IT.ts
  • echo/frontend/src/locales/nl-NL.po
  • echo/frontend/src/locales/nl-NL.ts
  • echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx
  • echo/server/dembrane/api/agentic.py
  • echo/server/tests/api/test_agentic_api.py
👮 Files not reviewed due to content moderation or server errors (4)
  • echo/frontend/src/locales/it-IT.po
  • echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx
  • echo/agent/tests/test_agent_tools.py
  • echo/frontend/src/components/chat/ChatHistoryMessage.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
echo/frontend/src/lib/api.ts (1)

1079-1086: ⚠️ Potential issue | 🟡 Minor

Guard language before sending to avoid backend 422s.

Line 1083 and Line 1092 allow optional language, but forwarding ""/whitespace will violate backend min_length=1. Normalize or omit it before POST. Not a big rewrite—just a defensive guard.

⚡ Proposed fix
 export const createAgenticRun = async (payload: {
 	project_id: string;
 	project_chat_id?: string;
 	message: string;
 	language?: string;
 }) => {
-	return api.post<unknown, AgenticRun>("/agentic/runs", payload);
+	const language = payload.language?.trim();
+	return api.post<unknown, AgenticRun>("/agentic/runs", {
+		...payload,
+		...(language ? { language } : {}),
+	});
 };

 export const appendAgenticRunMessage = async (
 	runId: string,
 	payload: {
 		message: string;
 		language?: string;
 	},
 ) => {
+	const language = payload.language?.trim();
 	return api.post<unknown, AgenticRun>(
 		`/agentic/runs/${runId}/messages`,
-		payload,
+		{
+			...payload,
+			...(language ? { language } : {}),
+		},
 	);
 };

Also applies to: 1088-1098

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/lib/api.ts` around lines 1079 - 1086, The createAgenticRun
function is forwarding an optional language value directly which can be
""/whitespace and trigger backend 422s; before calling api.post in
createAgenticRun (and the similar functions around lines 1088–1098), trim the
language value and omit it from the request payload if trimmed length is 0
(i.e., build a payload object that only sets language when
language?.trim().length > 0) so empty/whitespace languages are not sent to the
backend.
echo/frontend/src/locales/es-ES.po (3)

2587-2626: ⚠️ Potential issue | 🟡 Minor

Tool activity strings all untranslated.

All the new tool activity labels — "List project conversations", "Load project context", "Load conversation summary", "Load full transcript", "Search transcript" — are shipping with empty Spanish translations.

These are visible in the Agentic Chat UI tool activity panel per the AI summary. Would be clean to batch translate these with the other agentic strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/es-ES.po` around lines 2587 - 2626, The Spanish
locale file has empty translations for several agentic tool activity strings;
open echo/frontend/src/locales/es-ES.po and add appropriate Spanish msgstr
values for the msgid entries "List project conversations", "Load project
context", "Load conversation summary", "Load full transcript", and "Search
transcript" (and also provide a translation for "Live stream interrupted.
Falling back to polling." if needed) by filling the corresponding msgstr lines
so the Agentic Chat UI tool activity panel displays translated labels.

5268-5269: ⚠️ Potential issue | 🟠 Major

Regression: Previously translated string now empty.

Yo this is a downgrade — "Something went wrong" was legit translated as "Algo salió mal" before. Now it's empty string. Same vibes happening at lines 5294-5295 where "Welcome back" lost its "Bienvenido de nuevo" translation.

 msgid "Something went wrong"
-msgstr ""
+msgstr "Algo salió mal"

Check if this was intentional or if your extraction tooling nuked existing translations. 10x engineers don't regress i18n coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/es-ES.po` around lines 5268 - 5269, The Spanish
translations for several msgid entries (e.g., the string used in
useConversationIssueBanner: "We’re picking up some silence. Try speaking up so
your voice comes through clearly." and other lost strings like "Something went
wrong" and "Welcome back") were cleared during extraction; restore the correct
Spanish translations (e.g., "Algo salió mal", "Bienvenido de nuevo", and an
appropriate translation for the silence prompt) in
echo/frontend/src/locales/es-ES.po by locating the matching msgid entries and
repopulating their msgstr values, ensure no empty msgstr remains, and re-run the
extraction/compilation pipeline to confirm the translations persist (also audit
the extraction tool config that produced the empty strings to prevent future
regressions).

507-528: ⚠️ Potential issue | 🟠 Major

Missing Spanish translations for new Agentic Chat UI strings.

Ship it but these msgstr values are empty, my dude. Spanish users gonna see raw English which is kinda sus for a production locale. New strings like "Agent is working...", "Agent run failed", "Done", etc. all need actual translations.

Looks like ~30+ new msgids dropped without any Spanish love. Consider running your i18n extraction/translation workflow before merging or opening a fast-follow ticket.

As per coding guidelines: echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/es-ES.po` around lines 507 - 528, The Spanish .po
file is missing translations for newly added UI strings (e.g., msgids used in
AgenticChatPanel.tsx like "Agent is working..." and "Agent run failed", plus
strings from ChatModeSelector.tsx/ChatModeBanner.tsx and ChatAccordion.tsx);
open echo/frontend/src/locales/es-ES.po and provide accurate Spanish msgstr
values for each empty msgid, run your i18n extraction/translation workflow to
ensure all new msgids are present across supported locales (en-US, nl-NL, de-DE,
fr-FR, es-ES, it-IT), and commit the updated .po so Spanish users see localized
text instead of raw English.
echo/frontend/src/locales/fr-FR.po (1)

4279-4284: ⚠️ Potential issue | 🟠 Major

Duplicate active msgid entries are breaking the PO catalog.

The fr-FR.po file has duplicate msgid entries with empty msgstr values:

  • "Something went wrong" appears at lines 39 (translated) and 4282 (empty)
  • "Welcome back" appears at lines 54 (translated) and 5312 (empty)
  • "dashboard.dembrane.verify.title", "participant.verify.selection.title", "participant.verify.instructions.receive.artefact", and "participant.verify.instructions.approval.helps" all have empty msgstr values

Duplicate active msgid entries in a PO catalog cause ambiguous gettext/Lingui resolution. Either remove the duplicates entirely or regenerate translations using pnpm messages:extract && pnpm messages:compile rather than manually editing .po files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/fr-FR.po` around lines 4279 - 4284, The fr-FR.po
contains duplicate active msgid entries (e.g., "Something went wrong", "Welcome
back", "dashboard.dembrane.verify.title", "participant.verify.selection.title",
"participant.verify.instructions.receive.artefact",
"participant.verify.instructions.approval.helps") that break the PO catalog; fix
by removing the duplicate active entries (keep the correctly translated ones) or
fully regenerate the messages rather than hand-editing: run pnpm
messages:extract && pnpm messages:compile to rebuild the .po/.json catalogs and
ensure only one active msgid per key, then commit the regenerated files.
echo/frontend/src/locales/de-DE.po (1)

4258-4263: ⚠️ Potential issue | 🟠 Major

Don't re-add translated msgids as blank active entries.

Line 4261 and Line 5289 duplicate entries that already exist earlier in this catalog with German msgstr values at Line 39 and Line 54. That leaves two live entries for the same msgid, and the later blank one can win and wipe the shipped translation.

As per coding guidelines, echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT.

Also applies to: 5288-5291

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/de-DE.po` around lines 4258 - 4263, You added a
duplicate active entry for msgid "Something went wrong" with an empty msgstr
which can override the correct German translation; remove the later blank entry
(the one referenced by Login, ParticipantInitiateForm, ConversationEdit) or
replace its msgstr with the existing German translation so there is only one
active translation for that msgid in de-DE.po, then re-run PO linting/merge to
ensure no duplicate msgid remains across the de-DE catalog.
♻️ Duplicate comments (4)
echo/frontend/src/locales/fr-FR.po (1)

522-529: ⚠️ Potential issue | 🟠 Major

French agentic copy is still shipping untranslated.

These new run-state, tool, and transcript entries still have empty msgstr, so fr-FR stays incomplete for the agentic flow. Please sync the missing French copy through the normal Lingui localization flow before merge.

As per coding guidelines echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT.

Also applies to: 753-755, 1547-1549, 2071-2077, 2334-2337, 2431-2433, 2602-2604, 2625-2639, 2675-2677, 3083-3087, 3151-3153, 3898-3900, 3947-3950, 3978-3985, 4208-4211, 4789-4792, 4825-4827, 4839-4841

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/fr-FR.po` around lines 522 - 529, The fr-FR locale
file is missing translations for several agentic flow entries (e.g., msgid
"Agent is working..." and "Agent run failed" referenced from
AgenticChatPanel.tsx); run the Lingui localization workflow (extract/update
catalogs) and supply French msgstr values for those msgid keys in
echo/frontend/src/locales/fr-FR.po, and repeat for the other empty ranges listed
(lines around the other msgid groups) so the fr-FR catalog covers the same keys
as en-US and the other supported locales (nl-NL, de-DE, es-ES, it-IT) before
merging.
echo/cypress/e2e/suites/36-agentic-chat-local.cy.js (1)

80-85: ⚠️ Potential issue | 🟡 Minor

Keep the transcript-match fixture aligned with the API shape.

Line 82 still omits timestamp from each matches item. That makes this smoke test blind to timestamp-related regressions in transcript chips and deep links after the backend payload change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/cypress/e2e/suites/36-agentic-chat-local.cy.js` around lines 80 - 85,
The fixture for transcript matches is missing the required timestamp property on
each item in the matches array; update the test fixture in the
36-agentic-chat-local.cy.js file so each object in matches (the one using
transcriptChunkId and snippet) includes a timestamp field that matches the
backend API format (e.g., ISO8601 string or epoch ms as used elsewhere in the
codebase) so the transcript chip and deep-link behavior is exercised correctly
by the test.
echo/frontend/src/components/chat/agenticToolActivity.ts (1)

149-170: ⚠️ Potential issue | 🟠 Major

Verify the call-id guarantee before falling back to toolName.

Line 200 is only safe if callId from Line 149 is present on every on_tool_* event. If any lifecycle pair falls back to tool:${toolName}, concurrent calls to the same tool can still cross-wire exactly like before.

Run this read-only check and confirm the same per-call id is emitted on both start and terminal events:

#!/bin/bash
set -euo pipefail

fd 'agentic' echo/server -e py -x rg -n -C4 'on_tool_(start|end|error)|tool_call_id|call_id|run_id' {}
rg -n -C4 'type AgenticRunEvent|interface AgenticRunEvent|tool_call_id|call_id|run_id' \
  echo/frontend/src/lib/api.ts \
  echo/frontend/src/components/chat/agenticToolActivity.ts

Also applies to: 200-201, 226-246

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/agenticToolActivity.ts` around lines 149 -
170, The code is falling back to "tool:${toolName}" when callId (computed by
firstString from payload/data/outputKwargs) is missing, which can let concurrent
calls to the same tool cross-wire; update the logic that computes the per-call
key so it only falls back to toolName when you can guarantee single concurrency,
otherwise generate and persist a synthetic per-call id at the start event and
reuse it for terminal events (or require using an alternate stable identifier
like payload.id/run_id if present); specifically change the behavior around the
callId variable (and where toolName is used as a fallback) so on_tool_start
records a synthetic ID into an in-memory map keyed by any stable event id
available (payload.id/run_id) and on_tool_end/on_tool_error look up that
synthetic ID instead of defaulting to tool:${toolName}, and ensure all paths
using firstString(...) refer to this new lookup to avoid cross-wiring.
echo/frontend/src/locales/de-DE.po (1)

5155-5158: ⚠️ Potential issue | 🟠 Major

Don't blank the active Verify translations in de-DE.

These are live Verify-flow entries, not obsolete #~ rows. Shipping them with empty msgstr regresses German UI outside agentic chat and falls back to untranslated copy in a supported locale.

As per coding guidelines, echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT.

Also applies to: 5335-5338, 5445-5452

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/de-DE.po` around lines 5155 - 5158, Restore the
missing German translation for the msgid "dashboard.dembrane.verify.title" in
de-DE.po (and the other blank msgstr entries referenced around lines 5335-5338
and 5445-5452) by copying the correct German text from the canonical source
(e.g., en-US or the existing verified translation in another locale) or
providing an appropriate German translation, ensuring these Verify-flow entries
used by ProjectPortalEditor (msgid "dashboard.dembrane.verify.title") are not
left empty so the German UI does not fall back to untranslated text; update only
the msgstr values for those msgid keys in echo/frontend/src/locales/de-DE.po.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@echo/cypress/cypress.config.js`:
- Around line 11-18: The resolveAuthFromProcess function currently mixes scoped
and global auth fields independently; update it to treat email/password as a
pair: compute normalizedVersion, then check for both scoped vars
(CYPRESS_<normalizedVersion>_AUTH_EMAIL and
CYPRESS_<normalizedVersion>_AUTH_PASSWORD) — if both exist use them; else if
neither scoped var exists, check that both global vars (CYPRESS_AUTH_EMAIL and
CYPRESS_AUTH_PASSWORD) exist and use them; if exactly one of the scoped pair or
exactly one of the global pair is present, throw a descriptive error indicating
a half-configured credential for the given version so callers fail fast
(reference resolveAuthFromProcess and normalizedVersion).

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 392-409: The loop in AgenticChatPanel.tsx that pages through
getAgenticRunEvents is hard-capped at 100 pages (for (let page = 0; page < 100;
page += 1)), which can silently truncate events; replace the magic number with a
named constant or configurable MAX_PAGES and after the loop detects it reached
the cap (i.e., when page === MAX_PAGES - 1 and there are still events/next_seq),
emit a warning via your logger (include targetRunId, cursor/nextCursor, page
count and optional latestPayload summary) so you can monitor truncation in
production; update references to page, cursor, collected, latestPayload and
getAgenticRunEvents accordingly.
- Around line 262-283: The _original object in toHistoryMessage is being
asserted as a full ProjectChatMessage despite only providing a subset of fields;
change the type assertion to use Partial<ProjectChatMessage> (or adjust
HistoryLikeMessage to accept a partial) so the synthetic messages are correctly
typed—locate the toHistoryMessage function and replace the cast on the _original
object from ProjectChatMessage to Partial<ProjectChatMessage> (or update
HistoryLikeMessage’s _original field to Partial<ProjectChatMessage>) to reflect
the partial data shape.

In `@echo/frontend/src/components/chat/agenticToolActivity.ts`:
- Around line 120-123: The current merge logic always prefers parsed.headline
over existing.headline (e.g., in the update that constructs ParsedToolEvent),
which causes query-specific headlines to be lost when buildHeadline() returns a
headline without the query; change the assignment so that if the incoming parsed
object lacks a query (parsed.query is null/empty) you keep the more specific
existing.headline instead of overwriting it; update the code paths that set
headline (references: ParsedToolEvent, buildHeadline, parsed.headline,
existing.headline) to use something like: headline = parsed.query ?
parsed.headline : existing.headline (or otherwise detect missing query in parsed
and preserve existing.headline).

In `@echo/frontend/src/locales/es-ES.po`:
- Around line 5161-5162: The PO has empty translations for user-facing keys;
fill the msgstr for dashboard.dembrane.verify.title (and the related keys
participant.verify.selection.title and the verify instruction strings) with
appropriate Spanish text, preserving any placeholders and markup exactly; locate
the entries by their msgid values (e.g., "dashboard.dembrane.verify.title",
"participant.verify.selection.title" and the verify instruction msgids), provide
concise Spanish translations matching tone and punctuation of the original, and
ensure plural forms or format specifiers remain unchanged.

In `@echo/frontend/src/locales/it-IT.po`:
- Around line 696-704: Fill the empty msgstr entries for the Italian locale for
the untranslated msgid entries (e.g., "Agent is working...", "Agent run failed",
"Something went wrong", "Welcome back") and the other referenced msgid
occurrences so the it-IT .po contains Italian translations for all UI strings
mentioned in the comment; update each msgstr with the correct Italian text,
mirror existing translations in other locales when appropriate, and verify no
other msgstr are left empty across the listed occurrences (943-946, 1757-1760,
2321-2328, 2597-2601, 2708-2711, 2875-2878, 2900-2915, 2950-2953, 3377-3382,
3446-3449, 4157-4160, 4206-4210, 4237-4245, 4474-4478, 4553, 5102-5106,
5139-5142, 5153-5156, 5651) to prevent fallback to English and then run the PO
linter/CI to ensure no missing translations remain.

In `@echo/frontend/src/locales/nl-NL.po`:
- Around line 5341-5344: Several active localization keys have empty
translations (msgstr) causing raw keys to surface in UI; fill in Dutch
translations for the empty msgid entries such as
"dashboard.dembrane.verify.title" and the other affected keys around lines noted
(e.g., the groups at 5521-5524 and 5646-5654). Open the nl-NL.po entries for
those msgid strings, provide appropriate Dutch text in each msgstr, keep the
msgid values unchanged, save the .po file and then run the project's Lingui
extract/compile steps (or i18n build script) so the updated translations are
picked up by the app.

---

Outside diff comments:
In `@echo/frontend/src/lib/api.ts`:
- Around line 1079-1086: The createAgenticRun function is forwarding an optional
language value directly which can be ""/whitespace and trigger backend 422s;
before calling api.post in createAgenticRun (and the similar functions around
lines 1088–1098), trim the language value and omit it from the request payload
if trimmed length is 0 (i.e., build a payload object that only sets language
when language?.trim().length > 0) so empty/whitespace languages are not sent to
the backend.

In `@echo/frontend/src/locales/de-DE.po`:
- Around line 4258-4263: You added a duplicate active entry for msgid "Something
went wrong" with an empty msgstr which can override the correct German
translation; remove the later blank entry (the one referenced by Login,
ParticipantInitiateForm, ConversationEdit) or replace its msgstr with the
existing German translation so there is only one active translation for that
msgid in de-DE.po, then re-run PO linting/merge to ensure no duplicate msgid
remains across the de-DE catalog.

In `@echo/frontend/src/locales/es-ES.po`:
- Around line 2587-2626: The Spanish locale file has empty translations for
several agentic tool activity strings; open echo/frontend/src/locales/es-ES.po
and add appropriate Spanish msgstr values for the msgid entries "List project
conversations", "Load project context", "Load conversation summary", "Load full
transcript", and "Search transcript" (and also provide a translation for "Live
stream interrupted. Falling back to polling." if needed) by filling the
corresponding msgstr lines so the Agentic Chat UI tool activity panel displays
translated labels.
- Around line 5268-5269: The Spanish translations for several msgid entries
(e.g., the string used in useConversationIssueBanner: "We’re picking up some
silence. Try speaking up so your voice comes through clearly." and other lost
strings like "Something went wrong" and "Welcome back") were cleared during
extraction; restore the correct Spanish translations (e.g., "Algo salió mal",
"Bienvenido de nuevo", and an appropriate translation for the silence prompt) in
echo/frontend/src/locales/es-ES.po by locating the matching msgid entries and
repopulating their msgstr values, ensure no empty msgstr remains, and re-run the
extraction/compilation pipeline to confirm the translations persist (also audit
the extraction tool config that produced the empty strings to prevent future
regressions).
- Around line 507-528: The Spanish .po file is missing translations for newly
added UI strings (e.g., msgids used in AgenticChatPanel.tsx like "Agent is
working..." and "Agent run failed", plus strings from
ChatModeSelector.tsx/ChatModeBanner.tsx and ChatAccordion.tsx); open
echo/frontend/src/locales/es-ES.po and provide accurate Spanish msgstr values
for each empty msgid, run your i18n extraction/translation workflow to ensure
all new msgids are present across supported locales (en-US, nl-NL, de-DE, fr-FR,
es-ES, it-IT), and commit the updated .po so Spanish users see localized text
instead of raw English.

In `@echo/frontend/src/locales/fr-FR.po`:
- Around line 4279-4284: The fr-FR.po contains duplicate active msgid entries
(e.g., "Something went wrong", "Welcome back",
"dashboard.dembrane.verify.title", "participant.verify.selection.title",
"participant.verify.instructions.receive.artefact",
"participant.verify.instructions.approval.helps") that break the PO catalog; fix
by removing the duplicate active entries (keep the correctly translated ones) or
fully regenerate the messages rather than hand-editing: run pnpm
messages:extract && pnpm messages:compile to rebuild the .po/.json catalogs and
ensure only one active msgid per key, then commit the regenerated files.

---

Duplicate comments:
In `@echo/cypress/e2e/suites/36-agentic-chat-local.cy.js`:
- Around line 80-85: The fixture for transcript matches is missing the required
timestamp property on each item in the matches array; update the test fixture in
the 36-agentic-chat-local.cy.js file so each object in matches (the one using
transcriptChunkId and snippet) includes a timestamp field that matches the
backend API format (e.g., ISO8601 string or epoch ms as used elsewhere in the
codebase) so the transcript chip and deep-link behavior is exercised correctly
by the test.

In `@echo/frontend/src/components/chat/agenticToolActivity.ts`:
- Around line 149-170: The code is falling back to "tool:${toolName}" when
callId (computed by firstString from payload/data/outputKwargs) is missing,
which can let concurrent calls to the same tool cross-wire; update the logic
that computes the per-call key so it only falls back to toolName when you can
guarantee single concurrency, otherwise generate and persist a synthetic
per-call id at the start event and reuse it for terminal events (or require
using an alternate stable identifier like payload.id/run_id if present);
specifically change the behavior around the callId variable (and where toolName
is used as a fallback) so on_tool_start records a synthetic ID into an in-memory
map keyed by any stable event id available (payload.id/run_id) and
on_tool_end/on_tool_error look up that synthetic ID instead of defaulting to
tool:${toolName}, and ensure all paths using firstString(...) refer to this new
lookup to avoid cross-wiring.

In `@echo/frontend/src/locales/de-DE.po`:
- Around line 5155-5158: Restore the missing German translation for the msgid
"dashboard.dembrane.verify.title" in de-DE.po (and the other blank msgstr
entries referenced around lines 5335-5338 and 5445-5452) by copying the correct
German text from the canonical source (e.g., en-US or the existing verified
translation in another locale) or providing an appropriate German translation,
ensuring these Verify-flow entries used by ProjectPortalEditor (msgid
"dashboard.dembrane.verify.title") are not left empty so the German UI does not
fall back to untranslated text; update only the msgstr values for those msgid
keys in echo/frontend/src/locales/de-DE.po.

In `@echo/frontend/src/locales/fr-FR.po`:
- Around line 522-529: The fr-FR locale file is missing translations for several
agentic flow entries (e.g., msgid "Agent is working..." and "Agent run failed"
referenced from AgenticChatPanel.tsx); run the Lingui localization workflow
(extract/update catalogs) and supply French msgstr values for those msgid keys
in echo/frontend/src/locales/fr-FR.po, and repeat for the other empty ranges
listed (lines around the other msgid groups) so the fr-FR catalog covers the
same keys as en-US and the other supported locales (nl-NL, de-DE, es-ES, it-IT)
before merging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b564e47e-6bdd-481b-9be7-5b4a639bad55

📥 Commits

Reviewing files that changed from the base of the PR and between cd07159 and f413da8.

📒 Files selected for processing (18)
  • echo/cypress/cypress.config.js
  • echo/cypress/cypress.env.json
  • echo/cypress/e2e/suites/36-agentic-chat-local.cy.js
  • echo/frontend/src/components/chat/AgenticChatPanel.tsx
  • echo/frontend/src/components/chat/agenticToolActivity.ts
  • echo/frontend/src/lib/api.ts
  • echo/frontend/src/locales/de-DE.po
  • echo/frontend/src/locales/de-DE.ts
  • echo/frontend/src/locales/en-US.po
  • echo/frontend/src/locales/en-US.ts
  • echo/frontend/src/locales/es-ES.po
  • echo/frontend/src/locales/es-ES.ts
  • echo/frontend/src/locales/fr-FR.po
  • echo/frontend/src/locales/fr-FR.ts
  • echo/frontend/src/locales/it-IT.po
  • echo/frontend/src/locales/it-IT.ts
  • echo/frontend/src/locales/nl-NL.po
  • echo/frontend/src/locales/nl-NL.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
echo/frontend/src/components/chat/AgenticChatPanel.tsx (1)

643-650: ⚠️ Potential issue | 🟠 Major

Clear the draft only after the write succeeds.

setInput("") runs before createAgenticRun(...) / appendAgenticRunMessage(...). If that write fails, the prompt is gone with no retry path.

Suggested fix
		setError(null);
		setIsSubmitting(true);
-		setInput("");

		try {
			let targetRunId = runId;
			const nextLanguage = iso639_1 ?? "en";

			if (!targetRunId) {
				const created = await createAgenticRun({
					language: nextLanguage,
					message,
					project_chat_id: chatId,
					project_id: projectId,
				});
+				setInput("");
				targetRunId = created.id;
				setRunId(targetRunId);
				setRunStatus(created.status);
				window.localStorage.setItem(storageKeyForChat(chatId), targetRunId);
				invalidateChatQueries();
				const payload = await refreshEvents(targetRunId, 0);
				if (!isTerminalStatus(payload.status)) {
					void startStream(targetRunId, payload.next_seq);
				}
			} else {
				const updated = await appendAgenticRunMessage(targetRunId, {
					language: nextLanguage,
					message,
				});
+				setInput("");
				setRunStatus(updated.status);
				invalidateChatQueries();
				const payload = await refreshEvents(targetRunId, afterSeq);
				if (!isTerminalStatus(payload.status)) {
					void startStream(targetRunId, payload.next_seq);
				}
			}

Also applies to: 656-678

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 643 -
650, The input draft is being cleared too early in handleSubmit (setInput(""))
before the network writes (createAgenticRun and appendAgenticRunMessage)
succeed, so preserve the draft until those calls succeed and only call
setInput("") after a successful write; update handleSubmit to move setInput("")
(and any success-specific state changes) into the success path of the async
call(s), handle errors by leaving the input intact and setting setError /
setIsSubmitting(false) on failure, and ensure both code paths that call
createAgenticRun or appendAgenticRunMessage follow this pattern so retries can
use the original draft.
♻️ Duplicate comments (7)
echo/frontend/src/locales/fr-FR.po (1)

522-529: ⚠️ Potential issue | 🟠 Major

Blocker: agentic fr-FR catalog still ships empty translations for core UX strings.

Not LGTM yet — these msgstr "" entries will fall back to English in key agentic flows (run state, tool activity, transcript actions), causing mixed-language UI in production. Please complete these via the normal Lingui localization pipeline before merge.

As per coding guidelines echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT.

Also applies to: 753-755, 1547-1550, 2071-2078, 2334-2338, 2431-2434, 2602-2640, 2675-2678, 3083-3088, 3151-3154, 3898-3901, 3947-3951, 3978-3986, 4208-4212, 4789-4793, 4825-4828, 4839-4842

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/fr-FR.po` around lines 522 - 529, The fr-FR .po
file contains empty msgstr entries for core UX strings (e.g. msgid "Agent is
working..." and "Agent run failed" used in AgenticChatPanel.tsx), causing
fallback to English; open echo/frontend/src/locales/fr-FR.po and fill each empty
msgstr with the proper French translations (and repeat for all other listed
msgid occurrences), then run the Lingui localization pipeline (the project's
normal i18n build/extract commands) to validate and compile the updated catalogs
so the translations are included for runtime; ensure all
frontend/src/locales/**/*.po cover en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT as
per guidelines.
echo/frontend/src/locales/it-IT.po (1)

696-703: ⚠️ Potential issue | 🟠 Major

Blocker: empty msgstr values ship fallback English in critical paths.

At Line 696 and across the listed ranges, active Italian entries are still empty (msgstr ""), including core strings like Line 4553 (“Something went wrong”) and Line 5651 (“Welcome back”). This regresses localized UX in it-IT.

⚡ Fast fix (pattern)
 #: src/components/chat/AgenticChatPanel.tsx:723
 msgid "Agent is working..."
-msgstr ""
+msgstr "L'agente è al lavoro..."

 #: src/routes/auth/Login.tsx:159
 msgid "Something went wrong"
-msgstr ""
+msgstr "Qualcosa è andato storto"

 #: src/routes/auth/Login.tsx:109
 msgid "Welcome back"
-msgstr ""
+msgstr "Bentornato"

As per coding guidelines "echo/frontend/src/locales/**/*.po: All .po translation files in frontend/src/locales/ must cover supported languages: en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT".

Also applies to: 943-946, 1757-1760, 2321-2328, 2597-2601, 2708-2711, 2875-2878, 2900-2915, 2950-2953, 3377-3382, 3446-3449, 4157-4160, 4206-4210, 4237-4245, 4474-4478, 4553-4553, 5102-5106, 5139-5142, 5153-5156, 5651-5651

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/it-IT.po` around lines 696 - 703, The it-IT .po
file contains untranslated entries (empty msgstr) for critical UI strings like
"Agent is working..." and "Agent run failed" (and the other listed msgids such
as "Something went wrong" and "Welcome back"); update
echo/frontend/src/locales/it-IT.po so every msgid has a non-empty Italian
translation in the corresponding msgstr (covering the ranges called out and all
other occurrences in the file) to comply with the project locale guideline that
all supported languages must have complete translations.
echo/frontend/src/locales/es-ES.po (1)

4267-4269: ⚠️ Potential issue | 🟠 Major

Regression: previously translated Spanish keys were cleared to empty.

Something went wrong, Welcome back, and multiple Verify strings are now blank in es-ES, which regresses core auth + participant verify UX.

Based on learnings: .po files are auto-generated by i18n tools like lingui/cli and should not be manually edited.

Also applies to: 5160-5162, 5293-5295, 5340-5342, 5450-5457

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/locales/es-ES.po` around lines 4267 - 4269, The Spanish .po
entries for msgid "Something went wrong", "Welcome back" and various "Verify"
strings were cleared to empty; do not manually repopulate the .po—rebuild the
locale files using the i18n tooling (e.g., run the lingui/cli extraction/compile
commands used by the project) to regenerate es-ES.po so translation memory and
existing translations are restored, and ensure the build step that merges
translations (the same command that updates .po files) runs in CI; verify msgid
"Something went wrong", "Welcome back" and the Verify msgids now have non-empty
msgstr values after regeneration.
echo/frontend/src/components/chat/AgenticChatPanel.tsx (4)

468-474: ⚠️ Potential issue | 🟠 Major

Fallback polling still waits for a second stream failure.

With the current >= 2 / < 2 thresholds, one disconnect leaves isStreaming = false and streamFailureCount = 1, so the panel neither streams nor polls until reload.

Suggested fix
				setStreamFailureCount((count) => {
					const next = count + 1;
-					if (next >= 2) {
+					if (next >= 1) {
						setError(t`Live stream interrupted. Falling back to polling.`);
					}
					return next;
				});
…
-		if (isStreaming || streamFailureCount < 2) return;
+		if (isStreaming || streamFailureCount < 1) return;

Based on learnings: "In echo/frontend/src/components/chat/AgenticChatPanel.tsx (Dembrane/echo repo), a single agentic stream failure is intentionally designed to trigger fallback polling immediately — no stream reconnect retry is wanted. The design goal is: first stream error → fall back to polling, not retry streaming."

Also applies to: 545-548

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 468 -
474, The fallback logic currently waits for two stream failures before switching
to polling; update the failure handling in the setStreamFailureCount callback
used in AgenticChatPanel (the setStreamFailureCount(...) block and the duplicate
at the second occurrence around lines noted) so that the first stream error
immediately triggers fallback polling: increment the counter as before but
change the threshold check from "next >= 2" to "next >= 1" (or simply perform
setError/toggle to polling on the first failure), ensuring isStreaming becomes
false / polling is enabled on the first failure rather than after two.

590-593: ⚠️ Potential issue | 🟡 Minor

Don’t force-scroll while the user is reading older output.

This runs on every timeline.length bump, so live updates keep snapping the viewport back to the bottom and make the scroll button moot. Gate it on whether the bottom sentinel is already visible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 590 -
593, The effect currently calls scrollToBottom on every timeline.length change;
change it to only auto-scroll when the bottom sentinel is visible. In the
useEffect that depends on timeline.length and scrollToBottom, check an existing
bottom sentinel visibility (e.g., an isBottomVisible state or bottomRef via
IntersectionObserver) and only call scrollToBottom("smooth") when that sentinel
is visible; otherwise do nothing so user scroll position isn't forced. Update or
add the sentinel visibility check where useEffect is defined (referencing
timeline, scrollToBottom, and the bottom sentinel/ref).

1040-1045: ⚠️ Potential issue | 🟠 Major

Guard Enter-to-submit during IME composition.

Enter is also used to confirm IME candidate selection, so this will submit mid-composition for CJK input unless it bails out when event.nativeEvent.isComposing is true. (developer.mozilla.org)

Suggested fix
								onKeyDown={(event) => {
+									if (event.nativeEvent.isComposing) return;
									if (event.key === "Enter" && !event.shiftKey) {
										event.preventDefault();
										void handleSubmit();
									}
								}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 1040 -
1045, The onKeyDown handler in AgenticChatPanel.tsx submits on Enter but doesn't
guard IME composition, causing mid-composition submits; update the onKeyDown
callback (the handler that currently calls handleSubmit) to first check
event.nativeEvent.isComposing (or event.nativeEvent as any).isComposing and
return early if true, then proceed to preventDefault and call handleSubmit only
when not composing and Enter without Shift.

352-355: ⚠️ Potential issue | 🟡 Minor

Localize the copied speaker labels and keep dembrane lowercase.

The export path still hardcodes "User" / "Dembrane", so copied transcripts ignore the active locale and the assistant label breaks the lowercase brand convention. Feed formatMessage(...) translated labels instead.

As per coding guidelines, "Translations must use <Trans> component or t template literal in frontend React code" and "Use dembrane always lowercase in UI copy".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 352 -
355, computedChatForCopy currently passes hardcoded "User" and "Dembrane" to
formatMessage which bypasses i18n and breaks the lowercase brand rule; update
the hook to use the app's translation helper (e.g. obtain t via useTranslation
or use <Trans>) and pass translated labels to formatMessage using the
translation helper for the user label and the lowercase assistant label (ensure
the assistant label is always "dembrane" by using a lowercase translation key or
calling .toLowerCase() on the translated string). Reference:
computedChatForCopy, historyMessages, and formatMessage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 518-533: The catch block currently removes the persisted key for
any error which loses the ability to resume; change it to only remove
window.localStorage.removeItem(key) when the error clearly means the stored run
is invalid or not found (e.g., getAgenticRun returned null/404 or throws a
specific “not found” error), but for transient errors from loadAllEvents or
network failures simply log or rethrow and keep the storedRunId; locate this
logic around getAgenticRun, loadAllEvents, startStream, storedRunId and key and
adjust the catch so only invalid-run errors clear localStorage while other
errors preserve the persisted id.

In `@echo/frontend/src/locales/es-ES.po`:
- Around line 507-515: The Spanish .po file has empty msgstr entries for new
Agentic Chat UI keys (e.g., msgid "Agent is working..." and "Agent run failed"
referenced from AgenticChatPanel.tsx), so regenerate the localized catalogs via
the i18n pipeline (lingui/cli) rather than manually editing
echo/frontend/src/locales/es-ES.po: run the extraction/build commands that
produce/update echo/frontend/src/locales/**/*.po for all supported locales
(en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT), then supply or import the proper
Spanish translations for the new keys and commit the updated generated po files
so the msgstr entries are populated for the AgenticChatPanel keys and the other
ranges listed.

In `@echo/frontend/src/locales/fr-FR.po`:
- Around line 4282-4284: Restore the missing French translations that were
accidentally cleared: for msgid "Something went wrong", "Welcome back",
"dashboard.dembrane.verify.title" and all participant.verify.* msgids in
echo/frontend/src/locales/fr-FR.po (and the other impacted ranges referenced in
the review) — replace the empty msgstr values with the correct French strings
from the previous commit or the canonical translations in other locale files;
ensure all supported locales (en-US, nl-NL, de-DE, fr-FR, es-ES, it-IT) in
frontend/src/locales/**/*.po remain fully populated and verify that the
msgid-to-msgstr pairs for those keys match the established translations.

---

Outside diff comments:
In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 643-650: The input draft is being cleared too early in
handleSubmit (setInput("")) before the network writes (createAgenticRun and
appendAgenticRunMessage) succeed, so preserve the draft until those calls
succeed and only call setInput("") after a successful write; update handleSubmit
to move setInput("") (and any success-specific state changes) into the success
path of the async call(s), handle errors by leaving the input intact and setting
setError / setIsSubmitting(false) on failure, and ensure both code paths that
call createAgenticRun or appendAgenticRunMessage follow this pattern so retries
can use the original draft.

---

Duplicate comments:
In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 468-474: The fallback logic currently waits for two stream
failures before switching to polling; update the failure handling in the
setStreamFailureCount callback used in AgenticChatPanel (the
setStreamFailureCount(...) block and the duplicate at the second occurrence
around lines noted) so that the first stream error immediately triggers fallback
polling: increment the counter as before but change the threshold check from
"next >= 2" to "next >= 1" (or simply perform setError/toggle to polling on the
first failure), ensuring isStreaming becomes false / polling is enabled on the
first failure rather than after two.
- Around line 590-593: The effect currently calls scrollToBottom on every
timeline.length change; change it to only auto-scroll when the bottom sentinel
is visible. In the useEffect that depends on timeline.length and scrollToBottom,
check an existing bottom sentinel visibility (e.g., an isBottomVisible state or
bottomRef via IntersectionObserver) and only call scrollToBottom("smooth") when
that sentinel is visible; otherwise do nothing so user scroll position isn't
forced. Update or add the sentinel visibility check where useEffect is defined
(referencing timeline, scrollToBottom, and the bottom sentinel/ref).
- Around line 1040-1045: The onKeyDown handler in AgenticChatPanel.tsx submits
on Enter but doesn't guard IME composition, causing mid-composition submits;
update the onKeyDown callback (the handler that currently calls handleSubmit) to
first check event.nativeEvent.isComposing (or event.nativeEvent as
any).isComposing and return early if true, then proceed to preventDefault and
call handleSubmit only when not composing and Enter without Shift.
- Around line 352-355: computedChatForCopy currently passes hardcoded "User" and
"Dembrane" to formatMessage which bypasses i18n and breaks the lowercase brand
rule; update the hook to use the app's translation helper (e.g. obtain t via
useTranslation or use <Trans>) and pass translated labels to formatMessage using
the translation helper for the user label and the lowercase assistant label
(ensure the assistant label is always "dembrane" by using a lowercase
translation key or calling .toLowerCase() on the translated string). Reference:
computedChatForCopy, historyMessages, and formatMessage.

In `@echo/frontend/src/locales/es-ES.po`:
- Around line 4267-4269: The Spanish .po entries for msgid "Something went
wrong", "Welcome back" and various "Verify" strings were cleared to empty; do
not manually repopulate the .po—rebuild the locale files using the i18n tooling
(e.g., run the lingui/cli extraction/compile commands used by the project) to
regenerate es-ES.po so translation memory and existing translations are
restored, and ensure the build step that merges translations (the same command
that updates .po files) runs in CI; verify msgid "Something went wrong",
"Welcome back" and the Verify msgids now have non-empty msgstr values after
regeneration.

In `@echo/frontend/src/locales/fr-FR.po`:
- Around line 522-529: The fr-FR .po file contains empty msgstr entries for core
UX strings (e.g. msgid "Agent is working..." and "Agent run failed" used in
AgenticChatPanel.tsx), causing fallback to English; open
echo/frontend/src/locales/fr-FR.po and fill each empty msgstr with the proper
French translations (and repeat for all other listed msgid occurrences), then
run the Lingui localization pipeline (the project's normal i18n build/extract
commands) to validate and compile the updated catalogs so the translations are
included for runtime; ensure all frontend/src/locales/**/*.po cover en-US,
nl-NL, de-DE, fr-FR, es-ES, it-IT as per guidelines.

In `@echo/frontend/src/locales/it-IT.po`:
- Around line 696-703: The it-IT .po file contains untranslated entries (empty
msgstr) for critical UI strings like "Agent is working..." and "Agent run
failed" (and the other listed msgids such as "Something went wrong" and "Welcome
back"); update echo/frontend/src/locales/it-IT.po so every msgid has a non-empty
Italian translation in the corresponding msgstr (covering the ranges called out
and all other occurrences in the file) to comply with the project locale
guideline that all supported languages must have complete translations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8d313da-431b-40a3-9d1c-3a0027e2bf73

📥 Commits

Reviewing files that changed from the base of the PR and between a1af184 and ac8a941.

📒 Files selected for processing (8)
  • echo/frontend/src/components/chat/AgenticChatPanel.tsx
  • echo/frontend/src/locales/de-DE.po
  • echo/frontend/src/locales/en-US.po
  • echo/frontend/src/locales/es-ES.po
  • echo/frontend/src/locales/fr-FR.po
  • echo/frontend/src/locales/it-IT.po
  • echo/frontend/src/locales/nl-NL.po
  • echo/frontend/src/locales/nl-NL.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
echo/server/dembrane/api/agentic.py (1)

323-380: ⚠️ Potential issue | 🟠 Major

The fixed chunk cap can under-return transcript matches.

chunk_limit is applied before the per-conversation dedupe. If one conversation owns the most recent matching chunks, it can exhaust the cap and older matching conversations never make it into conversations_by_id, so this endpoint returns fewer than limit conversations even though more matches exist. Page until you collect normalized_limit unique conversation_ids, or fetch distinct matching conversations first.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/server/dembrane/api/agentic.py` around lines 323 - 380, chunk_limit is
applied before per-conversation dedupe so a single conversation can exhaust the
fetched rows and you may return fewer than normalized_limit unique
conversations; change the logic in the block that uses
chunk_rows/conversations_by_id to page (or repeatedly refetch with an increasing
offset/updated_at cursor) until you've accumulated normalized_limit unique
conversation identifiers (conversation_identifier) or there are no more rows,
rather than relying on a single get_items call with chunk_limit; ensure the loop
still uses _to_chunk_match and _to_agent_conversation_card and preserves the
per-conversation cap (3 matches) while merging new rows into
conversations_by_id, and stop fetching once conversations_by_id has
normalized_limit entries.
♻️ Duplicate comments (5)
echo/frontend/src/components/chat/AgenticChatPanel.tsx (5)

601-604: ⚠️ Potential issue | 🟠 Major

Auto-scroll still overrides manual scrollback.

This fires on every timeline.length bump without checking isVisible, so live updates yank the panel back to the bottom while someone is reading older output. Gate the scroll on the bottom sentinel already being visible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 601 -
604, The useEffect that calls scrollToBottom on timeline length changes (the
effect watching timeline.length and scrollToBottom) is causing auto-scroll even
when the user has scrolled up; modify the effect to first check the bottom
sentinel visibility (e.g., an existing isVisible or bottomSentinelIsVisible
flag) and only call scrollToBottom("smooth") when that sentinel is visible;
update the dependency array to include that visibility flag (e.g., isVisible or
bottomSentinelRef/state) so the effect gates automatic scrolling on the sentinel
being in view.

357-360: ⚠️ Potential issue | 🟡 Minor

Localize the copied speaker labels and keep dembrane lowercase.

formatMessage(message, "User", "Dembrane") hardcodes English export labels and breaks the lowercase brand convention. Feed translated labels into formatMessage instead.

Small fix
-			formatMessage(message, "User", "Dembrane"),
+			formatMessage(message, t`User`, t`dembrane`),

As per coding guidelines, "Translations must use <Trans> component or t template literal in frontend React code" and Use "dembrane" always lowercase in UI copy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 357 -
360, The computedChatForCopy memo currently hardcodes English labels via
formatMessage(message, "User", "Dembrane"); change it to pass translated labels
instead and ensure the brand remains lowercase: call formatMessage with a
translated user label (use the i18n helper, e.g. t`User` or <Trans> for React)
and the lowercase string "dembrane" (also wrapped in translation as required),
updating the useMemo invocation that builds from historyMessages so the
exported/copied speaker labels are localized and the brand stays lowercase.

473-479: ⚠️ Potential issue | 🟠 Major

Polling fallback still waits for a second stream failure.

These thresholds are still >= 2 / < 2, so one dropped stream leaves isStreaming = false and no polling loop. Drop both guards to first-failure behavior.

Small fix
 				setStreamFailureCount((count) => {
 					const next = count + 1;
-					if (next >= 2) {
+					if (next >= 1) {
 						setError(t`Live stream interrupted. Falling back to polling.`);
 					}
 					return next;
 				});
...
-		if (isStreaming || streamFailureCount < 2) return;
+		if (isStreaming || streamFailureCount < 1) return;

Based on learnings, in echo/frontend/src/components/chat/AgenticChatPanel.tsx a single agentic stream failure is intentionally designed to trigger fallback polling immediately — no stream reconnect retry is wanted.

Also applies to: 556-559

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 473 -
479, The stream-failure logic in AgenticChatPanel should trigger the polling
fallback on the first failure: update the setStreamFailureCount handler
(referencing setStreamFailureCount and setError) to remove the ">= 2" guard and
call setError(t`Live stream interrupted. Falling back to polling.`) immediately
when a failure increments the count (i.e., on first failure), and likewise
remove the corresponding "< 2" guard in the other failure site (the block around
lines 556-559) so that isStreaming is set false / polling starts immediately on
the first stream failure; ensure you only change the comparison guards and keep
the increment/return behavior intact.

1051-1056: ⚠️ Potential issue | 🟠 Major

Guard Enter-to-submit during IME composition.

Line 1052 submits on Enter even while event.nativeEvent.isComposing is true, so CJK composition can be cut off mid-selection. Bail out when composing before calling handleSubmit.

Small fix
 								onKeyDown={(event) => {
+									if (event.nativeEvent.isComposing) return;
 									if (event.key === "Enter" && !event.shiftKey) {
 										event.preventDefault();
 										void handleSubmit();
 									}
 								}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 1051 -
1056, The onKeyDown handler in AgenticChatPanel.tsx currently submits on Enter
even during IME composition; update the anonymous onKeyDown function to first
check event.nativeEvent.isComposing (or event.isComposing where available) and
return early if true, before the existing Enter/Shift checks and calling
handleSubmit, so IME composition isn't interrupted; keep the rest of the logic
(preventDefault and void handleSubmit()) unchanged.

817-834: 🛠️ Refactor suggestion | 🟠 Major

These new surfaces still hardcode theme colors.

Nice move on the tool-status vars, but the loading bubbles, tool cards, raw panels, and live-run pill still bake in slate / red palette classes, so they will drift from the app theme. Keep the layout utilities in Tailwind, but move the surface/text colors to CSS vars or Mantine tokens.

As per coding guidelines, "Keep static utility classes (borders, spacing, layout) in Tailwind; move theme-dependent colors to CSS variables".

Also applies to: 863-975, 1003-1025

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx` around lines 817 -
834, The Paper/Skeleton surfaces in AgenticChatPanel are still using hardcoded
Tailwind theme color classes (e.g., border-slate-200, text/red palettes) which
will drift from the app theme; update the components (Paper, Skeleton, Box
instances rendering the loading bubbles and tool cards) to keep only
layout/spacing Tailwind utilities and replace all palette classes with CSS
variables or Mantine tokens (e.g., use a surface border variable like
--surface-border and text/brand vars or Mantine theme props) so colors are
theme-driven; apply the same change to the other occurrences mentioned (around
the AgenticChatPanel render blocks referenced) and ensure the new vars are
defined in the global theme CSS or provided via Mantine theme.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@echo/server/tests/api/test_agentic_api.py`:
- Around line 504-522: The test currently uses _unexpected_generate_title
(patched onto agentic_api.generate_title) which raises AssertionError but
_maybe_generate_chat_title swallows exceptions and runs generation
fire-and-forget, so the test can pass falsely; update the test to use a call
counter/spy (e.g., a simple mutable counter or MagicMock) instead of raising,
patch agentic_api.generate_title to increment that counter, then after the POST
wait briefly (or yield to the event loop) to allow the background task to run
before asserting the counter remained zero and fake_chat_service.updated_titles
is empty; reference _unexpected_generate_title, generate_title, and
_maybe_generate_chat_title when locating where to replace the stub and add the
scheduling/window delay.

---

Outside diff comments:
In `@echo/server/dembrane/api/agentic.py`:
- Around line 323-380: chunk_limit is applied before per-conversation dedupe so
a single conversation can exhaust the fetched rows and you may return fewer than
normalized_limit unique conversations; change the logic in the block that uses
chunk_rows/conversations_by_id to page (or repeatedly refetch with an increasing
offset/updated_at cursor) until you've accumulated normalized_limit unique
conversation identifiers (conversation_identifier) or there are no more rows,
rather than relying on a single get_items call with chunk_limit; ensure the loop
still uses _to_chunk_match and _to_agent_conversation_card and preserves the
per-conversation cap (3 matches) while merging new rows into
conversations_by_id, and stop fetching once conversations_by_id has
normalized_limit entries.

---

Duplicate comments:
In `@echo/frontend/src/components/chat/AgenticChatPanel.tsx`:
- Around line 601-604: The useEffect that calls scrollToBottom on timeline
length changes (the effect watching timeline.length and scrollToBottom) is
causing auto-scroll even when the user has scrolled up; modify the effect to
first check the bottom sentinel visibility (e.g., an existing isVisible or
bottomSentinelIsVisible flag) and only call scrollToBottom("smooth") when that
sentinel is visible; update the dependency array to include that visibility flag
(e.g., isVisible or bottomSentinelRef/state) so the effect gates automatic
scrolling on the sentinel being in view.
- Around line 357-360: The computedChatForCopy memo currently hardcodes English
labels via formatMessage(message, "User", "Dembrane"); change it to pass
translated labels instead and ensure the brand remains lowercase: call
formatMessage with a translated user label (use the i18n helper, e.g. t`User` or
<Trans> for React) and the lowercase string "dembrane" (also wrapped in
translation as required), updating the useMemo invocation that builds from
historyMessages so the exported/copied speaker labels are localized and the
brand stays lowercase.
- Around line 473-479: The stream-failure logic in AgenticChatPanel should
trigger the polling fallback on the first failure: update the
setStreamFailureCount handler (referencing setStreamFailureCount and setError)
to remove the ">= 2" guard and call setError(t`Live stream interrupted. Falling
back to polling.`) immediately when a failure increments the count (i.e., on
first failure), and likewise remove the corresponding "< 2" guard in the other
failure site (the block around lines 556-559) so that isStreaming is set false /
polling starts immediately on the first stream failure; ensure you only change
the comparison guards and keep the increment/return behavior intact.
- Around line 1051-1056: The onKeyDown handler in AgenticChatPanel.tsx currently
submits on Enter even during IME composition; update the anonymous onKeyDown
function to first check event.nativeEvent.isComposing (or event.isComposing
where available) and return early if true, before the existing Enter/Shift
checks and calling handleSubmit, so IME composition isn't interrupted; keep the
rest of the logic (preventDefault and void handleSubmit()) unchanged.
- Around line 817-834: The Paper/Skeleton surfaces in AgenticChatPanel are still
using hardcoded Tailwind theme color classes (e.g., border-slate-200, text/red
palettes) which will drift from the app theme; update the components (Paper,
Skeleton, Box instances rendering the loading bubbles and tool cards) to keep
only layout/spacing Tailwind utilities and replace all palette classes with CSS
variables or Mantine tokens (e.g., use a surface border variable like
--surface-border and text/brand vars or Mantine theme props) so colors are
theme-driven; apply the same change to the other occurrences mentioned (around
the AgenticChatPanel render blocks referenced) and ensure the new vars are
defined in the global theme CSS or provided via Mantine theme.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f44b942b-102c-4ddf-b3f6-701c45afe087

📥 Commits

Reviewing files that changed from the base of the PR and between ac8a941 and a8b9f5c.

📒 Files selected for processing (3)
  • echo/frontend/src/components/chat/AgenticChatPanel.tsx
  • echo/server/dembrane/api/agentic.py
  • echo/server/tests/api/test_agentic_api.py

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant