Files
kandev/docs/plans/plugins/task-03-state-store.md
T
Kandev Agent c53f699714 feat: add plugin system with grpc runtime and native ui plugins
- go plugin backends ship as release tarballs (per-platform binaries, checksums) that
  kandev installs from url/upload or filesystem sideload+sync, then spawns and
  supervises via hashicorp/go-plugin (unix socket / loopback tcp+automtls)
- strict typed grpc contract (kandev.plugin.v1): DeliverEvent/InvokeTool/HandleWebhook
  plus a capability-gated host service (state, secrets, emit); author sdk in
  pkg/pluginsdk; packaging cli in cmd/plugin-pack
- native frontend bundles: window.registerKandevPlugin + registry/host loader with
  dynamic routes, nav items, slots and ws handlers; plugins settings page with
  install dialog, sync button and enable/disable/uninstall
- gated behind the plugins feature flag (prod off, dev/e2e on); covered by unit and
  integration tests plus playwright e2e against a real packaged fixture plugin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:55:45 +01:00

2.0 KiB

id, title, status, wave, depends_on, plan
id title status wave depends_on plan
task-03 plugin_state SQLite store done 1
docs/plans/plugins/plan.md

plugin_state SQLite store

Title

SQLite-backed plugin-scoped KV state store following the per-package initSchema() convention.

Inputs

  • Spec docs/specs/plugins/spec.md → "plugin_state (SQLite)" and "Plugin state API". Table:
    CREATE TABLE plugin_state (
      id TEXT PRIMARY KEY, plugin_id TEXT NOT NULL,
      scope TEXT NOT NULL DEFAULT 'instance', scope_id TEXT,
      state_key TEXT NOT NULL, value_json TEXT NOT NULL, updated_at TEXT NOT NULL,
      UNIQUE (plugin_id, scope, scope_id, state_key));
    
  • Scopes: instance | workspace | task | agent. Plugins cannot read others' state (enforced by always filtering on plugin_id — the API layer passes it, task-09).
  • Pattern reference: internal/task/repository/sqlite/base_schema.go (initSchema with CREATE TABLE IF NOT EXISTS) and how repos take a *sql.DB/db handle. Use the same db handle type the other stores use (db.Pool writer/reader or *sql.DB — match sibling stores; inspect internal/sentry/store.go).

Acceptance

  1. StateStore with initSchema() creating plugin_state (idempotent).
  2. Methods: Get(ctx, pluginID, scope, scopeID, key) (json.RawMessage, bool, error), Set(ctx, pluginID, scope, scopeID, key, value) (upsert on the UNIQUE key), Delete(ctx, pluginID, scope, scopeID, key), List(ctx, pluginID, scope, scopeID) ([]StateEntry, error).
  3. updated_at set to RFC3339 UTC on write. NULL scope_id handled for instance scope.

Files

  • apps/backend/internal/plugins/state/store.go
  • apps/backend/internal/plugins/state/store_test.go (in-memory or temp-file sqlite)

Verification

  • go test ./internal/plugins/state/... from apps/backend
  • make -C apps/backend lint

Output contract

Report: db handle type chosen (match siblings), upsert approach, scope_id NULL handling. Stay within internal/plugins/state/.

Dependencies

None.