Commit Graph

787 Commits

Author SHA1 Message Date
Lukas May
8f4fa2a233 fix: disambiguate CONFLICT errors in registerProject by inspecting the SQLite UNIQUE constraint column
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>
2026-03-06 14:37:21 +01:00
Lukas May
4c99f89462 feat: Improve RegisterProjectDialog UX for long-running clone
- Add Loader2 spinner with "Cloning repository…" label while pending
- Keep Cancel button always enabled (no disabled gate)
- Map INTERNAL_SERVER_ERROR to user-friendly clone failure message
- Invalidate listProjects cache on success so new card appears immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:34:34 +01:00
Lukas May
e2c489dc48 feat: teach errand agent how to ask questions interactively
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>
2026-03-06 14:31:03 +01:00
Lukas May
d867a5f397 Merge branch 'main' into cw/account-ui-conflict-1772803526476
# Conflicts:
#	apps/server/trpc/router.test.ts
#	docs/server-api.md
2026-03-06 14:27:38 +01:00
Lukas May
23964374d1 Merge branch 'cw/agent-details' into cw-merge-1772802959182 2026-03-06 14:15:59 +01:00
Lukas May
6f38ae21ed Merge branch 'main' into cw/agent-details-conflict-1772802863659
# Conflicts:
#	docs/server-api.md
2026-03-06 14:15:30 +01:00
Lukas May
30b27f8b4a fix: Conflict agent auto-dismiss fails on page load/remount
prevStateRef was initialized with current state, so when the page loaded
with an already-idle conflict agent, the transition guard was immediately
false and dismiss() never fired. Initialize with null instead.
2026-03-06 14:13:42 +01:00
Lukas May
0f1c578269 fix: Fail fast when agent worktree creation or branch setup fails
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.
2026-03-06 14:08:59 +01:00
Lukas May
343c6a83a8 feat: Add agent spawn infrastructure for errand mode
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>
2026-03-06 14:05:26 +01:00
Lukas May
b419981924 perf: Speed up conflict resolution agents by trimming prompt bloat
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.
2026-03-06 14:05:23 +01:00
Lukas May
a0574a1ae9 feat: Add subagent usage guidance to refine and plan prompts
Instruct architect agents to leverage subagents for parallel work —
page analysis, codebase verification, dependency mapping, and pattern
discovery — instead of doing everything sequentially.
2026-03-06 13:39:19 +01:00
Lukas May
a94e72ccbc feat: Wire AddAccountDialog and UpdateCredentialsDialog into health page and AccountCard
- health.tsx: Add Account button + AddAccountDialog, Refresh button with
  tooltip and spinner calling refreshAccounts mutation, inline account
  error state instead of full-page error, updated empty state text, and
  active-agent warning on delete confirm
- AccountCard.tsx: Show KeyRound button when credentials/token invalid,
  opens UpdateCredentialsDialog pre-populated with account identity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:35:14 +01:00
Lukas May
1629285778 Merge branch 'refs/heads/main' into cw/agent-details-conflict-1772799979862
# Conflicts:
#	apps/server/drizzle/meta/_journal.json
2026-03-06 13:34:28 +01:00
Lukas May
e3246baf51 feat: Show resolving_conflict activity state on initiative cards
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
2026-03-06 13:32:37 +01:00
Lukas May
b4baba67a5 Merge branch 'cw/account-ui-phase-updatecredentialsdialog-component' into cw-merge-1772800279801 2026-03-06 13:31:20 +01:00
Lukas May
05efa9c08e fix: Replace getTaskAgent polling with event-driven invalidation
Add getTaskAgent to the agent: prefix SSE invalidation rule so spawned
agents are picked up immediately instead of polling every 5s.
2026-03-06 13:30:18 +01:00
Lukas May
575ad48a55 feat: Add UpdateCredentialsDialog component for re-authenticating accounts
Implements a self-contained dialog that allows users to update credentials
for an existing account without deleting and re-adding it. Supports two
modes: token-based (Tab A) and credentials JSON (Tab B).

Also sets up web component test infrastructure: vitest now includes
apps/web/**/*.test.tsx files with happy-dom environment, @vitejs/plugin-react
for JSX, and @testing-library/{react,jest-dom,user-event} packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:27:13 +01:00
Lukas May
ebef093d3f fix: Add missing event routing for initiative status real-time refresh
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
2026-03-06 13:25:31 +01:00
Lukas May
52e238924c feat: Add agent spawn infrastructure for errand mode
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>
2026-03-06 13:22:15 +01:00
Lukas May
7088c511a9 feat: Add Details tab to agent right-panel with metadata, input files, and prompt sections
Adds an Output/Details tab bar to the agents page right-panel. The Details
tab renders AgentDetailsPanel, which surfaces agent metadata (status, mode,
provider, initiative link, task name, exit code), input files with a
file-picker UI, and the effective prompt text — all streamed via the new
getAgent/getAgentInputFiles/getAgentPrompt tRPC procedures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:21:59 +01:00
Lukas May
89282d33b3 feat: Add AddAccountDialog component for account management UI
Two-tab dialog (setup token + credentials JSON) with full validation,
error handling, provider select with fallback, and state reset on open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:21:55 +01:00
Lukas May
d52317ac5d feat: Add timestamps to agent logs and fix horizontal scroll
- 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
2026-03-06 13:18:42 +01:00
Lukas May
4ee71d45f4 test: Add regression tests for agent:account_switched emission in lifecycle controller
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>
2026-03-06 13:17:32 +01:00
Lukas May
b2f4004191 feat: Persist agent prompt in DB so getAgentPrompt survives log cleanup
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>
2026-03-06 13:13:01 +01:00
Lukas May
3e2a570447 feat: Emit account_switched event on account exhaustion in lifecycle controller
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>
2026-03-06 13:12:25 +01:00
Lukas May
3f3954672e feat: Add Agent Logs tab to task slide-over
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.
2026-03-06 13:10:46 +01:00
Lukas May
deffdc7c4f fix: Move project pills inline after initiative name 2026-03-06 13:07:25 +01:00
Lukas May
b6218584ee feat: Show project pills on initiative cards in list view
Add projects to the listInitiatives tRPC response and render them as
outline badge pills between the initiative name and activity row.
2026-03-06 13:06:33 +01:00
Lukas May
243f24a397 fix: Eliminate content page flickering from layout shifts and double invalidation
- Reserve fixed height for "Saving..." indicator instead of conditionally
  rendering it, preventing layout shift on every auto-save cycle
- Remove getPage from updatePage mutation cache invalidation — useAutoSave
  already handles optimistic updates, and SSE events cover external changes.
  This eliminates double-invalidation (mutation + SSE) refetch storms.
- Memoize TiptapEditor extensions array to avoid recreating extensions and
  pageLinkDeletionDetector on every render
- Memoize useLiveUpdates rules array in initiative detail page
2026-03-06 12:46:39 +01:00
Lukas May
08c1bed465 Merge branch 'cw/unified-event-flow' into cw-merge-1772797070237 2026-03-06 12:37:50 +01:00
Lukas May
a86a373d42 fix: Handle push to checked-out branch in local non-bare repos
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).
2026-03-06 12:37:21 +01:00
Lukas May
940b0f8ed2 feat: Add errands persistence layer — repository port, Drizzle adapter, migration, and tests
- Add errand-repository.ts port with ErrandRepository, ErrandWithAlias, ErrandStatus types
- Add DrizzleErrandRepository adapter with create, findById (left-joins agents for alias),
  findAll (optional projectId/status filters, desc by createdAt), update, delete
- Wire exports into repositories/index.ts and repositories/drizzle/index.ts
- Add migration 0035_faulty_human_fly.sql (CREATE TABLE errands) and drizzle snapshot
- Add 13 tests covering CRUD, filtering, ordering, agentAlias join, cascade/set-null FK behaviour
- Update docs/database.md to document the errands table and ErrandRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:35:06 +01:00
Lukas May
5d1292c7ad fix: Use update-ref for fast-forward to avoid dirty working tree failures
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.
2026-03-06 12:34:21 +01:00
Lukas May
6a76e17cef feat: Add errands table, errand agent mode, and push rollback on merge failure
- 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>
2026-03-06 12:31:41 +01:00
Lukas May
00e426ac00 fix: Roll back merge when push fails in initiative approval
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.
2026-03-06 12:31:35 +01:00
Lukas May
eac03862e3 fix: Prevent lost task completions after server restart
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()
2026-03-06 12:19:59 +01:00
Lukas May
a69527b7d6 fix: Remove upward box-shadow on ReviewHeader that covers tab bar
The sticky ReviewHeader had shadow-[0_-50px_0_0_hsl(var(--background))]
which painted a 50px background-color rectangle upward, overlapping the
tab navigation bar (only ~12px away). The header's bg-card is already
opaque, making the shadow unnecessary for scroll coverage.
2026-03-06 12:18:31 +01:00
Lukas May
6034f6d854 Merge branch 'main' into cw/unified-event-flow-conflict-1772795597661
# Conflicts:
#	apps/web/src/components/review/ReviewTab.tsx
#	apps/web/src/routes/initiatives/$id.tsx
2026-03-06 12:16:07 +01:00
Lukas May
17f92040c7 fix: Ensure agents write signal.json to the correct directory
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.
2026-03-06 12:14:37 +01:00
Lukas May
9f5715558e fix: Auto-dismiss conflict panel and re-check mergeability on completion
Instead of showing a manual "Re-check Mergeability" button after the
conflict agent finishes, auto-dismiss the agent and trigger mergeability
re-check immediately when the state transitions to completed.
2026-03-06 12:14:16 +01:00
Lukas May
f4dbaae0e3 fix: Guard worktree creation against branch === baseBranch
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.
2026-03-06 12:12:32 +01:00
Lukas May
b853b28751 fix: Resolve agent workdir probing for initiative project subdirectories
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
2026-03-06 12:03:20 +01:00
Lukas May
2814c2d3b2 fix: Fetch remote before merge/push in initiative approval
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.
2026-03-06 11:59:16 +01:00
Lukas May
1e723611e7 feat: Allow editing review comments
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.
2026-03-06 11:58:08 +01:00
Lukas May
49970eb1d7 fix: Use overflow-clip instead of overflow-hidden on FileCard
overflow-hidden creates a scroll container that breaks sticky positioning
for file headers. overflow-clip provides the same visual clipping for
rounded corners without affecting the scroll/sticky context.
2026-03-06 11:44:45 +01:00
Lukas May
0c04a1d273 fix: Prevent conflict resolution agent from destroying initiative branch
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.
2026-03-06 11:40:22 +01:00
Lukas May
f428ec027e fix: Sticky file headers sit below review header using CSS variable
Sets --review-header-h on the card wrapper from measured header height.
FileCard reads it for sticky top offset so file headers dock just below
the review header instead of overlapping it.
2026-03-06 11:36:28 +01:00
Lukas May
c87aac44cc fix: Cover transparent gap above sticky header with upward box-shadow
Uses a 50px upward box-shadow in bg-background color to paint over the
main padding gap that shows above the stuck review header.
2026-03-06 11:33:31 +01:00
Lukas May
cc181ee6ba fix: Parse merge-tree output from stdout instead of catch block
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.
2026-03-06 11:27:32 +01:00
Lukas May
5b497b84a0 fix: Restore sticky header and sidebar by simplifying layout
Removed wrapper divs that broke sticky positioning. ReviewHeader now
accepts a ref prop directly, with sticky top-0 on its own root element.
Card wrapper restored as the tall containing block so both header and
sidebar have room to stick within it.
2026-03-06 11:26:43 +01:00