Skip to content

feat: add Postgres sync plugin#1

Merged
sjawhar merged 15 commits intomainfrom
feat/postgres-sync-plugin
Apr 7, 2026
Merged

feat: add Postgres sync plugin#1
sjawhar merged 15 commits intomainfrom
feat/postgres-sync-plugin

Conversation

@sjawhar
Copy link
Copy Markdown
Owner

@sjawhar sjawhar commented Apr 4, 2026

Summary

  • add a standalone Postgres sync plugin for OpenCode
  • mirror session/message/part/todo/event data into Postgres
  • support resumable checkpoints and on-demand remote session pull
  • add CI for typecheck and tests

Why

This keeps Postgres sync and cross-machine resume logic out of the OpenCode monorepo while still validating the distributed workflow as an external plugin.

@sjawhar
Copy link
Copy Markdown
Owner Author

sjawhar commented Apr 4, 2026

Review guide for this PR:

This PR contains the external Postgres sync / cross-machine resume implementation. The paired OpenCode core-hook PR is anomalyco/opencode#21001.

Please focus review on:

  1. Replication correctness — event/materialized row flow into Postgres
  2. Restore semantics — normalization of unfinished tool state on pull/restore
  3. Checkpoint model — only marking sessions resumable at safe idle points
  4. Operational shape — metadata sync, on-demand pull, and query/search tooling

Fresh verification on this repo:

  • bun run typecheck
  • bun test
  • CI workflow test

Real distributed smoke was exercised against current host + sami-claude + sami + ghost-wispr, with sami-claude also proven to write resumed-session messages back into shared Postgres.

@sjawhar sjawhar force-pushed the feat/postgres-sync-plugin branch 3 times, most recently from e4109e4 to 73da380 Compare April 4, 2026 15:21
@sjawhar sjawhar force-pushed the feat/postgres-sync-plugin branch 3 times, most recently from 62df0a1 to c5f39da Compare April 5, 2026 14:16
sjawhar added 2 commits April 5, 2026 18:20
…ooks

- Add replayBus()/routeBus() to projectors.ts for bus event shape { type, properties }
- Rewrite index.ts: route all events through hooks.event() → replayBus()
- Inline consumer methods (todo/metadata/ensure/status/checkpoint) from local.ts
- Add 30s metadata sync timer with .unref()
- Remove consumer.ts (SSE stream loop, open(), reconnection logic)
- Remove serverUrl/env var config (OPENCODE_SHARED_DB, OPENCODE_SYNC_MACHINE)
- Plugin config now exclusively via PluginOptions (options.url, options.machine)
- Keep old replay() for backfill compatibility (reads local SQLite EventTable)
- 22 tests pass, typecheck clean
…fix test mock

- Add void backfill(sql, machine) fire-and-forget call on plugin init
- Collapse meta/tick duplicate functions into single sync()
- Fix index.check.ts mock: add backfill.js mock, add info() to log mock
- All 22 tests pass, typecheck clean, format clean
@sjawhar sjawhar force-pushed the feat/postgres-sync-plugin branch from c5f39da to 0be6308 Compare April 5, 2026 18:31
sjawhar added 13 commits April 5, 2026 18:32
… inserts

- Accept options.db (SQLite path) and options.backfill (max days) from plugin config
- Remove globalDb() auto-discovery — never guess which database to use
- backfill=0 skips backfill, backfill=-1 (default) backfills everything
- Filter sessions by time_created when maxDays > 0
- Scope messages/parts/todos/events to filtered session IDs
- Insert projects individually with try/catch so one failure doesn't block others
- 24 tests pass, typecheck clean
Ablation tested against 48GB database (22K sessions, 20K events):
- root_session_id: recursive parent_id CTE is same speed (73ms vs 80ms)
- event.origin: never read from Postgres, only written. Removed.
- origin_machine: caused 2s full scans on hot paths. Remote sessions
  now detected by diffing Postgres IDs against local SQLite IDs (18ms).

All three columns removed from Postgres schema, projectors, backfill,
and local sync. parent_id is the canonical tree relationship.
- Set name to @sjawhar/opencode-postgres-sync
- Set main to dist/index.js, add types and files fields
- Remove private: true
- Add prepublishOnly build step
…t insert (#4)

- Module-level singleton pool keyed by URL — drops from N connections (one per
  directory) to a single shared pool with max 3 connections
- ensureMessage stub before part insert — prevents FK violation when part event
  arrives before its parent message
- Published as @sjawhar/opencode-postgres-sync@0.1.2
…-machine resume

All SQLite opens now set PRAGMA busy_timeout = 5000 (5s wait before SQLITE_BUSY).
Fixes pullSession failing with 'database is locked' when multiple processes access
the 51GB global DB. Published as 0.1.3.
…Db() auto-discovery

Root cause of cross-machine resume failure: globalDb() picked opencode-.db (dev binary)
while OpenCode uses opencode.db (release binary). pullSession wrote to the wrong file.

Now all local.ts functions accept the db path from config. No auto-discovery.
Published as 0.1.5.
Plugin fetches /global/health to get the version string, parses the channel
(e.g., 1.3.13-sami.xxx → channel 'sami' → opencode-sami.db), and computes
the correct DB path. No config field needed for any build.

The db option still works as an override but is no longer required.
Published as 0.1.6.
- Check OPENCODE_DISABLE_CHANNEL_DB env var
- Handle empty channel (opencode-.db)
- Handle beta channel (opencode.db, not opencode-beta.db)
- Sanitize channel same as core: [^a-zA-Z0-9._-] → '-'
- Published as 0.1.7
- Runs CI (typecheck, format, test) first via workflow_call
- Publishes to npm with OIDC auth (id-token: write, no NPM_TOKEN secret)
- Only publishes when package.json version differs from npm registry
- Added workflow_call trigger to ci.yml for reuse
@sjawhar sjawhar force-pushed the feat/postgres-sync-plugin branch from fbcbaf0 to 9fc4baf Compare April 7, 2026 20:16
@sjawhar sjawhar merged commit efc0436 into main Apr 7, 2026
3 checks passed
@sjawhar sjawhar deleted the feat/postgres-sync-plugin branch April 7, 2026 20:38
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.

1 participant