Implements the errand workflow for small isolated changes that spawn a
dedicated agent in a git worktree:
- errand.create: branch + worktree + DB record + agent spawn
- errand.list / errand.get / errand.diff: read procedures
- errand.complete: transitions active→pending_review, stops agent
- errand.merge: merges branch, handles conflicts with conflictFiles
- errand.delete / errand.abandon: cleanup worktree, branch, agent
- errand.sendMessage: delivers user message directly to running agent
Supporting changes:
- Add 'errand' to AgentMode union and agents.mode enum
- Add sendUserMessage() to AgentManager interface and MockAgentManager
- MockAgentManager now accepts optional agentRepository to persist agents
to the DB (required for FK constraint satisfaction in tests)
- Add ORDER BY createdAt DESC, id DESC to errand findAll
- Fix dispatch/manager.test.ts missing sendUserMessage mock
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates the errands table (with conflictFiles column), errand-repository
port interface, DrizzleErrandRepository adapter, and wires the repository
into TRPCContext, the DI container, _helpers.ts requireErrandRepository guard,
and the test harness. Also fixes pre-existing TS error in controller.test.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aggregates all user-blocking action items into a single composite query:
- waitingForInput: agents paused on questions (oldest first)
- pendingReviewInitiatives: initiatives awaiting content review
- pendingReviewPhases: phases awaiting diff review
- planningInitiatives: active initiatives with all phases pending and no running agents
- blockedPhases: phases in blocked state with optional last-message snippet
Wired into appRouter and covered by 10 unit tests using in-memory Drizzle DB
and an inline MockAgentManager (no real processes required).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Option A ("ask inline, session stays open") described a path where the
errand agent could ask questions without writing signal.json, which broke
the server's completion detection (checkAgentCompletionResult polls for
done|questions|error status). Remove the Option A/B distinction and make
signal.json with questions status the single mechanism for all user-input
requests, consistent with how other agents handle blocking questions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The errand agent can now write { "status": "questions", ... } to
signal.json to pause mid-task and ask the user for clarification.
The session ends cleanly; the user answers via UI or CLI; the system
resumes the agent with their answers via sendUserMessage.
Two changes:
- buildErrandPrompt: adds "Option B" explaining the questions signal
format and the resume-on-answer lifecycle, alongside the existing
inline-question approach.
- sendUserMessage: extends allowed statuses from running|idle to also
include waiting_for_input, so agents paused on a questions signal
can be resumed when the user replies.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The `get(id)` method on SimpleGitWorktreeManager used `path.endsWith(id)`
to find worktrees. Since all agents working on the same project create
worktrees with the same project name suffix (e.g., "codewalk-district"),
cleanup for one agent could match and delete another agent's worktree.
Fix: match on `basename(worktreesDir)/id` so each manager's lookups are
scoped to its own worktree base directory.
- Adds syncAllMutation calling trpc.syncAllProjects once for all cards
- Shows Sync All button in header when ≥1 project exists (hidden otherwise)
- Propagates isPending to ProjectCard.syncAllPending to disable per-card sync
- On success: toasts all-success or failure count; invalidates listProjects + getProjectSyncStatus per project
- On network error: toasts Sync failed with err.message
- Adds unit tests for ProjectSyncManager.syncAllProjects: empty list, all succeed, partial failure, result shape, and failure counting logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously a single "that name or URL" message was thrown regardless of which
column violated uniqueness. Now the catch block inspects the error string from
SQLite to emit a name-specific or url-specific message, with a generic fallback
when neither column can be identified.
Adds vitest tests covering all four scenarios: name conflict, url conflict,
unknown column conflict, and non-UNIQUE error passthrough.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a dedicated "Asking questions" section to the errand prompt so the
agent knows it can pause, ask for clarification, and wait for the user
to reply via the UI chat input or `cw errand chat`. Previously the
prompt said "work interactively" with no guidance on the mechanism,
leaving the agent to guess.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, branch computation errors and ensureBranch failures were
silently swallowed for all tasks, allowing execution agents to spawn
without proper git isolation. This caused alert-pony to commit directly
to main instead of its task branch.
- manager.ts: Verify each project worktree subdirectory exists after
createProjectWorktrees; throw if any are missing. Convert passive
cwdVerified log to a hard guard.
- dispatch/manager.ts: Make branch computation and ensureBranch errors
fatal for execution tasks (execute, verify, merge, review) while
keeping them non-fatal for planning tasks.
Implements three primitives needed before errand tRPC procedures can be wired up:
- agentManager.sendUserMessage(agentId, message): resumes an errand agent with a
raw user message, bypassing the conversations table and conversationResumeLocks.
Throws on missing agent, invalid status, or absent sessionId.
- writeErrandManifest(options): writes .cw/input/errand.md (YAML frontmatter),
.cw/input/manifest.json (errandId/agentId/agentName/mode, no files/contextFiles),
and .cw/expected-pwd.txt to an agent workdir.
- buildErrandPrompt(description): minimal prompt for errand agents; exported from
prompts/errand.ts and re-exported from prompts/index.ts.
Also fixes a pre-existing TypeScript error in lifecycle/controller.test.ts (missing
backoffMs property in RetryPolicy mock introduced by a concurrent agent commit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace SESSION_STARTUP (full test suite run) and CONTEXT_MANAGEMENT
(progress file refs) with a minimal startup block (pwd, git status,
CLAUDE.md). Add skipPromptExtras option to SpawnAgentOptions to skip
inter-agent communication and preview deployment instructions. Conflict
agents now go straight to the resolution protocol — one post-resolution
test run instead of two.
Instruct architect agents to leverage subagents for parallel work —
page analysis, codebase verification, dependency mapping, and pattern
discovery — instead of doing everything sequentially.
Add 'resolving_conflict' to InitiativeActivityState and detect active
conflict agents (name starts with conflict-) in deriveInitiativeActivity.
Conflict resolution takes priority over pending_review since the agent
is actively working.
- Add resolving_conflict to shared types and activity derivation
- Include conflict agents in listInitiatives agent filter (name + mode)
- Map resolving_conflict to urgent variant with pulse in InitiativeCard
- Add merge: prefix to INITIATIVE_LIST_RULES for merge event routing
- Add spawnConflictResolutionAgent to INVALIDATION_MAP
- Add getActiveConflictAgent to detail page agent: SSE invalidation
7 of 12 initiative activity state transitions were broken due to missing
event routing at three layers: SSE event arrays, live-update prefix rules,
and mutation invalidation map.
- Add initiative:changes_requested to ALL_EVENT_TYPES and TASK_EVENT_TYPES
- Add initiative:/agent: prefix rules to initiatives list and detail pages
- Add approveInitiativeReview, requestInitiativeChanges, requestPhaseChanges
to INVALIDATION_MAP; add listInitiatives to approvePhase
- Extract INITIATIVE_LIST_RULES constant for reuse
Implements three primitives needed before errand tRPC procedures can be wired up:
- agentManager.sendUserMessage(agentId, message): resumes an errand agent with a
raw user message, bypassing the conversations table and conversationResumeLocks.
Throws on missing agent, invalid status, or absent sessionId.
- writeErrandManifest(options): writes .cw/input/errand.md (YAML frontmatter),
.cw/input/manifest.json (errandId/agentId/agentName/mode, no files/contextFiles),
and .cw/expected-pwd.txt to an agent workdir.
- buildErrandPrompt(description): minimal prompt for errand agents; exported from
prompts/errand.ts and re-exported from prompts/index.ts.
Also fixes a pre-existing TypeScript error in lifecycle/controller.test.ts (missing
backoffMs property in RetryPolicy mock introduced by a concurrent agent commit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getAgentOutput now returns timestamped chunks ({ content, createdAt }[])
instead of a flat string, preserving DB chunk timestamps
- parseAgentOutput accepts TimestampedChunk[] and propagates timestamps
to each ParsedMessage
- AgentOutputViewer displays HH:MM:SS timestamps on text, tool_call,
system, and session_end messages
- Live subscription chunks get client-side Date.now() timestamps
- Fix horizontal scroll: overflow-x-hidden + break-words on content areas
- AgentLogsTab polls getTaskAgent every 5s until an agent is found,
then stops polling for live subscription to take over
- Fix slide-over layout: details tab scrolls independently, logs tab
fills remaining flex space for proper AgentOutputViewer sizing
Adds controller.test.ts with three test cases asserting that handleAccountExhaustion
correctly emits agent:account_switched with previousAccountId, newAccountId, and
reason fields — and that the emit is skipped when no new account is available or
the agent has no accountId.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The `getAgentPrompt` tRPC procedure previously read exclusively from
`.cw/agent-logs/<name>/PROMPT.md`. Once the cleanup-manager removes
that directory, the prompt is gone forever.
Adds a `prompt` text column to the `agents` table and writes the fully
assembled prompt (including workspace layout, inter-agent comms, and
preview sections) to the DB in the same `repository.update()` call
that saves pid/outputFilePath after spawn.
`getAgentPrompt` now reads from DB first (`agent.prompt`) and falls
back to the filesystem only for agents spawned before this change.
Addresses review comment [MMcmVlEK16bBfkJuXvG6h].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Passes EventBus through LifecycleFactory and AgentLifecycleController so that
when an account is marked exhausted, an agent:account_switched event is emitted
with the previous and new account IDs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add getTaskAgent tRPC procedure to find the most recent agent for a task.
TaskSlideOver now has Details/Agent Logs tabs — logs tab renders
AgentOutputViewer when an agent exists, or an empty state otherwise.
git refuses to push to a branch that is currently checked out in a
non-bare repository. When the clone's origin is the user's local repo,
this blocks merge_and_push entirely.
On "branch is currently checked out" error, temporarily set
receive.denyCurrentBranch=updateInstead on the remote and retry.
This uses git's built-in mechanism to update the working tree safely
(rejects if dirty).
fastForwardBranch used git merge --ff-only which fails when the clone
has uncommitted files. This caused the ff to be silently skipped, the
merge to proceed on stale main, and the push to fail (non-fast-forward).
Switched to update-ref which only moves the branch pointer without
touching the working tree.
- Add `errands` table to schema with status enum and relations to agents/projects
- Add `errand` mode to agents.mode enum
- Add push rollback in orchestrator: if push fails after merge, reset to previousRef to preserve the review diff
- Extend MergeResult with previousRef for rollback support; update branch-manager and simple-git-branch-manager
- Add orchestrator tests for push rollback behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When merge_and_push failed at the push step, the local defaultBranch ref
was left pointing at the merge commit. This made the three-dot diff
(defaultBranch...initiativeBranch) return empty because main already
contained all changes — causing the review tab to show "no changes."
Now mergeBranch returns the previous ref, and approveInitiative restores
it on push failure. Also repaired the corrupted clone state.
Three bugs causing empty phase diffs when server restarts during agent
execution:
1. Startup ordering race: reconcileAfterRestart() emitted agent:stopped
before orchestrator registered listeners — events lost. Moved
reconciliation to after orchestrator.start().
2. Stuck in_progress tasks: recoverDispatchQueues() only re-queued
pending tasks. Added recovery for in_progress tasks whose agents
are dead (not running/waiting_for_input).
3. Branch force-reset destroys work: git branch -f wiped commits when
a second agent was dispatched for the same task. Now checks if the
branch has commits beyond baseBranch before resetting.
Also adds:
- agent:crashed handler with auto-retry (MAX_TASK_RETRIES=3)
- retryCount column on tasks table + migration
- retryCount reset on manual retryBlockedTask()
Two additional fixes to prevent agents from writing .cw/output/ in the
wrong location:
1. Always create .cw/output/ at the agent workdir root during spawn,
even when no inputContext is provided. This gives the agent a visible
anchor directory so it doesn't create one inside a project subdir.
2. Add absolute output path instruction to the workspace layout prompt
for multi-project agents, explicitly telling them to write .cw/output/
relative to the workdir root, not their current cd location.
Throws if branch and baseBranch are identical, preventing
git branch -f from force-resetting shared branches (like
the initiative branch) when accidentally passed as both.
Conflict-resolution agents (and any initiative-based agent) can write
.cw/output/signal.json inside a project subdirectory (e.g.
agent-workdirs/<name>/codewalk-district/.cw/output/) rather than the
parent agent workdir. This caused two failures:
1. spawnInternal wrote spawn-diagnostic.json before registering the
agent in activeAgents and starting pollForCompletion. If the .cw/
directory didn't exist (no inputContext provided), the write threw
ENOENT, orphaning the running process with no completion monitoring.
2. resolveAgentCwd in cleanup-manager and output-handler only probed
for a workspace/ subdirectory (standalone agents) but not project
subdirectories, so reconciliation and completion handling couldn't
find signal.json and marked the agent as crashed.
Fixes:
- Move activeAgents registration and pollForCompletion setup before
the diagnostic write; make the write non-fatal with mkdir -p
- Add project subdirectory probing to resolveAgentCwd in both
cleanup-manager.ts and output-handler.ts
approveInitiative was merging and pushing with a stale local
defaultBranch, causing "rejected (fetch first)" when origin/main
had advanced since the last project sync. Now fetches remote and
fast-forwards the target branch before merging.
Add update method to ReviewCommentRepository, updateReviewComment tRPC
procedure, and inline edit UI in CommentThread. Edit button appears on
user-authored comments (not agent comments) when unresolved. Uses the
existing CommentForm with a new initialValue prop.
spawnConflictResolutionAgent was passing the initiative branch as branchName,
causing SimpleGitWorktreeManager.create() to force-reset it to the target
branch. Now spawns on a unique temp branch based off the initiative branch,
with the agent using git update-ref to advance the initiative branch after
resolving conflicts. Also fixes stale diff/commits cache after resolution.
simple-git's .raw() resolves successfully even on exit code 1,
returning stdout content. git merge-tree --write-tree outputs
CONFLICT markers to stdout (not stderr), so the catch block
never fired and conflicts were reported as clean merges.
merge_requires_approval (initiatives) and requires_approval (tasks)
were removed from schema.ts in the task-approval removal but left in
the DB because 0030 assumed SQLite couldn't DROP COLUMN. SQLite 3.35+
supports it. These orphaned columns caused the old stale-build approval
code path to silently set detail tasks to pending_approval, stranding
them and blocking phase completion.
Pre-merge mergeability check via `git merge-tree --write-tree` (dry-run, no
side effects). When conflicts exist the "Merge & Push" button is disabled and
a ConflictResolutionPanel shows conflict files with options to resolve manually
or spawn a conflict-resolution agent. Agent questions appear inline via
QuestionForm; on completion the mergeability re-checks automatically.
New server-side: MergeabilityResult type, BranchManager.checkMergeability,
conflict-resolution prompt, checkInitiativeMergeability query,
spawnConflictResolutionAgent mutation, getActiveConflictAgent query.
New frontend: useConflictAgent hook, ConflictResolutionPanel component,
mergeability badge + panel integration in InitiativeReview.
handleTaskCompleted and handlePhaseAllTasksDone both bailed early when
initiative had no branch, silently skipping phase status transitions.
Also, merge failures would skip the phase completion check entirely.
- Decouple phase completion check from branch existence
- Wrap merge in try/catch so phase check runs even if merge fails
- Route updateTaskStatus through dispatchManager.completeTask when
completing, so the task:completed event fires for orchestration
Use a temp branch + update-ref to avoid "already checked out" error
when merging into the default branch. Also show actual branch name
in the Merge & Push button instead of "Default".
When re-dispatching tasks, the branch from a previous run may still
exist. Instead of failing with "a branch named X already exists",
reset the existing branch to the base and check it out.
Snapshots were stale since migration 0008. Generated a schema-derived
snapshot at 0032 so drizzle-kit generate works again (zero diff on
current schema.ts). Also fixed migration 0032 to use statement-breakpoint
separator required by better-sqlite3.
- Added 0032_snapshot.json derived from current schema.ts
- Fixed 0032 SQL to use --> statement-breakpoint between statements
- Updated CLAUDE.md and database-migrations.md with correct workflow
The migration file existed but wasn't in _journal.json, so drizzle-kit's
migrator never applied it. Adds the journal entry for 0032_add_comment_threading.