Update agent.md with latest implementation details. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
5.6 KiB
Agent Module
src/agent/ — Agent lifecycle management, output parsing, multi-provider support, and account failover.
File Inventory
| File | Purpose |
|---|---|
types.ts |
Core types: AgentInfo, AgentManager interface, SpawnOptions, StreamEvent |
manager.ts |
MultiProviderAgentManager — main orchestrator class |
process-manager.ts |
AgentProcessManager — worktree creation, command building, detached spawn |
output-handler.ts |
OutputHandler — JSONL stream parsing, completion detection, proposal creation |
file-tailer.ts |
FileTailer — watches output files, emits line events |
file-io.ts |
Input/output file I/O: frontmatter writing, signal.json reading, tiptap conversion |
markdown-to-tiptap.ts |
Markdown to Tiptap JSON conversion using MarkdownManager |
index.ts |
Public exports, ClaudeAgentManager deprecated alias |
Sub-modules
| Directory | Purpose |
|---|---|
providers/ |
Provider registry, presets (7 providers), config types |
providers/parsers/ |
Provider-specific output parsers (Claude JSONL, generic line) |
accounts/ |
Account discovery, config dir setup, credential management, usage API |
credentials/ |
AccountCredentialManager — credential injection per account |
lifecycle/ |
LifecycleController — retry policy, signal recovery, missing signal instructions |
prompts/ |
Mode-specific prompt builders (execute, discuss, breakdown, decompose, refine) |
Key Flows
Spawning an Agent
- tRPC procedure calls
agentManager.spawn(options) - Manager generates alias (adjective-animal), creates DB record
AgentProcessManager.createWorktree()— creates git worktree at.cw-worktrees/agent/<alias>/file-io.writeInputFiles()— writes.cw/input/with initiative, pages, phase, task as frontmatter- Provider config builds spawn command via
buildSpawnCommand() spawnDetached()— launches detached child process with file output redirectionFileTailerwatches output file, firesonEventandonRawContentcallbacksOutputHandler.handleStreamEvent()processes each JSONL line- DB record updated with PID, output file path, session ID
agent:spawnedevent emitted
Completion Detection
FileTailerdetects process exitOutputHandler.handleCompletion()triggered- Primary path: Reads
.cw/output/signal.jsonfrom agent worktree - Signal contains
{ status: "done"|"questions"|"error", result?, questions?, error? } - Agent DB status updated accordingly (idle, waiting_for_input, crashed)
- For
done: proposals created from structured output;agent:stoppedemitted - For
questions: parsed and stored aspendingQuestions;agent:waitingemitted - Fallback: If signal.json missing, lifecycle controller retries with instruction injection
Account Failover
- On usage-limit error,
markAccountExhausted(id, until)called findNextAvailable(provider)returns least-recently-used non-exhausted account- Agent re-spawned with new account's credentials
agent:account_switchedevent emitted
Resume Flow
- tRPC
resumeAgentcalled withanswers: Record<string, string> - Manager looks up agent's session ID and provider config
buildResumeCommand()creates resume command with session flagformatAnswersAsPrompt(answers)converts answers to prompt text- New detached process spawned, same worktree, incremented session number
Provider Configuration
Providers defined in providers/presets.ts:
| Provider | Command | Resume | Prompt Mode |
|---|---|---|---|
| claude | claude |
--resume <id> |
native (-p) |
| claude-code | claude |
--resume <id> |
native |
| codex | codex |
none | flag (--prompt) |
| aider | aider |
none | flag (--message) |
| cline | cline |
none | flag |
| continue | continue |
none | flag |
| cursor-agent | cursor |
none | flag |
Each provider config specifies: command, args, resumeStyle, promptMode, structuredOutput, sessionId extraction, nonInteractive options.
Output Parsing
The OutputHandler processes JSONL streams from Claude CLI:
text_deltaevents → accumulated as text output, emitted viaagent:outputinitevent → session ID extractedresultevent → final result with structured data- Signal file (
signal.json) → authoritative completion status
For providers without structured output, the generic line parser accumulates raw text.
Credential Management
AccountCredentialManager in credentials/ handles OAuth token lifecycle:
read()— extractsclaudeAiOauthfrom.credentials.json. OnlyaccessTokenis required;refreshTokenandexpiresAtmay be null (setup tokens).isExpired()— returns false whenexpiresAtis null (setup tokens never "expire" from our perspective).ensureValid()— if expired andrefreshTokenexists, refreshes. If expired with norefreshToken, returns invalid with error.
Setup Tokens
Setup tokens (from claude setup-token) are long-lived OAuth access tokens with no refresh token or expiry. Register via:
cw account add --token <token> --email user@example.com
Stored as credentials: {"claudeAiOauth":{"accessToken":"<token>"}} and configJson: {"hasCompletedOnboarding":true}.
Log Chunks
Agent output is persisted to agent_log_chunks table:
onRawContentcallback fires for every output chunk- Fire-and-forget DB insert (no FK to agents — survives deletion)
- Session tracking: spawn=1, resume=previousMax+1
- Read path concatenates all sessions for full output history