Commit Graph

68 Commits

Author SHA1 Message Date
root
1c03d6c277 feat(mcp): stdio MCP server exposing the four companion tools
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:45:42 +10:00
root
df03286415 feat(api): companion SSE turn endpoint + per-Space history
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:14:44 +10:00
root
d593234904 feat(ai): agent runtime tool-use loop with event streaming
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:30:46 +10:00
root
09a27e8495 feat(ai): Anthropic client + streaming callModel adapter
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:24:59 +10:00
root
02e2a633f0 feat(ai): wire the four v1 companion tools into a shared registry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:18:44 +10:00
root
7282729654 feat(ai): propose_change tool — drafts to pending_changes, never applies
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:16:53 +10:00
root
2e121ce6d4 feat(ai): context tool — resolve the active view entity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:14:42 +10:00
root
d80c550d2e feat(ai): search + read grounding tools
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:12:03 +10:00
root
de4b6a8403 feat(ai): extensible agent tool registry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:10:11 +10:00
root
f80fd278a5 feat(db): conversations.findOrCreateForSpace for the ambient companion
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:08:34 +10:00
root
cf0510872e feat(db): migration 007 — conversations.space_id + seed companion agent
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:06:34 +10:00
root
d0d61575e3 feat(ai): vault_path secret resolver (env:/file:)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:04:21 +10:00
root
13fac102dd feat(cron): daily sync.source_doc enqueue
node-cron schedules runSync at 03:00 local time; runSync enqueues
sync.source_doc for every source_docs row with sync_source='url'.
Started from server.js's CLI gate alongside the job queue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 10:14:07 +10:00
root
b10b68582d feat(api): capture routes YouTube/Vimeo URLs to ingest.video
POST /api/capture with a youtube.com / youtu.be / vimeo.com URL
enqueues ingest.video (Python worker) instead of ingest.url
(Node worker). Detection by URL hostname; idempotency_key + response
shape unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 10:08:16 +10:00
root
3d82f0e5d5 feat(jobs): blob worker fans out to extract.pdf / extract.image
After creating a ref, the Node-side ingest.blob worker enqueues a
follow-up job for the Python void-workers (Plan 4) to OCR / extract
text. Other kinds (file) get no follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 09:34:06 +10:00
root
24ce601d94 fix(ingest): pinnedDispatcher lookup must use undici array form
cb(null, address, family) was returning Invalid IP address: undefined
under undici v6. Returning the full records array (each {address, family})
gives undici what it expects and lets it pick the best family.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 04:10:47 +10:00
root
d7f9bde5e9 feat(api): karakeep webhook (HMAC-verified)
POST /api/ingest/karakeep accepts Karakeep webhook payloads. HMAC
signature on the raw body captured by express.json's verify hook.
Mounted on app before mountApi so it bypasses agentOrOwner — the
shared secret IS the auth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:55:57 +10:00
root
d1e986bc9c feat(jobs): ingest.karakeep worker
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:55:03 +10:00
root
de1d7e3476 feat(karakeep): bookmark fetch client
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:54:21 +10:00
root
f116811dda feat(search): hybrid FTS + vector with RRF + graceful Ollama fallback
Replaces FTS-only /api/search in place. RRF (k=60) fuses ts_rank and
pgvector cosine distance rankings. Vector branch silently skipped when
Ollama times out / errors, keeping search snappy and resilient.

Messages have no embeddings in Plan 3, so they participate in the FTS
branch only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:50:33 +10:00
root
99ab1ffb70 fix(ingest): pin resolved IP into safe_fetch to defeat DNS-rebinding
Replaces the validate-then-call-fetch pattern (which left a TOCTOU
window where the OS resolver could return a different IP at connect
time) with an undici Agent dispatcher whose lookup() returns the IP we
already validated. Same hardening on every redirect hop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:48:52 +10:00
root
e558be49a9 feat(jobs): repo-level embed triggers (pages/refs/source_docs)
create/update on embeddable repos enqueue embed.text with a singleton
key that coalesces rapid edits. No-op when the queue is not running
(server tests construct createApp without booting pg-boss).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:48:03 +10:00
root
37b7753360 feat(jobs): embed.text worker (Ollama → vector(1024))
Pads nomic-embed-text's 768 dims to 1024 zeros so a later 1024-dim model
swap is a re-embed, not a migration (per master spec).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:43:57 +10:00
root
5799ea663e feat(ai): ollama embed-text wrapper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:43:27 +10:00
root
afc20712cb feat(api): capture POST + upload + SSRF-safe URL fetch
safe_fetch.js validates URLs before fetch: rejects non-http(s), literal
or DNS-resolved loopback / RFC1918 / link-local / CGNAT / metadata
addresses; follows redirects manually with the same checks on each hop.
Test fixtures gate the check with VOID_INGEST_ALLOW_PRIVATE for offline
fixtures that hit 127.0.0.1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:42:54 +10:00
root
eceebd2947 feat(jobs): ingest.blob worker (content-addressed)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:36:15 +10:00
root
3ccfd20b5f feat(jobs): ingest.url worker (fetch + readability + idempotent ref)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:35:44 +10:00
root
6e973404e9 feat(ingest): content-addressed blob store
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:35:06 +10:00
root
c6e72e93d5 feat(ingest): readability wrapper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:34:51 +10:00
root
ec8517a82c feat(api): jobs routes (list/get/retry/delete, owner-only)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:29:52 +10:00
root
57efa4cbaa feat(jobs): jobs repo (list/getById/retry/remove)
Unifies pgboss.job (current, per-queue partitioned) and pgboss.archive
under one SELECT for operator views. retry promotes archived rows back
into the active partition.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:29:03 +10:00
root
53ffd705c4 feat(jobs): echo worker + CLI bootstrap
Job queue starts only in the CLI gate (not inside createApp), so tests
manage their own queue lifecycle. waitForJob() takes a (name, id) pair
to match pg-boss v10's getJobById signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:28:06 +10:00
root
17a13dddb8 feat(jobs): pg-boss singleton client
Per-name ensureQueue promise dedup so concurrent enqueue+subscribe
on the same queue do not race createQueue (Postgres deadlock).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 03:26:37 +10:00
root
69e26ada98 feat(api): unified FTS search
Single GET /api/search?q=&space_id=&kinds=&limit=&offset= unions FTS
hits across pages / refs / source_docs / messages with a `kind`
discriminator and ts_rank ordering. Each branch's to_tsvector matches
the GIN index expression on its source table so indexes are used.
Messages have no space_id and are excluded when a space filter is set.
Hybrid vector / RRF lands in Plan 3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 02:04:57 +10:00
root
ec96e4e2e3 feat(api): pending-changes + audit routes
Owner-only routes wired with an applyPendingChange dispatch helper
covering page/project/task/ref/resource/source_doc create/update/delete.
Approve and reject emit their own audit_log entries (actions already in
the CHECK vocab) so the audit trail is self-contained.

Documents a latent bug in security-followups.md: pending_changes.action
CHECK constraint blocks 'upsert' / 'add_dependency' / 'remove_dependency'
divertToPending paths in refs/resources routes when an agent at suggest
tier hits them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 02:01:10 +10:00
root
5aa6fe772d feat(api): links routes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:50:50 +10:00
root
2eb499c56f feat(api): tags routes
Add lib/api/routes/tags.js: list + upsert at /api/tags, and an
entity-scoped router mounted at /api/:entity_type/:entity_id/tags
for attach (idempotent), list, and detach. entity_type is bounded by
a zod enum covering space/project/task/page/ref/resource/source_doc/
conversation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:46:43 +10:00
root
74dac905d3 feat(api): conversations + messages routes
Add conversations CRUD-lite (list, create, get, PATCH status, PATCH
summary which flips status to summarized) and conversation-scoped
messages (append, list ordered by created_at).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:07:36 +10:00
root
5437b68316 feat(api): agents routes + token mgmt (owner-only)
Add lib/api/routes/agents.js: list/create/get, PATCH capabilities,
mint token (plaintext returned exactly once, then bcrypt-hashed),
revoke token. All endpoints gated by requireOwner so an agent token
can never bootstrap a new agent or grant itself capabilities.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:05:42 +10:00
root
56805053f0 feat(api): capability enforcement on writes
Add lib/api/cap.js: requireWrite(entity_type) maps HTTP method to
action, runs canAct, and tags req.capTier as allow|suggest|deny→403.
Mutating routes (pages, projects, tasks, refs, resources, source_docs)
now check req.capTier and either run the repo (allow) or divert to
pending_changes returning 202 (suggest). Owner and worker actors stay
on the allow path. requireOwner helper added for Task 11.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:03:52 +10:00
root
7862d22a03 feat(api): agent bearer auth middleware
Add lib/api/middleware/agent_auth.js: agentOrOwner accepts the owner
token (kind=user actor) or a hashed agent token (kind=agent actor
carrying capabilities + scopes). /api router now mounts this in place
of ownerOnly so agent tokens become first-class.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:59:29 +10:00
root
ba782ed690 feat(api): source-docs routes
Add lib/api/routes/source_docs.js: scoped POST under a resource,
get/patch/delete by id, and POST /:id/resync as a Plan 3 stub gated
by ENABLE_RESYNC (503 by default, 202 once workers ship). upstream_url
is required by the DB so the zod schema enforces it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:56:00 +10:00
root
a93e3ca20e feat(api): resources routes + dependencies + change history
Add lib/api/routes/resources.js: CRUD scoped to space; dependency
add/list/remove (cross-space attempts mapped to 409 conflict via the
composite FK); source-docs index per resource; change history via
audit.listForEntity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:53:10 +10:00
root
78f2e09c74 feat(api): refs routes
Add lib/api/routes/refs.js: list with space_id/kind filters and
paginated, create, get/patch/delete by id, and /upsert that maps to
repo.upsertByExternal for idempotent capture-source ingest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:50:38 +10:00
root
cf429da534 feat(api): pages routes + revisions + backlinks
Add lib/api/routes/pages.js: list by space, create/get/patch/delete,
get-by-slug, list revisions, and backlinks via entity_links.listTo
enriched with the source entity's title (whitelisted entity_type set
to keep the dynamic-table SELECT bounded).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:48:58 +10:00
root
50649bea5f feat(api): tasks routes
Add lib/api/routes/tasks.js: list by space (status filter), list by
project (position then created_at), create scoped to space with
optional project_id, get/patch/delete by id. status=done flips
completed_at via the repo's existing trigger logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:46:39 +10:00
root
1208b3bd40 fix(api): drop err.message from 500 response body (CWE-209)
Catch-all error handlers in lib/api/errors.js and server.js were
echoing raw err.message to clients. Replace with a fixed generic
message; the full error continues to be logged server-side via pino.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:45:08 +10:00
root
beb6da21c8 feat(api): projects routes
Add lib/api/routes/projects.js: list by space (with status filter),
create scoped to space, get/patch/delete by id. FK violation from
unknown space_id maps to 400 invalid_space.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:39:31 +10:00
root
ebb1e836ca feat(api): spaces routes
Add lib/api/routes/spaces.js: list, create, get-by-id, get-by-slug,
patch, delete. Mounted under /api/spaces. Server smoke restored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:38:23 +10:00
root
75afedaef0 feat(api): error + validate + pagination plumbing
Add lib/api/{errors,validate,pagination,index}.js: typed ApiError
subclasses, errorMiddleware, zod-backed validate(), parsePagination
with caps, and a mountApi() that owns /api routing + 404 + error tail.
server.js delegates /api to mountApi and drops the inline /api/spaces
smoke (returns in Task 2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 16:37:06 +10:00