Skip to content

Worktree engine: parallel tasks, live output, PR import, all comments#20

Merged
Ghvstcode merged 4 commits into
mainfrom
worktree-engine
Apr 22, 2026
Merged

Worktree engine: parallel tasks, live output, PR import, all comments#20
Ghvstcode merged 4 commits into
mainfrom
worktree-engine

Conversation

@Ghvstcode
Copy link
Copy Markdown
Owner

Summary

This branch lands the worktree-based engine rework plus several
user-visible upgrades that build on top of it.

  • Worktree engine migration. Each task now runs in its own git
    worktree so concurrent agents never step on each other's working
    trees or branches. Adds src-tauri/src/engine/worktree.rs and
    threads worktree paths through the engine + tasks table. Also adds
    PR import so existing PRs can be adopted by an agent.

  • Live streaming of Claude CLI output. The Agent tab now renders
    Claude's tool calls and assistant messages as they arrive instead
    of waiting for the final summary. New TaskLiveOutput component,
    useTaskOutputStream hook, and a task_agent_events table that
    persists the stream so it survives app restarts.

  • Parallel task execution with scan-aware concurrency. Multiple
    tasks can run in parallel up to the configured concurrency limit,
    with scans counted against the same budget so we don't flood the
    Claude CLI.

  • Full PR comment coverage. The watcher previously only addressed
    inline review comments. It now also handles general issue-level PR
    comments and the free-text body of review submissions. pr_comments
    gets a kind column (inline | issue | review_summary) with a
    composite unique index, and a rollout cutoff so existing active
    PRs aren't retroactively reworked. Bot replies carry a hidden
    marker for fetch-time dedup.

Users now see what Claude is doing in real time during task execution.
Previously, tasks ran opaquely for up to 30 minutes with only a static
status banner, making it impossible to tell if Claude was stuck or
working. A new dedicated Agent tab in the task detail view shows
Claude's text responses, tool uses, and final results as they happen.

Backend (Rust):
- Switch invoke_claude_cli from --output-format json to stream-json
  with --verbose for newline-delimited JSON events in real time
- Unified streaming implementation replaces dual stdin/no-stdin paths
- Parse each JSON event, extract structured content blocks (text,
  tool_use with target path, tool_result, thinking, result)
- Emit agent:task-output Tauri events for the frontend per event
- Synthesize a CliResult compatible with existing parse_implement_output
  and parse_review_output so worker logic is unchanged
- Thread AppHandle and task_id from engine_start_task and
  engine_address_review through worker to invoke_claude_cli

Frontend:
- TaskOutputEvent type with structured ContentBlock[] (camelCase via
  serde rename_all)
- useTaskOutputStream hook with 500-line buffer, DB persistence,
  historical event loading on mount
- TaskLiveOutput component: terminal-like log with per-block rendering,
  icons for tool use, auto-scroll with jump-to-bottom
- Structured result cards for review/implement JSON output (passed
  status, issues by severity, files modified)
- Syntax highlighting via highlight.js for fenced code blocks and
  diff output from tool_result
- Agent tab in TaskDetailView auto-selects when task transitions to
  in_progress, persists after completion via SQLite

Persistence:
- Migration 19 adds task_agent_events table (task_id, event_type,
  blocks_json, timestamp)
- Events saved async as they arrive via saveAgentEvent
- Historical events loaded on mount via listAgentEvents
Enable multiple tasks to run simultaneously now that the worktree
migration makes each task filesystem-isolated. Default concurrency
is 5, configurable in Settings → General. Scans count against the
same pool so SUSTN never over-subscribes Claude CLI.

Backend (Rust):
- EngineState.running_tasks is now Mutex<HashMap<String, CurrentTask>>
  instead of Mutex<Option<CurrentTask>>, tracking multiple tasks by id
- New concurrency_limit (RwLock<usize>, default 5), tokens_reserved
  (Mutex<i64>), active_scans (Mutex<usize>) on EngineState
- engine_start_task and engine_address_review: capacity check sums
  running_tasks + active_scans; register/release on task boundaries;
  reserve/release estimated tokens via budget::estimated_task_tokens
- engine_scan_now: increments active_scans for pass 1 and spawned pass 2,
  releases on both success and error paths
- budget::calculate_budget_status_with_reservation subtracts reserved
  tokens so concurrent starts do not over-commit the daily budget
- New engine_set_concurrency_limit Tauri command (clamped 1..=10)
- EngineStatusResponse exposes running_tasks and concurrency_limit

Frontend:
- useQueueProcessor: removed single-task processingRef, now checks
  runningTasks.length + concurrencyLimit via engine_get_status before
  dequeuing. Chains processNext on task completion or enqueue
- useScheduler: skips tick when at concurrency capacity instead of when
  any task is running
- TaskDetailView: queues only when at capacity, starts immediately when
  a slot is available
- TaskStatusBanner: checks runningTasks.some(t => t.taskId === id)
  for per-task working state
- Deep scan listener invalidates every running task in the affected repo
- useStartupRecovery syncs persisted concurrencyLimit to the Rust state
  at boot so the setting survives restarts

Scan scope control:
- Migration 21: scan_enabled column on agent_config (default 1)
- AgentConfig.scanEnabled wired through DB layer and useUpdateAgentConfig
- Scheduler and startup scan skip repos with scanEnabled=false
- pr-import sets scanEnabled=false on imported repos; they never
  auto-scan again unless the user explicitly re-enables in project
  settings (new toggle in the Automation section)

PR lifecycle parallelism:
- prLifecycleTick now processes active PRs via Promise.allSettled
  instead of a sequential for-await loop, so multiple PRs can sync
  and address in parallel (bounded by backend concurrency limit)

Settings:
- GlobalSettings.concurrencyLimit (default 5), persisted in
  global_settings via migration 20
- General settings section: parallel-tasks selector (1-10)
- useUpdateGlobalSetting propagates the limit to the Rust state on
  change so it takes effect immediately without restart
Previously the PR lifecycle only fetched inline review comments
(pulls/{n}/comments). General issue-level PR comments and the free-text
body of review submissions were either ignored or stored but never
surfaced to Claude, so whole classes of reviewer feedback went
unanswered.

The pr_comments table now carries a `kind` column (inline | issue |
review_summary) with a composite unique index on (kind,
github_comment_id) since those IDs come from different server-side
tables and can collide. Migration 22 also stamps a
pr_comments_rollout_cutoff timestamp so existing active PRs don't get
retroactively reworked — any source comment older than the cutoff is
inserted as already-addressed.

The service now pulls all three sources in each tick:
  - listPrComments  → kind=inline (existing behavior)
  - listIssueComments → kind=issue (new)
  - synthesized per review with non-empty body → kind=review_summary

Bot-authored issue-comment replies carry a hidden `<!-- sustn:task=... -->`
marker so they're filtered out on the next fetch without needing to
resolve the gh user's login (robust against auth swaps).

Each item sent to Claude is tagged [KIND: ...] in addition to the
existing COMMENT_ID tag; the reply JSON requires `kind` echoed back so
we can route inline replies to the pull-comment replies endpoint and
issue / review-summary replies to a new top-level issue comment. The
resolved-threads filter is now scoped to kind=inline to avoid dropping
issue rows whose ID coincidentally equals a resolved inline thread's
root ID.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sustn Ready Ready Preview, Comment Apr 22, 2026 8:28pm

@Ghvstcode Ghvstcode merged commit fcb7cd8 into main Apr 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant