277677f330
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
164 lines
7.3 KiB
Markdown
164 lines
7.3 KiB
Markdown
---
|
|
spec: docs/specs/plugins/requirements/plugins.md
|
|
adr: docs/decisions/0043-plugin-host-data-api.md
|
|
created: 2026-07-17
|
|
status: implemented
|
|
---
|
|
|
|
# Implementation Plan: Plugin Host data API
|
|
|
|
## Overview
|
|
|
|
Give plugins a typed, capability-gated path to read and write kandev domain data
|
|
over the existing `kandev.plugin.v1` Host gRPC channel, replacing direct SQLite
|
|
access. Land the contract first (proto + regen) and the one net-new aggregation
|
|
(per-session LOC), then the SDK's Go-native accessors, then kandev's server-side
|
|
implementation, and finally the proof (rewrite the agent-stats plugin) plus tests
|
|
and docs. The implemented write surface is `CreateTask`, `UpdateTask`, and
|
|
`SendMessage`; workflow transitions use `MoveTask`.
|
|
|
|
The draft contract at `docs/plans/plugins/HOST-DATA-API.proto` is largely settled
|
|
— refine field-level details against the real DTOs (`apps/backend/pkg/api/v1/`),
|
|
do not redesign.
|
|
|
|
---
|
|
|
|
## Backend
|
|
|
|
### Proto contract (`apps/backend/proto/kandev/plugin/v1/plugin.proto`)
|
|
Fold the read and write RPCs and DTOs from
|
|
`docs/plans/plugins/HOST-DATA-API.proto` into the frozen plugin proto. Decide
|
|
placement: add the RPCs to the existing `service Host` (simplest — reuses the
|
|
single Host broker connection and the existing capability interceptor) rather
|
|
than a separate `service HostData`, unless a separate service is needed to keep
|
|
the interceptor's per-RPC capability lookup clean. Regenerate stubs with
|
|
`make -C apps/backend proto`. Read RPCs: `ListTasks`, `GetTask`, `ListWorkspaces`,
|
|
`ListWorkflows`, `ListWorkflowSteps`, `ListAgentProfiles`, `ListRepositories`,
|
|
`ListSessions`, `ListSessionCodeStats`. DTOs mirror
|
|
`apps/backend/pkg/api/v1/{task,workflow,agent,workspace}.go` field-for-field with
|
|
RFC3339 string timestamps and `optional` nullables.
|
|
|
|
### Per-session LOC aggregation (`internal/analytics/repository/sqlite/`)
|
|
No existing method computes per-session committed + peak-pending LOC.
|
|
`internal/analytics/repository/sqlite/stats.go` aggregates git stats at workspace
|
|
and per-repository granularity only. Add a read-only aggregation returning, per
|
|
session id: `SUM(insertions)` / `SUM(deletions)` from `task_session_commits`, and
|
|
the peak pending-diff (`MAX` over each snapshot's `SUM(json_each(files).additions
|
|
/ .deletions)`) from `task_session_git_snapshots`. This mirrors the SQL the
|
|
agent-stats plugin runs today (`stats.go:sessionsQuery`). Use the `dialect`
|
|
helpers for SQLite/Postgres portability; filter by session-id set / workspace and
|
|
support pagination bounds. Expose it through the analytics (or task) service so
|
|
the Host handler calls a service method, not the repository.
|
|
|
|
### Host data RPC implementation (`internal/plugins/`)
|
|
Extend `pluginHost` (or add a sibling under `internal/plugins/`) to implement the
|
|
new Host data interface methods. Each read handler:
|
|
1. checks `manifest.CanRead("<resource>")` — reuse the existing per-plugin
|
|
binding and `permissionDenied("api_read:<resource>")` helper shape;
|
|
2. calls the relevant service method (`taskSvc` for tasks/workspaces/repositories/
|
|
sessions, workflow service for workflows/steps, agent settings for profiles,
|
|
the new aggregation for code stats) — never a repository directly;
|
|
3. maps internal models → the Go-native DTOs with explicit code.
|
|
Wire the new service dependencies into `plugins.Service` /
|
|
`plugins.NewService` / `Provide`, and thread them into `hostForPlugin`. The
|
|
implemented writes route through the task service and orchestrator delivery
|
|
path.
|
|
|
|
---
|
|
|
|
## Frontend
|
|
|
|
None. This feature has no SPA surface; it is a plugin-facing gRPC contract.
|
|
|
|
---
|
|
|
|
## SDK (`apps/backend/pkg/pluginsdk/`)
|
|
|
|
Extend the Go-native Host surface authors and kandev share:
|
|
- Add Go-native DTO structs (`Task`, `Session`, `SessionCodeStats`, `Workspace`,
|
|
`Workflow`, `WorkflowStep`, `AgentProfile`, `Repository`, page types) mirroring
|
|
the proto, with `toProto`/`fromProto` helpers alongside the existing ones in
|
|
`types.go`.
|
|
- Add data methods to the `Host` interface (or a `HostData` sub-interface exposed
|
|
via `host.Tasks()`, `host.Sessions()`, ...), with `grpcHostClient` (plugin-side)
|
|
and `grpcHostServer` (kandev-side) conversions in `host.go`.
|
|
- Author-facing ergonomics: typed accessors returning Go-native structs, e.g.
|
|
`host.Tasks().List(ctx, filter, page)`, `host.Sessions().List(...)`,
|
|
`host.Sessions().CodeStats(...)`.
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
- **DTO mapping (`internal/plugins/*_test.go`):** table-driven — internal model →
|
|
DTO for each resource, including nullable/optional and timestamp formatting.
|
|
- **Capability gating (`internal/plugins/host_test.go`):** each read RPC returns
|
|
`PermissionDenied` with `capability 'api_read:<resource>' not declared` when the
|
|
manifest omits the resource, and succeeds when it declares it. Write RPCs
|
|
enforce their `api_write` capabilities and return their task/message results.
|
|
- **Per-session LOC aggregation (`internal/analytics/repository/sqlite/*_test.go`):**
|
|
real SQLite fixture with commits + snapshots; assert committed sums and the
|
|
peak-pending `MAX` semantics (peak, not latest; not double-counted).
|
|
- **SDK conversions (`pkg/pluginsdk/*_test.go`):** proto↔native round-trip for the
|
|
new DTOs.
|
|
- **Integration:** a plugin (via the SDK Host client over the broker) calls
|
|
`ListSessions` + `ListSessionCodeStats` and gets rows without DB access — extend
|
|
the existing `internal/plugins/host_test.go` / integration harness.
|
|
|
|
---
|
|
|
|
## E2E Tests
|
|
|
|
None. No user-visible UI surface. The integration test above covers the plugin↔host
|
|
path.
|
|
|
|
---
|
|
|
|
## Implementation Waves
|
|
|
|
```
|
|
Wave 1 (parallel):
|
|
- [x] [task-01-proto-contract](task-01-proto-contract.md)
|
|
- [x] [task-02-session-loc-aggregation](task-02-session-loc-aggregation.md)
|
|
|
|
Wave 2:
|
|
- [x] [task-03-sdk-data-accessors](task-03-sdk-data-accessors.md)
|
|
|
|
Wave 3:
|
|
- [x] [task-04-host-data-impl](task-04-host-data-impl.md)
|
|
|
|
Wave 4 (parallel):
|
|
- [x] [task-05-rewrite-agent-stats-plugin](task-05-rewrite-agent-stats-plugin.md)
|
|
- [x] [task-06-tests](task-06-tests.md)
|
|
- [x] [task-07-docs](task-07-docs.md)
|
|
```
|
|
|
|
Rationale: task-01 (contract) and task-02 (aggregation) touch disjoint packages
|
|
and can run in parallel. The SDK (task-03) needs generated stubs from task-01. The
|
|
kandev impl (task-04) needs the stubs (task-01), the aggregation (task-02), and
|
|
the Go-native interface/DTOs (task-03). Wave 4 tasks touch disjoint trees (a
|
|
separate plugin repo, backend tests, public docs) and parallelize.
|
|
|
|
---
|
|
|
|
## Open Questions
|
|
|
|
- **Proto placement:** RPCs on the existing `service Host` vs a new
|
|
`service HostData` served on the same broker connection. Recommendation: extend
|
|
`service Host` to reuse the existing capability interceptor and single broker
|
|
wiring; revisit only if the interceptor's per-RPC capability mapping gets
|
|
unwieldy. Settle in task-01.
|
|
- **Aggregation home:** `internal/analytics` (where git stats already live) vs a
|
|
new method on the task repository's `git_snapshots.go`. Recommendation:
|
|
analytics repository, consistent with `GetGitStats`/`GetRepositoryStats`.
|
|
Settle in task-02.
|
|
|
|
## Related follow-on work
|
|
|
|
Exposing task dependencies on this same read model is tracked as its own plan,
|
|
[`docs/plans/plugin-task-dependency-projection/`](../plugin-task-dependency-projection/plan.md),
|
|
kept separate because this plan's directory nests an extra path segment
|
|
(`plugins/host-data-api`) that later tooling expects a single-segment
|
|
initiative directory to not have.
|
|
</content>
|