From 3ee1489cf9ef6d9030d893761ba01c2fefbbd35c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 16 May 2026 16:02:15 +0100 Subject: [PATCH] fix: preserve untyped sqlite transcript tails --- .../sessions/transcript-store.sqlite.test.ts | 31 +++++++++++++++++++ .../sessions/transcript-store.sqlite.ts | 6 ++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/config/sessions/transcript-store.sqlite.test.ts b/src/config/sessions/transcript-store.sqlite.test.ts index 3f293b8376b..5bcd08c085f 100644 --- a/src/config/sessions/transcript-store.sqlite.test.ts +++ b/src/config/sessions/transcript-store.sqlite.test.ts @@ -230,6 +230,37 @@ describe("SQLite session transcript store", () => { ]); }); + it("links untyped transcript events when appending against the database tail", () => { + const stateDir = createTempDir(); + const scope = { + env: { OPENCLAW_STATE_DIR: stateDir }, + agentId: "main", + sessionId: "session-1", + }; + appendSqliteSessionTranscriptEvent({ + ...scope, + event: { id: "first", parentId: null }, + parentMode: "database-tail", + now: () => 100, + }); + appendSqliteSessionTranscriptEvent({ + ...scope, + event: { id: "second", parentId: null }, + parentMode: "database-tail", + now: () => 200, + }); + + expect( + loadSqliteSessionTranscriptEvents(scope).map( + (entry) => entry.event as { id?: string; parentId?: string | null }, + ), + ).toEqual([ + { id: "first", parentId: null }, + { id: "second", parentId: "first" }, + ]); + expect(countSqliteSessionTranscriptDisplayMessages(scope)).toBe(2); + }); + it("keeps transcript events isolated by agent id", () => { const stateDir = createTempDir(); diff --git a/src/config/sessions/transcript-store.sqlite.ts b/src/config/sessions/transcript-store.sqlite.ts index 3a7639225b4..0117ba66cdb 100644 --- a/src/config/sessions/transcript-store.sqlite.ts +++ b/src/config/sessions/transcript-store.sqlite.ts @@ -247,7 +247,7 @@ function readLatestTranscriptTailEventId( .selectFrom("transcript_event_identities") .select(["event_id"]) .where("session_id", "=", sessionId) - .where("event_type", "!=", "session") + .where((eb) => eb.or([eb("event_type", "is", null), eb("event_type", "!=", "session")])) .where("has_parent", "=", 1) .orderBy("seq", "desc") .limit(1), @@ -829,7 +829,7 @@ export function countSqliteSessionTranscriptDisplayMessages( WITH latest_leaf AS ( SELECT event_id FROM transcript_event_identities - WHERE session_id = ${sessionId} AND event_type != 'session' AND has_parent = 1 + WHERE session_id = ${sessionId} AND (event_type IS NULL OR event_type != 'session') AND has_parent = 1 ORDER BY seq DESC LIMIT 1 ), @@ -845,7 +845,7 @@ export function countSqliteSessionTranscriptDisplayMessages( ) SELECT (SELECT COUNT(*) FROM transcript_event_identities WHERE session_id = ${sessionId} AND has_parent = 1) AS parent_link_count, - (SELECT COUNT(*) FROM active_chain WHERE event_type != 'session') AS active_count, + (SELECT COUNT(*) FROM active_chain WHERE event_type IS NULL OR event_type != 'session') AS active_count, ( SELECT COUNT(*) FROM transcript_events