Skip to content

feat(dashboard): agentic conversation tracking with live transcript view (v2, rebased+cleaned) - #10047

Closed
hartmark wants to merge 1 commit into
cheaper-inference:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking-v2
Closed

hartmark wants to merge 1 commit into
cheaper-inference:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking-v2

Conversation

@hartmark

@hartmark hartmark commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Replaces #9439 — same feature, rebased onto the current release/v3.8.50 tip, squashed to a single commit, and trimmed to the minimal diff the feature actually needs.

  • Every agentic chat request now gets a conversation id (X-ConversationId response header). OmniRoute detects when a follow-up request continues the same conversation via fingerprint + bounded prefix-hash matching, with a strict-growth invariant (turns.length <= candidate.lastMessageCount → reject) to prevent false merges between independent single-shot requests that happen to share identical opening content.
  • /dashboard/logs: new toggleable Conversation column.
  • /dashboard/logs/timeline: requests sharing a conversation id share a timeline lane, connected by an arrow, with a configurable lane-reuse window.
  • Request detail panel: new Full Conversation transcript, rendered above the raw SSE event stream, with Markdown rendering, per-turn timestamps, turn-relative view, click-any-turn-to-open-its-own-log navigation, live auto-refresh, and auto-scroll-to-bottom while a request is still streaming.
  • New /dashboard/conversations page listing only conversations with 2+ turns, no-forking model with pagination and duplicate-anchor fix.
  • Configurable auto-refresh intervals on both the timeline and conversations list pages.

What changed vs #9439

#9439 accumulated 23 commits across many rebases, including i18n-sync noise and fixes for bugs that were pre-existing on release/v3.8.50 itself, unrelated to this feature. This PR is a from-scratch minimal-diff pass:

  • Squashed to 1 commit.
  • i18n reverted to English-only for this feature's own keys. The old branch's translation files carried a ~7k-line diff from an i18n-sync tool reordering every key in all 42 locale files. Reset every non-en locale to upstream, then ran scripts/i18n/fill-missing-from-en.mjs to add only this feature's 3 new keys (sidebar.conversations, sidebar.conversationsSubtitle, logs.conversation) with their plain English fallback value — no reordering, no re-translation. Passes i18n-ui-coverage (fallback counts as present, not a placeholder) and i18n-vi-completeness (no __MISSING__ markers).
  • Dropped the SSE provider-response-summary-format changes (open-sse/utils/stream.ts, open-sse/utils/streamPayloadCollector.ts, its test) — orthogonal to conversation tracking, already covered by fix(sse): provider-response summary format bugs (dashboard Provider Response panel) #10037. Kept only the conversation-continuation half of that commit (excluding the system message from the identity anchor).
  • Dropped an unrelated cosmetic fix (OmniRouteLogo.tsx Dark Reader hydration-warning suppression) that had been opportunistically bundled in — not needed for this feature.
  • Dropped 4 commits fixing bugs already independently resolved on current upstream: onnxruntime standalone-asset registration, a HARD_COMPAT_REASONS duplicate declaration (base-red 🔴 Release branch not green: release/v3.8.50 #9298, now closed, upstream restructured the file), a missing mergeResponseToolNameMap import, and missing frontmatter in AGENTROUTER_WAF.md.
  • Renumbered the feature's own migrations from 135/136 to 147/148 — upstream now owns 135/136 for unrelated tables; 143-145 are documented KNOWN_GAPS (burned numbers), so used the next free slot past upstream's current highest (146).
  • Fixed one stale test (chatcore-log-truncation.test.ts) whose payload size assumed an 8KB truncation threshold; upstream's current default is 1MB, so bumped the test payload to match the sibling test's pattern.

⚠️ base-red inherited: #9985

Test plan

  • npm run typecheck:core — clean
  • npm run lint — clean
  • node --import tsx/esm scripts/check/check-migration-numbering.mjs — OK, 0 collisions
  • 116 unit tests, 0 failures: agenticConversations.test.ts, conversationTracker.test.ts, request-timeline-lane-allocation.test.ts, conversations-tree-route-seq-param.test.ts, conversations-active-call-log-id.test.ts, logs-detail-partial-reasoning-chunk-split.test.ts, inspector-conversation-normalizer.test.ts, sidebar-monitoring-reorg.test.ts, sidebar-visibility.test.ts, chatcore-log-truncation.test.ts, migration-135-numbering-collision.test.ts, check-migration-numbering.test.ts, db-job-registry-migration-renumber-139.test.ts, i18n-vi-completeness.test.ts
  • All original feat(dashboard): agentic conversation tracking with live transcript view #9439 live-verification (multi-turn tracking, turn-relative transcript, click-navigation, live-growing transcript, omniroute-dev deploy smoke) still applies unchanged — no feature behavior touched by this cleanup, only which diff carries it.

Every agentic chat request now gets a conversation id (X-ConversationId
response header). OmniRoute detects when a follow-up request continues the
same conversation via fingerprint + bounded prefix-hash matching, with a
strict-growth invariant to prevent false merges between independent
single-shot requests that happen to share identical opening content.
Continuation detection excludes the system message from the identity
anchor, since real coding-agent CLIs commonly regenerate it every request
with live context (timestamp, cwd, git status) — without this, that
volatility alone broke every continuation check against real traffic.

- `/dashboard/logs`: new toggleable Conversation column.
- `/dashboard/logs/timeline`: requests sharing a conversation id share a
  timeline lane, connected by an arrow, with a configurable lane-reuse
  window.
- Request detail panel: new Full Conversation transcript above the raw SSE
  event stream — Markdown rendering, per-turn timestamps, turn-relative
  view, click-any-turn navigation, live auto-refresh building the
  transcript in real time from the in-flight SSE chunk buffer while a
  request is still streaming, auto-scroll-to-bottom as the live turn grows.
- New `/dashboard/conversations` page listing conversations with 2+ turns,
  no-forking model (an edited/duplicated mid-history turn mints its own
  independent conversation instead of merging), pagination, duplicate-
  anchor fix.
- Configurable auto-refresh intervals on both the timeline and
  conversations list pages.
- Responses API tool-call gap fix: turnsFromOpenAiMessages only handled
  role-based Chat Completions messages, so bare {type:"function_call"} /
  {type:"function_call_output"} / {type:"reasoning"} items (real Responses
  API traffic) silently vanished from the Conversation Context panel.
- truncateForLog now counts input[] (Responses API), not just messages[]
  (Chat Completions), so a truncated /v1/responses request still shows a
  placeholder instead of nothing.
- RequestTimeline.tsx now reads the same debugEnabled/emailsVisible
  settings RequestLoggerV2.tsx already used, instead of hardcoding both
  false — the timeline view never showed SSE/stream-chunk events or
  respected email-masking, regardless of the actual setting.

Migrations 147/148 (agentic_conversations, conversation_turn_nodes) — 135
and 136 are now taken upstream; 143-145 are documented KNOWN_GAPS, so this
uses the next free slot past upstream's current highest.

Test plan:
- npm run typecheck:core — clean
- npm run lint — clean
- node --import tsx/esm scripts/check/check-migration-numbering.mjs — OK, 0 collisions
- 109 unit tests across the conversation-tracking, migration-renumber, and
  dashboard-wiring surface — 0 failures
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking-v2 branch from c0489b3 to 4fee3b2 Compare August 11, 2026 12:19
@hartmark

Copy link
Copy Markdown
Contributor Author

Note for later: while building previous_response_id virtualization on feat/openai-responses-store-toggle (real request/response reconstruction, not just display), I compared this PR's agentic_conversations/conversation_turn_nodes storage against the existing call-log pipeline artifact system (call_log_pipeline_enabled, full untruncated pipeline.clientRawRequest/providerResponse/clientResponse JSON per request).

Ended up not reusing this PR's tables as the reconstruction source — conversation_turn_nodes.text_preview is hard-truncated at TEXT_PREVIEW_LENGTH = 8000 chars, and block_kind/tool_name is a simplified representation built for dashboard rendering, not exact API replay. Fine for what this PR needs (identity linking + display), unsafe as a source of truth for reconstructing real request bodies.

Worth considering for this PR itself, though: the call-log artifact system already stores the same full-fidelity content this feature's own turn matching needs (conversation_turn_nodes duplicates conversation content that call_logs artifacts already persist, when both features are enabled on the same connection). If a future pass revisits this, reusing the call-log artifacts as the source (keeping only a lightweight identity/fingerprint index here, the way open-sse/services/responsesContinuationStore.ts does) would avoid storing the same conversation content twice under two different retention/gating policies.

Not blocking — just flagging so the two systems don't diverge on "where is the canonical copy of this conversation" going forward.

@hartmark

Copy link
Copy Markdown
Contributor Author

Restacked and reworked as hartmark#4, per the storage-reuse note above.

GitHub doesn't allow retargeting a cross-repo PR's base onto a branch that only exists in the head fork, and this PR now needs to sit on top of two other in-flight stacked branches (feat/openai-responses-store-toggle -> feat/responses-previous-response-id-virtualization) that live in hartmark/OmniRoute, not yet in diegosouzapw/OmniRoute -- so the restack had to become a same-fork PR instead of an update to this one.

Summary of the rework: conversation_turn_nodes (migration, renumbered 148->154 since 147/148 now collide with 147_api_keys_model_access_mode.sql on current release/v3.8.50) is identity-only now -- no more text_preview/block_kind/tool_name columns. Display content for the dashboard transcript view is resolved on demand from the call-log pipeline artifact each node's last_correlation_id points at, instead of being duplicated into this table. The dashboard UI itself (page.tsx, RequestLoggerDetail.tsx/RequestTimeline.tsx, sidebar, i18n) carried over unchanged -- the tree API's response shape stayed byte-identical.

Closing this one in favor of hartmark#4.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant