Skip to content

feat(electron): mobile-capture auto-run pipeline#203

Merged
websitebutlers merged 10 commits into
mainfrom
chris/recovery-combined
May 14, 2026
Merged

feat(electron): mobile-capture auto-run pipeline#203
websitebutlers merged 10 commits into
mainfrom
chris/recovery-combined

Conversation

@Prime8Chris
Copy link
Copy Markdown
Collaborator

@Prime8Chris Prime8Chris commented May 13, 2026

Summary

  • Wires the desktop half of the mobile-capture → auto-run flow: extracted tasks with shipIntent='ship_and_start' and high extractor confidence are now picked up by a new AutoRunDispatcher and run through Claude automatically; everything else (low confidence, create_only, or paused at the agent's approval gate) lands in the Kanban for human review.
  • Schema: taskItems gains shipIntent, autoRunConfidence, autoRunState, autoRunSessionId (Electron v54 + Swift v42 mirror, partial index for the dispatch query). Local shipIntent defaults to 'ship_and_start' to mirror the Supabase column.
  • UX: Ship & start / Create only chips on CapturesView; per-card Running / Resume / Auto-ran / Retry badges on the Kanban; Auto-run panel in TaskDetailSheet surfacing confidence + state + Resume.
  • Pause/resume: when Claude hits the approval gate mid-run, the task goes in_progress + paused_for_human, the transcript persists to a chatConversations thread, and the Resume button reopens the thread in AgentChat with full context.
  • Opt-in: autoRunEnabled defaults to false. autoRunConfidenceThreshold defaults to 0.8.
  • Dev-only capture:devSeedSession + DevSeedPanel for testing without the mobile companion. Seeds are hand-crafted to never modify existing repo content (fixture file path under .codefire/, gitignored).
  • Drive-by: header AccountMenu now prefers the locally-uploaded profileAvatarUrl from Settings → Me over the remote Supabase avatar_url, so the avatar a user picks actually shows in the top-right corner (and persists through loading/signed-out states).
  • Drive-by: WAL file no longer balloons to multi-GB during heavy daily use. The periodic SQLite checkpoint now runs wal_checkpoint(TRUNCATE) on a dedicated connection with a 500 ms busy_timeout (instead of PASSIVE on the main connection, which by design can only reuse WAL space internally and never shrinks the file on disk). Interval tightened to 60 s so quiet moments are caught sooner. Bounded main-thread stalls (≤500 ms × 1/min) during heavy writes. Companion fix to commit 6f1e38d: that commit reduced what gets written (binary skip rules); this one reduces how much space the WAL keeps reserved afterwards.
  • Drive-by: Activity-tab rows are now click-throughs. Avatar + name open a new UserProfileSheet (avatar, name, email + a multiline message box wired to the existing premium.sendTeamMessage). Task and note event descriptions resolve their remote entity_id back to a local row via a new premium:resolveLocalEntity IPC (backed by the syncState table) and open TaskDetailSheet / a modal-wrapped NoteEditor in place — no navigation away from the feed. The Activity avatar also prefers the locally-set profile photo for the signed-in user, matching the AccountMenu behavior above.

Test plan

Runbook is saved in CodeFire (project note: "Runbook: Mobile Capture → Auto-Run Smoke Tests"). High level:

  • cd electron && npm run dev, open the CodeFire project window, navigate to Captures, confirm the DEV seed panel renders.
  • Set autoRunEnabled: true in %APPDATA%/CodeFire/codefire-settings.json, restart desktop.
  • Happy path: click Ship + high-conf → expect task to land in Todo, flip to Running within ~30s, complete with green Auto-ran badge in Done. Verify .codefire/autorun-fixtures/seed-<ts>.txt was created.
  • Pause path: click Ship + ambiguous (should pause) → expect task to flip to in_progress with amber Resume badge. Click Resume → AgentChat opens the paused thread with full transcript + the synthetic "[Auto-run paused for human review]" system note. Continue the conversation; verify the agent picks back up.
  • Create-only path: click Create only → expect two tasks land in Todo and the dispatcher never touches them.
  • Real capture: send a small safe ask from the mobile companion → confirm it processes within 60s and (if ship_and_start + high confidence) auto-runs.
  • Reset via Kanban Retry button on any failed row.
  • Avatar: Settings → Me → upload a photo → confirm the top-right header icon updates immediately (no reload) in both MainLayout and a project window.
  • WAL: with desktop running, watch %APPDATA%\CodeFire\codefire.db-wal size during an hour of typical agent activity — should stay in the low-MB range (oscillating with the 60 s checkpoint), not grow unbounded.
  • Activity clicks: in a synced project's Activity tab, click another teammate's avatar → profile sheet opens; type a message + send → recipient gets a message notification. Click a task_* row → TaskDetailSheet opens with the local task. Click a note_* row → note modal opens with editable contents. Non-openable events (session/commit/review/doc/team) stay as plain text.

Safety

Auto-run is opt-in (autoRunEnabled: false default). Even when enabled, the dispatcher only runs tasks with source='mobile-capture'-style confidence (autoRunConfidence IS NOT NULL), so manually-created tasks never auto-run despite the default shipIntent. Claude runs in autoEdits permission mode — file edits proceed without approval but Bash + dangerous tools still pause for human input.

🤖 Generated with Claude Code

Prime8Chris and others added 10 commits May 13, 2026 14:25
…asks

Desktop half of the mobile-capture → auto-run flow. Mobile companion writes
captures into Supabase; CodeFire desktop picks them up, extracts tasks with
per-task confidence scores, and (when shipIntent='ship_and_start' and
confidence clears the threshold) fires Claude at the task automatically.

- v54 migration: shipIntent + autoRunConfidence + autoRunState +
  autoRunSessionId on taskItems, partial index on the dispatch candidates.
  Swift v42 mirror.
- CaptureIntakeService.extractTasks asks the model for a 0..1 confidence
  per task; stamps session.shipIntent onto every created task.
- New AutoRunDispatcher: polls eligible tasks, spawns ClaudeAgentSession
  in autoEdits mode, persists the transcript to a chatConversations
  thread so the run is resumable from AgentChat.
- Pause/resume handshake: approval-gate fires → autoRunState=
  'paused_for_human', synthetic system message logged in the thread,
  Kanban Resume button reopens the project window + AgentChat thread.
- 'shipped' added to CaptureStatus.
- New ConfigStore keys autoRunEnabled (default false) +
  autoRunConfidenceThreshold (default 0.8) — auto-run is opt-in.
- Dev-only capture:devSeedSession IPC + DevSeedPanel in CapturesView for
  testing without the mobile companion (gated by app.isPackaged + DEV).
- "Ship & start" / "Create only" chips on CapturesView; auto-run state
  badges (Running, Resume, Auto-ran, Retry) on Kanban cards.
- .codefire/ added to .gitignore for the seed harness fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e + CAPTURE pill

Four issues found while testing the auto-run pipeline against a real
mobile capture (task #800):

1. Kanban not refreshing on auto-run state transitions. Dispatcher only
   emitted the custom `tasks:autoRunChanged` event; the renderer's
   useTasks hook subscribes to `tasks:updated`. broadcastTaskChanged now
   emits both.
2. Idle agents stranded as 'running'. Claude's -p streaming subprocess
   sits at phase=ready between turns, so an agent that finishes its
   wrap-up message never emits phase=ended. Dispatcher now schedules a
   15s idle-completion timer on every phase=ready event; cleared by any
   new message or status event, fires finishRun(completed) on expiry.
3. Bypass-mode option for fully autonomous runs. New
   autoRunPermissionMode config key (default 'autoEdits', alternative
   'bypass'). autoEdits keeps Bash gated by the approval server (which
   triggers pause-for-human); bypass skips approval entirely so tasks
   needing shell can complete without intervention. Trade-off documented
   in the runbook note.
4. "CAPTURE" source pill on Kanban cards. mobile-capture tasks now
   render an orange CAPTURE badge alongside the priority/labels row so
   you can see at a glance which tasks came from the phone.

Verified end-to-end against capture session 440a319a → task #800: agent
ran autonomously in autoEdits mode, wrote CAPTURE_PIPELINE_E2E_TEST.md
at repo root with a complete pipeline-stage walkthrough, then sat idle
at phase=ready (which is what the idle-completion timer now catches).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sk #800)

This file was created by Claude Code running autonomously as the first
real end-to-end test of the mobile-capture → auto-run pipeline. The
capture originated on the CodeFire Capture phone app, propagated through
Supabase (session 440a319a), got extracted into local task #800, and was
auto-dispatched by AutoRunDispatcher running on commit 056f90a.

The agent itself wrote the contents, including the pipeline-stage
walkthrough and the run metadata header. Kept as a regression marker so
future pipeline changes can be diffed against a known-good autonomous
run. Safe to delete if it becomes stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ain page

The home-view CodeFireChat was landing on whichever conversation had the
freshest updatedAt — including empty rows created by clicking "+ New Chat"
without sending a message — so users saw the "Ask anything" empty state
instead of their last real thread. Mirror the project-page AgentChatPanel
behavior by skipping empty conversations during auto-select.

- chat:listConversations now returns messageCount via a correlated subquery
- ChatConversation.messageCount added as optional field
- CodeFireChat mount effect picks list.find(c => messageCount > 0), falling
  back to list[0] when every conversation is empty

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n columns

Add bg-[var(--bg-subtle)] to ProjectTaskSummary and all RecentEmails
states so Home cards sit on the subtle surface, and lift kanban column
.cf-kcolumn-surface to --bg-elevated (hover #2a2a2a) so columns read as
elevated above the card surface behind them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
File was a one-shot proof artifact for the mobile-capture -> auto-run
pipeline (task #800) and was explicitly marked safe to delete after
review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The top-right account icon was always pulling from the remote Supabase
avatar_url, ignoring the photo a user uploaded via Settings → Me. Now we
read profileAvatarUrl from AppConfig (refreshed on settings:changed) and
prefer it over user.avatarUrl across loading, signed-out, and signed-in
states. The display name falls back to the local profileName first as well.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PASSIVE checkpoints flush frames into the DB but never shrink the WAL
file's on-disk size — SQLite reuses space inside the WAL but the OS
file size only ever grows to the high-water mark. After a heavy day of
agent activity (session/audit/chat writes), the WAL file ends up
pinned at multi-GB even though almost no frames are actually unflushed.

Switch the periodic checkpoint to TRUNCATE on a dedicated connection
with a short (500ms) busy_timeout. The dedicated connection prevents
the short timeout from leaking into the main connection's 30s setting,
and bounds main-thread stalls during heavy writes. Interval tightened
from 5 min to 1 min so quiet moments are caught sooner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each Activity row now has two click targets: the avatar/name opens a
new UserProfileSheet wired to premium.sendTeamMessage, and the event
description opens the matching modal for openable event kinds (tasks →
TaskDetailSheet, notes → NoteSheet). Other event types (sessions,
commits, reviews, docs, team) stay as plain text.

Remote `entity_id`s from activity_events are resolved back to local
DB rows via a new SyncEngine.getLocalIdByRemoteId helper exposed as
`premium:resolveLocalEntity`, so the modals work for any task/note
that has been synced on this device.

UserAvatar prefers the locally-set profile avatar/name (Settings → Me)
over the remote avatarUrl for the signed-in user, matching the same
preference already used by AccountMenu, and reacts to settings:changed
so the avatar updates live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@websitebutlers websitebutlers merged commit 7d1c6f8 into main May 14, 2026
4 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.

2 participants