feat(native-chat): read ZCode session transcripts through the OpenCode SQLite reader

ZCode's CLI persists an OpenCode-v1-shaped history in its own SQLite store
(~/.zcode/cli/db/db.sqlite). The Agent Session History scan already lists
those sessions, but opening one in the native chat found nothing: the chat
transcript path had no ZCode awareness.

Resolve a zcode transcript against the ZCode database (native home, then WSL
homes) before any OpenCode install, tag every read (signal, page, subscribe)
with the agent, and apply the same hidden-transcript filter the scan uses so
ZCode's hidden bookkeeping rows stay out of chat renders.
This commit is contained in:
star-nebula
2026-10-10 17:05:02 -07:00
committed by Neil
parent 7d81ce26c4
commit ecc3433bdf
13 changed files with 166 additions and 42 deletions
@@ -16,6 +16,11 @@ import {
} from './transcript-opencode-part-blocks'
// Cursors are opaque provider order: SQLite rowid in v1, session sequence in v2.
// ZCode stores hidden bookkeeping transcripts in the same message table; the
// same predicate the AI Vault scan applies keeps them out of chat renders.
const ZCODE_HIDDEN_MESSAGE_FILTER =
"AND COALESCE(json_extract(data, '$.semantics.transcriptVisibility'), 'visible') != 'hidden'"
export type OpenCodeTranscriptItem = {
rowid: number
fingerprint: string
@@ -70,7 +75,8 @@ function rowsWithinBudget<T>(
export function readOpenCodeTranscriptSignal(
dbPath: string,
sessionId: string
sessionId: string,
agent?: 'zcode'
): OpenCodeTranscriptSignal | null {
const db = openOpenCodeDatabaseReadonly(dbPath)
try {
@@ -84,14 +90,22 @@ export function readOpenCodeTranscriptSignal(
// Aggregates without GROUP BY always yield exactly one row, so [0] is it.
const [messageRow] = rowsOf<{ message_count: number; max_message_rowid: number }>(
db.prepare(
'SELECT COUNT(*) AS message_count, COALESCE(MAX(rowid), 0) AS max_message_rowid FROM message WHERE session_id = ?'
`SELECT COUNT(*) AS message_count, COALESCE(MAX(rowid), 0) AS max_message_rowid FROM message WHERE session_id = ? ${
agent === 'zcode' ? ZCODE_HIDDEN_MESSAGE_FILTER : ''
}`
),
sessionId
)
const [partRow] = rowsOf<{ part_count: number; max_part_time_updated: number }>(
db.prepare(
'SELECT COUNT(*) AS part_count, COALESCE(MAX(time_updated), 0) AS max_part_time_updated FROM part WHERE session_id = ?'
`SELECT COUNT(*) AS part_count, COALESCE(MAX(time_updated), 0) AS max_part_time_updated
FROM part WHERE session_id = ? AND message_id IN (
SELECT id FROM message WHERE session_id = ? ${
agent === 'zcode' ? ZCODE_HIDDEN_MESSAGE_FILTER : ''
}
)`
),
sessionId,
sessionId
)
return {
@@ -110,6 +124,7 @@ export function readOpenCodeTranscriptPage(args: {
sessionId: string
limit: number
beforeMessageRowId?: number
agent?: 'zcode'
}): OpenCodeTranscriptPage | null {
const db = openOpenCodeDatabaseReadonly(args.dbPath)
try {
@@ -127,7 +142,9 @@ export function readOpenCodeTranscriptPage(args: {
const select = db.prepare(
`SELECT rowid AS message_rowid, id, time_created, time_updated, CASE WHEN length(CAST(data AS BLOB)) <= ${OPENCODE_TRANSCRIPT_MAX_ROW_BYTES} THEN data ELSE NULL END AS data
FROM message
WHERE session_id = ? AND rowid < ?
WHERE session_id = ? AND rowid < ? ${
args.agent === 'zcode' ? ZCODE_HIDDEN_MESSAGE_FILTER : ''
}
ORDER BY rowid DESC
LIMIT ?`
)