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.
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>
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.
Critical: review/merge tasks hit an early return in handleTaskCompleted()
that skipped the phase completion check, leaving phases stuck in
in_progress forever. Changed to an if-block wrapping only the merge step.
Also adds requestChangesOnInitiative() which creates/reuses a
"Finalization" phase for initiative-level review feedback, with dedup
guards for both phase and initiative request-changes flows.
Path-prefix routing (`localhost:9100/<id>/`) broke SPAs because absolute
asset paths (`/assets/index.js`) didn't match the `handle_path /<id>/*`
route. Subdomain routing (`<id>.localhost:9100/`) resolves this since
all paths are relative to the root. Chrome/Firefox resolve *.localhost
to 127.0.0.1 natively — no DNS setup needed.
The Caddyfile was using the host port (e.g., 9100) as the Caddy listen
address, but Docker maps host:9100 → container:80. Caddy inside the
container was listening on 9100 while Docker only forwarded to port 80,
causing all health checks to fail with "connection reset by peer".
Completed phases showed "No phases pending review" because:
1. Frontend filtered only pending_review phases
2. Server rejected non-pending_review phases
3. After merge, three-dot diff returned empty (merge base moved)
Fix: store pre-merge merge base hash on phase, use it to reconstruct
diffs for completed phases. Frontend now shows both pending_review and
completed phases with read-only mode (Merged badge) for completed ones.
Two fixes:
- Call previewsQuery.refetch() in startPreview.onSuccess so the UI
transitions from "building" to the preview link without a page refresh.
- Switch from subdomain routing (*.localhost) to path-based routing
(localhost:<port>/<id>/) since macOS doesn't resolve wildcard
localhost subdomains.
Docker compose requires project names to be lowercase alphanumeric
with hyphens/underscores only. The default nanoid alphabet includes
uppercase and special characters, causing build failures.
Same orphaned-changeset pattern as deletePhase: manually deleting all
tasks from a detail changeset now marks it reverted. Also added
deleteTask to the invalidation map (was missing entirely).
Manually deleting phases left their parent changeset as "applied",
causing the Plan tab to show a stale "Created N phases" banner with
no phases visible.
- deletePhase now checks if all phases from a changeset are gone and
marks it reverted
- PlanSection filters out dismissed agents so dismissed banners stay
hidden
- revertChangeSet marks reverted before entity deletion to prevent
ghost state on partial failure
- deletePhase invalidation now includes listChangeSets
Both phaseQueue and taskQueue are in-memory Maps lost on restart. Now
the orchestrator's start() method scans active initiatives and:
- Re-queues approved phases into the phase dispatch queue
- Re-queues pending tasks for in_progress phases into the task dispatch queue
- Triggers a dispatch cycle if anything was recovered
This fixes stuck phases/tasks after server restarts.
The in-memory phaseQueue (Map) in DefaultPhaseDispatchManager is lost on
server restart. After approving a phase review, dispatchNextPhase() found
nothing in the empty queue, so the next unblocked phase never started.
Now the orchestrator re-queues all approved phases for the initiative from
the DB before attempting to dispatch, making the queue self-healing.
Adds `cw account extract [--email <email>]` subcommand to the accountCommand
group. Reads directly from the local Claude config via extractCurrentClaudeAccount()
without requiring a server connection. Supports optional email verification,
outputting JSON with email, configJson (stringified), and credentials fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the addAccountByToken procedure to accountProcedures(), which
accepts an email and raw OAuth token, stores the token as claudeAiOauth
credentials, and upserts the account (create or updateAccountAuth based
on findByEmail). Covers the four scenarios with unit tests: new account,
existing account, empty email, and empty token validation errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strip the unused .input(z.object({ lastEventId })) from all 6 subscription
procedures — the parameter was never consumed by eventBusIterable. Remove the
now-unused zod import. Add at-most-once delivery JSDoc to the EventBus interface
to make the real guarantee explicit. Add compliance comment above
onConversationUpdate noting what to wire when a conversation view is built.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task dispatch computed baseBranch as the phase branch name but never
ensured it existed in the git clone. When phases weren't dispatched
through the PhaseDispatchManager (which creates branches), the
git worktree add failed with "fatal: invalid reference".
Now DefaultDispatchManager calls ensureBranch for both the initiative
and phase branches before spawning, matching what PhaseDispatchManager
already does.
Blocked tasks (from spawn failures) were a dead-end with no way to
recover. Add retryBlockedTask to DispatchManager that resets status
to pending and re-queues, a tRPC mutation that also kicks dispatchNext,
and a Retry button in the task slide-over when status is blocked.