Commit graph

360 commits

Author SHA1 Message Date
fabro-sh-0530[bot]
333b603f5b
Encode stage visits in run stage URLs (#206)
### Summary
Stages that re-enter the same workflow node now get distinct
`node@visit` identities end to end, so looped stages like `verify@1` and
`verify@2` no longer collapse to the same sidebar link, event stream,
graph selection, or turns view.

### What changed
- `RunStage.id` now uses the full `StageId` string (`node_id@visit`),
with required `node_id` and `visit` fields in the OpenAPI schema and
generated clients. This intentionally replaces the old `dot_id` field.
- The server builds `/runs/{id}/stages` from
`RunProjection::iter_stages()` instead of checkpoint `completed_nodes`,
preserving visit information and including in-flight stages from
projection data.
- Stage status is derived from the latest lifecycle event for each exact
`stage_id`, so retrying stages do not appear failed while a retry is
underway.
- The frontend maps and displays visits with `(N)` suffixes, filters
fallback turns by `stage_id`, invalidates suffixed stage-turn query keys
from SSE, and aggregates graph nodes by `node_id` with latest-visit
click targets.

### Plan Summary
- Preserve per-visit stage identity across API, server projection,
generated clients, and UI routing.
- Keep graph nodes keyed by workflow node while routing clicks to the
latest visit.
- Add coverage for multi-visit stages, retrying status derivation,
suffixed SSE invalidation, sidebar labels, and stage event filtering.

### Reviewer notes
This is a breaking API shape change for `RunStage`: consumers should use
`node_id` for graph/node identity and `id` for per-visit stage identity.
The old `dot_id` field is removed rather than kept as a compatibility
alias.

### Fabro Details

<details>
<summary>Ran 9 stages in 54m 55s for $41.40</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 8s | – | 0 |
| preflight_lint | 2m 14s | – | 0 |
| implement | 31m 38s | $17.65 | 0 |
| simplify_opus | 10m 2s | $2.40 | 0 |
| simplify_gpt | 6m 9s | $21.35 | 0 |
| verify | 2m 3s | – | 0 |
| fmt | 2s | – | 0 |
| **Total** | **54m 55s** | **$41.40** | **0** |

</details>

<details>
<summary>Ran <code>ImplementPlan.fabro</code> (12 nodes and 15
edges)</summary>

```dot
digraph ImplementPlan {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { model: claude-opus-4-7; }
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
    verify            [label="Verify", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings, test failures, and generated docs errors.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo +nightly-2026-04-14 fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=succeeded"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=succeeded"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=succeeded"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=succeeded"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:27:11 -04:00
Bryan Helmkamp
b5b08e78d3
refactor(api): reuse board column contract across clients
Make BoardColumnDefinition.id reference the existing BoardColumn schema and carry that typed contract through generated TypeScript, server responses, demo data, and the runs board UI.
2026-05-04 15:52:24 -04:00
Bryan Helmkamp
63940fdddc
fix(web): recover cross-tab SSE coordination after fallback
Reset coordinator state when the last subscriber leaves, clear pending debounce timers on close, and keep coordinated EventSource construction owned by the coordinator while fallback subscriptions keep their local factories.
2026-05-04 15:52:18 -04:00
Bryan Helmkamp
e4e51511e0
refactor(web): simplify cross-tab SSE message parsing and helpers
Use unknown.ts helpers in parseMessage, factor out parseLeaderPair/Triple
and per-variant parsers to remove repeated typeof guards. Extract
leaderIsFresh() for the staleness check used in three places, and make
RecentEventCache amortized O(1) by walking expired entries from the
oldest instead of scanning the whole map per event. Drop the
closeOnTerminal parameter in run-events; the fallback path computes
close at its single call site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:38:19 -04:00
Bryan Helmkamp
6529845554
fix(web): clean up cross-tab SSE lifecycle
Prune stale election candidates as generations advance, reset coordination availability on explicit close, and keep fallback subscribers tracked so coordinator shutdown can clean them up consistently.
2026-05-04 15:29:42 -04:00
Bryan Helmkamp
38726666af
fix(web): harden cross-tab SSE fallback
Stop coordinated election and leadership work when BroadcastChannel posting fails, so tabs degrade cleanly to per-subscriber fallback without stale resync or heartbeat side effects. Expand election coverage for the edge cases called out in the coordination plan.
2026-05-04 15:24:25 -04:00
Bryan Helmkamp
ade721ae65
feat(web): coordinate SSE subscriptions across tabs
Elect a single browser tab to own the global attach stream and broadcast run events to sibling tabs. Keep the existing per-tab EventSource path as the fallback when cross-tab coordination is unavailable.
2026-05-04 14:54:39 -04:00
Bryan Helmkamp
f39e512990
feat(web): split Queued column out of Initializing on the run board
Submitted and Queued lifecycle statuses now live in a dedicated Queued
column rendered to the left of Initializing; Starting stays in
Initializing. The column is omitted from the board when it has no items
so day-to-day boards stay compact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:13:01 -04:00
Bryan Helmkamp
8064aa269e
fix(web): hide runs landing zero-state until data resolves
Render kanban column shells while board/auth/system queries load, so the
"Your runs will appear here" panel no longer flashes before runs arrive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:46:58 -04:00
Bryan Helmkamp
253af11508
refactor(billing): simplify run-billing post-review cleanups
Use BilledTokenCounts::default() for the non-LLM branch, hoist the
by-model stage count and hasLlmStages predicate out of JSX, and drop
the in-test for-loop in favor of iterator-based assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:01:04 -04:00
Bryan Helmkamp
95eb13750a
fix(billing): render non-LLM run stages
Include completed stages without LLM usage in run billing responses so command-only runs still show runtime rows. Keep token and model aggregates scoped to billed LLM usage, and render placeholder values in the web billing table.
2026-05-03 22:49:33 -04:00
Bryan Helmkamp
93f255c6c4
fix(web): preserve run-overview exit-node color after archive
Use the archived status's prior terminal kind so the Exit node keeps
its succeeded/failed fill instead of falling back to the default
transparent server fill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:42:07 -04:00
Bryan Helmkamp
6f1d87c878
refactor(web): simplify interview-dock
Drop a resync useEffect that healed activeIndex back to safeIndex —
safeIndex already clamped reads, so the effect only triggered an
extra render. Reuse the shared ErrorMessage from ui.tsx instead of
the inline copy. Drop a useMemo over a tiny per-render array.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:59:07 -04:00
Bryan Helmkamp
7247fd6b7c
feat(web): add interview dock for answering blocked runs from the UI
Replaces the read-only BlockedRunNotice with a viewport-fixed dock that
lets users answer pending human-in-the-loop questions without dropping
to the CLI. Supports YesNo, Confirmation, MultipleChoice, MultiSelect,
and Freeform question types, plus the allow_freeform fallback for
choice-with-write-in. Multiple pending questions surface a "+N more"
pill so a parallel-handler run can be drained from one place.

The dock subscribes to interview.* SSE events for auto-refresh and
posts answers via the existing /runs/{id}/questions/{qid}/answer
endpoint. Cancel is consolidated into the page header (now shown for
blocked runs) so the dock chrome stays focused on the conversation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:39:01 -04:00
Bryan Helmkamp
7cb120b96c
refactor: drop type assertions and reuse generated enums
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Replace local CommandTermination/CommandOutputStream literal unions with
the generated enums from fabro-api-client, drop `as` casts and the `id!`
non-null assertion in run-stages, flatten the 6-deep status ternary into
streamStatus(), and use fabro_util::time::elapsed_ms in handler/llm/cli.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 22:45:38 -04:00
Bryan Helmkamp
9482eda85e
refactor(web): narrow event property reads via shared helpers 2026-04-30 22:45:38 -04:00
Bryan Helmkamp
e50df2b58b
feat(command): distinguish cancelled commands from timeouts
Represent command termination explicitly across sandbox results, events,
run projections, API types, and the run stage UI. This removes the fake
-1 exit code path for timeout/cancel and lets consumers tell cancelled
commands apart from timed-out commands.
2026-04-30 22:45:37 -04:00
Bryan Helmkamp
8ac400df1c
feat(command): stream command logs from CAS-backed storage
Persist command stdout/stderr through scratch logs and finalized CAS refs, expose byte-offset tailing through the API, and render separate streaming panels in the web run view.

Resolve command output blob refs for execution-time consumers such as edge routing and retros, and make Docker streaming timeout/cancel drain output before returning.
2026-04-30 22:45:37 -04:00
Bryan Helmkamp
b5f200d701
chore(web): namespace static images under /images and skip in HTTP logs
Move favicon, logo, logotype, and PNG icons from /public/ root to
/public/images/ so the HTTP log middleware can drop them by path
prefix. Extends the existing /assets/ skip in http_log_middleware to
cover /images/ as well, removing favicon/logo entries from the server
log without filtering by extension (which would risk muting future
extension-suffixed API routes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:57:14 -04:00
Bryan Helmkamp
95649f0a7a
refactor(web): drop source/sandbox path line from run header
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:31:09 -04:00
Bryan Helmkamp
b3cabca8d3
style(web): widen runs empty-state to max-w-xl
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:10:24 -04:00
Bryan Helmkamp
118ce83966
refactor: simplify after stage status unification
Share ACTIVE_STAGE_STATES/SUCCEEDED_STAGE_STATES across stage-sidebar and
run-overview, collapse the nested match in active_stage_state_from_events,
and drop a few WHAT-comments that narrated the recent rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:02:08 -04:00
Bryan Helmkamp
d65c1fa635
refactor(types): remove stage status compatibility 2026-04-30 06:48:47 -04:00
Bryan Helmkamp
ba529c23f9
refactor(web): regenerate client for precise stage states 2026-04-30 06:33:12 -04:00
Bryan Helmkamp
f16391485b
refactor(workflow): update stage outcome semantics 2026-04-30 06:06:51 -04:00
Bryan Helmkamp
97b616b6e3
refactor(web): drop graph and working dir from run workflow panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:10:28 -04:00
Bryan Helmkamp
70cf37f26c
refactor(web): share settings panel UI between server and run pages
Extract Panel, Row, ViewToggle, and value renderers into a shared module
so the run settings page can adopt the same paneled layout and Settings/
JSON toggle as the server settings page. The run page groups its frozen
snapshot into Workflow, Sandbox, Git, and Artifacts panels and falls
back to raw JSON for everything else.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:10:07 -04:00
Bryan Helmkamp
9e3305bbf3
refactor(web): regroup settings into Server, Data, Security, Integrations
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:55:17 -04:00
Bryan Helmkamp
6156f65799
fix(run-files): normalize degraded file diffs
Return degraded run files with the same FileDiff[] shape as live responses, using nullable contents and per-file unified patches so the web sidebar and deep links work consistently.
2026-04-29 13:00:55 -04:00
Bryan Helmkamp
dede93dceb
Merge remote-tracking branch 'origin/main' 2026-04-29 11:51:47 -04:00
Bryan Helmkamp
7d7931e56a
feat(web): show GitHub App install return state on /setup
When GitHub redirects back to /setup after installing the app, render a
distinct view that confirms the install and points users to retry the
run, instead of the first-time terminal setup instructions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:41:28 -04:00
Bryan Helmkamp
beff3985f0
ui(web): add Settings/JSON view toggle on settings page
Place a Settings | JSON toggle on the right of the description row. The
JSON view renders the full server settings object as syntax-highlighted
server-settings.json via the existing CollapsibleFile component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:12:40 -04:00
Bryan Helmkamp
81f73e8246
ui(web): syntax-highlight DOT source on run graph page
Reuse the existing @pierre/diffs Shiki highlighter and registered DOT
grammar (already used on the workflow definition page) so the Source
view renders workflow.fabro with proper highlighting instead of plain
monospace text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:09:45 -04:00
Bryan Helmkamp
b36be97675
fix(web): keep graph mounted when toggling to source view
Toggling to Source unmounted the graph container, so switching back
mounted a fresh inner div without re-running the render effect — leaving
"Loading diagram..." stuck. Hide the graph via the hidden attribute
instead so the cached SVG and pan/zoom state survive view switches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:01:38 -04:00
Bryan Helmkamp
066cd9fa15
ui(web): right-align Graph/Source toggle on run graph page
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:00:42 -04:00
Bryan Helmkamp
9ad47990f4
fix(web): include server URL in auth quick start
Expose the configured server.web.url in system info so the empty runs quick start can show a runnable fabro auth login command instead of a placeholder.
2026-04-29 07:45:17 -04:00
Bryan Helmkamp
ab9b28875b
fix: close sandbox-native metadata gaps
Ensure local runs use the worktree checkpoint path by default, expose source and sandbox paths in API/web surfaces, and remove dead fork/rewind push controls. Update docs for clone-based sandboxes and durable checkpoint timelines.
2026-04-28 08:05:18 -07:00
Bryan Helmkamp
0543c5c8fe
chore: simplify sandbox-native git metadata code
- Reuse fabro_sandbox::shell_quote in sandbox_metadata.rs and sandbox_git.rs
  (CLAUDE.md mandates the shared helper, not local reimplementations).
- Skip git_diff call on first checkpoint when prev SHA equals new SHA;
  previously diffed a SHA against itself, costing one sandbox round-trip.
- Drop tuple-match theatre in write_snapshot cleanup.
- Type LEVEL_COLOR as Record<LogLevel, string> so the lookup is exhaustive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 06:39:01 -07:00
Bryan Helmkamp
cdd46b4fa8
Make git metadata sandbox-native 2026-04-27 21:43:15 -07:00
Bryan Helmkamp
fd1087fe2d
feat(api): expose workflow graph source as raw DOT
Add GET /api/v1/runs/{id}/graph/source returning text/vnd.graphviz so
the run graph can be inspected as the original Graphviz DOT in addition
to the rendered SVG. Refactor get_graph to share DOT loading with the
new handler. The web run-graph view gains a Graph | Source toggle that
lazy-loads and displays the DOT with a copy button.
2026-04-27 16:24:17 -07:00
Bryan Helmkamp
cdc43e05fc
feat(web): drop Unarchive action from archive toast
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:10:16 -07:00
Bryan Helmkamp
4beee7358b
feat(web): colorize run logs by level and target
Parse each tracing line in the run logs panel and tint the timestamp,
level, target, and message separately. Errors and warnings now stand
out at a glance (coral/amber) while debug/trace and the surrounding
chrome recede. Original whitespace is preserved so the formatter's
column alignment is intact.
2026-04-27 16:06:02 -07:00
Bryan Helmkamp
90b6db1e52
feat(web): polish runs board and install nav
Hide the Steer action on board cards outside demo mode so the action
list reflects what the operator can actually do. Hide the lifecycle
status pill on cards in the Initializing column since the column header
already conveys the state. Shorten the install wizard top nav label
"Object store" to "Storage".
2026-04-27 16:02:54 -07:00
Bryan Helmkamp
2460ffc37a
feat(install): add sandbox provider step to web install wizard
Operators choose Docker (default, zero-config) or Daytona (validated
via Daytona SDK) during browser install. Selection is captured in
settings.toml under [run.sandbox] -- explicitly even for Docker, so the
choice is locked in. Daytona keys land in the vault as DAYTONA_API_KEY
(Environment secret). Step always runs after object_store and before
the LLM step.

Server adds POST /install/sandbox/test (validates Daytona key via
client.list) and PUT /install/sandbox; both reuse the install-token
auth and InstallSecret redaction patterns established by object-store.
A resolve_install_sandbox_state helper preserves a saved Daytona key
when the operator revisits the step without re-entering it. The
in-memory api_key is dropped from PendingInstall after finish, matching
the manual_credentials cleanup for S3 access keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:59:17 -07:00
Bryan Helmkamp
44d1d7f5e2
refactor(api): reuse run summary domain type 2026-04-26 23:28:04 -04:00
Bryan Helmkamp
7ecd8df32e
refactor(web): simplify run logs view
Drop unjustified useMemo around byteCount, add void to mutate(), let
errorMessage return undefined for non-Error values so the description
doesn't duplicate the retry button label, and reuse formatBytes (hoisted
to lib/format.ts from insights-editor) so log size renders as "1.23 MB"
instead of "1,234,567 bytes".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:47:10 -04:00
Bryan Helmkamp
4e9d3774ef
feat(web): add run logs view
Add a "Run Logs" entry to the run detail sidebar that fetches the
worker tracing log via GET /api/v1/runs/{id}/logs and renders it with
auto-refresh while the run is live. Refreshes the embedded SPA bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:38:22 -04:00
Bryan Helmkamp
c28b040c6f
fix(install): reject wildcard public URLs
Normalize bind-address wildcards before presenting install URLs, reject wildcard public origins at CLI and server install boundaries, and surface recovery guidance in the installer and doctor output.
2026-04-25 18:58:00 -04:00
Bryan Helmkamp
d1b0548197
fix(web): unbox file tree sidebar
Drop the border, rounding, and background from the FileTree wrapper
(and its empty state) so the tree sits directly on the sidebar
container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:43:12 -04:00
Bryan Helmkamp
40fa088410
refactor(web): redesign settings page as titled panels
Replace the raw JSON dump with three panels (Server, Access & Capacity,
Integrations & Artifacts), each rendering a small set of curated rows.
Each row uses an aligned two-column layout — title and help on the
left, a typed value renderer on the right (toggle dot, mono path,
URL link, badge, tabular-nums count, listen/object-store summaries).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:37:55 -04:00