Files
Codewalkers/docs/server-api.md
Lukas May 5e77bf104c feat: Add remote sync for project clones
Fetch remote changes before agents start working so they build on
up-to-date code. Adds ProjectSyncManager with git fetch + ff-only
merge of defaultBranch, integrated into phase dispatch to sync
before branch creation.

- Schema: lastFetchedAt column on projects table (migration 0029)
- Events: project:synced, project:sync_failed
- Phase dispatch: sync all linked projects before creating branches
- tRPC: syncProject, syncAllProjects, getProjectSyncStatus
- CLI: cw project sync [name] --all, cw project status [name]
- Frontend: sync button + ahead/behind badge on projects settings
2026-03-05 11:45:09 +01:00

262 lines
12 KiB
Markdown

# Server & API Module
`apps/server/server/` — HTTP server, `apps/server/trpc/` — tRPC procedures, `apps/server/coordination/` — merge queue.
## HTTP Server
**Framework**: Native `node:http` (no Express/Fastify)
**Default**: `127.0.0.1:3847`
**PID file**: `~/.cw/server.pid`
### Routes
| Route | Method | Purpose |
|-------|--------|---------|
| `/health` | GET | Health check (`{ status, uptime, processCount }`) |
| `/status` | GET | Full server status with process list |
| `/trpc/*` | POST | All tRPC procedure calls |
### Lifecycle
- `CoordinationServer.start()` — checks PID file, creates HTTP server, emits `server:started`
- `CoordinationServer.stop()` — emits `server:stopped`, closes server, removes PID file
- `GracefulShutdown` handles SIGTERM/SIGINT/SIGHUP with 10s timeout
### tRPC Adapter
`trpc-adapter.ts` converts `node:http` IncomingMessage/ServerResponse to fetch Request/Response for tRPC. Subscriptions stream via ReadableStream bodies (SSE).
## tRPC Context
All procedures share a context with optional dependencies:
```typescript
interface TRPCContext {
eventBus: EventBus // always present
serverStartedAt: Date | null
processCount: number
agentManager?: AgentManager // optional
taskRepository?: TaskRepository
// ... all 10 repositories, 3 managers, credentialManager, workspaceRoot
}
```
Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTERNAL_SERVER_ERROR)` if a dependency is missing.
## Procedure Reference
### System
| Procedure | Type | Description |
|-----------|------|-------------|
| health | query | Health check with uptime |
| status | query | Server status with process list |
| systemHealthCheck | query | Account, agent, project health |
### Agents
| Procedure | Type | Description |
|-----------|------|-------------|
| spawnAgent | mutation | Spawn new agent (taskId, prompt, provider, mode) |
| stopAgent | mutation | Stop agent by name or ID |
| deleteAgent | mutation | Delete agent and clean up worktree |
| dismissAgent | mutation | Dismiss agent (set userDismissedAt) |
| resumeAgent | mutation | Resume with answers |
| listAgents | query | All agents |
| getAgent | query | Single agent by name or ID |
| getAgentResult | query | Execution result |
| getAgentQuestions | query | Pending questions |
| getAgentOutput | query | Full output from DB log chunks |
| getActiveRefineAgent | query | Active refine agent for initiative |
| listWaitingAgents | query | Agents waiting for input |
| onAgentOutput | subscription | Live raw JSONL output stream via EventBus |
### Tasks
| Procedure | Type | Description |
|-----------|------|-------------|
| listTasks | query | Child tasks of parent |
| getTask | query | Single task |
| updateTaskStatus | mutation | Change task status |
| updateTask | mutation | Update task fields (name, description) |
| createInitiativeTask | mutation | Create task on initiative |
| createPhaseTask | mutation | Create task on phase |
| listInitiativeTasks | query | All tasks for initiative |
| listPhaseTasks | query | All tasks for phase |
| listPendingApprovals | query | Tasks with status=pending_approval |
| deleteTask | mutation | Delete a task by ID |
| listPhaseTaskDependencies | query | All task dependency edges for tasks in a phase |
| listInitiativeTaskDependencies | query | All task dependency edges for tasks in an initiative |
| approveTask | mutation | Approve and complete task |
### Initiatives
| Procedure | Type | Description |
|-----------|------|-------------|
| createInitiative | mutation | Create with optional branch/projectIds/description, auto-creates root page (seeded with description); if description provided, auto-spawns refine agent |
| listInitiatives | query | Filter by status and/or projectId; returns `activity` (state, activePhase, phase counts) computed from phases |
| getInitiative | query | With projects array |
| updateInitiative | mutation | Name, status |
| deleteInitiative | mutation | Cascade delete initiative and all children |
| updateInitiativeConfig | mutation | mergeRequiresApproval, executionMode, branch |
### Phases
| Procedure | Type | Description |
|-----------|------|-------------|
| createPhase | mutation | Create in initiative |
| listPhases | query | By initiative |
| getPhase | query | Single phase |
| updatePhase | mutation | Name, content, status |
| approvePhase | mutation | Validate and approve |
| deletePhase | mutation | Cascade delete |
| createPhasesFromPlan | mutation | Bulk create from agent output |
| createPhaseDependency | mutation | Add dependency edge |
| removePhaseDependency | mutation | Remove dependency edge |
| listInitiativePhaseDependencies | query | All dependency edges |
| getPhaseDependencies | query | What this phase depends on |
| getPhaseDependents | query | What depends on this phase |
| getPhaseReviewDiff | query | Full branch diff for pending_review phase |
| getPhaseReviewCommits | query | List commits between initiative and phase branch |
| getCommitDiff | query | Diff for a single commit (by hash) in a phase |
| approvePhaseReview | mutation | Approve and merge phase branch |
| requestPhaseChanges | mutation | Request changes: creates revision task from unresolved comments, resets phase to in_progress |
| listReviewComments | query | List review comments by phaseId |
| createReviewComment | mutation | Create inline review comment on diff |
| resolveReviewComment | mutation | Mark review comment as resolved |
| unresolveReviewComment | mutation | Mark review comment as unresolved |
### Phase Dispatch
| Procedure | Type | Description |
|-----------|------|-------------|
| queuePhase | mutation | Queue approved phase |
| queueAllPhases | mutation | Queue all approved phases for initiative |
| dispatchNextPhase | mutation | Start next ready phase |
| getPhaseQueueState | query | Queue state |
| createChildTasks | mutation | Create tasks from detail parent |
### Architect (High-Level Agent Spawning)
| Procedure | Type | Description |
|-----------|------|-------------|
| spawnArchitectDiscuss | mutation | Discussion agent |
| spawnArchitectPlan | mutation | Plan agent (generates phases). Passes initiative context (phases, execution tasks only, pages) |
| spawnArchitectRefine | mutation | Refine agent (generates proposals) |
| spawnArchitectDetail | mutation | Detail agent (generates tasks). Passes initiative context (phases, execution tasks only, pages) |
### Dispatch
| Procedure | Type | Description |
|-----------|------|-------------|
| queueTask | mutation | Add task to dispatch queue |
| dispatchNext | mutation | Dispatch next ready task |
| getQueueState | query | Queue state |
| completeTask | mutation | Complete with approval check |
### Coordination (Merge Queue)
| Procedure | Type | Description |
|-----------|------|-------------|
| queueMerge | mutation | Queue task for merge |
| processMerges | mutation | Process merge queue |
| getMergeQueueStatus | query | Queue state |
| getNextMergeable | query | Next ready-to-merge task |
### Projects
| Procedure | Type | Description |
|-----------|------|-------------|
| registerProject | mutation | Clone git repo, create record. Validates defaultBranch exists in repo |
| listProjects | query | All projects |
| getProject | query | Single project |
| updateProject | mutation | Update project settings (defaultBranch). Validates branch exists in repo |
| deleteProject | mutation | Delete clone and record |
| getInitiativeProjects | query | Projects for initiative |
| updateInitiativeProjects | mutation | Sync junction table |
| syncProject | mutation | `git fetch` + ff-only merge of defaultBranch, updates `lastFetchedAt` |
| syncAllProjects | mutation | Sync all registered projects |
| getProjectSyncStatus | query | Returns `{ ahead, behind, lastFetchedAt }` for a project |
### Pages
| Procedure | Type | Description |
|-----------|------|-------------|
| getRootPage | query | Auto-creates if missing |
| getPage | query | Single page |
| getPageUpdatedAtMap | query | Bulk updatedAt check |
| listPages | query | By initiative |
| listChildPages | query | By parent page |
| createPage | mutation | Create, emit page:created |
| updatePage | mutation | Title/content/sortOrder, emit page:updated |
| deletePage | mutation | Delete, emit page:deleted |
### Accounts
| Procedure | Type | Description |
|-----------|------|-------------|
| listAccounts | query | All accounts |
| addAccount | mutation | Create account |
| removeAccount | mutation | Delete account |
| refreshAccounts | mutation | Clear expired exhaustion |
| updateAccountAuth | mutation | Update credentials |
| markAccountExhausted | mutation | Set exhaustion timer |
| listProviderNames | query | Available provider names |
### Proposals
| Procedure | Type | Description |
|-----------|------|-------------|
| listProposals | query | By agent or initiative |
| acceptProposal | mutation | Apply side effects, auto-dismiss agent |
| dismissProposal | mutation | Dismiss, auto-dismiss agent |
| acceptAllProposals | mutation | Bulk accept with error collection |
| dismissAllProposals | mutation | Bulk dismiss |
### Subscriptions (SSE)
| Procedure | Type | Events |
|-----------|------|--------|
| onEvent | subscription | All 30+ event types |
| onAgentUpdate | subscription | agent:* events (8 types) |
| onTaskUpdate | subscription | task:* + phase:* events (8 types) |
| onPageUpdate | subscription | page:created/updated/deleted |
Subscriptions use `eventBusIterable()` — queue-based async generator, max 1000 events, 30s heartbeat.
## Coordination Module
`apps/server/coordination/` manages merge queue:
- **CoordinationManager** port: `queueMerge`, `getNextMergeable`, `processMerges`, `handleConflict`, `getQueueState`
- **DefaultCoordinationManager** adapter: in-memory queue, dependency-ordered processing
- **ConflictResolutionService**: creates resolution tasks for merge conflicts
- Merge flow: queue → check deps → merge via WorktreeManager → handle conflicts
- Events: `merge:queued`, `merge:started`, `merge:completed`, `merge:conflicted`
## Preview Procedures
Docker-based preview deployments. No database table — Docker is the source of truth.
| Procedure | Type | Description |
|-----------|------|-------------|
| `startPreview` | mutation | Start preview: `{initiativeId, phaseId?, projectId, branch}` → PreviewStatus |
| `stopPreview` | mutation | Stop preview: `{previewId}` |
| `listPreviews` | query | List active previews: `{initiativeId?}` → PreviewStatus[] |
| `getPreviewStatus` | query | Get preview status: `{previewId}` → PreviewStatus |
Context dependency: `requirePreviewManager(ctx)` — requires `PreviewManager` from container.
## Conversation Procedures
Inter-agent communication for parallel agents.
| Procedure | Type | Description |
|-----------|------|-------------|
| `createConversation` | mutation | Ask a question: `{fromAgentId, toAgentId?, phaseId?, taskId?, question}` → Conversation |
| `getPendingConversations` | query | Poll for incoming questions: `{agentId}` → Conversation[] |
| `getConversation` | query | Get conversation by ID: `{id}` → Conversation |
| `answerConversation` | mutation | Answer a conversation: `{id, answer}` → Conversation |
Target resolution: `toAgentId` → direct; `taskId` → find running agent by task; `phaseId` → find running agent by any task in phase.
Context dependency: `requireConversationRepository(ctx)`, `requireAgentManager(ctx)`.
## Chat Session Procedures
Persistent chat loop for iterative phase/task refinement via agent.
| Procedure | Type | Description |
|-----------|------|-------------|
| `sendChatMessage` | mutation | Send message: `{targetType, targetId, initiativeId, message, provider?}``{sessionId, agentId, action}` |
| `getChatSession` | query | Get active session with messages: `{targetType, targetId}` → ChatSession \| null |
| `closeChatSession` | mutation | Close session and dismiss agent: `{sessionId}``{success}` |
`sendChatMessage` finds or creates an active session, stores the user message, then either resumes the existing agent (if `waiting_for_input`) or spawns a fresh one with full chat history + initiative context. Agent runs in `'chat'` mode and signals `"questions"` after applying changes, staying alive for the next message.
Context dependency: `requireChatSessionRepository(ctx)`, `requireAgentManager(ctx)`, `requireInitiativeRepository(ctx)`, `requireTaskRepository(ctx)`.