From a9a4ccb9f2abec000fae17c965300e64313114e6 Mon Sep 17 00:00:00 2001 From: varutasu <104105839+varutasu@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:02:47 +0000 Subject: [PATCH] feat: implement durable chat persistence with PostgreSQL integration and advisory lock verification --- .convoys/ai-agent-studio.md | 117 + .../brief-1-ai-workspace-tabs.md | 50 + .../brief-2-agent-registry-marketplace.md | 51 + .../brief-3-ai-agent-builder.md | 49 + .convoys/repo-discovery-onboarding.md | 47 + .../repo-discovery-onboarding/brief-1-api.md | 50 + .../repo-discovery-onboarding/brief-2-ui.md | 40 + .../brief-3-ingestion.md | 51 + .../brief-4-agent-mcp.md | 48 + .convoys/workspace-chat-foundation.md | 108 + .../brief-1-chat-persistence.md | 75 + .../brief-2-chat-realtime.md | 55 + .../brief-3-chat-ui.md | 55 + .../web/app/(app)/[workspaceSlug]/ai/page.tsx | 221 +- apps/web/app/api/chat/route.test.ts | 54 + apps/web/app/api/chat/route.ts | 15 +- apps/web/app/api/chat/schema.ts | 28 + apps/web/components/ai/agent-builder.ts | 20 + apps/web/components/ai/agents-mode.tsx | 103 + apps/web/components/ai/ai-workspace.test.tsx | 155 + apps/web/components/ai/ai-workspace.tsx | 98 + apps/web/components/ai/chat-mode.tsx | 166 + apps/web/components/ai/composer-events.ts | 12 + apps/web/components/ai/workspace-composer.tsx | 121 + .../ai/workspace-interaction.test.ts | 125 + .../components/ai/workspace-interaction.ts | 50 + apps/web/components/ai/workspace-mode.ts | 27 + apps/web/package.json | 4 +- apps/web/server/root.ts | 2 + apps/web/server/routers/chat.test.ts | 205 + apps/web/server/routers/chat.ts | 218 + apps/web/vitest.config.ts | 15 + .../database/migrations/0018_early_risque.sql | 26 + .../migrations/0019_petite_monster_badoon.sql | 109 + .../migrations/0020_demonic_hammerhead.sql | 19 + ...21_chat_direct_membership_immutability.sql | 48 + .../migrations/meta/0018_snapshot.json | 4782 ++++++++++++++ .../migrations/meta/0019_snapshot.json | 5683 ++++++++++++++++ .../migrations/meta/0020_snapshot.json | 5814 +++++++++++++++++ .../database/migrations/meta/_journal.json | 30 +- packages/database/package.json | 1 + .../src/chat/channel-drizzle-adapter.ts | 312 + packages/database/src/chat/drizzle-adapter.ts | 223 + packages/database/src/chat/index.ts | 4 + .../database/src/chat/manage-channels.test.ts | 173 + packages/database/src/chat/manage-channels.ts | 463 ++ .../database/src/chat/send-message.test.ts | 242 + packages/database/src/chat/send-message.ts | 256 + packages/database/src/index.ts | 1 + .../src/schema/chat-migration.test.ts | 25 + packages/database/src/schema/chat.test.ts | 220 + packages/database/src/schema/chat.ts | 333 + packages/database/src/schema/index.ts | 1 + packages/shared/package.json | 1 + packages/shared/src/chat/contracts.test.ts | 468 ++ packages/shared/src/chat/index.ts | 387 ++ packages/shared/src/index.ts | 1 + .../Epic-repo-discovery-onboarding.md | 66 + .../Task-agent-mcp-starter.md | 55 + .../Task-forge-discovery-api.md | 55 + .../Task-repo-doc-ingestion.md | 59 + .../Task-repo-picker-modal.md | 51 + .../Epic-agent-participation.md | 80 + .../Task-agent-builder-editor-drafts.md | 57 + .../Task-agent-builder-review-sandbox.md | 57 + .../Task-agent-channel-membership.md | 65 + .../Task-agent-chat-run-delivery.md | 56 + .../Task-agent-harness-chat-streaming.md | 56 + .../Task-agent-identities-and-jobs.md | 56 + .../Task-agent-install-lifecycle.md | 58 + .../Task-agent-marketplace-library-ui.md | 57 + .../Task-agent-registry-api.md | 56 + .../Task-agent-registry-marketplace.md | 58 + .../Task-ai-assisted-agent-builder.md | 55 + .../Task-ai-workspace-tabs.md | 54 + .../Task-direct-agent-chat.md | 56 + .../Epic-unified-notifications.md | 55 + .../Task-notification-inbox-foundation.md | 53 + ...-notification-producers-and-preferences.md | 55 + .../Epic-workspace-chat.md | 59 + .../Task-chat-persistence.md | 68 + .../Task-chat-realtime-delivery.md | 55 + .../Task-chat-ui-and-navigation.md | 55 + .../Task-contextual-chat-authorization.md | 59 + .../Plan-workspace-communications.md | 104 + 85 files changed, 23508 insertions(+), 219 deletions(-) create mode 100644 .convoys/ai-agent-studio.md create mode 100644 .convoys/ai-agent-studio/brief-1-ai-workspace-tabs.md create mode 100644 .convoys/ai-agent-studio/brief-2-agent-registry-marketplace.md create mode 100644 .convoys/ai-agent-studio/brief-3-ai-agent-builder.md create mode 100644 .convoys/repo-discovery-onboarding.md create mode 100644 .convoys/repo-discovery-onboarding/brief-1-api.md create mode 100644 .convoys/repo-discovery-onboarding/brief-2-ui.md create mode 100644 .convoys/repo-discovery-onboarding/brief-3-ingestion.md create mode 100644 .convoys/repo-discovery-onboarding/brief-4-agent-mcp.md create mode 100644 .convoys/workspace-chat-foundation.md create mode 100644 .convoys/workspace-chat-foundation/brief-1-chat-persistence.md create mode 100644 .convoys/workspace-chat-foundation/brief-2-chat-realtime.md create mode 100644 .convoys/workspace-chat-foundation/brief-3-chat-ui.md create mode 100644 apps/web/app/api/chat/route.test.ts create mode 100644 apps/web/app/api/chat/schema.ts create mode 100644 apps/web/components/ai/agent-builder.ts create mode 100644 apps/web/components/ai/agents-mode.tsx create mode 100644 apps/web/components/ai/ai-workspace.test.tsx create mode 100644 apps/web/components/ai/ai-workspace.tsx create mode 100644 apps/web/components/ai/chat-mode.tsx create mode 100644 apps/web/components/ai/composer-events.ts create mode 100644 apps/web/components/ai/workspace-composer.tsx create mode 100644 apps/web/components/ai/workspace-interaction.test.ts create mode 100644 apps/web/components/ai/workspace-interaction.ts create mode 100644 apps/web/components/ai/workspace-mode.ts create mode 100644 apps/web/server/routers/chat.test.ts create mode 100644 apps/web/server/routers/chat.ts create mode 100644 apps/web/vitest.config.ts create mode 100644 packages/database/migrations/0018_early_risque.sql create mode 100644 packages/database/migrations/0019_petite_monster_badoon.sql create mode 100644 packages/database/migrations/0020_demonic_hammerhead.sql create mode 100644 packages/database/migrations/0021_chat_direct_membership_immutability.sql create mode 100644 packages/database/migrations/meta/0018_snapshot.json create mode 100644 packages/database/migrations/meta/0019_snapshot.json create mode 100644 packages/database/migrations/meta/0020_snapshot.json create mode 100644 packages/database/src/chat/channel-drizzle-adapter.ts create mode 100644 packages/database/src/chat/drizzle-adapter.ts create mode 100644 packages/database/src/chat/index.ts create mode 100644 packages/database/src/chat/manage-channels.test.ts create mode 100644 packages/database/src/chat/manage-channels.ts create mode 100644 packages/database/src/chat/send-message.test.ts create mode 100644 packages/database/src/chat/send-message.ts create mode 100644 packages/database/src/schema/chat-migration.test.ts create mode 100644 packages/database/src/schema/chat.test.ts create mode 100644 packages/database/src/schema/chat.ts create mode 100644 packages/shared/src/chat/contracts.test.ts create mode 100644 packages/shared/src/chat/index.ts create mode 100644 plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Epic-repo-discovery-onboarding.md create mode 100644 plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-agent-mcp-starter.md create mode 100644 plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-forge-discovery-api.md create mode 100644 plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-repo-doc-ingestion.md create mode 100644 plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-repo-picker-modal.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Epic-agent-participation.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-builder-editor-drafts.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-builder-review-sandbox.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-channel-membership.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-chat-run-delivery.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-harness-chat-streaming.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-identities-and-jobs.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-install-lifecycle.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-marketplace-library-ui.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-registry-api.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-registry-marketplace.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-ai-assisted-agent-builder.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-ai-workspace-tabs.md create mode 100644 plans/Plan-workspace-communications/Epic-agent-participation/Task-direct-agent-chat.md create mode 100644 plans/Plan-workspace-communications/Epic-unified-notifications/Epic-unified-notifications.md create mode 100644 plans/Plan-workspace-communications/Epic-unified-notifications/Task-notification-inbox-foundation.md create mode 100644 plans/Plan-workspace-communications/Epic-unified-notifications/Task-notification-producers-and-preferences.md create mode 100644 plans/Plan-workspace-communications/Epic-workspace-chat/Epic-workspace-chat.md create mode 100644 plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-persistence.md create mode 100644 plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-realtime-delivery.md create mode 100644 plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-ui-and-navigation.md create mode 100644 plans/Plan-workspace-communications/Epic-workspace-chat/Task-contextual-chat-authorization.md create mode 100644 plans/Plan-workspace-communications/Plan-workspace-communications.md diff --git a/.convoys/ai-agent-studio.md b/.convoys/ai-agent-studio.md new file mode 100644 index 0000000..e7541be --- /dev/null +++ b/.convoys/ai-agent-studio.md @@ -0,0 +1,117 @@ +--- +name: ai-agent-studio +classification: feature +success_metric: The existing AI rail destination provides accessible Chat and Agents modes, versioned agent foundations, and a provider-agnostic service that streams validated agent draft proposals. +skip: [] +status: in-progress +created: 2026-09-25 +model_policy: + roles: + role-conductor: claude-planning + role-architect: claude-planning + role-implementer: qwen-coder + role-reviewer: qwen-coder + escalate_to: claude-planning +--- + +# AI agent studio and marketplace + +## Why + +The existing AI rail destination is already the natural home for AI interaction, but it currently provides only one stateless full-page chat. Users should be able to chat with AI or switch modes and use AI itself to create, review, install, and manage reusable agents. This should feel like one coherent AI workspace rather than separate Chat, Settings, and marketplace products. + +Canonical backlog: `plans/Plan-workspace-communications/Epic-agent-participation/Epic-agent-participation.md`. + +## Scope + +- Brief 1: refactor `//ai` into URL-backed Chat and Agents tabs with one shared composer shell. +- Brief 2: add first-class, versioned agent persistence and shared validation/permission contracts. +- Brief 3: turn Agents-mode prompts into validated clarification and structured draft-patch events; it does not implement the editor or persistence. +- Follow-on backlog tasks add the builder editor/private-draft persistence, marketplace library UI, permission review/sandbox/activation, durable jobs, direct saved-agent chat, channel membership, harness events, and chat delivery. +- Preserve the existing Sparkles `AI` rail entry; do not add a separate Agents rail item. +- Reuse model routing, harness routing, template UI patterns, and workspace authorization without conflating those records with agent identity. + +Out of scope: channel participation/jobs, commercial marketplace billing/ratings, arbitrary public publishing, and autonomous channel access. Those follow after the agent registry exists. + +## Role activity and kickoff — 2026-10-22 + +- User approved beginning the integrated chat/agent direction, not merge or release. +- This session performed conductor reconciliation and architect access/registry/dependency review. Existing IA/UX/architecture sections are design guidance, not evidence of historical role completion; independent IA, UX and reviewer completion is not asserted. +- Brief 1 and its canonical task are `in_progress`: an independent implementer owns the AI shell app changes. This planning session changes no app code. Brief 2 is ready; Brief 3 waits for its dependencies. +- `in-progress` is local README bookkeeping for the active brief. No DB registration, task claim, event logging or MCP transition occurred; those tools are unavailable here. + +## Full product versus this foundation + +These three briefs provide a shell, schema/contracts, and inert builder service—not a working marketplace, install API, contextual chat or agent execution. The [registry API task](../plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-registry-api.md) and [install/lifecycle task](../plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-install-lifecycle.md) now explicitly own the missing server boundaries. Library/editor/review tasks consume them rather than creating duplicate registries or lifecycle endpoints. + +AI Chat, Agents creation/library, channel mentions and durable jobs use **one workspace agent registry**. Installation/activation does not grant conversation access. Follow the [plan's context and mention-to-add contracts](../plans/Plan-workspace-communications/Plan-workspace-communications.md): channel addition or expanded access needs authorized explicit confirmation, reviewed version/history/tool scope, idempotency, and revocation checks; workspace role alone cannot grant private-object access. + +## Todos + +- [x] User approved kickoff of the AI Chat/Agents direction on 2026-10-22; merge/release gates remain pending. +- [ ] Implement the Chat/Agents shell without regressing existing AI chat. +- [ ] Implement versioned agent persistence and shared registry contracts. +- [ ] Implement provider-agnostic conversational clarification and validated structured draft-patch streaming. +- [ ] Reviewer validates tenant isolation, permission safety, provider agnosticism, accessibility, and spec alignment. +- [ ] Human performs merge and release gates. + +## IA + +- Keep `//ai` as the canonical route and Sparkles rail destination. +- Use URL-backed top-level tabs: `Chat` and `Agents`; Chat remains the default for old links. +- The shared composer stays centered and visually stable across modes. +- Chat preserves current conversation behavior; saved-agent selection arrives in the direct-agent-chat follow-on, not the shell brief. +- Agents renders scaffolded library regions under/around the composer; the follow-on marketplace task populates Featured/Templates, Installed, Created by your workspace, and Drafts. +- This convoy provides placeholders for future agent cards/detail actions and focuses `Create new` on the builder composer. +- Brief 3 exposes testable builder service events; the follow-on editor wires them into Agents mode and turns them into editable, persisted drafts. + +## UX + +- Switching tabs changes intent, placeholder, suggestions, submit label, and result content—not the fundamental composer size or interaction language. +- Agents-mode examples include outcome-oriented prompts rather than raw configuration fields. +- Validated builder events expose what the proposed agent could read, requested tools, invocation policy, budget, and requested model role/harness route; the follow-on editor presents the full review experience. +- Draft proposal generation never persists, activates, publishes, grants tools, or joins channels automatically. +- Marketplace lifecycle/provenance visuals are owned by the follow-on library task; this convoy reserves compatible states in the shell. +- Keyboard focus survives tab switches where sensible; tabs, cards, dialogs, draft fields, and permission review are accessible. + +## Architecture + +### Product boundaries + +- An agent definition is a stable service identity and versioned policy bundle. +- A role is a requested capability used by model/harness routing; it is not an agent identity. +- A model provider or OmniRoute profile performs/routes inference; it is not an agent. +- A harness executes a run; it is not an agent. +- An `agent_run` is execution history and references the pinned agent version where applicable. + +### Builder flow + +1. The Agents tab submits a workspace-authorized builder request through a dedicated typed streaming endpoint, not the normal chat endpoint. +2. The builder resolves a model role through existing routing and returns validated clarification or structured draft-patch events. +3. This convoy stops at the typed event stream; it does not edit or persist drafts. +4. A follow-on editor consumes those events and saves private draft versions owned by the current member. +5. A later review flow handles permission approval, sandbox tests, install, activation, and workspace publication. + +### Marketplace model + +- Schemas/contracts support global curated templates that are readable but not tenant-owned; catalog seeding and browse APIs are follow-on work. +- Workspace agents/installations, drafts, permissions, and versions are always scoped by `workspace_id`. +- Installation contracts require workspace-owned versioned configuration and prohibit copying secrets. +- First release supports curated templates and workspace sharing, not open public submissions, ratings, or payments. + +### Security + +- Generated output is untrusted and must pass zod validation plus server-side policy checks. +- Tool grants, channel access, budgets, and activation are never inferred as silent side effects. +- Provider credentials remain referenced through existing provider configuration and are never embedded in an agent definition or prompt transcript. +- Agent handles are unique per workspace; immutable IDs/version IDs drive references and audit history. + +## Briefs + +| Brief | Backlog task | Depends on | +| --- | --- | --- | +| 1 | `Task-ai-workspace-tabs.md` | none | +| 2 | `Task-agent-registry-marketplace.md` | none; may proceed in parallel with brief 1 | +| 3 | `Task-ai-assisted-agent-builder.md` | briefs 1 and 2 | + +Follow-on scheduling: registry API → install/lifecycle → library and editor/review UI; durable jobs → direct-chat and reviewed channel membership; normalized harness → sandbox execution and run delivery. UI-only review scaffolding can precede the harness, but sandbox execution cannot. Contextual channel integration additionally waits for a verified object access policy. These are canonical backlog dependencies, not extra deliverables of the three foundation briefs. diff --git a/.convoys/ai-agent-studio/brief-1-ai-workspace-tabs.md b/.convoys/ai-agent-studio/brief-1-ai-workspace-tabs.md new file mode 100644 index 0000000..1b76e98 --- /dev/null +++ b/.convoys/ai-agent-studio/brief-1-ai-workspace-tabs.md @@ -0,0 +1,50 @@ +--- +brief: 1 +convoy: ai-agent-studio +role: role-implementer +title: AI workspace Chat and Agents tabs +status: in_progress +priority: P1 +slice_dependencies: [] +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 1 — `Task-ai-workspace-tabs.md` + +## Execution note — 2026-10-22 + +User approved kickoff. The code slice and `/api/chat` role-boundary hardening are implemented; standards/spec reviews are clean, web tests pass (34 tests), and web type-check/lint pass with pre-existing warnings only. Browser verification remains required for native back/forward behavior, Radix keyboard focus, streaming scroll restoration, responsive layout, and screen-reader announcements, so the brief remains in progress. No merge/release or DB transition is claimed. Agents remains an explicit placeholder/adapter stub: the shell must not claim working creation, installation, mention-to-add, saved-agent execution, or private-context access. + +## Goal + +Refactor the existing workspace AI page into accessible, deep-linkable Chat and Agents modes that share one visual composer shell without regressing current streaming chat. + +## Files + +- `apps/web/app/(app)/[workspaceSlug]/ai/page.tsx` — preserve the canonical route; split orchestration from presentation. +- `apps/web/components/ai/` — add a workspace frame, URL-backed tab switcher, shared composer shell, Chat mode, and Agents placeholder/library frame. +- Existing `/api/chat` integration — preserve for Chat mode only. +- Adjacent component tests. + +## Required design + +- Keep the existing `AI` Sparkles item in `apps/web/components/layout/icon-rail.tsx`; do not add an Agents rail item. +- `Chat` is the default for `//ai`; tab state is URL-backed and browser-navigation safe. +- The composer keeps the same dimensions, shape, attachment/action affordances, and keyboard conventions in both modes. +- Mode-specific behavior is explicit: Chat submits conversation messages; Agents emits typed builder requests through a separate adapter stub until Brief 3. +- Tab changes preserve each mode's independent draft input and relevant scroll state. +- Reuse existing UI primitives and responsive shell patterns. + +## Acceptance + +- Old AI links still open functional Chat mode. +- Users can switch/deep-link/refresh/back-forward between Chat and Agents. +- Chat streaming, error, stop, and focus behavior remains functional and workspace-scoped. +- Agents shows the shared composer plus library loading/empty/error scaffolding without submitting to `/api/chat`. +- Tabs and composer pass keyboard/screen-reader checks. +- `pnpm lint && pnpm type-check && pnpm test` pass. + +## Prompt + +Read the current AI page, `/api/chat` route, icon rail, tRPC provider, and UI primitives before editing. Separate shared presentation from mode-specific state; do not introduce the registry schema or persist generated agents in this brief. diff --git a/.convoys/ai-agent-studio/brief-2-agent-registry-marketplace.md b/.convoys/ai-agent-studio/brief-2-agent-registry-marketplace.md new file mode 100644 index 0000000..346808b --- /dev/null +++ b/.convoys/ai-agent-studio/brief-2-agent-registry-marketplace.md @@ -0,0 +1,51 @@ +--- +brief: 2 +convoy: ai-agent-studio +role: role-implementer +title: Versioned agent registry schema and contracts +status: ready +priority: P1 +slice_dependencies: [] +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 2 — `Task-agent-registry-marketplace.md` + +## Goal + +Add durable workspace agent definitions and shared validation/permission contracts that later draft, marketplace, and builder APIs can consume. + +## Files + +Expected scope; confirm names against current exports. + +- `packages/database/src/schema/` and a generated migration — template, agent, version, and installation/provenance records. +- `packages/shared/src/` — zod contracts for identity, configuration, policies, versions, permissions, and marketplace filters. +- Adjacent schema/contract/permission-policy tests. + +## Required design + +- Agents are not `users`, role IDs, provider aliases, harness aliases, or `agent_runs`. This is the single identity/version registry for AI Chat, Agents builder/library, mentions, installations and jobs; never add a second channel-agent registry. +- Separate lifecycle/tool approval roles from context delegation and per-conversation access. Installation/activation grants no channel/private-object access. Follow the parent plan's permission matrix; schemas/contracts must support reviewed, version-bound scoped grants without inventing project ACL tables. +- Workspace agents have immutable IDs, unique workspace handles, versioned configuration, lifecycle status, provenance, and creator/audit metadata. +- Configuration references model role and harness route aliases but contains no provider secrets. +- Schemas support global curated-template provenance and workspace-owned installations without storing provider secrets. +- Reusable policy contracts encode: members manage their own private drafts/use active agents; `admin`/`owner` manage template installation, shared lifecycle, and ordinary allowlisted grants; only `owner` approves mutating sandbox policy or sensitive workspace-wide tools. +- Contracts expose lifecycle, visibility, provenance, permissions, and update state for later APIs and UI. + +## Follow-on boundaries + +[Registry API](../../plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-registry-api.md) owns catalog/private-draft procedures. [Install lifecycle](../../plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-install-lifecycle.md) owns reviewed lifecycle transitions. These are not hidden deliverables of this schema brief; UI tasks consume them. + +## Acceptance + +- Tenant ownership, handle uniqueness, and supporting indexes/foreign keys are enforced at the schema/contract layer; workspace-scoped query enforcement is required in the follow-on CRUD APIs. +- Version/updated-at precondition contracts prevent silent last-write-wins behavior in later APIs. +- Contracts cover capabilities, requested/granted tools, context/invocation policy, model role, harness route, budget, provenance, permissions, and version changes without weakening private-draft boundaries. +- No schema or contract treats existing workflow role IDs as agent identities. +- `pnpm db:generate`, `pnpm lint`, `pnpm type-check`, and relevant tests pass. + +## Prompt + +Read existing model/harness registry schemas and settings UI, template CRUD/editor patterns, workspace authorization helpers, and schema exports. Keep this brief focused on registry/marketplace; do not implement channel participation or builder inference. diff --git a/.convoys/ai-agent-studio/brief-3-ai-agent-builder.md b/.convoys/ai-agent-studio/brief-3-ai-agent-builder.md new file mode 100644 index 0000000..22be9c3 --- /dev/null +++ b/.convoys/ai-agent-studio/brief-3-ai-agent-builder.md @@ -0,0 +1,49 @@ +--- +brief: 3 +convoy: ai-agent-studio +role: role-implementer +title: Conversational AI agent builder +status: blocked +priority: P1 +slice_dependencies: + - brief-1-ai-workspace-tabs + - brief-2-agent-registry-marketplace +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 3 — `Task-ai-assisted-agent-builder.md` (service slice) + +## Goal + +Implement the provider-agnostic builder service and streaming contracts that transform an Agents-mode request into validated clarification or structured draft-patch events. UI editing and private-draft persistence remain a follow-on slice. + +## Files + +Expected scope; confirm routing conventions before implementation. + +- `packages/shared/src/` — versioned builder request/event/draft-patch schemas. +- `packages/ai/src/prompts/` — agent-builder prompts and structured-output instructions. +- `apps/web/app/api/` or a suitable streaming tRPC boundary — workspace-authorized builder stream separate from `/api/chat`. +- Adjacent service tests for validation, workspace authorization, provider abstraction, stream event ordering, and malformed output. + +## Required design + +- The builder requests an existing model role/capability and uses provider resolution; no direct OmniRoute/provider branch exists in UI or route code. +- Model output is untrusted. Only versioned zod-validated clarification and draft-patch events may affect client state. +- The service emits deterministic base revision IDs and structured patches so a follow-on UI can maintain conflict-safe draft history. +- Generated requested tools, channel access, activation, and workspace publication remain inert, unapproved fields in validated output. +- The endpoint does not persist, publish, install, activate, or execute an agent. + +## Acceptance + +- Outcome-oriented prompts create valid drafts or targeted clarification questions. +- The service supports initial requests and follow-up refinements through validated clarification/draft-patch events. +- Invalid or policy-violating model output becomes a typed error event and is never persisted. +- Requested tools and access remain marked unapproved and cannot be exercised by this endpoint. +- No plaintext provider secret appears in events, logs, or client payloads. +- `pnpm lint && pnpm type-check && pnpm test` pass; `pnpm build` passes for changed server/AI exports. + +## Prompt + +Read model routing, `packages/ai` provider adaptation, prompt conventions, registry contracts from Brief 2, and workspace guards. Use the existing AI SDK only as a normalized model API; keep the builder compatible with OmniRoute and other providers through the resolved model contract. Do not build the editor or draft CRUD in this brief. diff --git a/.convoys/repo-discovery-onboarding.md b/.convoys/repo-discovery-onboarding.md new file mode 100644 index 0000000..7b4b414 --- /dev/null +++ b/.convoys/repo-discovery-onboarding.md @@ -0,0 +1,47 @@ +--- +name: repo-discovery-onboarding +classification: feature +success_metric: Operators can authorize GitHub or configure Forgejo/Gitea, discover their repositories, bulk-connect selected repos, import supported repository context, and receive usable agent onboarding instructions. +skip: [] +status: open +created: 2026-09-25 +model_policy: + roles: + role-conductor: claude-planning + role-architect: claude-planning + role-implementer: qwen-coder + role-reviewer: qwen-coder + escalate_to: claude-planning +--- + +# Repo discovery and onboarding + +## Why + +Echodo currently requires workspace admins to paste one remote URL at a time and reference a server-side token environment variable. That is adequate for worker execution but too cumbersome for onboarding a fleet of existing repositories. + +The target flow is account/instance connection, repository discovery, explicit selection, context import, and agent setup. `plans/` remains the canonical product backlog; this convoy defines the implementation slices and gates. + +## Scope and security decisions + +- GitHub discovery uses the current user's linked Auth.js GitHub account and its server-side access token. The browser never receives the token. +- GitHub OAuth requests the minimum scopes needed for repository discovery. Existing accounts must explicitly re-authorize when their stored scope is insufficient. +- Forgejo/Gitea v1 uses an instance URL plus a **token environment-variable reference**. A PAT value is never stored in Echodo or sent back to the browser. +- Every tRPC procedure is workspace-scoped. Mutations require workspace owner/admin; discovery additionally verifies that the linked forge identity belongs to the current user. +- A connected repository remains a workspace resource. Importing repositories does not automatically create new workspaces. +- Repository context import is idempotent and records source repo/path/revision metadata. The canonical remote file is not silently overwritten by Echodo. + +## Delivery order + +1. **Forge discovery API** — shared schemas, credential resolution, paginated GitHub and Forgejo/Gitea discovery, and `connectedRepos.listAvailable`. +2. **Repo picker and batch connect** — searchable selection UI and transactional/idempotent bulk connection. +3. **Repository context ingestion** — import supported docs and existing convoy manifests with provenance and retryable status. +4. **Agent onboarding** — client-specific, copyable MCP setup based on the actual Echodo deployment model. + +Briefs map 1:1 to the matching `plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-*.md` files so the worker can claim and complete backlog work. + +## Gates + +- Gate 1 must confirm the credential boundary above before Brief 1 is dispatched. +- Gate 2 reviews auth handling, tenant scoping, pagination, and redaction before merge. +- Live GitHub/Forgejo verification uses operator-provided test accounts; automated tests must use mocked HTTP responses and contain no tokens. diff --git a/.convoys/repo-discovery-onboarding/brief-1-api.md b/.convoys/repo-discovery-onboarding/brief-1-api.md new file mode 100644 index 0000000..52a101b --- /dev/null +++ b/.convoys/repo-discovery-onboarding/brief-1-api.md @@ -0,0 +1,50 @@ +--- +brief: 1 +convoy: repo-discovery-onboarding +role: role-implementer +title: Forge account repository discovery API +status: ready +priority: P1 +slice_dependencies: [] +recommended_model: qwen-coder +model_tier: local +workspace_id: global +--- + +# Brief 1 — Forge account repository discovery API + +## Goal + +A workspace admin can request a normalized, paginated list of repositories visible to either their linked GitHub identity or a configured Forgejo/Gitea instance. Already-connected repositories are identified without exposing credentials to the client. + +Backlog task: `plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-forge-discovery-api.md`. + +## Security and product contract + +- Do not accept or return raw access tokens in tRPC input/output. +- GitHub credentials come from the current user's `accounts` row for `provider='github'`; never use another workspace member's account. +- Update the existing GitHub provider authorization parameters to request repository discovery scopes. Return a typed `reauthorization_required` state when the stored account lacks the required scope instead of treating it as a forge API failure. +- Forgejo/Gitea discovery uses a workspace-admin-supplied instance descriptor and `tokenEnvVar`; resolve the token only on the server with the existing `readToken` pattern. +- Only workspace owner/admin may call discovery. All connected-repo comparisons filter by `workspace_id`. +- Keep the existing paste-URL flow working. + +## Files + +- `packages/shared/src/connected-repos/schemas.ts` — add zod schemas/types for discovery source, normalized remote repository summary, pagination cursor/page, and typed connection state; export through `packages/shared/src/connected-repos/index.ts`. +- `apps/web/lib/auth.ts` — configure GitHub authorization scopes while preserving the existing sign-in and account-linking flow. +- `apps/web/server/lib/forge/adapters.ts` — add a discovery context separate from repo-specific `ForgeAdapterContext`; implement paginated GitHub `/user/repos` and Forgejo/Gitea `/user/repos` mapping. Preserve `fetchRepoMetadata` and `createPullRequest` behavior. +- `apps/web/server/routers/connected-repos.ts` — add admin-only `listAvailable`; resolve credentials server-side, call the selected adapter, and mark an item connected using the unique tuple `(workspace_id, api_base_url, owner, repo_name)`. +- Add focused tests for response validation, pagination, missing credentials/scopes, private repository mapping, and already-connected matching. If web tests require missing infrastructure, add the smallest Vitest setup consistent with existing package configs. + +## Acceptance + +- GitHub and Forgejo/Gitea payloads map to one zod-validated result containing stable remote ID, name/full name, owner, HTTPS clone URL, web URL, default branch, privacy flag, description, and updated time. +- Pagination is explicit; the implementation does not silently truncate accounts with more than 100 repositories. +- A credentials or scope problem returns a typed connection state suitable for UI remediation and does not leak a token or provider response body. +- Existing connected repos are marked accurately within the current workspace only. +- Existing manual connect/test/update/disconnect procedures continue to work. +- `pnpm --filter @tasks/shared test`, `pnpm --filter @tasks/web lint`, and `pnpm --filter @tasks/web type-check` pass. + +## Prompt for implementer + +Implement only Brief 1. Read `AGENTS.md`, the matching backlog task, `apps/web/lib/auth.ts`, `apps/web/server/lib/forge/adapters.ts`, and `apps/web/server/routers/connected-repos.ts` before editing. Use TDD for adapter normalization and credential-state behavior. Do not build the picker UI, batch connection, document ingestion, or agent configuration in this brief. Do not persist plaintext PATs. diff --git a/.convoys/repo-discovery-onboarding/brief-2-ui.md b/.convoys/repo-discovery-onboarding/brief-2-ui.md new file mode 100644 index 0000000..0543ad9 --- /dev/null +++ b/.convoys/repo-discovery-onboarding/brief-2-ui.md @@ -0,0 +1,40 @@ +--- +brief: 2 +convoy: repo-discovery-onboarding +role: role-implementer +title: Repository picker and batch connection +status: ready +priority: P1 +slice_dependencies: [1] +recommended_model: qwen-coder +model_tier: local +workspace_id: global +--- + +# Brief 2 — Repository picker and batch connection + +## Goal + +Workspace admins can search discovered repositories, select multiple entries, and connect them to the current workspace in one idempotent operation while retaining the existing paste-URL workflow. + +Backlog task: `plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-repo-picker-modal.md`. + +## Files + +- `apps/web/server/routers/connected-repos.ts` — add zod-validated, admin-only `batchConnect`. Re-resolve or validate selected remote IDs server-side; do not trust client-supplied owner, URLs, forge type, or token references. Insert with workspace scoping and conflict-safe behavior against `connected_repos_workspace_repo_unique`. +- `apps/web/app/(app)/[workspaceSlug]/settings/repos/page.tsx` — retain manual connection and add a discovery entry point with source/connection-state handling. +- `apps/web/components/settings/repo-picker-dialog.tsx` (new only if extraction improves the existing page) — search, pagination/load-more, multi-select, connected/disabled states, loading/error/empty states, and import progress using existing UI primitives. + +## Acceptance + +- The picker supports GitHub and Forgejo/Gitea connection states returned by Brief 1 and gives actionable reauthorization/configuration messages. +- Search matches full name and description without discarding selection when paging. +- Already-connected repos cannot be selected. +- `batchConnect` is atomic for valid selections, idempotent for duplicates, tenant-scoped, and returns per-repo connected/already-connected results. +- A stale or forged client selection cannot connect an arbitrary URL. +- Successful import invalidates `connectedRepos.list` and `listAvailable` and leaves the manual paste-URL dialog usable. +- Lint and type-check pass; cover server-side validation and selection-state helpers with tests where feasible. + +## Prompt for implementer + +Implement only Brief 2 after Brief 1 is complete. Read the matching backlog task and existing repo settings page before editing. Reuse Radix and `components/ui`; do not implement repository document ingestion or agent setup. Never send forge credentials to a client component. diff --git a/.convoys/repo-discovery-onboarding/brief-3-ingestion.md b/.convoys/repo-discovery-onboarding/brief-3-ingestion.md new file mode 100644 index 0000000..bc3f2d8 --- /dev/null +++ b/.convoys/repo-discovery-onboarding/brief-3-ingestion.md @@ -0,0 +1,51 @@ +--- +brief: 3 +convoy: repo-discovery-onboarding +role: role-implementer +title: Idempotent repository context ingestion +status: ready +priority: P1 +slice_dependencies: [1, 2] +recommended_model: qwen-coder +model_tier: local +workspace_id: global +--- + +# Brief 3 — Idempotent repository context ingestion + +## Goal + +After repositories are connected, an explicit retryable ingestion action imports supported repository documentation and existing convoy manifests with provenance, without making remote API latency part of the batch-connect transaction. + +Backlog task: `plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-repo-doc-ingestion.md`. + +## Architecture constraints + +- Use the canonical object type `document`, not `doc`. +- Do not import an unbounded repository tree. V1 paths are root `README.md`, `AGENTS.md`, `CONTRIBUTING.md`, and `.convoys/*.md`; recursive `docs/**` and `plans/**` are deferred until limits and conflict semantics are designed. +- Import is idempotent by `(workspace, connectedRepoId, sourcePath)`, and records source forge, repo, path, revision/sha, and last import result in metadata. +- Keep remote content read-only. Re-import updates the matching imported object only when the remote revision changes. +- Share convoy parsing/reconciliation logic from a neutral package; do not import `apps/mcp-server` source into `apps/web`. +- A missing optional file is not an error. Authentication, rate-limit, parse, and persistence failures are visible per repo and retryable. + +## Files + +- Shared connected-repo schemas/services — add bounded tree/list-file and fetch-text-file contracts with size/type validation. +- Forge adapters — implement GitHub and Forgejo/Gitea directory/file reads against the connected repo's default branch. +- Extract reusable convoy markdown parsing from `apps/mcp-server/src/lib/convoy-files.ts` into a package usable by web and MCP, preserving local-file behavior. +- Database schema/migration if needed — add durable repository-source identity and ingestion status. Every new tenant row includes `workspace_id` and an index. +- Web tRPC/service — add admin-only `startImport`/`importStatus` or an equivalent bounded background execution path. Do not perform all remote reads inside the `batchConnect` transaction. + +## Acceptance + +- Importing a repo creates or updates `document` objects for supported files with source provenance. +- Re-running an unchanged import creates no duplicates. +- Existing `.convoys/*.md` manifests reconcile to workspace-scoped convoy projects. Brief files are imported only when their schema is valid and can be linked safely. +- One bad file does not roll back successful files; the result reports imported, unchanged, skipped, and failed counts. +- File count and byte limits prevent accidental large imports. +- Unit/integration tests cover idempotency, missing files, malformed convoy frontmatter, tenant isolation, and partial failure. +- Generate and commit a new migration for schema changes; lint, type-check, test, and relevant build checks pass. + +## Prompt for implementer + +Implement only Brief 3 after Briefs 1 and 2. Read `config/CursorSync.md` before changing sync behavior. Preserve the local-first `.convoys/` contract and never mutate remote files. Keep ingestion bounded and retryable; do not silently invent conflict resolution for `plans/**`. diff --git a/.convoys/repo-discovery-onboarding/brief-4-agent-mcp.md b/.convoys/repo-discovery-onboarding/brief-4-agent-mcp.md new file mode 100644 index 0000000..a7495ae --- /dev/null +++ b/.convoys/repo-discovery-onboarding/brief-4-agent-mcp.md @@ -0,0 +1,48 @@ +--- +brief: 4 +convoy: repo-discovery-onboarding +role: role-implementer +title: Agent onboarding and repository bootstrap +status: ready +priority: P2 +slice_dependencies: [2, 3] +recommended_model: qwen-coder +model_tier: local +workspace_id: global +--- + +# Brief 4 — Agent onboarding and repository bootstrap + +## Goal + +After connection/import, the operator receives accurate, client-specific instructions for connecting an agent to Echodo and can optionally open a bootstrap PR for repository-side convoy guidance. + +Backlog task: `plans/Plan-automation-foundation/Epic-repo-discovery-onboarding/Task-agent-mcp-starter.md`. + +## Product and security contract + +- Do not generate snippets containing `DATABASE_URL` or other infrastructure secrets. +- Do not assume an `ECHODO_WORKSPACE_ID` environment variable is consumed unless the MCP transport/config actually supports it. +- Support the deployment's real MCP transport first. If this remains local stdio, show the command plus non-secret workspace slug used in tool arguments. If remote authenticated MCP is added separately, link to that setup rather than fabricating it here. +- Client formats differ. Render templates from typed server data for explicitly supported clients (OpenCode and Cursor first); label unsupported clients instead of presenting one generic JSON object as universal. +- Any repository bootstrap change is a commit on a branch followed by `createPullRequest`; `createPullRequest` alone cannot create files. + +## Files + +- Add a typed onboarding-config procedure returning only non-secret deployment and workspace/repo identifiers. +- Add an Agent setup panel to connected-repo settings with client tabs, copy controls, prerequisites, and verification steps. +- Add bootstrap service only if the forge adapter has branch/file/commit primitives; otherwise scope this brief to guidance and create a follow-up task rather than simulating a PR. +- Bootstrap content should reference the canonical `.convoys/README.md` contract and avoid overwriting an existing file without explicit confirmation. + +## Acceptance + +- OpenCode and Cursor examples are syntactically valid for their actual configuration formats and contain no database URL, PAT, access token, or API-key value. +- Instructions identify the selected workspace and repository and include a smoke test such as `query_manifest_status`. +- Copy controls provide success/error feedback and work over secure browser contexts. +- Bootstrap is idempotent: existing `.convoys/README.md` is detected, and any remote change is shown to the user through a PR URL. +- Authorization is workspace owner/admin for remote writes; read-only members may view non-secret setup guidance if allowed by workspace policy. +- Lint, type-check, and relevant UI/service tests pass. + +## Prompt for implementer + +Implement only Brief 4 after Briefs 2 and 3. First verify the MCP server's supported transport and environment contract in code. Never place infrastructure credentials in browser-rendered snippets. Do not claim a bootstrap PR is possible unless branch/file commit support is implemented and tested. diff --git a/.convoys/workspace-chat-foundation.md b/.convoys/workspace-chat-foundation.md new file mode 100644 index 0000000..f521851 --- /dev/null +++ b/.convoys/workspace-chat-foundation.md @@ -0,0 +1,108 @@ +--- +name: workspace-chat-foundation +classification: feature +success_metric: Two workspace members can exchange durable channel messages, reconnect without loss, and open Chat from the left rail with accurate unread state. +skip: [] +status: open +created: 2026-09-25 +model_policy: + roles: + role-conductor: claude-planning + role-architect: claude-planning + role-implementer: qwen-coder + role-reviewer: qwen-coder + escalate_to: claude-planning +--- + +# Workspace chat foundation + +## Why + +Echodo has collaborative documents, convoy operator notes, and AI chat surfaces, but no durable workspace conversation layer. The first slice must establish trustworthy persistence and realtime delivery before notifications or agent participation depend on it. + +Canonical backlog: `plans/Plan-workspace-communications/Epic-workspace-chat/Epic-workspace-chat.md`. + +## Scope + +- Brief 1: tenant-scoped chat schema, channel lifecycle, message/outbox transaction, and workspace-authorized tRPC API. +- Brief 2: outbox publication, authenticated WebSockets, Redis fan-out, and reconnect catch-up. +- Brief 3: workspace Chat routes, channel/message UI, and left-rail unread/mention affordance. +- Postgres is authoritative; Redis and WebSockets accelerate delivery. +- Yjs/Hocuspocus may support presence/typing/drafts, but does not own message history. + +Out of scope for this convoy: unified non-chat inbox, agent execution, external delivery, search, and federation. + +## Role activity and kickoff — 2026-10-22 + +- User approved beginning the integrated contextual-chat/AI-agent direction; this does not authorize merge or release. +- This session performed conductor reconciliation and architect schema/access/dependency review. IA, UX, implementation, review and release work are not claimed complete by the existing section headings. +- Chat Brief 1 remains the next implementable slice; no chat implementation is claimed started. The independent AI shell implementer is tracked in the AI convoy. +- Files are local planning artifacts. No convoy DB registration, task claim, event logging, or MCP gate transition was performed; those tools are unavailable here. `open` is the local README status, not a server lifecycle claim. + +## Full product versus this foundation + +The approved product includes workspace, project and supported-object chat plus mention-to-add agents. This convoy implements **workspace-context human chat only**, with an explicit fail-closed context policy seam. Object contexts must be rejected until the canonical follow-on [context authorization task](../plans/Plan-workspace-communications/Epic-workspace-chat/Task-contextual-chat-authorization.md) lands; do not treat a same-workspace object lookup as an ACL. Mention-to-add belongs to [agent channel membership](../plans/Plan-workspace-communications/Epic-agent-participation/Task-agent-channel-membership.md), not these three briefs. + +All briefs follow the [plan's context and agent permission contracts](../plans/Plan-workspace-communications/Plan-workspace-communications.md). Projects are generic `objects` rows; the inspected schema has no private-object authorization model. This gap is a blocker for contextual rollout, not a reason to silently widen access. + +## Todos + +- [x] User approved kickoff of the integrated direction on 2026-10-22; merge/release gates remain pending. +- [ ] Implement brief 1 and validate tenant isolation and idempotency. +- [ ] Implement brief 2 and validate outage/reconnect behavior. +- [ ] Implement brief 3 and validate accessibility and multi-device unread state. +- [ ] Reviewer checks standards and the three linked backlog tasks. +- [ ] Human performs merge and release gates. + +## IA + +- Add `Chat` as a first-class left-rail destination within the current workspace. +- `/[workspaceSlug]/chat` selects or redirects to a channel. +- `/[workspaceSlug]/chat/[channelSlug]` renders channel navigation and conversation. +- Threads open in the existing right-panel pattern where practical, preserving channel context. +- The global notification inbox is a later epic; Chat still exposes its own durable unread and mention counts. + +## UX + +- Ordinary unread activity uses a dot; mentions/direct messages use a numeric badge. +- Badges include accessible labels and never rely on color alone. +- Optimistic messages show pending, failed, and retry states. +- Reconnect and catch-up are silent when successful and explicit when recovery fails. +- Keyboard navigation covers rail entry, channel switching, message history, composer, and thread opening. + +## Architecture + +### Data and command path + +1. A workspace-authorized tRPC mutation validates the command and channel membership. +2. Brief 1 owns one Postgres transaction that inserts the message and its versioned outbox event. +3. Brief 2 owns an outbox worker that claims and publishes committed events through Redis. +4. Realtime instances fan events out to authenticated local WebSocket subscribers. +5. Reconnecting clients query Postgres after their last durable sequence cursor. + +### Boundaries + +- `packages/database`: tenant-scoped chat and outbox schema plus migration. +- `packages/shared`: zod contracts and versioned realtime event types when shared across processes. +- `apps/web`: workspace-authorized tRPC router, routes, React Query state, and shell navigation. +- `apps/collab-server` or a narrowly separated realtime entry point: authenticated subscriptions and Redis fan-out. +- Existing Hocuspocus/Yjs infrastructure remains available for ephemeral presence, typing, and collaborative drafts. + +### Security and scale + +- Every query enforces workspace AND context AND channel access. Foundation accepts workspace context only; public workspace channels require active workspace membership, private/direct also require channel membership, and archived channels are read-only. Channel owners or workspace admins/owners administer only channels they may access; role alone grants no private/direct read or self-join bypass. Object contexts require a separate verified policy. +- Brief 1 defines the versioned outbox contract and owns the atomic message/outbox transaction; Brief 2 owns claim, publication, retry, and consumer delivery. +- Message commands accept client idempotency keys and generate a per-channel monotonic sequence; reconnect cursors are `(channel_id, sequence)` pairs. +- Realtime events carry identifiers and cursors, not authorization truth; subscription and catch-up endpoints authorize independently. +- Redis failure cannot roll back a committed message. Outbox retries and Postgres catch-up restore delivery. +- Use bounded pagination and backpressure; do not broadcast entire message histories. +- This convoy supports one-level threads in the persistence/API contract; full thread-panel polish may remain minimal in Brief 3. +- Read state is the highest message sequence the member has intentionally viewed. In this convoy, mention counts are derived from validated persisted message-mention rows above that cursor; the notification epic later projects those mentions into the unified inbox. Mark-read emits an invalidation event for other devices. + +## Briefs + +| Brief | Backlog task | Depends on | +| --- | --- | --- | +| 1 | `Task-chat-persistence.md` | none | +| 2 | `Task-chat-realtime-delivery.md` | brief 1 | +| 3 | `Task-chat-ui-and-navigation.md` | briefs 1 and 2 | diff --git a/.convoys/workspace-chat-foundation/brief-1-chat-persistence.md b/.convoys/workspace-chat-foundation/brief-1-chat-persistence.md new file mode 100644 index 0000000..e001cc8 --- /dev/null +++ b/.convoys/workspace-chat-foundation/brief-1-chat-persistence.md @@ -0,0 +1,75 @@ +--- +brief: 1 +convoy: workspace-chat-foundation +role: role-implementer +title: Durable chat persistence and workspace API +status: in_progress +priority: P1 +slice_dependencies: [] +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 1 — `Task-chat-persistence.md` + +## Execution checkpoint — 2026-10-22 + +- Shared command/context validation and the fail-closed authorization policy are implemented and accepted after standards/spec review; `@tasks/shared` has 65 passing tests. +- The tenant-scoped chat schema and generated migration are implemented and accepted after standards/spec/migration review; database tests pass. A separate generated `0018` repairs pre-existing omitted budget tables, and chat is isolated in generated migration `0019`. +- The concrete Drizzle human send/reply transaction, mention-aware idempotency, atomic sequence allocation, validated `chat.message.created.v1` outbox event, and workspace-authorized `chat.send` tRPC procedure are implemented and review-clean. Acceptance remains blocked on the live PostgreSQL integration gate below; channel lifecycle, history, edit/tombstone, mention counts, and read-state operations are pending. +- **Blocking integration gate:** against disposable PostgreSQL with migrations through `0019`, verify advisory-lock blocking under simultaneous same-key sends; exactly one message, mention set, logical outbox event, and sequence increment; full rollback of the sequence/message/mentions/outbox after an injected post-allocation failure; and Drizzle's runtime `bigint` shape for `UPDATE … RETURNING next_sequence - 1`. Unit fakes verify orchestration only and must not be cited as transaction/concurrency proof. +- IA/UX review completed; private-object authorization remains blocked, and agent approval semantics and AI side-panel convergence require resolution before their dependent integrations ship. + +## Goal + +Implement the durable, tenant-scoped channel/message foundation and workspace-authorized tRPC API described by `plans/Plan-workspace-communications/Epic-workspace-chat/Task-chat-persistence.md`. + +## Files + +Expected scope; confirm exact module names against current package exports before editing. + +- `packages/database/src/schema/` — add chat channel, membership, message, read-state, and the outbox contract schemas and export them. +- `packages/database/migrations/` — generate a new migration and metadata; never edit an existing migration. +- `packages/shared/src/` — add reusable zod schemas/types for chat commands, author/message states, and the versioned outbox event where cross-process sharing is needed. +- `apps/web/server/routers/` — add a `workspaceProcedure` chat router for channel list/create/archive, membership management, history/send/reply/edit/delete, and mark-read. +- Adjacent `*.test.ts` files — authorization, provisioning, pagination, idempotency, thread, and cross-workspace isolation coverage. + +## Required design + +- Every tenant-owned table has non-null indexed `workspace_id`. +- Channel types are `public`, `private`, and `direct`; visibility is separate from context. This brief accepts only an explicit workspace context (including a documented workspace default for existing callers); reject object references/unknown context keys rather than dropping them. Add a shared authorization seam with workspace AND context AND channel checks; do not implement a project ACL in this brief. +- Public workspace channels are readable by active workspace members; private/direct channels additionally require active channel membership. Every list/title/preview/history/thread/mention/count/read-state query uses the same access predicate. Archived channels permit authorized reads only. +- Active members create workspace public/private channels and become channel owner. An authorized channel owner or workspace `admin`/`owner` can manage that channel only with channel access; role alone is not a private/direct read bypass or self-join mechanism. All membership targets must themselves be authorized. Direct-channel participant sets are immutable; changes resolve a different exact-participant channel. Object/private-project access remains blocked until an authoritative context policy exists. +- Message authors may edit their own non-deleted messages. They may tombstone their own messages; channel owners and workspace `admin`/`owner` may tombstone messages for moderation. System/agent edits require the owning service path, not an impersonated user mutation. +- Channel lifecycle includes public/private creation, rename/archive, private membership management, and idempotent direct-channel creation for an exact sorted participant set. +- Message authors use a discriminated user/agent/system representation without fake user rows. +- A client-generated idempotency key is unique within the workspace, channel, and author scope. +- Every message receives a transactionally allocated, per-channel monotonic sequence. History and reconnect cursors are `(channel_id, sequence)`; message ID breaks ties only if allocation semantics ever permit one. +- Sending a message or thread reply inserts its versioned outbox event in the same transaction. Brief 1 owns this boundary; Brief 2 consumes the outbox. +- Threads are one level. Channel history returns root messages only plus reply counts/latest-reply metadata; thread history returns direct replies ordered by the same channel sequence; replies cannot themselves receive replies. Every root reference must resolve inside the same workspace and channel. +- Deletes are tombstones where thread/history integrity requires them. +- Read state stores the highest intentionally viewed message sequence per member/channel. `markChannelRead` may only advance it and inserts a read-state invalidation event for other devices. +- Mention parsing creates validated, workspace-scoped persisted message-mention rows in the message transaction. This convoy derives unread mention counts from those rows above the recipient's read cursor; the notifications epic later creates inbox projections from the same source events. +- All reads and mutations authorize through `ctx.workspace` plus the channel-type and role rules above. + +## Scope checkpoint and acceptance + +Next implementable chat brief: this one. Start with failing policy tests and schema contracts, then migration and workspace-only router/outbox. Existing `objects` includes projects but has no private-object ACL: do not invent project memberships or authorize by `workspace_id` alone. See [context follow-on](../../plans/Plan-workspace-communications/Epic-workspace-chat/Task-contextual-chat-authorization.md). + +- Same-workspace unsupported object contexts fail closed; no fallback public channel is created. +- Nonmember workspace admins cannot read/self-add to private/direct channels via administrative endpoints; counts/previews cannot reveal them. +- Access removal is respected by subsequent queries/writes; stale membership cannot override context denial. +- Foundation mention parsing cannot install/add/invoke agents; the agent-channel-membership task owns reviewed addition. + +- Duplicate send retries result in one persisted message. +- Cross-workspace and unauthorized private-channel requests return non-disclosing errors. +- Stable cursor pagination works for forward catch-up and backward history. +- Channel creation, rename/archive, private membership changes, immutable exact-participant direct channels, and message edit/moderation enforce the documented ownership and workspace-role rules. +- One-level thread roots/replies, persisted mention rows/counts, and durable per-member read state have working API coverage. +- `markChannelRead` only advances the per-channel sequence and atomically emits its invalidation event. +- Message and thread-reply commits atomically include the corresponding outbox event. +- `pnpm db:generate`, `pnpm lint`, `pnpm type-check`, and relevant tests pass. + +## Prompt + +Read `AGENTS.md`, `docs/Glossary.md`, database schema exports, `apps/web/server/trpc.ts`, and `apps/web/server/lib/resolve-workspace.ts` before writing. Use TDD for authorization and idempotency. Keep this brief limited to durable persistence/API and atomic outbox writes; do not implement outbox publication, WebSockets, or UI. diff --git a/.convoys/workspace-chat-foundation/brief-2-chat-realtime.md b/.convoys/workspace-chat-foundation/brief-2-chat-realtime.md new file mode 100644 index 0000000..7d65331 --- /dev/null +++ b/.convoys/workspace-chat-foundation/brief-2-chat-realtime.md @@ -0,0 +1,55 @@ +--- +brief: 2 +convoy: workspace-chat-foundation +role: role-implementer +title: Transactional outbox and realtime chat delivery +status: blocked +priority: P1 +slice_dependencies: + - brief-1-chat-persistence +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 2 — `Task-chat-realtime-delivery.md` + +## Goal + +Deliver committed chat events to authenticated subscribers across instances while preserving Postgres replay as the recovery path. + +## Files + +Expected scope; refine after brief 1 lands. + +- `packages/database/src/schema/` — consume the outbox and per-channel sequence contracts from brief 1; add a generated migration only if claim/retry state genuinely requires a schema extension. +- `packages/shared/src/` — consume or refine the versioned chat event envelopes and `(channel_id, sequence)` cursor schemas from brief 1 without creating a second contract. +- `apps/collab-server/src/index.ts` and focused modules — authenticated WebSocket subscriptions and Redis fan-out. +- `apps/worker/src/` or the established background-job boundary — claim/publish/retry outbox records. +- `apps/web/` realtime client integration — authenticated connection and catch-up trigger only; visual chat UI belongs to brief 3. +- Adjacent tests for authorization, retries, ordering, reconnect, and Redis outage behavior. + +## Required design + +- Commands remain HTTP/tRPC; do not introduce browser gRPC. +- Brief 1 already commits message/read-state rows and outbox events atomically; this brief must preserve that boundary and must not publish before commit. +- Publishers are at-least-once; consumers and clients handle duplicates by outbox event ID and durable entity ID. +- Redis carries cross-instance fan-out, not canonical history. +- Subscriptions and replay use Brief 1's workspace AND context AND channel authorization, never workspace membership alone. Public is relative to the authorized context; private/direct also require membership. Archived channels allow authorized read/catch-up only. Recheck on delivery and permission changes, close revoked subscriptions and stop fan-out; a Redis topic or cached subscription is not authority. Object contexts remain rejected until the context-policy follow-on. +- Each subscribed channel tracks its own `(channel_id, sequence)` cursor. Reconnect catch-up queries Postgres independently for each authorized channel; no workspace-global ordering is implied. +- Message, thread-reply, tombstone, channel lifecycle, membership, and read-state events use one versioned envelope. `markChannelRead` invalidations update badges on the user's other devices. +- Reuse Hocuspocus/Yjs only for ephemeral presence, typing, or drafts. + +## Acceptance + +- A committed message remains queryable and is eventually delivered after Redis recovers. +- Two realtime instances fan the same event to their local authorized subscribers. +- Duplicate outbox publishes do not duplicate rendered durable messages or read-state changes. +- Multi-channel reconnect catches each channel up from its own sequence without assuming cross-channel ordering. +- Public, private, direct, and archived subscription rules match Brief 1 and unauthorized attempts disclose no data. +- A read-state event clears or recalculates unread state on another connected device. +- Heartbeat, bounded buffers/backpressure, graceful shutdown, and reconnect behavior are covered. +- `pnpm lint && pnpm type-check && pnpm test && pnpm build` pass. + +## Prompt + +Read `config/CursorSync.md` for collab-server lifecycle, the current Hocuspocus/Redis configuration, and brief 1's resulting schema/API. Keep durable messages out of Yjs. Document any deploy-impacting port or process changes explicitly. diff --git a/.convoys/workspace-chat-foundation/brief-3-chat-ui.md b/.convoys/workspace-chat-foundation/brief-3-chat-ui.md new file mode 100644 index 0000000..1f058e8 --- /dev/null +++ b/.convoys/workspace-chat-foundation/brief-3-chat-ui.md @@ -0,0 +1,55 @@ +--- +brief: 3 +convoy: workspace-chat-foundation +role: role-implementer +title: Chat workspace UI and left-rail navigation +status: blocked +priority: P1 +slice_dependencies: + - brief-1-chat-persistence + - brief-2-chat-realtime +recommended_model: auto/coding +model_tier: standard +--- + +# Brief 3 — `Task-chat-ui-and-navigation.md` + +## Goal + +Expose workspace chat through the existing application shell with accessible channel navigation, durable history/read state, and realtime updates. + +## Files + +Expected scope; confirm conventions against nearby workspace routes and UI components. + +- `apps/web/app/(app)/[workspaceSlug]/chat/page.tsx` — channel selection/redirect or empty state. +- `apps/web/app/(app)/[workspaceSlug]/chat/[channelSlug]/page.tsx` — channel conversation route. +- `apps/web/components/layout/icon-rail.tsx` — Chat destination and accessible unread/mention badge. +- `apps/web/components/chat/` — channel list, paginated message feed, composer, thread affordance, connection state, and retry UI. +- `apps/web/components/providers/` or focused hooks — realtime event reconciliation with tRPC/React Query caches. +- Adjacent component/unit tests. + +## Required design + +- Server state is authoritative; realtime events invalidate or reconcile durable query data. Foundation UI supports workspace-context channels only; project/object entry points wait for the context authorization task. Never expose unauthorized titles/previews, mention candidates or counts; purge inaccessible cached conversations on denial/revocation. Mention UI must not promise agent addition/execution before its reviewed membership flow exists. +- Ordinary unread activity uses a dot. In this convoy, the numeric badge is computed from persisted message-mention rows above each channel read cursor plus unread direct-channel activity. The later notifications epic adds unified inbox projections without changing the rail contract. +- Unread means a channel's highest message sequence is greater than the member's last-read sequence. Opening a channel does not mark unseen content read until the client intentionally advances the visible boundary. +- Direct-channel unread activity contributes to the numeric Chat badge; ordinary public/private channel activity contributes only the unread dot unless the user is mentioned. +- Optimistic messages and thread replies have deterministic pending, committed, failed, and retry states keyed by the idempotency key. +- History and thread history use per-channel sequence cursor pagination and preserve scroll position while loading older messages. +- Realtime read-state invalidations reconcile rail/channel badges across the user's devices. +- Keyboard and screen-reader behavior cover navigation, unread indicators, composer, threads, and failures. +- Use existing Radix/Tailwind primitives and the current right-panel pattern for a minimally complete thread list/reply surface. + +## Acceptance + +- A user can enter Chat from the left rail, switch channels, send/retry messages and thread replies, intentionally advance the read boundary, and reload without losing state. +- Realtime messages and replies appear once and per-channel reconnect catch-up converges to server truth. +- Rail/channel unread, direct-message, and mention indicators update across devices after durable mark-read operations. +- Loading, empty, unauthorized, archived, offline, and failed-send states are handled. +- Responsive layout and keyboard navigation work without inaccessible color-only signals. +- `pnpm lint && pnpm type-check && pnpm test` pass. + +## Prompt + +Read the workspace layout, icon rail, right panel, UI primitives, tRPC provider, and the APIs/events produced by briefs 1–2. Do not add a separate design system or raw internal fetch calls. Keep notification inbox and agent participation out of this slice. diff --git a/apps/web/app/(app)/[workspaceSlug]/ai/page.tsx b/apps/web/app/(app)/[workspaceSlug]/ai/page.tsx index 766fec9..b279073 100644 --- a/apps/web/app/(app)/[workspaceSlug]/ai/page.tsx +++ b/apps/web/app/(app)/[workspaceSlug]/ai/page.tsx @@ -1,212 +1,23 @@ -"use client"; +import { Suspense } from "react"; -import { useEffect, useRef } from "react"; -import { useParams } from "next/navigation"; -import { useChat } from "@ai-sdk/react"; -import { AlertTriangle, Bot, Loader2, Send, Sparkles, User } from "lucide-react"; +import { AIWorkspace } from "@/components/ai/ai-workspace"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; - -export default function AIPage() { - const params = useParams(); - const workspaceSlug = - typeof params?.workspaceSlug === "string" ? params.workspaceSlug : ""; - - const scrollRef = useRef(null); - const inputRef = useRef(null); - - const { - messages, - input, - handleInputChange, - handleSubmit, - status, - error, - setInput, - } = useChat({ - api: "/api/chat", - body: { workspace: workspaceSlug }, - }); - - const isBusy = status === "submitted" || status === "streaming"; - - useEffect(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [messages, status]); +export default async function AIPage({ + params, +}: { + params: Promise<{ workspaceSlug: string }>; +}) { + const { workspaceSlug } = await params; return ( -
-
-
- -
-
-

AI Assistant

-

- Ask me anything about your workspace -

-
-
- -
- {messages.length === 0 ? ( -
-
- -
-
-

How can I help you today?

-

- Ask me to break down a task, draft an outline, or summarize what - you have open. -

-
-
- {[ - "Break this project into subtasks", - "Summarize my open tasks", - "Draft a sprint plan for this week", - ].map((suggestion) => ( - - ))} -
-
- ) : ( -
- {messages.map((msg) => ( -
-
- {msg.role === "user" ? ( - - ) : ( - - )} -
-
- {msg.content} -
-
- ))} - {status === "submitted" && ( -
-
- -
-
- -
-
- )} -
- )} - - {error ? : null} -
- -
-
-