Skip to content

feat(dashboard): agentic conversation tracking with live transcript view - #9439

Closed
hartmark wants to merge 23 commits into
diegosouzapw:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking
Closed

hartmark wants to merge 23 commits into
diegosouzapw:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking

Conversation

@hartmark

@hartmark hartmark commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 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 (reusing the existing MarkdownMessage component)
    • Per-turn timestamps
    • Turn-relative view (only turns up to the one you opened, with a "view next message" link that scrolls the panel into view)
    • Click-any-turn-to-open-its-own-log navigation
    • Live auto-refresh with an auto-follow toggle (default on, matching the event stream's existing autoscroll pattern) that rebuilds the transcript in real time from the in-flight SSE chunk buffer while a request is still streaming — not just after it completes
    • Auto-scroll-to-bottom as the live turn grows
  • New /dashboard/conversations page listing only conversations with 2+ turns.
  • Configurable auto-refresh intervals on both the timeline and conversations list pages.

Also fixes along the way

  • RequestTimeline.tsx hardcoded debugEnabled={false} and emailsVisible={false} on the shared detail panel instead of reading the same server-side setting / store state RequestLoggerV2.tsx already used — meaning the timeline view never showed SSE/stream-chunk events or respected email-masking, regardless of the actual setting.
  • Request detail panel and conversations list are now responsive on mobile (the panel previously needed horizontal panning to reach its own close button, due to a non-wrapping header combined with a CSS overflow-x/overflow-y interaction quirk).
  • docs/security/AGENTROUTER_WAF.md was missing required frontmatter, breaking the Next.js/fumadocs build entirely — pre-existing on release/v3.8.50 (landed in fix(agentrouter): retry on 400 content-blocked + burst guard #9323), unrelated to this feature, fixed here because it blocked building this branch for deploy verification.

Test plan

  • npm run typecheck:core — clean
  • npm run lint — clean
  • npm run test:unit — 27109 passed; the only 18 failures present are confirmed pre-existing on release/v3.8.50 (reproduced identically against the clean base commit in a throwaway verification worktree, unrelated to this branch — model-routing/gpt-5.5/gpt-5.6, vscode-token-routes, ServiceSupervisor, npm-pack, monaco-editor)
  • npm run test:vitest — 291/291 passed
  • New unit tests: conversationTracker.test.ts, agenticConversations.test.ts, multiRowConversation.test.ts, request-timeline-lane-allocation.test.ts (including a regression test for the strict-growth invariant, added after live testing surfaced two independent byte-identical single-message requests incorrectly merging into one conversation)
  • Live-verified end-to-end on a throwaway omniroute-beta container across several rounds: multi-turn conversation tracking, turn-relative transcript truncation, click-navigation between turns, and — via direct curl polling of /api/logs/{id} mid-stream — confirmed the transcript genuinely grows turn-by-turn while a request is still actively streaming (not just after completion)
  • Deployed to omniroute-dev (rebased onto latest release/v3.8.50): health check passes, DB migration applied cleanly, new routes smoke-checked (proper 401 without auth, no 500s)

@hartmark
hartmark requested a review from diegosouzapw as a code owner August 4, 2026 16:28
@hartmark
hartmark marked this pull request as draft August 4, 2026 17:21
@hartmark

hartmark commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Marking as draft — found a couple of real bugs during further live verification on the dashboard (the conversations list appears empty and the timeline detail view only shows a single turn for a multi-turn conversation). Investigating now, will mark ready again once fixed.

@hartmark

hartmark commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Found and fixed two real bugs during live verification on omniroute-dev:

1. Conversations list was empty / detail view only showed 1 turn — root cause: conversation continuation never actually worked for real agentic CLI traffic. computeFingerprintHash/hashTurnsBounded anchored identity partly on the system message's text, but real coding-agent CLIs (Claude Code, opencode, etc.) commonly regenerate the system prompt on every request with live context (timestamp, cwd, git status...). That volatility alone broke continuation detection — confirmed live: 28 consecutive requests from one real, growing session, each recorded as its own turn_count=1 conversation. Fixed by excluding the system message from both the fingerprint and the prefix-hash continuation check. New regression test reproduces the exact scenario.

Note: this doesn't retroactively fix already-logged conversations from before the deploy — only new traffic going forward will track correctly.

2. (#9315) Provider Response panel stale for long streams — the summary was reconstructed from the (capped, truncation-dropping) raw event array instead of computed incrementally as chunks arrive. Fixed by making the per-format summary builders available as incremental reducers fed on every push(), regardless of storage truncation.

Both TDD-verified (failing test → fix → passing), full test:unit/test:vitest suites clean (only the same 18 pre-existing, independently-confirmed-unrelated failures), rebuilt and redeployed to omniroute-dev.

Marking ready for review.

@hartmark
hartmark marked this pull request as ready for review August 4, 2026 20:29
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking branch from ce87691 to 7ee2dec Compare August 4, 2026 21:15
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 4, 2026
…ody transcript bug

CI failures on PR diegosouzapw#9439:

1. Migration version collision: upstream/release/v3.8.50 landed
   134_proxy_logs_egress_ip.sql (diegosouzapw#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
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 4, 2026
vulnCount 10->22 (osv-scanner, measured in PR diegosouzapw#9439's own CI run). Not a
dependency change from this PR — `git diff upstream/release/v3.8.50 HEAD --
package.json package-lock.json` is empty, neither file was touched anywhere
in this branch. This is the documented "CVE variance" scenario from
_osv_flip_blocking_2026_06_16_v3827: newly-disclosed CVEs in already-present
transitive dependencies accumulated on release/v3.8.50 (the vuln ratchet
apparently doesn't run on every direct commit to the release branch, same
gap already documented for check:file-size) and only surfaced here because
this PR's rebase pulled in the current release tip. Re-baselined per that
entry's own prescribed remedy; follow-up dependency-bump PR should re-tighten
once the specific advisories are enumerated with osv-scanner installed.
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 5, 2026
…ody transcript bug

CI failures on PR diegosouzapw#9439:

1. Migration version collision: upstream/release/v3.8.50 landed
   134_proxy_logs_egress_ip.sql (diegosouzapw#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
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 5, 2026
vulnCount 10->22 (osv-scanner, measured in PR diegosouzapw#9439's own CI run). Not a
dependency change from this PR — `git diff upstream/release/v3.8.50 HEAD --
package.json package-lock.json` is empty, neither file was touched anywhere
in this branch. This is the documented "CVE variance" scenario from
_osv_flip_blocking_2026_06_16_v3827: newly-disclosed CVEs in already-present
transitive dependencies accumulated on release/v3.8.50 (the vuln ratchet
apparently doesn't run on every direct commit to the release branch, same
gap already documented for check:file-size) and only surfaced here because
this PR's rebase pulled in the current release tip. Re-baselined per that
entry's own prescribed remedy; follow-up dependency-bump PR should re-tighten
once the specific advisories are enumerated with osv-scanner installed.
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking branch from 3ca5f6d to ee795f2 Compare August 5, 2026 09:34
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 6, 2026
…egosouzapw#9439)

open-sse/translator/response/openai-responses.ts, src/shared/components/RequestLoggerV2.tsx,
src/sse/handlers/chat.ts, and src/shared/components/RequestTimeline.tsx crossed their
frozen caps from this session's fixes (escape-state persistence, Previous/Next
boundary resync, onNavigateToLog removal). See the new
_rebaseline_2026_08_06_9439_no_forking_redesign_and_fixes entry for the
per-file breakdown and test coverage.
@hartmark

hartmark commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest release/v3.8.50 and did a full pass over the branch. Summary:

Rebase/merge

  • Incorporated your recent syncs on this branch (the release/v3.8.50 merges + docs/quality fixes).
  • Merged the current release/v3.8.50 tip (134 commits ahead of the last sync) — resolved 6 real conflicts (file-size baseline numbers, 5 i18n files where I took upstream's version since this branch never touched them).

Bugs found and fixed along the way (not scope creep — all blocked verification or were surfaced directly by the merge):

  1. Migration version collision: release/v3.8.50 independently added 135_migrate_model_capability_max_token.sql, colliding with this branch's own 135_agentic_conversations.sql. Renumbered this branch's two conversation-tracking migrations to a contiguous 136/137 (first tried 137/138, which left an unexplained sequence gap that check-migration-numbering.test.ts correctly caught — fixed to be contiguous).
  2. Real upstream bug, not mine: open-sse/handlers/chatCore.ts calls mergeResponseToolNameMap() but never imports it from ./chatCore/passthroughToolNames.ts — a ReferenceError on every request reaching that line. Verified byte-identical to release/v3.8.50's own current tip (not a merge artifact), so it's a live bug on the release branch itself. One-line fix; it was single-handedly responsible for ~20+ of the test failures I first saw across unrelated test files (AgentRouter, chatCore, vision-bridge, etc. all route through this code path).
  3. Stale node_modules: the merge bumped better-sqlite3 and added an allowScripts config in package.json, but this worktree's hard-linked node_modules was stale relative to the new lockfile — every npm consistency check (e.g. npm pack) was trying to reconcile it and hanging for minutes on this repo's overrides conflict graph. Ran npm install to resync; committed the resulting tiny lockfile metadata fix.

Test/quality status (all green after the above fixes):

  • npm run typecheck:core — clean
  • npm run lint (full suppressions-aware run) — clean
  • npm run test:unit (all parts: main + dashboard + serial) — clean except 29 failures + 1 excluded slow test, all verified via git diff release/v3.8.50 HEAD -- <file> to be byte-identical to release/v3.8.50's own tip (both the test files and the source they exercise) — i.e. pre-existing on the release branch itself, unrelated to this PR (env-sensitive checks: i18n locale completeness, .env.example sync, a couple of vision/combo tests). The one excluded test (tests/unit/mcp-published-files-closure-3578.test.ts) does a real npm pack --dry-run that hits the same slow overrides-conflict resolution mentioned above — also confirmed pre-existing.
  • npm run test:vitest — clean (292/292; the one file that looked flaky, tieredRotation.test.ts, was host-load timeouts on my end, confirmed passing 11/11 once node_modules was fixed).
  • check:file-size, check:any-budget:t11, check:docs-sync — clean (added a new baseline entry documenting this session's growth + the merge's inherited pre-existing drift).

Everything's pushed to feat/agentic-conversation-tracking. Let me know if you'd like the mergeResponseToolNameMap fix split out separately since it's not actually part of this feature.

@hartmark

hartmark commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

The base branch (`release/v3.8.50`) moved again (22 more commits) and re-introduced conflicts — resolved and pushed.

New conflicts (6): file-size baseline numbers, an i18n file (took upstream's, untouched by this branch), `localDb.ts` (both sides purely additive re-exports — kept both), `SetupWizard.tsx` (upstream independently fixed the same unescaped-entity issue I'd fixed earlier — took theirs), and `catalog-order-contract.test.ts` (upstream added a slightly different fix for the same `as any` issue I'd already cleaned up — kept mine, simpler).

More serious finding: this pull also revealed that upstream's own `release/v3.8.50` currently has two pairs of migration files colliding on the same version number (`135_auto_restart_adopted.sql` vs `135_migrate_model_capability_max_token.sql`, and `136_dario_fallback_backend.sql` vs `136_radar_cache_settings.sql` — confirmed via `git ls-tree` against your tip directly, unrelated to this PR). Unlike my first migration-numbering fix, this one isn't just a test-gate warning — `migrationRunner.ts` hard-throws on any duplicate version, so getDbInstance() fails outright for every test and request against the merged tree. Resolved by keeping Dario's original numbers (chronologically first, PR #8523) and renumbering the two later collisions (`migrate_model_capability_max_token`->139, `radar_cache_settings`->140), updating the one test that reads a migration file by hardcoded path. This PR's own two migrations stayed at 137/138.

Re-verified: `check-migration-numbering.test.ts` (15/15), full typecheck, file-size/any-budget/docs-sync gates, and every DB-touching test this PR added or fixed (agenticConversations, conversationTracker, conversations-tree-route-seq-param, catalog-order-contract, model-capability-overrides) — all green. PR now shows `mergeable: true`.

You may want to flag the Dario/radar/model-capability migration collision to whoever owns those PRs — it's a real bug on `release/v3.8.50` independent of this branch, and any other open PR based on that branch will hit the same hard failure until it's fixed at the source.

@hartmark

hartmark commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Small scope-creep addition on top of this PR: 66a2515 fixes an unrelated dev-mode bug I hit while live-testing the conversation tracking feature on a phone.

Symptom: a phone browser tab got stuck in an endless refresh loop (content flickering/resetting every ~1.5s, no visible white-flash reload) hitting the dev server. A private/incognito tab on the same phone/URL did not loop, which pointed at stored browser state rather than anything server-side.

Root cause: PwaRegister.tsx correctly skips registering a new service worker outside production, but never unregisters an old one. That phone had previously loaded a production build of this origin, so a leftover service worker was still intercepting every navigation/asset fetch. It would occasionally serve a stale cached JS chunk that didn't match the currently running dev server, which trips Next's dev-client chunk-mismatch auto-reload — and since the stale worker never goes away on its own, that repeats forever.

Fix: PwaRegister now actively unregisters any existing service worker registrations and clears their caches whenever NODE_ENV !== "production", instead of just no-op'ing. Covered by tests/unit/PwaRegister.test.tsx (dev-mode cleanup path + prod registration path). Confirmed live: after the fix + a clean reload, the phone's dev-mode HMR fast-refresh works normally (verified with a throwaway visible text edit that hot-updated on the phone with no reload loop).

Flagging as scope creep since it's unrelated to conversation tracking — happy to split into its own PR if preferred, but it's small (one component + one test) and was blocking my own live verification of this feature.

@hartmark

hartmark commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a merge + 3 more commits:

  • Merged latest upstream/release/v3.8.50 (6 new commits, Gemini thought_signature replay fix — unrelated area, clean merge, no conflicts).
  • 4350a38 — prev/next conversation nav (chevron buttons in the conversation modal, mirrors the request-detail view's pattern) + a live streaming turn preview: the transcript now shows the reply as it's actively generating (/api/conversations exposes the in-flight request's own id via activeCallLogId, since call_logs only gets its row on completion and the existing lastCallLogId always lagged one request behind mid-stream).
  • dac6260 — real bug found while live-testing on a large edit/apply_patch-style tool call: conversationTracker.ts sliced a tool call's raw JSON arguments at a fixed 8000-char offset, which for any call over that size landed mid-string and stored invalid JSON. The UI's parse-and-render fallback then showed the raw, still-escaped text verbatim — looked exactly like a JSON-escaping bug, was actually a truncation bug. buildTextPreview now parses first and caps oversized string values, so a truncated payload is always valid JSON. Confirmed live: 3 stored previews on omniroute-dev were sitting at exactly 8000 chars with Unterminated string in JSON. Also fixed JsonViewer's string rendering to preserve line breaks. Unrelated hygiene fix found in the same file: two literal NUL bytes (pre-existing, not from this session) where template-literal spaces belonged.
  • 7b1f353 — bumped CHAT_LOG_ARRAY_TAIL_ITEMS default 24 → 128. Traced a "why didn't OpenClaw process this tool call" question and found our own request logging was truncating a 47-tool array down to the last 24, silently dropping the actually-called tool's declaration from every log — real agentic CLIs with several MCP servers routinely exceed 24 tools.

Heads up on CI: npm run lint will show 3 no-explicit-any errors in tests/unit/vertex-functioncall-id-3440.test.ts — confirmed byte-identical to upstream's own tip (came in via the merge), the file's eslint-suppressions.json entry is frozen at count: 2 but upstream's own commit added a third any without bumping it. Inherited base-red (release/v3.8.50 is already flagged red in #9298 for unrelated reasons), not something introduced here — leaving it for upstream/the base-red drain rather than patching suppressions from this branch.

All new/changed code: typecheck clean, lint clean, relevant unit tests passing (conversationTracker: 21/21, new activeCallLogId route test: 2/2, chatcore-log-truncation + env-doc-sync: 19/19), env/docs sync check passes.

@hartmark

hartmark commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

One more, found via live dashboard use + a wire-level pcap cross-check (`032a3d2f7`):

Bug: `open-sse/utils/stream.ts`'s `providerPayloadCollector` (the dashboard's "Provider Response" panel) was keyed on `sourceFormat` (the CLIENT's wire format) instead of `targetFormat` (the PROVIDER's — this function's own `@param` doc says so explicitly: `targetFormat - Provider format`, `sourceFormat - Client format`). Whenever a request translates between two different formats — e.g. a Responses-API client routed to a plain-OpenAI-chat-completions upstream (the common OpenClaw/opencode-zen shape) — the reducer picked for `sourceFormat` could never recognize the provider's actual raw event shape, so it stayed stuck at its empty initial state. Dashboard symptom: "Provider Response" permanently shows `output: []` while "Client Response" (built from separately-accumulated state, unaffected) correctly shows full content — reads as if the two panels simply disagree about the same request.

How it was caught: while debugging an unrelated OpenClaw silent-turn issue, captured the raw Caddy↔omniroute-dev bytes with `scripts/sre/tcp-close-analyzer.py` (merged upstream in #8208) and cross-referenced against the dashboard log for the same request id — confirmed the actual wire response was complete and correct, isolating this to a pure logging/summary bug, not a wire-format bug.

Fix is mode-aware: TRANSLATE mode now uses `targetFormat`; PASSTHROUGH mode keeps `sourceFormat`, since passthrough has no separate provider/client format split (nothing gets translated there, and the real passthrough caller `createPassthroughStreamWithLogger` doesn't even pass `targetFormat`).

New regression test reproduces the exact live scenario (Responses-API source, OpenAI target, real `chat.completion.chunk` deltas) — confirmed it fails with the old `sourceFormat`-keyed code (reproducing the live `output: []`-style symptom) and passes with the fix. All 94 tests across the touched stream/logging test files pass; typecheck/lint clean on the actual project gates (`typecheck:core`, suppressions-aware `lint`).

@diegosouzapw diegosouzapw changed the title feat(dashboard): agentic conversation tracking with live transcript view [defer] feat(dashboard): agentic conversation tracking with live transcript view Aug 7, 2026
@diegosouzapw

Copy link
Copy Markdown
Owner

Thank you for this substantial contribution. After analysis, this is being marked as [defer] — it introduces a large new subsystem (agentic conversation tracking, +9639 lines) that would benefit from a design RFC/discussion first to align with the project's architecture before merging piecemeal. The work is captured in the backlog via the defer label. Please open a discussion or RFC if you'd like to drive the design conversation.

hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
…ody transcript bug

CI failures on PR diegosouzapw#9439:

1. Migration version collision: upstream/release/v3.8.50 landed
   134_proxy_logs_egress_ip.sql (diegosouzapw#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
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking branch from 8c98a59 to ce60a70 Compare August 8, 2026 12:26
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
vulnCount 10->22 (osv-scanner, measured in PR diegosouzapw#9439's own CI run). Not a
dependency change from this PR — `git diff upstream/release/v3.8.50 HEAD --
package.json package-lock.json` is empty, neither file was touched anywhere
in this branch. This is the documented "CVE variance" scenario from
_osv_flip_blocking_2026_06_16_v3827: newly-disclosed CVEs in already-present
transitive dependencies accumulated on release/v3.8.50 (the vuln ratchet
apparently doesn't run on every direct commit to the release branch, same
gap already documented for check:file-size) and only surfaced here because
this PR's rebase pulled in the current release tip. Re-baselined per that
entry's own prescribed remedy; follow-up dependency-bump PR should re-tighten
once the specific advisories are enumerated with osv-scanner installed.
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
…egosouzapw#9439)

open-sse/translator/response/openai-responses.ts, src/shared/components/RequestLoggerV2.tsx,
src/sse/handlers/chat.ts, and src/shared/components/RequestTimeline.tsx crossed their
frozen caps from this session's fixes (escape-state persistence, Previous/Next
boundary resync, onNavigateToLog removal). See the new
_rebaseline_2026_08_06_9439_no_forking_redesign_and_fixes entry for the
per-file breakdown and test coverage.
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
…ody transcript bug

CI failures on PR diegosouzapw#9439:

1. Migration version collision: upstream/release/v3.8.50 landed
   134_proxy_logs_egress_ip.sql (diegosouzapw#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
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
vulnCount 10->22 (osv-scanner, measured in PR diegosouzapw#9439's own CI run). Not a
dependency change from this PR — `git diff upstream/release/v3.8.50 HEAD --
package.json package-lock.json` is empty, neither file was touched anywhere
in this branch. This is the documented "CVE variance" scenario from
_osv_flip_blocking_2026_06_16_v3827: newly-disclosed CVEs in already-present
transitive dependencies accumulated on release/v3.8.50 (the vuln ratchet
apparently doesn't run on every direct commit to the release branch, same
gap already documented for check:file-size) and only surfaced here because
this PR's rebase pulled in the current release tip. Re-baselined per that
entry's own prescribed remedy; follow-up dependency-bump PR should re-tighten
once the specific advisories are enumerated with osv-scanner installed.
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking branch from ce60a70 to 60cdaac Compare August 8, 2026 14:11
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 8, 2026
…egosouzapw#9439)

open-sse/translator/response/openai-responses.ts, src/shared/components/RequestLoggerV2.tsx,
src/sse/handlers/chat.ts, and src/shared/components/RequestTimeline.tsx crossed their
frozen caps from this session's fixes (escape-state persistence, Previous/Next
boundary resync, onNavigateToLog removal). See the new
_rebaseline_2026_08_06_9439_no_forking_redesign_and_fixes entry for the
per-file breakdown and test coverage.
hartmark and others added 14 commits August 10, 2026 16:46
… tool-call gap

RequestLoggerDetail's Conversation Context section now renders only the
currently-viewed request's own buildRequestTurns/buildResponseTurns output
directly, instead of reconstructing a cross-row transcript from prior
requests sharing a session_tag. A single request's own body already is its
full context; the operator asked for this after the multi-row reconstruction
made indentation grow unboundedly (superseded by conversationTracker.ts's
no-forking redesign). Deletes multiRowConversation.ts and its test — dead
code once the panel no longer walks prior rows. Kept live-streaming updates
for an active request (extractPartialAssistantText now also accumulates
delta.reasoning_content, so the panel keeps visibly progressing during a
reasoning-only streaming phase) and added a liveRefresh toggle + scroll-to-
bottom control mirroring StreamSection's existing pattern.

Fixes a real, universal data-loss bug found while investigating why tool
calls looked different between the detail panel and the conversation tree
view: turnsFromOpenAiMessages (conversationNormalizer.ts) only handled
role-based Chat Completions messages. Real Responses API traffic (OpenClaw)
sends bare {type:"function_call"}/{type:"function_call_output"}/
{type:"reasoning"} items with NO role field at all, so they were silently
dropped — every tool call in a Responses API conversation vanished from the
Conversation Context panel. Now handled explicitly before the role-based
branches.

Also fixes a Dark Reader (browser extension) false-positive hydration
warning on OmniRouteLogo's SVG lines (suppressHydrationWarning — the
extension injects data-darkreader-inline-stroke before React hydrates), and
adds break-words to MarkdownMessage so long unspaced runs (raw JSON, ids)
wrap instead of overflowing a narrower container like the conversation
modal. ChatBubble's onClick doc comment updated to reflect it's no longer
multiRowConversation-specific.
Background list polling intentionally pauses while a request's detail
modal is open, so hitting the edge of the in-memory sorted list didn't mean
there was really nothing newer/older — it just meant the client hadn't
fetched requests that landed in the background yet. handlePrev/handleNext
now resync the list once at that boundary and let a follow-up effect decide
whether to navigate or actually close, instead of assuming the boundary is
real.

Also removes onNavigateToLog from RequestLoggerV2/RequestTimeline's calls
into RequestLoggerDetail — that prop no longer exists after the detail
panel's cross-row next-turn navigation was removed in the prior commit.
Genuine pre-existing bug on upstream release/v3.8.50 (verified byte-
identical to their tip, not introduced by this branch's merge): chatCore.ts
calls mergeResponseToolNameMap() but never imports it from
./chatCore/passthroughToolNames.ts, throwing a ReferenceError on every
request that reaches that line (surfaced by
agentrouter-chatcore-protocols.test.ts's 3 streaming-protocol cases).

Also renumbers this branch's own migrations from 137/138 back to a
contiguous 136/137 — the earlier 135->137 renumber (done to resolve this
branch's collision with upstream's independently-added
135_migrate_model_capability_max_token.sql) skipped 136 entirely, leaving
an unexplained sequence gap that check-migration-numbering.test.ts's real-
migrations-dir check correctly flagged.
…view

Adds chevron buttons in the conversation modal to step to the adjacent
conversation in the loaded list, matching the request-detail view's
prev/next pattern.

Also surfaces the true live reply while it's still generating: turn nodes
only get written once the client resends a turn as history on its NEXT
request, so the transcript had nothing new to show mid-stream even though
the request was actively producing text. /api/conversations now exposes
activeCallLogId — the in-flight request's own id from usageHistory's
pendingById, since call_logs only gets its row on completion and the
existing lastCallLogId join always lagged one request behind. The modal
polls that id's partial text (same source/cadence as RequestLoggerDetail)
and renders it as a provisional bubble.

Co-authored-by: Markus Hartung <markus.hartung@gmail.com>
A tool_use/tool_result turn's text is the tool call's raw JSON arguments
(or a stringified result) — slicing that raw JSON at a fixed character
offset (TEXT_PREVIEW_LENGTH) routinely landed mid-string, storing INVALID
JSON. The conversations page's toTurn() then failed to JSON.parse it and
fell back to showing the raw, still-escaped text verbatim: a large
edit/write/apply_patch-style tool call with a long content field rendered
with literal `\n` sequences visible instead of real line breaks, looking
exactly like a JSON-escaping bug rather than a big diff. Confirmed live on
omniroute-dev: 3 stored `edit` tool_use previews were sitting at exactly
8000 chars with "Unterminated string in JSON" on parse.

buildTextPreview now parses first and caps oversized string VALUES inside
the JSON instead of slicing the raw blob, so a truncated payload is always
valid, re-parseable JSON. Plain text turns are unaffected (still a simple
slice — a cut-off sentence is harmless).

Also fixes JsonViewer's string rendering to preserve line breaks
(whitespace-pre-wrap) — a correctly-parsed multi-line tool argument was
still visually squashing onto one line without it.

Unrelated cleanup found while editing: conversationTracker.ts had two
literal NUL bytes (pre-existing, not introduced by this change) sitting
where a template-literal space belonged, making the file register as
binary to grep/rg/file. Restored to plain spaces.

Co-authored-by: Markus Hartung <markus.hartung@gmail.com>
… provider's

providerPayloadCollector (dashboard "Provider Response" panel) was keyed on
sourceFormat (the CLIENT's wire format) instead of targetFormat (the
PROVIDER's — see createSSEStream's own @PARAM doc: "targetFormat - Provider
format", "sourceFormat - Client format"). Whenever a request translates
between two different formats — e.g. a Responses-API client routed to a
plain-OpenAI-chat-completions upstream, the common OpenClaw/opencode-zen
shape — the reducer picked for sourceFormat could never recognize the
provider's actual raw event shape, so it stayed stuck at its empty initial
state. The dashboard's "Provider Response" panel showed a permanently empty
`output: []` while "Client Response" (built from separately-accumulated
state, unaffected by this bug) correctly showed full content — reading as
if the two panels simply disagreed about the same request.

Confirmed live via a wire-level pcap capture (scripts/sre/tcp-close-
analyzer.py) cross-referenced against the dashboard log
(1786032832181-1c6275): the actual response was complete and correct: this
was purely a logging/summary bug, never a wire-format bug.

Fix is mode-aware: TRANSLATE mode uses targetFormat (the provider's true
format); PASSTHROUGH mode keeps sourceFormat, since passthrough has no
separate provider/client format split — nothing gets translated there, and
real passthrough callers (createPassthroughStreamWithLogger) don't even
pass targetFormat.

New regression test reproduces the exact live scenario (Responses-API
source, OpenAI target, real chat.completion.chunk deltas) and asserts the
provider summary reflects them — confirmed it fails with the old
`sourceFormat`-keyed code (reproducing the live `output: []`-style
symptom) and passes with the fix.

Co-authored-by: Markus Hartung <markus.hartung@gmail.com>
Turbopack failed with "the name HARD_COMPAT_REASONS is defined multiple
times" — two top-level const declarations of the same name existed in
comboStructure.ts. The unused one (with an extra "output_tokens" entry,
zero references anywhere) was dead weight left over from upstream;
the active one (tools/vision/structured_output, used by
hasHardCapabilityFailure/describeCapabilityFilterExhaustion/
filterTargetsByRequestCompatibility) is unaffected.

Confirmed pre-existing on upstream/release/v3.8.50's own tip before
this branch's merge (git show 7f36b19 already has both), so this is
an inherited base-red fix, not new behavior.

⚠️ base-red inherited: diegosouzapw#9298
…ments

Leftover from the 137/138 -> 142/143 renumber — the header comment and the
cross-reference to agentic_conversations still named the old numbers.
…e reasoning preview

/api/logs/[id]'s live "Generating… / Thinking…" preview
(extractPartialAssistantText) parsed each stream-chunk-log array element
independently. Each element is one raw network read, timestamp-prefixed for
the debug display — not one complete SSE `data:` line — so a single JSON
value (a `reasoning_content` delta) routinely splits across two or more
elements. Parsing per-element in isolation silently failed JSON.parse on the
split pieces and dropped them, leaving gaps in the reconstructed text that
read as garbled/scrambled reasoning once the survivors were concatenated —
reported live via a dashboard screenshot of an in-progress conversation.

Fix: strip each element's timestamp prefix and concatenate the whole array
into one continuous string first, then split into lines and parse — so a
value split across elements rejoins correctly before JSON.parse ever sees
it. Same reconstruction technique already proven correct for the final,
completed response body; this brings the in-flight preview path in line.

Covered by tests/unit/logs-detail-partial-reasoning-chunk-split.test.ts,
including a direct demonstration that the pre-fix per-element parse fails on
each half of a split value in isolation (proving genuine TDD, not a
coincidental pass) while the fixed concatenate-first parse recovers the full
text.
…sset (diegosouzapw#9687)

Docker/standalone builds of the LLMLingua SLM compression tier failed at
runtime with "Error: libonnxruntime.so.1: cannot open shared object file:
No such file or directory" (open-sse/services/compression/engines/llmlingua's
worker, via @huggingface/transformers -> onnxruntime-node).

onnxruntime-node's dist/binding.js is a normal JS file Next.js's standalone
trace bundles correctly, but binding.js dlopen()s a platform-specific native
library shipped under bin/napi-v3/<platform>/<arch>/libonnxruntime.so.1 — a
dynamic native load static file tracing can't see (same blind-spot class as
the separate colocateLlmlinguaOptionals stub bug, just for a .so instead of
a JS import, via NATIVE_ASSET_ENTRIES instead). That directory was simply
never registered, unlike better-sqlite3's native binary, which already goes
through the exact same mechanism correctly.

Fix: add an entry for onnxruntime-node/bin, mirroring the existing
better-sqlite3 entry. Confirmed against a real Docker build of the
Dockerfile's own post-build verification step: this was the very next
failure once the separate llmlingua-2 stub bug was fixed and the build
progressed far enough to reach it.

Covered by tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts
(fails against the pre-fix code on both assertions, passes after).
…e rebase auto-merge

This branch's own earlier commit (b8601c4, "remove duplicate
HARD_COMPAT_REASONS declaration") deleted one of two duplicate
top-level declarations that existed on an older release/v3.8.50 tip.
By the time this branch was rebased onto the current, much newer
upstream/release/v3.8.50 tip, upstream had already independently
deduplicated its own copy down to a single declaration — so the old
delete-only diff's context lines matched the file's new (already
single-declaration) shape closely enough to auto-merge without a
conflict, silently deleting the only remaining declaration and
breaking typecheck (Cannot find name 'HARD_COMPAT_REASONS', 3 call
sites in this file).

Restored the exact declaration upstream itself carries; the file is
now byte-identical to upstream's own comboStructure.ts (confirmed via
diff), so there is no remaining divergence to reconcile.
….8.50

npm install after rebasing picked up a small lockfile metadata drift
left over from resolving the package-lock.json rebase conflict by
taking upstream's version wholesale (a mechanical hasInstallScript
sync commit from before the rebase was dropped as redundant, since
upstream's lockfile had already moved past it).
…ffect

agenticConversations.test.ts and conversationTracker.test.ts set
process.env.DATA_DIR to a fresh mkdtemp dir before importing the
DB-touching modules under test, but used static `import` statements.
ES modules instantiate the whole dependency graph -- dependencies
first, in the order encountered during linking -- before the entry
module's own top-level code runs, regardless of source-line order.
So src/lib/db/core.ts's `export const DATA_DIR = ...` (read once at
module load) captured the real host DATA_DIR before the override
line executed, and every test run silently wrote migrations and test
rows into the operator's actual host database instead of an isolated
temp dir.

Confirmed with a minimal repro (a dependency module logging
process.env.MY_VAR at its own top level, evaluated before the entry
script's env-var assignment that textually precedes the import).

Fix: switch the DB-touching imports in both files to dynamic
`await import(...)`, which is not hoisted and executes exactly where
it appears, after the DATA_DIR override.

This is a systemic pattern (other test files use the same
static-import-after-env-override shape, e.g. reasoning-cache.test.ts)
that stays silently harmless on CI/fresh-checkout runners with no
pre-existing data at the default DATA_DIR fallback -- it only became
visible here because the host running this rebase had real data
there. Scoped this fix to the two files this PR's own test suite
touches; the wider pattern is a separate cleanup.

Verified: both files' full suites (39 tests) pass, and the resolved
DATA_DIR in the startup log now correctly points at the mkdtemp dir
instead of the host default.
createSSEStream's providerPayloadCollector.build() falls back to the
synthesized responseBody as the "Provider Response" dashboard summary
whenever sourceFormat/targetFormat isn't OPENAI_RESPONSES (in both the
passthrough and translate branches) -- but responseBody is built purely
for the client and never carries an `object` field at all, so the
summary ended up with `object: undefined` instead of the expected
"chat.completion", even though everything else (choices, usage) was
correct.

Caught by this PR's own new regression test ("createSSEStream translate
mode: providerPayload summary reflects the PROVIDER's format, not the
client's") -- the code itself was unchanged by the rebase (applied
cleanly from the original commit), so this was a latent gap in the
original fix, not a rebase regression.

Fix: stamp `object: "chat.completion"` on a shallow copy used only for
the provider summary in both branches; responseBody itself (sent to the
client elsewhere) stays untouched.

Verified: tests/unit/stream-utils.test.ts 51/52 passing (the one
remaining failure is an unrelated, pre-existing v3.6.6-era test,
confirmed present and failing identically on a pristine
upstream/release/v3.8.50 checkout -- base-red inherited: diegosouzapw#9985).
typecheck/lint clean (pre-existing unrelated errors elsewhere in the
file, confirmed identical to upstream).
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking branch from 60cdaac to 60ed352 Compare August 10, 2026 16:15
@hartmark

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest release/v3.8.50 tip (61014aec5 → now 8fc4023f9, ~231 commits of drift). Summary:

  • 34 commits → 20 after dropping ones already landed upstream separately (Git auto-detected empty patches).
  • Conflicts resolved: ~40 i18n message files (3-way merge preferring non-placeholder translations), several config/quality/file-size-baseline.json re-baselines (took upstream's current values — they've moved independently many times), a couple of genuine code conflicts (import supersets, additive array entries, stale test fixtures already matching upstream's own independent fix for the same bug class).
  • Fixed a real pre-existing bug found while validating: tests/unit/agenticConversations.test.ts / conversationTracker.test.ts used static imports after a process.env.DATA_DIR override — ES modules evaluate imported modules before the entry file's own top-level code, so the override silently missed and tests wrote to the real host DB instead of an isolated temp dir. Switched to dynamic imports.
  • Fixed a real gap in this PR's own providerPayload summary fix (caught by this PR's own new regression test): the translate/passthrough fallback path never stamped object: "chat.completion" on the synthesized summary.
  • Updated KNOWN_GAPS in scripts/check/check-migration-numbering.mjs — migrations 143/144 are no longer gaps now that this PR fills them.

npm run test:unit (~19k tests completed before a runner limit), npm run test:vitest (355/355), typecheck, and lint all clean except pre-existing base-red failures (confirmed identical on a pristine upstream/release/v3.8.50 checkout).

⚠️ base-red inherited: #9985

@hartmark

Copy link
Copy Markdown
Contributor Author

Superseded by #10047 — same feature, rebased onto the current release/v3.8.50 tip and cleaned up (23 commits → 14). The dropped commits were all fixes for bugs that were pre-existing on release/v3.8.50 itself and have since been independently resolved upstream (verified against the current tip before dropping each one); two other genuinely-standalone fixes were already split out as #10037 and #10038. See #10047's description for the full commit-by-commit breakdown.

Closing this one in favor of #10047.

@hartmark hartmark closed this Aug 10, 2026
@hartmark
hartmark deleted the feat/agentic-conversation-tracking branch August 10, 2026 20:07
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 11, 2026
… Responses API bodies

Extracted from PR diegosouzapw#9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: diegosouzapw#9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: diegosouzapw#9985
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 12, 2026
… Responses API bodies

Extracted from PR diegosouzapw#9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: diegosouzapw#9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: diegosouzapw#9985
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 12, 2026
… Responses API bodies

Extracted from PR diegosouzapw#9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: diegosouzapw#9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: diegosouzapw#9985
hartmark added a commit to hartmark/OmniRoute that referenced this pull request Aug 12, 2026
… Responses API bodies

Extracted from PR diegosouzapw#9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: diegosouzapw#9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: diegosouzapw#9985
diegosouzapw added a commit that referenced this pull request Aug 13, 2026
… Responses API bodies (#10038)

* fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies

Extracted from PR #9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: #9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: #9985

* docs(logging): consolidate CHAT_LOG_MAX_BODY_KB into a single entry per file

The variable was already documented (with a stale src/lib/chatLogTruncation.ts
reference in .env.example); keep the new richer entries next to the CHAT_LOG_*
family and drop the old duplicates.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… Responses API bodies (diegosouzapw#10038)

* fix(logging): document CHAT_LOG_MAX_BODY_KB, capture messageCount for Responses API bodies

Extracted from PR diegosouzapw#9439 (agentic conversation tracking). Most of the
original scope this commit was cherry-picked from (CHAT_LOG_MAX_BODY_KB
env var support, the estimateSizeFast() earlyExitAt parameterization)
turned out to already be present on the current upstream/release/v3.8.50
tip -- confirmed via diff and by running check-env-doc-sync.test.ts /
tests/unit/chatcore-log-truncation.test.ts against pristine upstream
before making any changes here. Only two genuine gaps remained:

1. CHAT_LOG_MAX_BODY_KB was read by getChatLogMaxBodyBytes() but
   undocumented in .env.example and docs/reference/ENVIRONMENT.md --
   tests/unit/check-env-doc-sync.test.ts flags any env var read in code
   but missing from both doc files. Documented it (both required --
   the same test enforces the pairing).

2. truncateForLog()'s summary only computed messageCount from
   obj.messages (OpenAI-chat/Gemini field name) -- a large /v1/responses
   request (which uses input[], not messages[]) got summarized with no
   count at all, leaving the dashboard's "Full Conversation" panel
   nothing to base its "N messages not shown" placeholder on for any
   Responses-API conversation, even though the same summarization logic
   applies to it.

Test plan:
- TDD: tests/unit/chatcore-log-truncation.test.ts's new regression test
  ("captures a message count for Responses API bodies too") confirmed
  failing against the pre-fix code, passing after.
- tests/unit/check-env-doc-sync.test.ts confirms CHAT_LOG_MAX_BODY_KB no
  longer appears in codeMissingEnv (remaining drift in that test is
  pre-existing/unrelated -- ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS,
  COMMANDCODE_API_URL, OMNIROUTE_STRICT_SYSTEM_PROVIDERS,
  TLS_FINGERPRINT_PROVIDERS -- confirmed identical on a pristine
  upstream/release/v3.8.50 checkout, base-red inherited: diegosouzapw#9985).
- tests/unit/chatcore-log-truncation.test.ts -- 19/19 passing.
- npx tsc --noEmit / npm run lint -- clean.

⚠️ base-red inherited: diegosouzapw#9985

* docs(logging): consolidate CHAT_LOG_MAX_BODY_KB into a single entry per file

The variable was already documented (with a stale src/lib/chatLogTruncation.ts
reference in .env.example); keep the new richer entries next to the CHAT_LOG_*
family and drop the old duplicates.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants