mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 02:02:13 +03:00
Every conversation is now a single straight line: when a request's turn history diverges from what's on file (real OpenClaw traffic edits/ duplicates turns to keep provider-side prompt caches warm), the diverging history mints its own independent conversation instead of forking a branch inside the old one. Distinguished via anchorHasChild — whether the reconnect anchor already has a recorded child. Also fixes the actual production-blocking bug this surfaced: real agentic traffic is full of byte-identical repeated turns (tool-polling loop output, heartbeat acks — one real conversation had 28 duplicates of a single turn). findReconnectMatch used to return on the first candidate anchor found for a repeated turn's content hash — in practice the oldest, stalest occurrence — whose recorded next-turn differs from the current request, so it looked like a divergence on every single request instead of ever reconnecting. Now every candidate anchor is evaluated and the one that verifiably extends furthest wins (ties break toward the anchor with no recorded child). Dashboard: /dashboard/conversations lists conversations by actual turn-node count instead of request-touch count (a freshly-forked conversation can carry hundreds of turns from a single insert but start at turn_count=1, which wrongly excluded it from the old turn_count>=2 filter). The conversation view loads the last 20 turns with a "Load more" button, scrolls to bottom on open and stays pinned there via a ResizeObserver while large/late-settling content keeps growing (a single requestAnimationFrame undershoots for a page containing multi-KB tool-output turns), and resyncs its "Goto latest request"/summary fields from the background list poll so they don't go stale while the modal stays open (keyed off the id, not the whole row object, so the poll-for-new-turns interval isn't reset every tick by that resync). X-ConversationId threading: chat.ts now passes the request's own correlationId into resolveConversationId so new turn-chain nodes can be tagged with a request identifier that exists before the call_logs row itself does. usageHistory's in-memory pending-request state (byModel/ byAccount/details/pendingById) is reused across Next.js dev HMR module re-evaluations via a globalThis singleton (same pattern as db/core.ts), so a live poll against a request that started before a hot-reload doesn't silently lose its partialAssistantText/isActive tracking.
31 lines
1.2 KiB
TypeScript
31 lines
1.2 KiB
TypeScript
/**
|
|
* Regression test for /api/conversations/[id]/tree's query-param parsing.
|
|
*
|
|
* Real bug: `Number(searchParams.get("beforeSeq"))` is 0 (not NaN) when the
|
|
* param is absent, since `Number(null) === 0`. That made an ABSENT
|
|
* beforeSeq/afterSeq look like "beforeSeq=0"/"afterSeq=0" was explicitly
|
|
* given, which — because the DB layer checks `opts.afterSeq != null` (true
|
|
* for 0) BEFORE checking limit — forced every single request into the
|
|
* uncapped "poll for new turns" branch, ignoring `limit` entirely and
|
|
* returning the conversation's ENTIRE history on every load.
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { parseSeqParam } from "../../src/app/api/conversations/[id]/tree/route.ts";
|
|
|
|
test("parseSeqParam: an absent query param returns undefined, not 0", () => {
|
|
assert.equal(parseSeqParam(null), undefined);
|
|
assert.equal(parseSeqParam(""), undefined);
|
|
});
|
|
|
|
test("parseSeqParam: a real numeric string parses to that number, including a literal '0'", () => {
|
|
assert.equal(parseSeqParam("0"), 0);
|
|
assert.equal(parseSeqParam("42"), 42);
|
|
});
|
|
|
|
test("parseSeqParam: a non-numeric string returns undefined rather than NaN", () => {
|
|
assert.equal(parseSeqParam("not-a-number"), undefined);
|
|
});
|