Skip to content

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

Closed
star-nebula wants to merge 2 commits into
stablyai:mainfrom
star-nebula:feat/zcode-native-chat-transcript
Closed

star-nebula wants to merge 2 commits into
stablyai:mainfrom
star-nebula:feat/zcode-native-chat-transcript

Conversation

@star-nebula

@star-nebula star-nebula commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

ZCode (Z.ai's coding agent) keeps its chat history in its own database. Orca's Agent Session History already finds and lists those sessions, but clicking one in the native chat showed nothing — the chat reader simply did not know where ZCode keeps its history, and the only way into a chat was a structured session a ZCode row can never adopt into. This PR teaches the chat reader about ZCode's database and gives ZCode rows their own way into a chat: a read-only chat tab that renders the conversation straight from ZCode's store.

What Changed

Two commits, two layers of the same gap:

1. The read path (0e1e6737):

  • resolveNativeChatTranscriptAgent('zcode') routes to the OpenCode SQLite reader — ZCode's CLI stores an OpenCode-v1-shaped history — while keeping the ZCode agent identity for UI behavior.
  • Transcript database discovery probes ZCode's own store (~/.zcode/cli/db/db.sqlite, plus WSL homes) before any OpenCode install, so a ZCode session can never be claimed by an unrelated OpenCode database (the same separation the AI Vault scan already draws).
  • Every read leg (change-signal, page read, live subscribe) threads an agent: 'zcode' tag through the shared OpenCode SQLite worker, and the signal/page queries apply the same hidden-transcript filter the scan uses (semantics.transcriptVisibility != 'hidden'), so ZCode's hidden bookkeeping rows stay out of chat renders.

2. The entry path (368fe163):

  • An Agent Session History row whose agent reads its own store (zcode today) now opens as a read-only chat tab instead of falling through the structured-resume flow it can never satisfy. handleResumeInNewChat hands such a row to openAiVaultSessionHistoryChatForRow (toast on no active workspace, else the tab opens).
  • The tab reuses the agent-session content type with the session id as its entity, so the tab bar, persistence and close commands work unchanged; AiVaultSessionHistoryChatLayer (mirroring StructuredAgentSessionPaneOverlayLayer) paints it with AiVaultSessionHistoryChatView — the same live-session hook, message list, find bar and font-size handling the chat overlay uses, polling the ZCode SQLite store through nativeChat:subscribe. No PTY involved.
  • There is no composer: nothing sends into a conversation the CLI may still be holding.
  • The eligibility gates (resolveAiVaultSessionResumeInChatEligibility and the workspace resolver) answer from the history-chat branch alone — no host adoption, capability, or workspace matching — while keeping the remote and empty-content refusals. Reopening a row focuses its existing tab instead of duplicating it.

Before: a ZCode session opened in native chat reported "Transcript unavailable", and "Resume in chat" did nothing for it. After: "Resume in chat" opens a read-only chat tab rendering the full conversation with pagination, live tailing, and Cmd/Ctrl+F.

Why

The AI Vault scan gained ZCode support (discovery, listing, previews), but the chat read path was not wired to the same store, and the chat entry path only knew how to adopt a conversation into a host-registered structured session. Reusing the OpenCode SQLite reader is the natural fit — ZCode's schema is OpenCode v1 — and routing discovery through ZCode-specific paths first preserves the store separation the scanner already enforces. The alternative (teaching discovery to probe ZCode only when OpenCode misses) was rejected because an OpenCode install that shares a home would then win sessions it cannot own.

For the entry path, a tab over the agent's own store is the honest shape: a ZCode conversation has no Orca-side host to adopt into, so the read-only view is the whole conversation, not a fork of it. Mirroring the structured-pane layer keeps one retained-pane pattern per agent-session content type.

Linked Issue

Fixes #27130

Visual Proof

Before: opening a ZCode session in native chat shows the empty state ("No conversation preview available" / transcript unavailable); "Resume in chat" is a no-op. After: "Resume in chat" opens a read-only chat tab rendering the conversation (header shows agent · session id), and the structured-chat routes are untouched for Claude/Codex rows.

Reproduction used for verification: a real ~/.zcode/cli/db/db.sqlite (219 sessions / 20,899 messages), reading a 35-message session through the new signal + page queries (sub-millisecond on the real store), with the visibility filter confirmed against the live data distribution (visible 16,430 / hidden 1,588 / null 2,882).

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • src/shared/native-chat-agent-support.test.ts covers the new zcode mapping (zcode → supported, transcript agent opencode, identity stays zcode).

  • The four resume/launch gate suites (28 tests) pass unchanged — the history-chat branch is exercised by the same eligibility contracts.

  • Verified against a real production ZCode store on Windows 11: signal query 0–1 ms, page query returns the expected window, hidden rows excluded.

  • Existing native-chat suites (transcript-watch, transcript-opencode-subscribe, full native-chat renderer set — 3,677 tests) pass unchanged; all legs without the agent tag behave exactly as before.

  • Platform note: discovery covers the native home first and then WSL home dirs through the existing WSL reader chain, matching session-scanner-zcode-sources; no new platform-specific code paths. SSH/remote note: transcript reads go through the same execution-host rules as before — the read change only widens which local store an opencode-routed read may probe, and the new tab lives entirely in the renderer.

AI Disclosure

Authored with ZCode (GLM-5.3-flash) as the coding agent; every change reviewed and verified by a human on a real machine.

Review

Diff is 21 files, +495/−42 across the two commits: the transcript read path (13 files) and the chat-tab entry path (8 files, all renderer). No IPC surface changes (worker protocol gains an optional field — additive, backward compatible with an older worker; the tab reuses the existing agent-session content type and nativeChat:subscribe channel), no new dependencies.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no new inputs trusted — the agent tag is an internal enum, the SQL filter is a constant string, all user-derived values remain bound parameters.
  • Cross-platform: store path resolution uses os.homedir()/path.join; WSL reads reuse the existing gated access helpers.
  • Performance: the agent tag is a no-op for non-zcode agents (the filter concatenates to ''); the zcode part-count query gains a subquery only when tagged.
  • Backwards compatibility: every new parameter is optional; an older worker ignores it and behavior is unchanged.

Checklist

  • This PR is small and focused

…e 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.
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →Review in Change Stack →

📝 Walkthrough
📝 Walkthrough

Walkthrough

ZCode is supported as a native chat transcript agent and uses the OpenCode SQLite transcript reader. ZCode reads probe its native database path and matching WSL home paths. ZCode signal aggregates and transcript pages exclude messages marked hidden; missing or null visibility values are treated as visible. The agent selector is passed through transcript reads, worker requests, and subscriptions. Eligible AI Vault session history entries open in read-only chat tabs that render retained transcript content.



Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to 368fe

Opening a ZCode session in native chat can fail or load the wrong conversation, because full reads do not search the ZCode store. A slow WSL lookup can also block the native database. History tabs can open in the wrong worktree, and find cannot scroll to matches. Fix these before merging.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 41.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 21 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Issue #27130 requires native chat to read ZCode session transcripts through the OpenCode SQLite reader, use the ZCode store, and exclude hidden transcript rows. The PR maps zcode to opencode, prob…
Out of Scope Changes check Passed The changes stay within issue #27130. The renderer additions provide the required open and resume path for ZCode transcripts, and the main-process changes provide database discovery, filtering, pagina…
Title check Passed The title clearly and concisely describes the primary change: reading ZCode session transcripts through the OpenCode SQLite reader.
Description check Passed The description follows the required template and explains the user impact, implementation, rationale, linked issue, visual behavior, testing, compatibility, and review scope. The checklist is only pa…

  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: af1b5c72-2c2c-47ac-86f0-4c30314bf7d2
📥 Commits

Reviewing files that changed from the base of the PR and between 8ba3cf4 and 0e1e673.

📒 Files selected for processing (13)
  • src/main/ai-vault/session-scanner-opencode-sqlite-dispatch.ts
  • src/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.ts
  • src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts
  • src/main/native-chat/transcript-opencode-database.ts
  • src/main/native-chat/transcript-opencode-sqlite-query.ts
  • src/main/native-chat/transcript-opencode-subscribe.ts
  • src/main/native-chat/transcript-opencode.ts
  • src/main/native-chat/transcript-read-cache.ts
  • src/main/native-chat/transcript-reader.ts
  • src/main/native-chat/transcript-tail-reader.ts
  • src/main/native-chat/transcript-watch.ts
  • src/shared/native-chat-agent-support.test.ts
  • src/shared/native-chat-agent-support.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +79 to +80
const homes = await waitForPromiseWithSignal(getAiVaultWslHomeDirs(), boundedSignal)
const native = await findSession(zcodeTranscriptDatabasePaths([]))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Probe the native ZCode database before querying WSL homes.

If WSL-home discovery stalls, this await reaches the five-second discovery deadline before findSession checks a usable native ZCode database. The native transcript then fails to open even though it is available. Move getAiVaultWslHomeDirs() after the native probe, as the existing OpenCode branch does.

deps: OpenCodeTranscriptDeps = {},
signal?: AbortSignal
signal?: AbortSignal,
agent?: 'zcode'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Pass agent to full-read database discovery.

When agent is 'zcode', readOpenCodeNativeChatTranscriptFull passes the selector to page reads but not to resolveDbPath at Line 145. Discovery then searches OpenCode stores. A ZCode-only session returns “Transcript unavailable”; a matching session ID in an OpenCode store can select the wrong conversation. Pass agent as the third discovery argument. This also fixes the full reads called by src/main/native-chat/transcript-reader.ts and src/main/native-chat/transcript-read-cache.ts.

ZCode has no structured-session host adapter, so an Agent Session History
row could not reach a chat at all: the resume paths only knew how to adopt
a conversation into a host-registered structured session.

Rows whose agent reads its own store (zcode today) now open as a read-only
chat tab. The tab reuses the agent-session content type with the session id
as its entity, and AiVaultSessionHistoryChatLayer paints it with the same
live-session hook and message list the chat overlay uses — polling the
ZCode SQLite store through nativeChat:subscribe, no PTY involved. There is
no composer: nothing sends into a conversation the CLI may still be holding.

The eligibility gates answer from the history-chat branch alone — no host
adoption, capability, or workspace matching — while keeping the remote and
empty-content refusals, and reopening focuses the existing tab instead of
duplicating it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 24004019-3627-48cd-b15b-218a79d1d38f
📥 Commits

Reviewing files that changed from the base of the PR and between 0e1e673 and 368fe16.

📒 Files selected for processing (8)
  • src/renderer/src/components/WorkspacePaneOverlayLayers.tsx
  • src/renderer/src/components/native-chat/AiVaultSessionHistoryChatLayer.tsx
  • src/renderer/src/components/native-chat/AiVaultSessionHistoryChatView.tsx
  • src/renderer/src/components/right-sidebar/ai-vault-session-history-chat-open.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-history-chat.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-workspace.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

})
const { revealLatest } = useNativeChatRevealLatest()
const messageListRef = useRef<NativeChatMessageListHandle | null>(null)
useNativeChatMessageListHandle(messageListRef, revealLatest)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

cat src/renderer/src/components/native-chat/AiVaultSessionHistoryChatView.tsx
rg -n 'useNativeChatMessageListHandle' src/renderer

Repository: stablyai/orca

Length of output: 6406


🏁 Script executed:

printf '%s\n' '--- use-native-chat-reveal-latest.ts ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-reveal-latest.ts | sed -n '1,150p'
printf '%s\n' '--- NativeChatMessageList relevant region ---'
nl -ba src/renderer/src/components/native-chat/NativeChatMessageList.tsx | sed -n '285,335p'
printf '%s\n' '--- regular native chat view candidates ---'
rg -n -F --glob '*.tsx' -- 'useNativeChatFind' src/renderer/src/components/native-chat
rg -n -F --glob '*.tsx' -- 'messageListRef' src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 9132


🏁 Script executed:

printf '%s\n' '--- NativeChatResolvedView ref/find wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatResolvedView.tsx | sed -n '112,140p;320,395p'
printf '%s\n' '--- NativeChatStructuredSession ref/find wiring ---'
nl -ba src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx | sed -n '155,185p;260,285p'
printf '%s\n' '--- use-native-chat-find reveal call ---'
rg -n -F -- 'revealFindMatch' src/renderer/src/components/native-chat/use-native-chat-find.ts
nl -ba src/renderer/src/components/native-chat/use-native-chat-find.ts | sed -n '1,220p'

Repository: stablyai/orca

Length of output: 15870


Remove the parent message-list handle.

NativeChatMessageList already owns messageListRef and installs readerScrollInput.revealFindMatch. The extra useNativeChatMessageListHandle(messageListRef, revealLatest) replaces that handle with the default no-op revealFindMatch, so find matches cannot be revealed. Remove the extra hook and its unused useNativeChatRevealLatest call. Keep passing messageListRef to NativeChatMessageList and useNativeChatFind.

Suggested fix
-import {
-  useNativeChatMessageListHandle,
-  useNativeChatRevealLatest,
-  type NativeChatMessageListHandle
-} from './use-native-chat-reveal-latest'
+import type { NativeChatMessageListHandle } from './use-native-chat-reveal-latest'
...
-  const { revealLatest } = useNativeChatRevealLatest()
   const messageListRef = useRef<NativeChatMessageListHandle | null>(null)
-  useNativeChatMessageListHandle(messageListRef, revealLatest)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useNativeChatMessageListHandle(messageListRef, revealLatest)

}
export function openAiVaultSessionHistoryChatTab(session: AiVaultSession): Tab | null {
const state = useAppStore.getState()
const worktreeId = state.activeWorktreeId

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

rg -n 'targetWorktreeId|openAiVaultSessionHistoryChat' src/renderer/src/components/right-sidebar
sed -n '180,240p' src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts
cat src/renderer/src/components/right-sidebar/ai-vault-session-history-chat-open.ts

Repository: stablyai/orca

Length of output: 9266


🏁 Script executed:

nl -ba src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts | sed -n '190,225p'
printf '\n--- launch target ---\n'
nl -ba src/renderer/src/components/right-sidebar/ai-vault-session-launch-target.ts | sed -n '1,90p'
printf '\n--- resume workspace references ---\n'
rg -n -F --glob '*.ts' -- 'ai-vault-session-resume-in-chat-workspace' src/renderer/src/components/right-sidebar src/renderer/src
printf '\n--- resume workspace file candidates ---\n'
rg --files src/renderer/src | rg 'ai-vault-session-resume-in-chat-workspace'
for f in $(rg --files src/renderer/src | rg 'ai-vault-session-resume-in-chat-workspace'); do
  echo "--- $f"
  nl -ba "$f"
done
printf '\n--- relevant callers ---\n'
rg -n -F --glob '*.ts' -- 'handleResumeInNewChat' src/renderer/src

Repository: stablyai/orca

Length of output: 15800


Open the history tab in the selected worktree.

When targetWorktreeId is provided, pass it to the history-chat opener and use it for tab lookup and creation. Resume eligibility can select a session worktree that differs from the active worktree.

🐛 Suggested fix
-      if (openAiVaultSessionHistoryChatForRow(session)) {
+      if (openAiVaultSessionHistoryChatForRow(session, targetWorktreeId)) {
-export function openAiVaultSessionHistoryChatForRow(session: AiVaultSession): boolean {
+export function openAiVaultSessionHistoryChatForRow(
+  session: AiVaultSession,
+  targetWorktreeId?: string
+): boolean {
...
-  const tab = openAiVaultSessionHistoryChatTab(session)
+  const tab = openAiVaultSessionHistoryChatTab(session, targetWorktreeId)
...
-export function openAiVaultSessionHistoryChatTab(session: AiVaultSession): Tab | null {
+export function openAiVaultSessionHistoryChatTab(
+  session: AiVaultSession,
+  targetWorktreeId?: string
+): Tab | null {
   const state = useAppStore.getState()
-  const worktreeId = state.activeWorktreeId
+  const worktreeId = targetWorktreeId ?? state.activeWorktreeId

@nwparker

Copy link
Copy Markdown
Contributor

Closing in favor of #27142, which retains @star-nebula’s original authored reader commit and adds the fixes and verification from this review. The report and initial reader wiring are valid and credited.

Regression tests reproduced the discarded subscription session ID, missing ZCode selection on full reads, and local reads waiting on WSL discovery. Review also found insertion order could scramble imported conversations, and the renderer could undo recorded order. The replacement fixes those paths and verifies hidden rows, stable pagination, live updates, and rendered behavior.

The added read-only overlay was excluded: “Resume in chat” would open a view that cannot resume, ignore the requested target workspace, and bypass normal host routing. The replacement uses the existing terminal/chat flow. Its new hidden Electron test fails on main and passes twice on the published head; before/after proof is attached.

@nwparker nwparker closed this Oct 10, 2026
star-nebula added a commit to star-nebula/orca that referenced this pull request Oct 10, 2026
…d pass agent to full-read discovery

Address two CodeRabbit majors on stablyai#27131:
- zcode discovery awaited getAiVaultWslHomeDirs() before probing the
  usable native database, so slow WSL discovery could eat the whole
  5s deadline and fail an available native transcript; probe native
  first as the OpenCode branch does.
- readOpenCodeNativeChatTranscriptFull passed the agent selector to
  page reads but not resolveDbPath, so zcode full reads discovered
  against OpenCode stores: zcode-only sessions reported
  'Transcript unavailable' and a colliding session id could select
  the wrong conversation.
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.

[Bug]: Native chat cannot read ZCode session history (sessions list fine, transcript unreadable)

2 participants