Follow-up to the earlier truncated-request-body transcript fix, found by
re-checking the live dashboard: a specific /v1/responses request still
showed nothing for its own turn even though its body genuinely was
truncated by logTruncation.ts's truncateForLog().
Root cause (two parts):
1. truncateForLog() only counted messages[] (Chat Completions) and
contents[] (Gemini) — never input[] (Responses API) — so a truncated
Responses API request's summary carried NO count field at all.
2. buildMultiRowConversation()'s earlier fix defaulted an unknown count to
0, which silently produced "0 new turns" instead of surfacing that the
count was simply unavailable — same end symptom as the original bug
(nothing shown) despite the row being genuinely truncated.
Fixes:
- logTruncation.ts now also sets messageCount for input[] bodies (root
fix, only helps requests logged from here forward).
- multiRowConversation.ts now distinguishes "known count" (existing
specific "N messages not shown" placeholder + correct bookkeeping) from
"unknown count" (a generic placeholder, since we can't safely diff
against previousTotal without a real number) — needed for the
already-persisted historical data on omniroute-dev that will never
retroactively get a messageCount.
Test plan:
- New TDD tests for both gaps (Responses API count capture in
logTruncation, unknown-count placeholder in multiRowConversation),
confirmed failing before each fix and passing after
- npm run typecheck:core / npm run lint / npm run check:file-size — clean
- npm run test:unit — 27223 tests, same 4 pre-existing/unrelated failures
as the last confirmed-clean run (no new regressions)
- npm run test:vitest — 291/291 passed
- Rebuilt and redeployed to omniroute-dev
CI failures on PR #9439:
1. Migration version collision: upstream/release/v3.8.50 landed
134_proxy_logs_egress_ip.sql (#9291) after this branch's original rebase,
colliding with this branch's own 134_agentic_conversations.sql. Renamed
to 135_agentic_conversations.sql (re-rebased onto the current tip first).
2. check:file-size: rebaselined the files this PR's own feature growth pushed
over their frozen/cap thresholds (RequestLoggerDetail.tsx, RequestTimeline.tsx,
RequestLoggerV2.tsx, chat.ts, chatCore.ts — see the new
_rebaseline_2026_08_04_9439 entry for the itemized justification) plus
open-sse/executors/base.ts, which was already over its own frozen baseline
on release/v3.8.50 independent of this branch (confirmed via `git diff
upstream/release/v3.8.50 HEAD -- open-sse/executors/base.ts` — empty).
3. A third real bug, found by re-checking the live dashboard after the
previous round's fixes: a request with a long real conversation chain
showed only its own response in the "Full Conversation" panel. Root
cause: open-sse/handlers/chatCore/logTruncation.ts's truncateForLog()
replaces any request body over ~8KB with a bare {_truncated,
_originalBytes, messageCount, ...} summary, dropping messages/input
entirely — the norm, not the exception, for any conversation with real
substance. buildRequestTurns() legitimately found nothing to parse, so
the transcript silently rendered only that row's response, and (more
subtly) every subsequent row's delta-slicing bookkeeping was computed
against the wrong running total (0 instead of the row's real turn
count), which would have corrupted the rest of the reconstruction too
for any longer chain built on top of a truncated row.
Fix: buildMultiRowConversation now detects a truncated request body via
its messageCount field, uses that count for delta bookkeeping instead of
silently treating it as zero, and renders one explicit placeholder turn
("N messages not shown — the request body was too large to log")
instead of just disappearing.
Test plan:
- New regression tests for the truncation case (single truncated row, and
a truncated row followed by a real row to verify bookkeeping stays
correct)
- npm run check:migration-numbering / check:file-size — clean
- npm run typecheck:core / npm run lint — clean
- npm run test:unit — 27086 tests, only 4 failures remain (down from 18 —
2 were fixed by the newer upstream commits pulled in by this re-rebase),
all independently pre-existing/unrelated (ServiceSupervisor timing,
monaco-editor path, npm-pack)
- npm run test:vitest — 291/291 passed
Every agentic chat request now gets a conversation id (X-ConversationId
response header), and 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.
Dashboard changes:
- /dashboard/logs: toggleable Conversation column
- /dashboard/logs/timeline: same-conversation requests 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, with Markdown rendering, per-turn timestamps, turn-relative
view (only turns up to the one you opened, with a jump-to-next link),
click-any-turn-to-open-its-log navigation, and live auto-refresh (with an
auto-follow toggle, matching the event stream's autoscroll pattern) that
rebuilds the transcript in real time from the in-flight SSE chunk buffer
while a request is still streaming
- New /dashboard/conversations page listing only conversations with 2+ turns
- Configurable auto-refresh intervals on both the timeline and conversations
list pages
Also fixes a pre-existing bug where the timeline view never showed SSE/
stream-chunk events or respected email-masking, because RequestTimeline.tsx
hardcoded debugEnabled/emailsVisible instead of reading the same
server-side/store state RequestLoggerV2.tsx already used, and makes the
request detail panel and conversations list responsive on mobile.