Skip to content

feat(dashboard): agentic conversation tracking — v4, decoupled + storage-architecture concern resolved - #10263

Merged
diegosouzapw merged 15 commits into
cheaper-inference:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking-v4
Aug 18, 2026
Merged

diegosouzapw merged 15 commits into
cheaper-inference:release/v3.8.50from
hartmark:feat/agentic-conversation-tracking-v4

Conversation

@hartmark

Copy link
Copy Markdown
Contributor

Summary

This is the 4th iteration of agentic conversation tracking (v1 = #9439, v2 = #10047, v3 = hartmark/OmniRoute#4), addressing the original deferral directly:

"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" — @diegosouzapw on #9439

What changed since that deferral:

  1. Size roughly halved: +9639 lines then → +4681/-232 across 83 files now (production-only, excluding tests: +3225/-216 across 71 files).
  2. The architectural concern is resolved, not worked around. The original version stored full turn content (text, tool-call args, tool results) in its own table — a second, parallel content store duplicating what call_logs already persists. This version's conversation_turn_nodes table is identity-only: just the hash-chain id, role, and a content hash for reconnect-anchor lookups. Actual turn content (text/tool-call/tool-result) is resolved on demand from the existing call-log pipeline artifact via last_correlation_id — the same full, untruncated payloads call_logs already persists, already gated by call_log_pipeline_enabled, already covered by the existing retention/cleanup lifecycle. No second copy of conversation content exists anywhere. (See open-sse/services/conversationTurnContent.ts and the migration comments in 153_agentic_conversations.sql/154_conversation_turn_nodes.sql for the full reasoning.)

What remains, honestly stated — this is not purely UI wiring:

  • Two new tables (agentic_conversations: conversation-root record; conversation_turn_nodes: identity-only hash chain)
  • One new service (open-sse/services/conversationTracker.ts): resolves a stable conversation id per request via fingerprint + bounded prefix-hash matching, with a strict-growth invariant so independent single-shot requests sharing identical opening content never get merged
  • A thin content-resolution helper (conversationTurnContent.ts) and a couple of new dashboard API routes/pages

That's the full new backend surface — bounded, and it now follows the same storage pattern the rest of the codebase already uses (reuse the call-log artifact system) instead of inventing a new one, which is the specific thing the RFC concern was about.

Feature recap (unchanged in substance from the original description):

  • /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, live auto-refresh building the transcript in real time from the in-flight SSE chunk buffer while a request is still streaming.
  • 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, prev/next navigation.
  • 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 (confirmed live on omniroute-dev: 28 consecutive requests from one real growing session each recorded as separate turn_count=1 conversations before this fix).

Independent of and no longer stacked on feat/openai-responses-store-toggle/#10121 (already merged) or feat/responses-previous-response-id-virtualization/#10262 (separately submitted) — this PR stands alone against current release/v3.8.50.

Related Issues

Validation

  • Focused tests: agenticConversations, conversationTracker, conversationTurnContent, conversations-active-call-log-id, conversations-tree-route-seq-param, logs-detail-partial-reasoning-chunk-split, request-timeline-lane-allocation, chatcore-log-truncation, sidebar-monitoring-reorg, sidebar-visibility, check-migration-numbering — 97/97 pass
  • npm run lint — clean (pre-existing baseline debt only, no new violations)
  • npx tsc -p tsconfig.typecheck-core.json — clean
  • check-migration-numbering.test.ts — 0 gaps, 0 collisions (15/15)
  • Production-code changes include new automated tests in this PR

Tests Added Or Updated

  • tests/unit/agenticConversations.test.ts, conversationTracker.test.ts, conversationTurnContent.test.ts, conversations-active-call-log-id.test.ts, conversations-tree-route-seq-param.test.ts, request-timeline-lane-allocation.test.ts, logs-detail-partial-reasoning-chunk-split.test.ts (all new)

Coverage Notes

  • New backend surface (conversationTracker.ts, conversationTurnContent.ts, agenticConversations.ts) is covered by dedicated unit tests exercising the real fingerprint/hash-chain/reconnect logic, not just mocked branches.

Reviewer Notes

  • Migrations 153_agentic_conversations.sql / 154_conversation_turn_nodes.sql are purely additive new tables, no backfill needed.
  • Happy to split further if any specific piece (e.g. the timeline lane-reuse UI, or the tracker service itself) would review better as its own PR — flagging that option rather than assuming this exact shape is the only way to land it.

@hartmark

Copy link
Copy Markdown
Contributor Author

CI here is failing on pre-existing base-red on release/v3.8.50, not this PR — reproduced identically on a pristine checkout of the base tip. For correlated tracking:

@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking-v4 branch from ff88cf3 to 4aa7e4d Compare August 14, 2026 15:52
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for the thorough writeup, and for directly addressing the earlier RFC deferral rather than just resubmitting — the identity-only conversation_turn_nodes design (resolving display content from the existing call-log artifact instead of storing a second copy) is a real fix for the original architectural concern, and the comments documenting the real-traffic edge cases you hit (system-prompt regeneration, sliding context windows, cache-aware turn edits) make the reasoning easy to follow and verify.

One blocking issue found in review: 153_agentic_conversations.sql and 153_call_logs_response_id.sql both use version prefix 153, which is already taken on the current release/v3.8.50 tip by 153_radar_local_model_state.sql (merged after this branch's fork point). This isn't cosmetic — migrationRunner.ts's runtime collision guard throws on getDbInstance() once all three files are present together, which is exactly why all 4 Unit Tests fast-path shards, Vitest (fast-path), and the migration-numbering gate are currently red on this PR (confirmed directly in the job logs: Migration version collision detected: version=153 → [agentic_conversations, call_logs_response_id]). Renumbering the two files (e.g. to 155/156) should clear all of that at once.

The dead-code gate failure (411 > baseline 409) looks like separate base drift rather than something this PR caused — release/v3.8.50's baseline was already rebaselined to 415 after this branch's fork point, but that value isn't in this branch's quality-baseline.json yet. A rebase onto the current tip should pick it up automatically.

Two smaller, non-blocking items:

  • resolveConversationId() is called directly with no try/catch in the hot chat-completion path (chat.ts:616), and withChatAdmission rethrows uncaught errors rather than converting them into a sanitized response. Every other side-effect call nearby in that same function is defensively wrapped — worth doing the same here so a DB hiccup in this new, best-effort tracking feature can't turn an otherwise-working chat request into a hard failure.
  • getConversationTurnTree (agenticConversations.ts:230) looks like a leftover export from the tree-vs-flat-list redesign — no callers remain outside its own file/tests.

Once the migration rename lands (and ideally the resolveConversationId guard), this looks like solid, mergeable work.

@hartmark

Copy link
Copy Markdown
Contributor Author

Addressed both non-blocking items from review:

  1. resolveConversationId() try/catch (chat.ts) — wrapped the call so a DB hiccup in this best-effort tracking feature falls back to conversationId = null and logs a warning instead of turning a working chat request into a hard failure, matching the try { ... } catch {} pattern used by the other side-effect calls nearby in the same function (e.g. recordRejectedRequestUsage, getComboByName).

  2. getConversationTurnTree — confirmed it: knip's project/entry config in knip.json excludes tests/** entirely, so an export used only by tests can never register as "used" no matter how many call sites exist — this wasn't a leftover, it was an intentional test-support export, but it was permanently unwinnable against the dead-code gate as written. Rather than keep a second whole-chain query path alive solely for that, I swapped its 8 test call sites (across agenticConversations.test.ts and conversationTracker.test.ts) to the paginated getConversationTurnPage(id, { limit: 500 }) — already the dashboard's canonical query — and deleted the export. All 31 tests in both files still pass with identical assertions (they only ever read .length/.map(contentHash), so the extra seq field and page wrapper are inert for them).

Also caught and fixed pre-existing docs-sync drift on this branch (42 docs/i18n/*/llm.txt mirrors were stale against root llm.txt) — unrelated to this PR's content, just blocking the pre-commit hook.

Verified: npm run typecheck:core clean, node scripts/check/check-dead-code.mjs → 415 (matches current baseline, no regression), both test files green (31/31), t11:any-budget / tracked-artifacts / docs-sync pre-commit gates all pass.

Pushed: 6e54ce1a1

hartmark and others added 10 commits August 17, 2026 13:51
…ess of upstream support

OmniRoute now exposes OpenAI-compatible previous_response_id/store
continuation to clients unconditionally, even when the selected upstream
provider has no native Responses-API state support. Reconstruction happens
server-side in handleChatImplementation, before any downstream validation
or provider translation: OmniRoute resolves the response id back to the
full input/output it previously produced, prepends it to the client's
delta, and forwards the full reconstructed history upstream exactly as it
does today. Client<->OmniRoute traffic shrinks to the new delta only;
OmniRoute<->provider traffic is unchanged.

Storage reuses the existing call-log pipeline artifact (already gated by
call_log_pipeline_enabled, already retained/cleaned up by the existing
call-log lifecycle) instead of duplicating conversation content into a
second store -- only a lightweight call_logs.response_id index is new.
Every lookup is scoped by api_key_id so one client can never resolve
another client's stored conversation, and any unresolvable/missing/
size-limit-omitted state fails closed with OpenAI's own
previous_response_not_found contract.

Stacked on feat/openai-responses-store-toggle (cheaper-inference#10121).
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
…ript content

conversation_turn_nodes no longer stores turn text/tool-call content
(text_preview/block_kind/tool_name) -- it's identity-only now (id/parent/
content_hash), matching agentic_conversations' existing lightweight-index
shape. Every node's originating request is already fully captured by the
call-log pipeline artifact its last_correlation_id points at, so the
/dashboard/conversations tree view resolves each node's actual display
content on demand from there (open-sse/services/conversationTurnContent.ts),
re-running the same extractCanonicalTurns/hashTurnContent the write path
used and matching by content_hash, instead of duplicating conversation
content into a second store under a separate retention/gating policy. This
also drops the old 8000-char text_preview truncation entirely -- resolved
content is always full and untruncated.

The frontend contract is unchanged (tree API still returns
{textPreview, blockKind, toolName} per node), so the dashboard UI itself
(page.tsx, RequestLoggerDetail/RequestTimeline, sidebar, i18n) needed no
changes.

Renumbered the cherry-picked 147/148 migrations to 153/154 -- 147 now
collides with 147_api_keys_model_access_mode.sql, which landed on
release/v3.8.50 after this work was originally built.

Also includes a standalone, unrelated fix carried along from this rebase:
close isProviderModelHidden's missing function-body brace in
modelSelectModalHelpers.ts (separately landed as cheaper-inference#10206).

Stacked on feat/responses-previous-response-id-virtualization (#3), which
is itself stacked on feat/openai-responses-store-toggle (cheaper-inference#10121).
…l starts immediately

openConversation() seeded activeConversation (and therefore activeCallLogId,
which gates the live-partial-text poll effect) from whatever row snapshot the
list's own fixed-interval poll last produced. A conversation opened right
after a reply started streaming -- after that tick, before the next -- had
activeCallLogId still null, so the live-text poll never started; only a
subsequent background list-poll resync (already existed) picked it up,
which is why closing and reopening the same conversation "just worked".

loadConversations() is now a shared callback so openConversation can force
one immediately on open instead of waiting on pollSeconds.

Live-verified against omniroute-dev: opening a conversation mid-stream now
shows live reasoning on the first open.
…aper-inference#10262

153/154 (originally 154/155) were chosen back when this branch stacked on
top of the previous_response_id migration (153_call_logs_response_id.sql).
Decoupling removed that migration from this branch's history, leaving an
unused 153 slot that check-migration-numbering.test.ts correctly flags as
a gap.
…the 1000-line file-size cap

Both files exceeded check-file-size's new-file cap after this PR's own
additions (RequestTimeline 1048, RequestLoggerDetail 1163). Extracted pure
non-component logic (types, constants, allocateLanes and its helpers) out
of RequestTimeline.tsx into RequestTimeline.utils.ts, and the two
self-contained presentational sub-components (PayloadSection,
ConversationContextSection + its private helper) out of
RequestLoggerDetail.tsx into RequestLoggerDetail.sections.tsx. No behavior
change; existing external imports (default exports, allocateLanes,
TimelineLog, CONVERSATION_LANE_REUSE_STORAGE_KEY) still resolve from the
original file paths.
…ion + sync migration-count docs

The refresh-merge of release/v3.8.50 exposed that the feature's three
migrations collided at slot 153 with the base's radar_local_model_state
(153) and its own call_logs_response_id. Migration runner enforces unique
numeric prefixes -> every DB init threw, red-ing Vitest, all Unit shards and
the DB-backed quality gates. Renumber the feature's pair to
155_agentic_conversations / 156_conversation_turn_nodes and move
call_logs_response_id to 154 (keeps 153_radar base-owned, preserves
agentic-before-turn_nodes ordering). Update SQL headers and the
154/156 references in feature code + tests.

Migration count is now 151 (was 148 stale in README/AGENTS/llm.txt) — sync
the doc counts to clear the docs-accuracy gate.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…om RequestTimeline

Knip 6.32 (baseline 415) flags the public re-export of
CONVERSATION_LANE_REUSE_STORAGE_KEY from RequestTimeline.tsx as dead: no
external consumer imports it through that re-export (it is imported and
used directly from RequestTimeline.utils.ts inside the component). Removed
the unused re-export; the internal import stays. DEAD_TOTAL 416 -> 415,
back to the frozen baseline.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…ole-chain export

- Wrap resolveConversationId() in try/catch in chat.ts, matching the
  defensive pattern used by every other best-effort side call nearby, so a
  DB hiccup in conversation tracking can't turn a working chat request into
  a hard failure.
- Remove getConversationTurnTree: knip's project scope excludes tests/**,
  so an export used only by tests can never register as used there. Swap
  its 8 test call sites to the paginated getConversationTurnPage (already
  the dashboard's canonical query) with a generous limit, collapsing to one
  query path instead of keeping a second whole-chain export alive solely
  for test convenience.
- Regenerate i18n llm.txt mirrors from root (pre-existing drift on this
  branch, unrelated to the above, caught by the docs-sync pre-commit gate).

Addresses PR review feedback.
@hartmark
hartmark force-pushed the feat/agentic-conversation-tracking-v4 branch from 6e54ce1 to fd84f72 Compare August 17, 2026 12:03
@hartmark

Copy link
Copy Markdown
Contributor Author

Rebased onto current release/v3.8.50 (head 122611b0a). Both review items addressed and verified directly in source:

  • Migration collision: renumbered to 154_call_logs_response_id.sql / 155_agentic_conversations.sql / 156_conversation_turn_nodes.sql — this landed cleanly since feat(responses): virtualize previous_response_id continuation regardless of upstream support #10262 (this repo's own 154_call_logs_response_id.sql, responsesContinuationStore.ts) is now merged upstream; the rebase folded the two into one canonical implementation instead of duplicating it.
  • resolveConversationId() guard: wrapped in try/catch with a log.warn fallback (chat.ts:656) — a DB hiccup in this best-effort tracking no longer risks the chat request.
  • getConversationTurnTree dead export: removed, no remaining callers.

Also found and fixed two CI failures while rebasing:

  • i18n-vi-completeness.test.ts: requestLogger.columns.conversation (this PR's own new column) was missing from fr.json/vi.json.
  • check-docs-sync.mjs: the docs/i18n/*/llm.txt mirrors still said "117 domain-specific files" after an earlier rebase updated the migration count but not this companion number — fixed across all 42 locales.

The remaining red (chat-cooldown-aware-retry.test.ts, "expected cooldown-aware retry wait, got 60ms") is unrelated — that test file's history has no overlap with this branch's changes, and the assertion shape (a hardcoded wait-time expectation) looks like CI-runner timing flakiness rather than a real regression.

@diegosouzapw ready for another look.

…dules count drift

- fr.json, vi.json were missing requestLogger.columns.conversation (added
  in the conversation-tracking feature), failing i18n-vi-completeness.test.ts.
- docs/i18n/*/llm.txt mirrors still said 117 domain-specific files after an
  earlier rebase fixed the migration count but missed this companion number,
  failing check-docs-sync.mjs across all 42 locales.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw force-pushed the feat/agentic-conversation-tracking-v4 branch from 122611b to 6f86310 Compare August 18, 2026 03:43
…c red)

.env.example and docs/reference/ENVIRONMENT.md were both missing the
PROXY_LOG_INCLUDE_IPS entry that src/lib/proxyLogger.ts already reads
(confirmed present at this branch's merge-base too, so this predates
the conversation-tracking work and is unrelated to it) -- the entry
was added on release/v3.8.50 after this branch's last sync and this
branch never picked it up. That gap red-lines
tests/unit/check-env-doc-sync.test.ts and
tests/unit/issue-7793-env-doc-sync-repro.test.ts (Unit Tests
fast-path 2/4 in CI). Restore both entries verbatim from the current
release/v3.8.50 tip -- no feature-code change.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw force-pushed the feat/agentic-conversation-tracking-v4 branch from 6f86310 to cf7f736 Compare August 18, 2026 03:49
…ic-conversation-tracking-v4

# Conflicts:
#	AGENTS.md
#	README.md
#	docs/i18n/ar/llm.txt
#	docs/i18n/az/llm.txt
#	docs/i18n/bg/llm.txt
#	docs/i18n/bn/llm.txt
#	docs/i18n/cs/llm.txt
#	docs/i18n/da/llm.txt
#	docs/i18n/de/llm.txt
#	docs/i18n/es/llm.txt
#	docs/i18n/fa/llm.txt
#	docs/i18n/fi/llm.txt
#	docs/i18n/fr/llm.txt
#	docs/i18n/gu/llm.txt
#	docs/i18n/he/llm.txt
#	docs/i18n/hi/llm.txt
#	docs/i18n/hu/llm.txt
#	docs/i18n/id/llm.txt
#	docs/i18n/in/llm.txt
#	docs/i18n/it/llm.txt
#	docs/i18n/ja/llm.txt
#	docs/i18n/ko/llm.txt
#	docs/i18n/mr/llm.txt
#	docs/i18n/ms/llm.txt
#	docs/i18n/nl/llm.txt
#	docs/i18n/no/llm.txt
#	docs/i18n/phi/llm.txt
#	docs/i18n/pl/llm.txt
#	docs/i18n/pt-BR/llm.txt
#	docs/i18n/pt/llm.txt
#	docs/i18n/ro/llm.txt
#	docs/i18n/ru/llm.txt
#	docs/i18n/sk/llm.txt
#	docs/i18n/sv/llm.txt
#	docs/i18n/sw/llm.txt
#	docs/i18n/ta/llm.txt
#	docs/i18n/te/llm.txt
#	docs/i18n/th/llm.txt
#	docs/i18n/tr/llm.txt
#	docs/i18n/uk-UA/llm.txt
#	docs/i18n/ur/llm.txt
#	docs/i18n/vi/llm.txt
#	docs/i18n/zh-CN/llm.txt
#	docs/i18n/zh-TW/llm.txt
#	llm.txt
…ic-conversation-tracking-v4

# Conflicts:
#	AGENTS.md
#	README.md
#	docs/i18n/ar/llm.txt
#	docs/i18n/az/llm.txt
#	docs/i18n/bg/llm.txt
#	docs/i18n/bn/llm.txt
#	docs/i18n/cs/llm.txt
#	docs/i18n/da/llm.txt
#	docs/i18n/de/llm.txt
#	docs/i18n/es/llm.txt
#	docs/i18n/fa/llm.txt
#	docs/i18n/fi/llm.txt
#	docs/i18n/fr/llm.txt
#	docs/i18n/gu/llm.txt
#	docs/i18n/he/llm.txt
#	docs/i18n/hi/llm.txt
#	docs/i18n/hu/llm.txt
#	docs/i18n/id/llm.txt
#	docs/i18n/in/llm.txt
#	docs/i18n/it/llm.txt
#	docs/i18n/ja/llm.txt
#	docs/i18n/ko/llm.txt
#	docs/i18n/mr/llm.txt
#	docs/i18n/ms/llm.txt
#	docs/i18n/nl/llm.txt
#	docs/i18n/no/llm.txt
#	docs/i18n/phi/llm.txt
#	docs/i18n/pl/llm.txt
#	docs/i18n/pt-BR/llm.txt
#	docs/i18n/pt/llm.txt
#	docs/i18n/ro/llm.txt
#	docs/i18n/ru/llm.txt
#	docs/i18n/sk/llm.txt
#	docs/i18n/sv/llm.txt
#	docs/i18n/sw/llm.txt
#	docs/i18n/ta/llm.txt
#	docs/i18n/te/llm.txt
#	docs/i18n/th/llm.txt
#	docs/i18n/tr/llm.txt
#	docs/i18n/uk-UA/llm.txt
#	docs/i18n/ur/llm.txt
#	docs/i18n/vi/llm.txt
#	docs/i18n/zh-CN/llm.txt
#	docs/i18n/zh-TW/llm.txt
#	llm.txt
…ic-conversation-tracking-v4

# Conflicts:
#	src/sse/handlers/chat.ts
@diegosouzapw
diegosouzapw merged commit beb6ec8 into cheaper-inference:release/v3.8.50 Aug 18, 2026
2 of 5 checks passed
diegosouzapw pushed a commit that referenced this pull request Aug 18, 2026
Two independently-merged PRs (#10263 agentic-conversation-tracking-v4
and #10362 exclusive-managed-session-leases) each picked migration
slot 155 against different base states, landing a real collision on
release/v3.8.50 (155_agentic_conversations.sql vs
155_exclusive_connection_leases.sql; #10263 also claimed 156 via
156_conversation_turn_nodes.sql). Renumbered #10362's migration to the
next free slot (157) and updated its own regression test
(exclusive-connection-leases.test.ts) that asserted the literal
filename/slot. No retroactive guard needed: CREATE TABLE IF NOT
EXISTS is idempotent under either number.

Confirmed via check-migration-numbering.mjs (154 migrations, 0
duplicates) and the full exclusive-connection-leases test suite
(11/11 pass).
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…age-architecture concern resolved (cheaper-inference#10263)

* feat(responses): virtualize previous_response_id continuation regardless of upstream support

OmniRoute now exposes OpenAI-compatible previous_response_id/store
continuation to clients unconditionally, even when the selected upstream
provider has no native Responses-API state support. Reconstruction happens
server-side in handleChatImplementation, before any downstream validation
or provider translation: OmniRoute resolves the response id back to the
full input/output it previously produced, prepends it to the client's
delta, and forwards the full reconstructed history upstream exactly as it
does today. Client<->OmniRoute traffic shrinks to the new delta only;
OmniRoute<->provider traffic is unchanged.

Storage reuses the existing call-log pipeline artifact (already gated by
call_log_pipeline_enabled, already retained/cleaned up by the existing
call-log lifecycle) instead of duplicating conversation content into a
second store -- only a lightweight call_logs.response_id index is new.
Every lookup is scoped by api_key_id so one client can never resolve
another client's stored conversation, and any unresolvable/missing/
size-limit-omitted state fails closed with OpenAI's own
previous_response_not_found contract.

Stacked on feat/openai-responses-store-toggle (cheaper-inference#10121).

* feat(dashboard): agentic conversation tracking with live transcript view

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

* refactor(dashboard): reuse call-log artifacts for conversation transcript content

conversation_turn_nodes no longer stores turn text/tool-call content
(text_preview/block_kind/tool_name) -- it's identity-only now (id/parent/
content_hash), matching agentic_conversations' existing lightweight-index
shape. Every node's originating request is already fully captured by the
call-log pipeline artifact its last_correlation_id points at, so the
/dashboard/conversations tree view resolves each node's actual display
content on demand from there (open-sse/services/conversationTurnContent.ts),
re-running the same extractCanonicalTurns/hashTurnContent the write path
used and matching by content_hash, instead of duplicating conversation
content into a second store under a separate retention/gating policy. This
also drops the old 8000-char text_preview truncation entirely -- resolved
content is always full and untruncated.

The frontend contract is unchanged (tree API still returns
{textPreview, blockKind, toolName} per node), so the dashboard UI itself
(page.tsx, RequestLoggerDetail/RequestTimeline, sidebar, i18n) needed no
changes.

Renumbered the cherry-picked 147/148 migrations to 153/154 -- 147 now
collides with 147_api_keys_model_access_mode.sql, which landed on
release/v3.8.50 after this work was originally built.

Also includes a standalone, unrelated fix carried along from this rebase:
close isProviderModelHidden's missing function-body brace in
modelSelectModalHelpers.ts (separately landed as cheaper-inference#10206).

Stacked on feat/responses-previous-response-id-virtualization (cheaper-inference#3), which
is itself stacked on feat/openai-responses-store-toggle (cheaper-inference#10121).

* fix(dashboard): resync conversation list on open so the live-text poll starts immediately

openConversation() seeded activeConversation (and therefore activeCallLogId,
which gates the live-partial-text poll effect) from whatever row snapshot the
list's own fixed-interval poll last produced. A conversation opened right
after a reply started streaming -- after that tick, before the next -- had
activeCallLogId still null, so the live-text poll never started; only a
subsequent background list-poll resync (already existed) picked it up,
which is why closing and reopening the same conversation "just worked".

loadConversations() is now a shared callback so openConversation can force
one immediately on open instead of waiting on pollSeconds.

Live-verified against omniroute-dev: opening a conversation mid-stream now
shows live reasoning on the first open.

* style: prettier formatting for conversationTurnContent.test.ts

* fix(db): close migration numbering gap left by decoupling from cheaper-inference#3/cheaper-inference#10262

153/154 (originally 154/155) were chosen back when this branch stacked on
top of the previous_response_id migration (153_call_logs_response_id.sql).
Decoupling removed that migration from this branch's history, leaving an
unused 153 slot that check-migration-numbering.test.ts correctly flags as
a gap.

* refactor(dashboard): split RequestTimeline/RequestLoggerDetail under the 1000-line file-size cap

Both files exceeded check-file-size's new-file cap after this PR's own
additions (RequestTimeline 1048, RequestLoggerDetail 1163). Extracted pure
non-component logic (types, constants, allocateLanes and its helpers) out
of RequestTimeline.tsx into RequestTimeline.utils.ts, and the two
self-contained presentational sub-components (PayloadSection,
ConversationContextSection + its private helper) out of
RequestLoggerDetail.tsx into RequestLoggerDetail.sections.tsx. No behavior
change; existing external imports (default exports, allocateLanes,
TimelineLog, CONVERSATION_LANE_REUSE_STORAGE_KEY) still resolve from the
original file paths.

* fix(db): renumber agentic-conversation migrations to clear 153 collision + sync migration-count docs

The refresh-merge of release/v3.8.50 exposed that the feature's three
migrations collided at slot 153 with the base's radar_local_model_state
(153) and its own call_logs_response_id. Migration runner enforces unique
numeric prefixes -> every DB init threw, red-ing Vitest, all Unit shards and
the DB-backed quality gates. Renumber the feature's pair to
155_agentic_conversations / 156_conversation_turn_nodes and move
call_logs_response_id to 154 (keeps 153_radar base-owned, preserves
agentic-before-turn_nodes ordering). Update SQL headers and the
154/156 references in feature code + tests.

Migration count is now 151 (was 148 stale in README/AGENTS/llm.txt) — sync
the doc counts to clear the docs-accuracy gate.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(ui): drop unused CONVERSATION_LANE_REUSE_STORAGE_KEY re-export from RequestTimeline

Knip 6.32 (baseline 415) flags the public re-export of
CONVERSATION_LANE_REUSE_STORAGE_KEY from RequestTimeline.tsx as dead: no
external consumer imports it through that re-export (it is imported and
used directly from RequestTimeline.utils.ts inside the component). Removed
the unused re-export; the internal import stays. DEAD_TOTAL 416 -> 415,
back to the frozen baseline.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(agentic-conversations): guard resolveConversationId, drop dead whole-chain export

- Wrap resolveConversationId() in try/catch in chat.ts, matching the
  defensive pattern used by every other best-effort side call nearby, so a
  DB hiccup in conversation tracking can't turn a working chat request into
  a hard failure.
- Remove getConversationTurnTree: knip's project scope excludes tests/**,
  so an export used only by tests can never register as used there. Swap
  its 8 test call sites to the paginated getConversationTurnPage (already
  the dashboard's canonical query) with a generous limit, collapsing to one
  query path instead of keeping a second whole-chain export alive solely
  for test convenience.
- Regenerate i18n llm.txt mirrors from root (pre-existing drift on this
  branch, unrelated to the above, caught by the docs-sync pre-commit gate).

Addresses PR review feedback.

* fix(i18n): close requestLogger conversation-column gap, fix domain-modules count drift

- fr.json, vi.json were missing requestLogger.columns.conversation (added
  in the conversation-tracking feature), failing i18n-vi-completeness.test.ts.
- docs/i18n/*/llm.txt mirrors still said 117 domain-specific files after an
  earlier rebase fixed the migration count but missed this companion number,
  failing check-docs-sync.mjs across all 42 locales.

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

* fix(docs): restore PROXY_LOG_INCLUDE_IPS env/doc entries (env-doc-sync red)

.env.example and docs/reference/ENVIRONMENT.md were both missing the
PROXY_LOG_INCLUDE_IPS entry that src/lib/proxyLogger.ts already reads
(confirmed present at this branch's merge-base too, so this predates
the conversation-tracking work and is unrelated to it) -- the entry
was added on release/v3.8.50 after this branch's last sync and this
branch never picked it up. That gap red-lines
tests/unit/check-env-doc-sync.test.ts and
tests/unit/issue-7793-env-doc-sync-repro.test.ts (Unit Tests
fast-path 2/4 in CI). Restore both entries verbatim from the current
release/v3.8.50 tip -- no feature-code change.

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

---------

Co-authored-by: hartmark <hartmark@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.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
Two independently-merged PRs (cheaper-inference#10263 agentic-conversation-tracking-v4
and cheaper-inference#10362 exclusive-managed-session-leases) each picked migration
slot 155 against different base states, landing a real collision on
release/v3.8.50 (155_agentic_conversations.sql vs
155_exclusive_connection_leases.sql; cheaper-inference#10263 also claimed 156 via
156_conversation_turn_nodes.sql). Renumbered cheaper-inference#10362's migration to the
next free slot (157) and updated its own regression test
(exclusive-connection-leases.test.ts) that asserted the literal
filename/slot. No retroactive guard needed: CREATE TABLE IF NOT
EXISTS is idempotent under either number.

Confirmed via check-migration-numbering.mjs (154 migrations, 0
duplicates) and the full exclusive-connection-leases test suite
(11/11 pass).
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