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
+53 -9
View File
@@ -20,11 +20,16 @@ import { openCodeTranscriptPageLimit } from '../../shared/opencode-transcript-pa
// OpenCode SQLite reads use the same bounded worker as AI Vault.
export type OpenCodeTranscriptDeps = {
resolveDbPath?: (sessionId?: string, signal?: AbortSignal) => Promise<string | null>
resolveDbPath?: (
sessionId?: string,
signal?: AbortSignal,
agent?: 'zcode'
) => Promise<string | null>
readSignal?: (
dbPath: string,
sessionId: string,
signal?: AbortSignal
signal?: AbortSignal,
agent?: 'zcode'
) => Promise<OpenCodeTranscriptSignal | null>
readPage?: (
args: {
@@ -32,6 +37,7 @@ export type OpenCodeTranscriptDeps = {
sessionId: string
limit: number
beforeMessageRowId?: number
agent?: 'zcode'
},
signal?: AbortSignal
) => Promise<OpenCodeTranscriptPage | null>
@@ -43,17 +49,51 @@ export function resolveOpenCodeTranscriptDbPath(sessionId?: string): Promise<str
export const openCodeTranscriptDefaultDeps: Required<OpenCodeTranscriptDeps> = {
resolveDbPath: discoverOpenCodeTranscriptDatabase,
readSignal: (dbPath, sessionId, signal) =>
readOpenCodeTranscriptSignalViaWorker({ dbPath, sessionId }, signal),
readSignal: (dbPath, sessionId, signal, agent) =>
readOpenCodeTranscriptSignalViaWorker({ dbPath, sessionId, agent }, signal),
readPage: (args, signal) => readOpenCodeTranscriptPageViaWorker(args, signal)
}
/**
* Resolve a subscribe's read legs against its own abort signal, with the agent
* tag (when any) already applied to every leg. Kept here so the poll loop stays
* free of default-vs-override plumbing.
*/
export function bindOpenCodeTranscriptDeps(
deps: OpenCodeTranscriptDeps,
signal: AbortSignal,
agent?: 'zcode'
): {
resolveDbPath: () => Promise<string | null>
readSignal: (dbPath: string, sessionId: string) => Promise<OpenCodeTranscriptSignal | null>
readPage: (
page: Parameters<NonNullable<OpenCodeTranscriptDeps['readPage']>>[0]
) => Promise<OpenCodeTranscriptPage | null>
} {
return {
resolveDbPath: () =>
(deps.resolveDbPath ?? openCodeTranscriptDefaultDeps.resolveDbPath)(undefined, signal, agent),
readSignal: (dbPath, sessionId) =>
(deps.readSignal ?? openCodeTranscriptDefaultDeps.readSignal)(
dbPath,
sessionId,
signal,
agent
),
readPage: (page) =>
(deps.readPage ?? openCodeTranscriptDefaultDeps.readPage)(
{ ...page, ...(agent ? { agent } : {}) },
signal
)
}
}
export type OpenCodeTailResult =
| { messages: NativeChatMessage[]; hasMore: boolean; beforeOffset: number }
| { error: string; notFound?: true }
export async function readOpenCodeNativeChatTranscriptTail(
args: { sessionId: string; limit: number; beforeOffset?: number },
args: { sessionId: string; limit: number; beforeOffset?: number; agent?: 'zcode' },
deps: OpenCodeTranscriptDeps = {},
signal?: AbortSignal
): Promise<OpenCodeTailResult> {
@@ -61,7 +101,8 @@ export async function readOpenCodeNativeChatTranscriptTail(
try {
const dbPath = await (deps.resolveDbPath ?? openCodeTranscriptDefaultDeps.resolveDbPath)(
args.sessionId,
signal
signal,
args.agent
)
if (!dbPath) {
return { error: 'Transcript unavailable', notFound: true }
@@ -71,7 +112,8 @@ export async function readOpenCodeNativeChatTranscriptTail(
dbPath,
sessionId: args.sessionId,
limit,
...(args.beforeOffset !== undefined ? { beforeMessageRowId: args.beforeOffset } : {})
...(args.beforeOffset !== undefined ? { beforeMessageRowId: args.beforeOffset } : {}),
...(args.agent ? { agent: args.agent } : {})
},
signal
)
@@ -91,7 +133,8 @@ export async function readOpenCodeNativeChatTranscriptTail(
export async function readOpenCodeNativeChatTranscriptFull(
sessionId: string,
deps: OpenCodeTranscriptDeps = {},
signal?: AbortSignal
signal?: AbortSignal,
agent?: 'zcode'
): Promise<ReadTranscriptResult> {
// Pages arrive newest-window first; reverse windows, preserving message order.
const pages: NativeChatMessage[][] = []
@@ -113,7 +156,8 @@ export async function readOpenCodeNativeChatTranscriptFull(
dbPath,
sessionId,
limit: 500,
...(cursor !== undefined ? { beforeMessageRowId: cursor } : {})
...(cursor !== undefined ? { beforeMessageRowId: cursor } : {}),
...(agent ? { agent } : {})
},
signal
)