feat: add OpenCode session history support - #27
Conversation
Add OpenCode (opencode.ai) as a fourth supported agent. OpenCode stores session data in a SQLite database at ~/.local/share/opencode/opencode.db rather than individual files, so this uses a separate DB-backed sync path. - Parser: opens the opencode DB read-only, queries project/session/ message/part tables, builds ParsedSession + ParsedMessage from parts (text, tool, reasoning types) - Sync engine: syncOpenCode() checks DB file mtime as change signal, per-session skip uses time_updated stored as file_mtime, virtual file paths use dbPath#sessionID for uniqueness - Config: OPENCODE_DIR env var, defaults to ~/.local/share/opencode - Frontend: purple agent dot color for opencode sessions - Tests: comprehensive parser tests with in-memory test DB Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- WAL-aware change detection: use composite fingerprint from both opencode.db and opencode.db-wal (mtime + size) so writes that live in the WAL file are not missed between checkpoints - Contiguous ordinals: use separate counter incremented only on append, not loop index, so skipped roles/empty messages don't create gaps - Log per-session errors: buildOpenCodeSession failures now logged with session ID instead of silently dropped - Tests: ordinal continuity test with mixed roles and empty content, sqliteFingerprint unit tests for WAL presence/modification/absence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass --model gemini-3-pro-preview to the gemini CLI when generating insights, and record the model name in the result. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract geminiInsightModel constant to avoid drift between the CLI flag and Result.Model. Add TestGenerateGemini_ModelFlag with a fake gemini binary that captures argv and verifies --model flag and Result.Model are both set correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for startup performance and OpenCode sync: 1. OpenCode parser queried non-existent `path` column instead of `worktree` in the project table, causing sync failures. 2. Non-interactive codex files (~7000) were re-parsed on every restart because the skip cache was in-memory only. Add a `skipped_files` table that persists skip decisions across restarts. The engine pre-populates its in-memory cache from the DB on startup, eliminating redundant parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The real OpenCode database stores role and part type inside JSON data columns, not as separate SQL columns. Rewrite the parser to extract these from JSON blobs and update all test fixtures. - Message: extract role from json_extract(data, '$.role') - Part: extract type from json_extract(data, '$.type') - Part query uses session_id directly (no JOIN through message) - Fix tool callID JSON tag (was "id", real data uses "callID") Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fragile sqliteFingerprint-based skip (which re-parsed all opencode sessions whenever the WAL file changed) with per-session time_updated comparison. Only sessions whose time_updated actually changed are re-parsed. Add timing logs to SyncAll phases (file workers, opencode sync, opencode writes, persist skip cache) so bottlenecks are visible. - Add ListOpenCodeSessionMeta for lightweight change detection - Remove sqliteFingerprint (and its test file) - syncOpenCode now queries session metadata first, compares each against stored file_mtime, and only parses changed sessions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for sync performance: 1. GetFileInfoByPath (and GetSessionFileInfo) returned ok=false when file_size was NULL, which is the case for all opencode sessions (virtual paths with size 0). This caused all opencode sessions to be fully re-parsed and FTS5-reindexed on every restart. Remove the NULL check — row existence is sufficient. 2. writeBatch now uses incremental append instead of full replace. Session files are append-only, so when re-syncing a session that already has messages in the DB, only new messages (ordinal > max stored ordinal) are inserted. This avoids the expensive FTS5 delete+reinsert cycle for existing messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Add OpenCode to all agent lists, supported agents table, and environment variable documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Two fixes from code review: 1. persistSkipCache now returns the snapshot size so callers log it without reading len(e.skipCache) unsynchronized. 2. SyncSingleSession and syncSingleOpenCode use a new writeSessionFull method that does ReplaceSessionMessages instead of incremental append. Explicit re-syncs need to rebuild all messages, not just append new ones. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Extract shared conversion helpers (toDBSession, toDBMessages) from writeBatch and writeSessionFull to eliminate duplication and reduce drift risk between the two write paths. Add TestSyncSingleSessionReplacesContent to verify that explicit re-syncs replace existing message content when ordinals are unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OpenCode messages can change in place (streaming updates, tool result pairing), so the incremental append optimization used for file-based agents is not safe. Use writeSessionFull for OpenCode sessions in the bulk SyncAll path, matching the behavior already used by SyncSingleSession. The incremental append remains for Claude/Codex/Gemini where session files are strictly append-only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Windows timer resolution (~15ms) can miss a 1ns deadline, causing the timeout handler to not fire before the handler completes. Use Microsecond instead — still too short for any real handler, but within Windows timer granularity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
The test used a sub-millisecond timeout expecting real handlers to always exceed it. On Windows (timer resolution ~15ms), fast empty-DB handlers could return before the timer fired, causing flaky 200-instead-of-503 failures. Use a 10ms timeout with a 100ms handler delay injected via withHandlerDelay test option, so the handler deterministically exceeds the deadline regardless of platform timer granularity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
## Summary - Add OpenCode (opencode.ai) as a fourth supported agent alongside Claude Code, Codex, and Gemini CLI - OpenCode stores sessions in a SQLite database (`~/.local/share/opencode/opencode.db`) with schema: `project` → `session` → `message` → `part`, where role and part type live inside JSON `data` columns - Parse sessions, messages, tool calls, and reasoning parts from the OpenCode DB into the same `ParsedSession`/`ParsedMessage` structures used by other agents - Add persistent skip cache (`skipped_files` table) so non-interactive sessions survive process restarts without re-parsing ~7000 codex files - Add per-session change detection for OpenCode using `time_updated` comparison instead of fragile WAL fingerprinting - Add incremental message sync (append-only) to avoid expensive FTS5 delete+reinsert when re-syncing large active sessions - Add sync telemetry (timing logs per phase) for diagnosing performance - Add `OPENCODE_DIR` env var and config, purple agent color in UI - Use `gemini-3-pro-preview` model for insight generation ## Test plan - [ ] `CGO_ENABLED=1 go test -tags fts5 ./...` — all tests pass - [ ] `golangci-lint run ./...` — no issues - [ ] Manual: `make build && ./agentsview` with OpenCode installed — sessions appear with correct project names, messages, tool calls - [ ] Verify second restart skips opencode sessions (0 updated) and file sync uses incremental append 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
~/.local/share/opencode/opencode.db) with schema:project→session→message→part, where role and part type live inside JSONdatacolumnsParsedSession/ParsedMessagestructures used by other agentsskipped_filestable) so non-interactive sessions survive process restarts without re-parsing ~7000 codex filestime_updatedcomparison instead of fragile WAL fingerprintingOPENCODE_DIRenv var and config, purple agent color in UIgemini-3-pro-previewmodel for insight generationTest plan
CGO_ENABLED=1 go test -tags fts5 ./...— all tests passgolangci-lint run ./...— no issuesmake build && ./agentsviewwith OpenCode installed — sessions appear with correct project names, messages, tool calls🤖 Generated with Claude Code