chore: version 2.0.0-alpha.5 + plan-5 completion doc
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
32
CHANGELOG.md
32
CHANGELOG.md
@@ -3,6 +3,38 @@
|
||||
All notable changes to Void 2.0 are documented here.
|
||||
Format: [Keep a Changelog](https://keepachangelog.com).
|
||||
|
||||
## [2.0.0-alpha.5] — 2026-06-01
|
||||
|
||||
### Added (Plan 5: Companion chat)
|
||||
|
||||
- **Right-rail companion chat** — an always-visible, per-Space AI assistant.
|
||||
Label-led turns (YOU / Companion) with left/right alignment, live
|
||||
tool-activity chips, streamed answers (markdown via DOMPurify), and inline
|
||||
approve/reject draft cards. Loads its space's history on first paint via the
|
||||
`space-active` state event.
|
||||
- **Lean agent runtime** (`lib/ai/agent/runtime.js`) on the Anthropic SDK
|
||||
directly — no Mastra. `runTurn` drives a tool-use loop (max-iteration
|
||||
guarded), streams text deltas, and emits `tool` / `delta` / `draft` events.
|
||||
`callModel` is injectable (the SSE endpoint takes a fake in tests, so the
|
||||
suite never hits the network).
|
||||
- **Extensible shared tool registry** (`lib/ai/agent/registry.js`) with four
|
||||
v1 tools: `search` (hybrid FTS), `read`, `context` (resolves the active
|
||||
view), and `propose_change`. Adding a tool is a one-line `registerTool`;
|
||||
a future MCP server re-exposes the same defs.
|
||||
- **`propose_change` never applies** — it only writes a `pending_changes` row,
|
||||
capability-gated via `canAct` (default `suggest`). Prompt-injection
|
||||
containment is structural: a poisoned document can at most produce a draft
|
||||
the owner must approve. Drafts render inline in chat AND in the Inbox (same
|
||||
row; approving from either flips it).
|
||||
- **Companion API** — `GET /api/spaces/:id/companion` (history) and
|
||||
`POST /api/spaces/:id/companion/turn` (SSE). One ambient conversation per
|
||||
Space (`conversations.space_id` via migration 007); one assistant message
|
||||
per turn with the tool trace + draft ids in `metadata`.
|
||||
- **`@anthropic-ai/sdk`** dependency; key resolved via the `env:`/`file:`
|
||||
`vault_path` resolver (`lib/ai/secret.js`) — Vaultwarden swap still deferred.
|
||||
- Default model `claude-sonnet-4-6`, overridable per-agent (`agents.model`)
|
||||
and via `ANTHROPIC_MODEL` — the seam for scope-C local personas.
|
||||
|
||||
## [2.0.0-alpha.4] — 2026-06-01
|
||||
|
||||
### Added (Plan 4: Python void-workers)
|
||||
|
||||
54
docs/plan-5-complete.md
Normal file
54
docs/plan-5-complete.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Plan 5 — Complete
|
||||
|
||||
**Date:** 2026-06-01
|
||||
**Version:** 2.0.0-alpha.5
|
||||
**Tests:** full suite green (run cleanly) — companion adds tool/registry/runtime/api unit + integration tests; no network in tests (injected `callModel`).
|
||||
**Branch:** `plan5-companion-chat` (16 tasks, subagent-driven TDD).
|
||||
|
||||
## Scope delivered (scope B — knowledge assistant + drafting via approval)
|
||||
|
||||
### Agent runtime + tools
|
||||
- **`lib/ai/secret.js`** — `resolveSecret('env:KEY' | 'file:/path' | raw)`. The Anthropic key is configured this way; Vaultwarden swap is a pointer change later.
|
||||
- **`lib/ai/anthropic.js`** — `getAnthropicClient()` (key via resolver) + `makeCallModel({client,model})` returning a stable `callModel({system,messages,tools,onTextDelta}) -> {text,toolUses,stopReason,usage}`. Wraps `client.messages.stream()` + `finalMessage()` (verified against `@anthropic-ai/sdk@0.40.1`). `DEFAULT_MODEL=claude-sonnet-4-6`.
|
||||
- **`lib/ai/agent/registry.js`** — `createRegistry()` → `{registerTool,getTool,listTools,toAnthropicTools}`. Extensible; handlers stripped from the Anthropic schema.
|
||||
- **Four v1 tools** (`lib/ai/agent/tools/`): `search` (wraps `repos/search.js fts`), `read` (whitelisted table by kind), `context` (resolves the active view entity), `propose_change` (capability-gated draft → `pending_changes`, **never applies**). Wired in `tools/index.js` as `companionRegistry`.
|
||||
- **`lib/ai/agent/runtime.js`** — `runTurn(...)` tool-use loop. Streams `delta`s, runs tools via the registry, builds the correct Anthropic assistant/tool_result turn structure, collects draft ids, emits one `tool` event per call (`status` done/error) + `draft` events, and stops at `maxIterations` (guard).
|
||||
|
||||
### API + persistence
|
||||
- **Migration 007** — `conversations.space_id` (FK, indexed) + seeds the default `companion` agent (`read+suggest`, no write).
|
||||
- **`conversations.findOrCreateForSpace`** — one ambient open conversation per Space+agent.
|
||||
- **`lib/api/routes/companion.js`** — `GET /api/spaces/:id/companion` (history) and `POST …/companion/turn` (SSE). Persists the user message, runs `runTurn` with the **agent actor** (so drafts are suggest-tier), streams events as SSE frames, persists one assistant message with `{tool_trace, draft_ids, usage}` in metadata. `callModel` from `app.locals` in tests, real client otherwise. Mounted in `lib/api/index.js`.
|
||||
|
||||
### Frontend
|
||||
- **`public/sse.js`** — `streamTurn()` reads an authenticated POST→SSE stream (EventSource can't send the bearer token).
|
||||
- **`public/markdown.js`** — `renderMarkdown` = `DOMPurify.sanitize(marked.parse(...))` (reuses existing vendor bundles).
|
||||
- **`public/components/rightrail.js`** — the chat UI (approved label-led + left/right design). Loads history, streams turns, renders tool chips + inline draft cards (approve/reject → existing `/api/pending-changes/:id/{approve,reject}`, shared with the Inbox). Safe-DOM throughout; assistant markdown only via the sanitized `html:` path.
|
||||
- **`public/state.js` + `public/app.js`** — `state.spaceId`/`state.view` set in `renderView`, broadcast via a `space-active` event so the rail loads the right space on initial load, navigation, and between spaces (same-space re-renders are guarded to preserve the conversation).
|
||||
|
||||
## Security
|
||||
|
||||
- **`propose_change` never applies** — verified by review + test (target table stays empty; only a `pending_changes` row is written). Capability enforced via `canAct`; the route passes the **agent** actor (`kind:'agent'`), not the owner, so even allow-tier never auto-applies in v1.
|
||||
- **Prompt-injection containment is structural** — untrusted content can at most yield a draft requiring owner approval.
|
||||
- **XSS** — no unsanitized path to the DOM; user text is a text node, assistant markdown is DOMPurify-sanitized (review-confirmed).
|
||||
- **Cost guardrails** — per-turn `max_tokens` + `maxIterations` loop guard.
|
||||
|
||||
## Deviations from the plan (all reviewed)
|
||||
|
||||
- Plan test snippets used `../../lib/pool.js`; real path is `lib/db/pool.js` — corrected.
|
||||
- `pages` uses `body_md` (+ requires `slug`); `fts` returns `title_or_snippet` — tests adjusted.
|
||||
- A plan note string didn't match its own test regex (T7) — reconciled.
|
||||
- Runtime emits ONE `tool` event per call (not running+done) to match the test; the UI renders a chip per event accordingly.
|
||||
- Rail initial-load wired through the `space-active` state event (cleaner than a hashchange listener).
|
||||
|
||||
## Open items for the user
|
||||
|
||||
- **Live smoke + deploy (alpha-5).** Standing rule: no production deploy without explicit OK. Needs a real `ANTHROPIC_API_KEY` in `/opt/void-server/.env` on CT 311 (the `.216` box). Snapshot CT 310 + 311 first, then `TARGET=root@192.168.1.216 ./deploy/push.sh`, verify `/health` = alpha-5, and do the manual chat smoke (ask a question → tool chips + streamed answer; "create a task" → inline draft → approve → appears in `/api/tasks` and clears from Inbox).
|
||||
- **Rail accent colour** — the app's `--accent` is blackflame orange `#ff4f2e`, so the rail renders orange, not the mockup's violet. Add a `--rail-accent` if you want the purple.
|
||||
- **Branch** — `plan5-companion-chat` is ready to merge to `main` once you've signed off (use finishing-a-development-branch).
|
||||
|
||||
## What's left after Plan 5
|
||||
|
||||
- **Scope C** — multi-agent personas + per-persona local (Ollama) models via `agents.model`.
|
||||
- **MCP server** — re-expose `companionRegistry` to external agents.
|
||||
- **Plan 6** — Sacred Valley widgets ported from Void 1.x.
|
||||
- **Vaultwarden** — swap the env/file key for a vault item id.
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "void-server",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "2.0.0-alpha.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "void-server",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "2.0.0-alpha.5",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.40.1",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "void-server",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "2.0.0-alpha.5",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { registerWorkers } from './lib/jobs/index.js';
|
||||
import { router as ingestRouter } from './lib/api/routes/ingest.js';
|
||||
import { startCron } from './lib/cron/index.js';
|
||||
|
||||
const VERSION = '2.0.0-alpha.4';
|
||||
const VERSION = '2.0.0-alpha.5';
|
||||
|
||||
export function createApp() {
|
||||
const app = express();
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('server', () => {
|
||||
const res = await request(app).get('/health');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.db_ok).toBe(true);
|
||||
expect(res.body.version).toBe('2.0.0-alpha.4');
|
||||
expect(res.body.version).toBe('2.0.0-alpha.5');
|
||||
});
|
||||
|
||||
it('GET /api/spaces without token returns 401', async () => {
|
||||
|
||||
Reference in New Issue
Block a user