Repository navigation
feat(native-chat): read OpenCode transcripts from its SQLite session store - #15026
xodmd45-ctrl wants to merge 2 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdded OpenCode support to native chat through SQLite transcript reads. The change adds transcript part decoding, backward and forward pagination, row-based cursors, change signals, fingerprints, and worker-backed access. It routes OpenCode reads and polling through dedicated paths, handles missing or remote transcripts, and adds model catalogs, slash commands, profiles, and session support. Tests cover pagination, rendering, polling, error recovery, cursor consistency, and routing. Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: 🟡 Moderate · up to OpenCode transcript pagination can return mixed-source data or permanently omit large messages. These issues should be fixed before merge. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
src/shared/agent-session-option-catalog-opencode.ts (1)
5-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCondense the new rationale comments.
Keep the non-obvious rationale, but reduce each block to one concise sentence. The current blocks restate catalog behavior and implementation flow.
As per coding guidelines, “Comments must be concise, non-obvious, and brief—prefer one line; do not explain obvious behavior or walk through code.”
Also applies to: 17-22, 25-27, 42-53
Source: Coding guidelines
src/main/native-chat/transcript-opencode-part-blocks.ts (1)
104-115: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winKeep the tool error text in the result block.
When
state.outputis absent andstate.errorholds a message, the block is emitted withoutput: ''. The user then sees an errored tool with no reason. Use the error string as the output when no output exists.♻️ Proposed change
blocks.push({ type: 'tool-result', - output: typeof output === 'string' ? output : '', + output: + typeof output === 'string' + ? output + : typeof error === 'string' + ? error + : error != null + ? JSON.stringify(error) + : '', ...(error != null ? { isError: true } : {}) })src/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.ts (1)
88-121: 🚀 Performance & Scalability | 🔵 TrivialConsider queue priority for live chat reads on the shared client.
The shared client is single-flight FIFO. A vault scan can enqueue many
parsecalls at once. A live-chat signal poll or page read enqueued behind them waits for the whole burst, so transcript updates can lag during a scan. Options: give native-chat requests queue priority, or use a second client instance for native chat.src/main/native-chat/transcript-read-cache.test.ts (1)
20-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpread the original module in the mock factory.
The factory replaces every export of
./transcript-opencodewith one function. Today the graph pulls onlyreadOpenCodeNativeChatTranscriptFull, so it works. If a transitively imported module later needsreadOpenCodeNativeChatTranscriptTailorsubscribeOpenCodeNativeChatTranscript, the import fails.orchestration-worker-output.test.tsalready uses theimportOriginalpattern.♻️ Proposed change
-vi.mock('./transcript-opencode', () => ({ - readOpenCodeNativeChatTranscriptFull: vi.fn(async () => ({ error: 'opencode-routed' })) -})) +vi.mock('./transcript-opencode', async (importOriginal) => ({ + ...(await importOriginal<object>()), + readOpenCodeNativeChatTranscriptFull: vi.fn(async () => ({ error: 'opencode-routed' })) +}))src/main/native-chat/transcript-read-cache.ts (1)
105-112: 🚀 Performance & Scalability | 🔵 TrivialConsider a signal-keyed cache entry for OpenCode later.
Each
readNativeChatTranscriptCached('opencode', …)call now pages the whole session over the worker, so repeated reads pay full cost.readOpenCodeTranscriptSignalalready returns a cheap change fingerprint that could serve as the invalidation key in place of mtime. No change is required in this PR.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1da1a860-c242-4ed3-9619-76b609c9ae71
📥 Commits
Reviewing files that changed from the base of the PR and between 81d7f9b and 1c2feb74565c0d861e5163b9d68ba25550dfc7d6.
📒 Files selected for processing (31)
src/main/ai-vault/session-scanner-opencode-sqlite-native-chat-dispatch.tssrc/main/ai-vault/session-scanner-opencode-sqlite-worker-client.tssrc/main/ai-vault/session-scanner-opencode-sqlite-worker-entry.tssrc/main/ai-vault/session-scanner-opencode-sqlite-worker-protocol.tssrc/main/ai-vault/session-scanner-opencode-sqlite-worker-spawn.tssrc/main/ipc/native-chat.tssrc/main/native-chat/session-file-resolver.tssrc/main/native-chat/transcript-opencode-part-blocks.tssrc/main/native-chat/transcript-opencode-sqlite-query.test.tssrc/main/native-chat/transcript-opencode-sqlite-query.tssrc/main/native-chat/transcript-opencode.test.tssrc/main/native-chat/transcript-opencode.tssrc/main/native-chat/transcript-read-cache.test.tssrc/main/native-chat/transcript-read-cache.tssrc/main/native-chat/transcript-reader.tssrc/main/native-chat/transcript-tail-reader.tssrc/main/native-chat/transcript-watch-contract.tssrc/main/native-chat/transcript-watch.tssrc/main/runtime/orchestration/worker-transcript-read-opencode.tssrc/main/runtime/orchestration/worker-transcript-read.test.tssrc/main/runtime/orchestration/worker-transcript-read.tssrc/main/runtime/rpc/methods/orchestration-worker-output.test.tssrc/shared/agent-session-option-catalog-opencode.test.tssrc/shared/agent-session-option-catalog-opencode.tssrc/shared/agent-session-option-catalog.tssrc/shared/native-chat-agent-profiles.test.tssrc/shared/native-chat-agent-profiles.tssrc/shared/native-chat-agent-support.test.tssrc/shared/native-chat-agent-support.tssrc/shared/native-chat-slash-commands.test.tssrc/shared/native-chat-slash-commands.ts
Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.
|
Thanks for the thorough review — all four inline findings were real bugs and are fixed in ecdae70f:
Nitpicks: adopted the tool-error-as-output mapping, the Each fix ships with a regression test (first-page retry, window-overflow replace, cursor interleaving, tool-error output); Also — thanks for turning me on to a genuinely great PR reviewer. The signal-vs-page interleaving catch in particular was subtle and easy to miss. 🙇 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/native-chat/transcript-opencode.ts (1)
238-267: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winLatch
lastSignalonly after replacement succeeds.Line 240 stores the fingerprint before
replaceWithInitialWindowcompletes. If the replacement read returnsnullor throws, no replacement frame is emitted. The next poll sees the same fingerprint and skips the retry at Lines 220-223. The UI can then retain stale content or miss an overflow replacement until another transcript change occurs.Make
replaceWithInitialWindowreturn success. Keep the prior fingerprint when it fails. Add a regression test for a successful diff read followed by a failed replacement read.Proposed fix
- lastSignal = fingerprint if (firstSnapshot) { + lastSignal = fingerprint rememberItems(page.items) args.onInitialSnapshot?.( page.items.map((item) => item.message), @@ if (changed) { - await replaceWithInitialWindow(dbPath) + if (await replaceWithInitialWindow(dbPath)) { + lastSignal = fingerprint + } scheduleTick() return } @@ if (appended.length > 0 && appended.length === page.items.length && page.hasMore) { - await replaceWithInitialWindow(dbPath) + if (await replaceWithInitialWindow(dbPath)) { + lastSignal = fingerprint + } scheduleTick() return } + lastSignal = fingerprint
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2fa69022-99c8-46a7-9a69-275078b4e923
📥 Commits
Reviewing files that changed from the base of the PR and between 1c2feb74565c0d861e5163b9d68ba25550dfc7d6 and ecdae70f6b2ed05e5a843c127da7d686f54e01bd.
📒 Files selected for processing (9)
src/main/native-chat/transcript-opencode-part-blocks.tssrc/main/native-chat/transcript-opencode-sqlite-query.test.tssrc/main/native-chat/transcript-opencode.test.tssrc/main/native-chat/transcript-opencode.tssrc/main/native-chat/transcript-read-cache.test.tssrc/main/native-chat/transcript-tail-reader.tssrc/main/runtime/orchestration/worker-transcript-read-opencode.tssrc/main/runtime/orchestration/worker-transcript-read.test.tssrc/shared/agent-session-option-catalog-opencode.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/shared/agent-session-option-catalog-opencode.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c777fc4f-f939-485a-b6ab-2245063caf87
📥 Commits
Reviewing files that changed from the base of the PR and between ecdae70f6b2ed05e5a843c127da7d686f54e01bd and 514f7e01953687e60ef6f7d9cd2266f2c080619f.
📒 Files selected for processing (2)
src/main/native-chat/transcript-opencode.test.tssrc/main/native-chat/transcript-opencode.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.
|
any updates on this 👀 |
|
any updates? |
f38fcf1 to
764e064
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 64b224d4-a753-4635-8635-88c1183471ff
📥 Commits
Reviewing files that changed from the base of the PR and between f38fcf1d39ad958f35c60d0e7e57be12d8ebea14 and 764e0642076dd65bbd51c4f704c754c8449d2bd6.
📒 Files selected for processing (23)
src/main/ai-vault/session-scanner-opencode-sqlite-worker-client.tssrc/main/ipc/native-chat.tssrc/main/native-chat/agent-session-journal/journal-legacy-import.tssrc/main/native-chat/session-file-resolver.tssrc/main/native-chat/transcript-opencode-part-blocks.tssrc/main/native-chat/transcript-opencode.test.tssrc/main/native-chat/transcript-opencode.tssrc/main/native-chat/transcript-read-cache.test.tssrc/main/native-chat/transcript-read-cache.tssrc/main/native-chat/transcript-tail-reader.tssrc/main/native-chat/transcript-watch-contract.tssrc/main/native-chat/transcript-watch.test.tssrc/main/native-chat/transcript-watch.tssrc/main/runtime/orchestration/worker-transcript-read-opencode.tssrc/main/runtime/orchestration/worker-transcript-read.test.tssrc/main/runtime/orchestration/worker-transcript-read.tssrc/main/runtime/rpc/methods/orchestration/worker/worker-output.test.tssrc/shared/agent-session-option-catalog-opencode.tssrc/shared/native-chat-agent-profiles.test.tssrc/shared/native-chat-agent-profiles.tssrc/shared/native-chat-agent-support.tssrc/shared/native-chat-slash-commands.test.tssrc/shared/native-chat-slash-commands.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
|
Hey @xodmd45-ctrl — are you still iterating on this? The approach looks promising and it's the most recent one. Happy to help get it out of conflict + green CI, or take over if stalled. Coordinating on #9307 first: #9307 (comment) |
c457f4a to
9100b97
Compare
…store - DB discovery reuses listOpenCodeDatabases; SQLite worker dispatch extracted to its own module (file-size ratchet) - chronological (oldest-first) page ordering with overshoot-trim cursor - watcher diffs a bounded window; bridges poll-gap bursts and holds back instead of dropping rows past the cap - pending-settle parity for sessions that have not flushed yet (stablyai#8401) - orchestration cursor: rowid boundary-checkpoint, max-rowid rebuild guard, and dev/ino file-identity fingerprint for same-path replacements - remote/WSL opencode workers fall back to the terminal
…ipt reads - same-path replacement between the pre-read stat and the page read served the new DB's rows under the old fingerprint; both success branches now re-attest and report source_changed on mismatch - covered by 'reports source_changed when the DB file is replaced between the stat and the page read'; vitest 24/24, tsc node clean, oxlint/oxfmt clean (CodeRabbit 2026-09-13 stablyai#1)
9100b97 to
2bd3a4c
Compare
|
@justeozan I've sent you a collaborator invite to the fork (xodmd45-ctrl/orca) — once you accept, feel free to push |
ELI5
OpenCode keeps its chat history in a SQLite database instead of the JSONL files other agents use, so Orca's native Chat UI showed nothing for OpenCode sessions. This PR teaches Orca to read that database — on a background worker thread, with the same paging and live-update behavior Claude/Codex already have.
What Changed
transcript-opencode-sqlite-query.ts): rowid-cursored backward/forward pages that count the page limit in renderable messages (batching past step-start-only rows), overshoot-trim cursors that re-read trimmed messages instead of skipping them, and a cheap count/max change signal.transcript-opencode-part-blocks.ts): text/reasoning/tool parts, synthetic-text filtering, and file parts mapped to image refs (data:/http url, file:// decoded to a local path, plain path).transcript-opencode.ts): DB discovery reusinglistOpenCodeDatabases(honorsOPENCODE_DB/XDG env, siblingopencode-*.db, WSL gate) — no per-session file to resolve, so the mtime file cache is bypassed by design; tail/full/watch contracts implemented via signal polling (SQLite has no fs events).worker-transcript-read-opencode.ts): rowid-offset continuation mirroring the JSONL byte-offset semantics, incl.source_changedguards for rebuilt DBs; test deps namespaced per agent ({ opencode?: ... }) so the agent-generic signature stays generic.session-scanner-opencode-sqlite-native-chat-dispatch.ts): native-chat reads ride the existing shared SQLite worker client; the dispatch module keeps the client file under the max-lines cap without anyeslint-disable.NATIVE_CHAT_SUPPORTED_AGENTS, agent profiles, slash commands, and the session options catalog wire OpenCode the same way grok/cursor are wired.All five native-chat entry points (full reader, tail reader, cached read, watcher, orchestration worker read) route through the same
resolveNativeChatTranscriptAgent(agent) === 'opencode'guard, matching how claude/codex/grok/omp branch today.Why
Implements the desktop half of OpenCode native-chat support. OpenCode 1.17+ persists sessions in one
opencode.db(message/partrows with JSON payloads), so the JSONL decoders can never find a transcript. Riding the existing AI-Vault SQLite worker keepsnode:sqliteoff the main process and reuses its FIFO/timeout/respawn lifecycle.Linked Issue
Fixes #9307
Also covers most of #9308 (mobile eligibility flows through the same shared support set and main-process read path), though mobile renderer verification was not performed here.
Visual Proof
N/A — main-process transcript plumbing; no UI markup/styling changed. The user-visible effect (OpenCode sessions now render in the existing Chat UI) is covered by the transcript tests below.
Testing
New suites:
transcript-opencode.test.ts(discovery, tail/full/watch incl. poll-gap bridging, cap hold-back, pending settle, error recovery),transcript-opencode-sqlite-query.test.ts(paging cursors, overshoot-trim re-read, sparse walks, signal, part mapping incl. image/file parts),worker-transcript-read.test.tsopencode cases (initial window, continuation, pinned boundaries,source_changed, stat-failure identity, worker-failure mapping),agent-session-option-catalog-opencode.test.ts, plus opencode subscribe-routing cases intranscript-watch.test.ts.Updated:
transcript-read-cache.test.ts(opencode routing bypasses the file resolver),orchestration-worker-output.test.ts(DB discovery stubbed for hermeticity), shared registry suites.Branch rebased onto
origin/mainand squashed to a single commit. Gates:tsc --noEmit -p config/tsconfig.node.jsonclean; targeted vitest 79/79 across the three opencode suites; changed-code quality gate 0 new findings; max-lines ratchet OK; oxfmt clean on touched files.Blind review loop (three independent lanes — orchestration cursor, watcher/tail, test adequacy — five rounds): every must-fix triaged against the code and applied; the final round returned "no must-fix found" from all three lanes.
E2E (Windows, headless,
SKIP_BUILD=1on a--mode e2ebuild, no pnpm):native-chat-first-flush-race([Bug]: Native chat "No transcript found" on a just-created session that is actually alive #8401 pending-settle path) andorchestration-worker-transcript-providers(shared worker path) pass.native-chat-history-prepend-anchortimes out at the 120s per-test budget on this desktop (twice, sameLoad earlier messagesclick step) while the failure snapshot shows a correctly rendered transcript — it seeds a claude JSONL session and touches no code this PR changes (renderer untouched; shared reader diffs are opencode-gated branches plus a value-identical constant extraction), so this reads as a slow-runner timeout, not a regression. CI verdict pending.CodeRabbit review (2026-09-13): post-read DB identity attestation applied with test (
c457f4a); payload-bounding cursor comment declined for JSONL-reader parity (replies posted on both threads).I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Developed with AI (OhMyOpenCode orchestration): implementation, test authoring, and five rounds of blind AI code review (fresh reviewers, code-only, no design claims shared) with all valid must-fix findings applied.
Review
query_onlypragma); no injection surface (prepared statements only); capability tokens redacted in fallback terminal output (pre-existing, covered by test).transcriptPathdeliberately ignored (DB discovery is owned by the reader).Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)