mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
157 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ceb246b5a
|
feat: Add batch archive/unarchive API endpoints and update web bulk act… (#380)
## Summary
The web UI previously issued one archive/unarchive HTTP request per
selected run. This PR adds `POST /api/v1/runs/archive` and `POST
/api/v1/runs/unarchive` endpoints that process up to 250 runs in a
single fail-soft, non-transactional request, then wires the web
bulk-action toolbar and board column menu to use them.
### Plan Summary
- **OpenAPI contract** — four new schemas (`BatchRunLifecycleRequest`,
`BatchRunLifecycleResponse`, `BatchRunLifecycleResult`,
`BatchRunLifecycleSummary`) and two new paths; Rust and TypeScript
clients regenerated.
- **Server handlers** — `batch_archive_runs` / `batch_unarchive_runs`
behind `RequiredUser`; full request validation (empty, >250, duplicates,
unparseable IDs) before any mutation; per-item outcome mapping
(`archived`, `already_archived`, `unarchived`, `not_archived`,
`conflict`, `not_found`, `error`).
- **Frontend helpers** — `archiveRuns` / `unarchiveRuns` wrappers in
`run-actions.ts`; single-run helpers unchanged.
- **UI integration** — `BulkActionToolbar` and `ColumnActionsMenu`
replaced `Promise.allSettled` fan-out with one batch call; new
`summarizeBatchLifecycleAction` helper drives toast copy for
all-success, partial, and all-failure cases.
## Key Design Decisions
**Fail-soft `200` for valid batches.** A batch where some items fail is
still a successfully *processed* request; the per-item `ok` flag and
`summary` counts communicate individual outcomes without requiring the
caller to handle HTTP errors for partial failures. Request-level
problems (bad IDs, empty list) still return `400`.
**`RequiredUser` only.** Batch endpoints accept any-run mutations from a
request body, so a run-scoped worker token must not be accepted. This is
enforced at the handler level, separate from existing single-run
lifecycle routes.
**Request validation before any mutation.** Empty list, >250 IDs,
duplicate IDs, and unparseable IDs all return `400` before touching any
run — avoiding partial mutation surprises from invalid input.
**Idempotent outcomes are successes.** `already_archived` (archive of an
already-archived run) and `not_archived` (unarchive of a terminal
non-archived run) both set `ok=true`. This matches the existing
single-run semantics and avoids spurious failures in retry scenarios.
**`ask_fabro_readiness` hoisted out of the per-item loop.** Readiness
resolution involves LLM credential work; it's identical for every run in
the batch, so it's resolved once before the loop and shared via
`&AskFabroReadiness`.
**`uniqueItems: true` / `Set<string>` workaround.** The OpenAPI
generator maps `uniqueItems` arrays to `Set<T>` in TypeScript, but the
HTTP wire format is still a JSON array. The frontend helper casts
through `unknown` to send an array so Axios serializes correctly.
### Fabro Details
<details>
<summary>Ran 8 stages in 47m 48s for $23.53</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 4s | – | 0 |
| preflight_lint | 2m 16s | – | 0 |
| implement | 20m 17s | $15.09 | 0 |
| simplify_opus | 10m 27s | $6.18 | 0 |
| simplify_gpt | 3m 58s | $2.25 | 0 |
| verify | 8m 14s | – | 0 |
| **Total** | **47m 48s** | **$23.53** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
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.", model="gpt-55", reasoning_effort="xhigh"]
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="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 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 format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
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 -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
|
||
|
|
39fa73d5e2
|
Add context-window snapshot API for agent stages (#378)
## Summary
Adds a best-effort `GET
/api/v1/runs/{id}/stages/{stageId}/context-window` endpoint that exposes
model-visible input-token usage, broken down by category (system prompt,
tools, MCP tools, skills, memory, conversation, other). The endpoint
degrades gracefully: it returns a stored projection snapshot when the
stage is inactive, and `available: false` when no snapshot has ever been
observed rather than surfacing count gaps as HTTP errors.
### Plan Summary
- **Unit 1** – OpenAPI schemas (`StageContextWindow`,
`StageContextWindowProjection`, breakdown/enum types) and generated Rust
+ TypeScript clients, with `fabro-api` build-time type replacements
pointing at the hand-written `fabro-types` structs.
- **Unit 2** – `ToolSource` enum on `RegisteredTool` (Native / Mcp /
Skill) + `ToolDefinitionWithSource`; new `context_window.rs` builder in
`fabro-agent` that assembles a content-free category breakdown at
request-assembly time; `fabro-llm::token_count` narrow public helpers
(`estimate_message_tokens`, `estimate_tool_definition_tokens`,
`estimate_request_control_tokens`).
- **Unit 3** – `AgentEvent::ContextWindowSnapshot` carries a
`StageContextWindowProjection`; the session emits a local snapshot
immediately, then a provider-scaled replacement (or
response-usage-scaled replacement) asynchronously; fingerprinting
prevents double-counting the same request.
- **Unit 4** – Server endpoint (stubbed routing; full handler targets a
follow-up) returning the latest projected snapshot.
- **Unit 5** – `queryKeys.runs.stageContextWindow`,
`useRunStageContextWindow` hook, and SSE invalidation for
`agent.context_window.snapshot` and all stage-lifecycle events.
### Key design decisions
**Agent-side counting, not server-side.** The exact `fabro_llm::Request`
only exists inside the active agent session. Rather than moving raw
prompt/message content into server-managed state, the session counts the
request it already has and emits content-free projection events. The
HTTP endpoint just reads the latest durable snapshot.
**Hybrid category ownership.** `fabro-agent` owns the category taxonomy
(it sees memory documents, skills, MCP registration, and session
history); `fabro-llm` exposes narrow estimation helpers. Neither crate
leaks the other's concerns.
**Provider count is async and non-blocking.** A spawned task calls
`Client::count_input_tokens(..., PreferProvider)` with a clone of the
request. It is cancelled via `close_token` when the session closes.
Failures produce a warning on the snapshot, not a stage error.
**`available: false` instead of 4xx for known-but-unobserved stages.**
The sidebar needs stable empty states; HTTP errors only mean the run or
stage doesn't exist.
```mermaid
flowchart TB
A[Session::build_request] --> B[build_local_snapshot\nLocalEstimate]
B --> C[emit ContextWindowSnapshot]
C --> D{provider count\nspawned task}
D -- success --> E[scaled_snapshot\nProviderApiScaledBreakdown]
D -- failure --> F[warning appended to local snapshot]
E --> G[emit ContextWindowSnapshot]
G --> H[run_state reducer\nupdates StageProjection.context_window]
F --> H
H --> I[GET context-window endpoint\nreturns projection]
```
**`ToolSource` on every `RegisteredTool`.** All 20+ `make_*_tool` call
sites are updated to set `ToolSource::Native`; MCP tools get
`ToolSource::Mcp { server_name }` at registration time;
`make_use_skill_tool` gets `ToolSource::Skill`. A parallel
`definitions_with_source_for_policy` method preserves existing
`definitions_for_policy` behaviour unchanged.
### Fabro Details
<details>
<summary>Ran 8 stages in 90m 57s for $70.01</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 1m 59s | – | 0 |
| preflight_lint | 2m 11s | – | 0 |
| implement | 45m 14s | $48.47 | 0 |
| simplify_opus | 25m 50s | $18.29 | 0 |
| simplify_gpt | 6m 12s | $3.25 | 0 |
| verify | 8m 59s | – | 0 |
| **Total** | **90m 57s** | **$70.01** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
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.", model="gpt-55", reasoning_effort="xhigh"]
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="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 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 format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
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 -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
|
||
|
|
bad8c0baf5
|
Sidebar polish: visit syntax, multi-select archive, settings logos, workspace prefs (#376)
## Summary - Render repeated stage visits as `verify@2` in the sidebar (and waterfall/header/artifacts) to match Fabro's stage-reference syntax instead of the parenthesized `verify (2)` form. - Persist runs workspace preferences across sessions. - Add multi-select with bulk archive/unarchive on the runs list view. - Show provider logos on settings/models and integration logos on settings/integrations, with a slightly wider logo-to-text gap. ## Test plan - [ ] `cd apps/fabro-web && bun test` passes. - [ ] Sidebar shows `verify`, `verify@2`, `verify@3` for a looped node on a run with multiple visits. - [ ] Runs list: select multiple runs and bulk archive/unarchive. - [ ] Workspace preference on the runs page persists after reload. - [ ] Settings → Models and Settings → Integrations render provider/integration logos with the new spacing. Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f73f2a53f3
|
Replace queued with pending/runnable and add approval flow (web + API s… (#371)
## Summary
Replaces the single `queued` pre-execution state with explicit `pending`
and `runnable` states, and wires approve/deny actions for
parent-generated child runs that require human approval before they can
execute. This diff covers the web UI and OpenAPI spec layers of that
change.
## What changed
**Run status model**
- `queued` is removed from all TypeScript types, display maps, column
routing, and tests.
- `pending` (awaiting approval) and `runnable` (eligible for the
scheduler) replace it as distinct board columns and `RunStatus` variants
with their own labels and colors (`runnable` gets cyan; `pending` stays
muted).
**Approval actions**
- New `approveRun` / `denyRun` API calls in `run-actions.ts` invoke the
new `POST /runs/{id}/approve` and `POST /runs/{id}/deny` endpoints.
- `canApprove` predicate requires both `status.kind === "pending"` and
`lifecycle.approval?.state === "pending"` — a run whose status is
pending but has no approval record does not expose the action.
- `useApproveRun` / `useDenyRun` mutations in `mutations.ts` follow the
same pattern as `useCancelRun`.
- `ActionsMenu` in `run-detail.tsx` gains Approve (lifecycle group) and
Deny (destructive group) menu items.
**Board and event plumbing**
- `columnForStatus` now routes `pending → pending column` and `runnable
→ runnable column`; `submitted` stays in the pending column.
- `BOARD_STATUS_EVENTS` and `RUN_SUMMARY_EVENTS` replace `run.queued`
with `run.start_requested`, `run.pending`, `run.approved`, `run.denied`,
and `run.runnable`.
- The `pending` column is hidden when empty (same behaviour the old
`queued` column had).
**Waterfall phases (`run-phases.ts`)**
- `queued` phase is removed; `pending` and `runnable` phases are added
in order.
- The submitted phase closes at `run.start_requested` rather than
`run.queued`.
- Each phase derives its timestamps from its own event rather than a
single `firstTs` lookup, making multi-phase pre-execution timelines
accurate.
**OpenAPI spec**
- `POST /api/v1/runs/{id}/approve` and `POST /api/v1/runs/{id}/deny`
endpoints added with 200/404/409 responses.
- `startRun` description updated to describe the pending/runnable
branching behaviour.
- `cancelRun` description updated to reference `pending`/`runnable`
instead of `queued`.
### Plan Summary
- **Task 3** (OpenAPI schema additions for approve/deny endpoints) —
complete in this diff.
- **Task 6** (Web UI surfaces: board columns, run-detail actions,
waterfall phases, event subscriptions) — complete in this diff.
- **Task 7** (doc cleanup: references to `queued` replaced in plans,
brainstorms, and QA docs) — complete in this diff.
### Fabro Details
<details>
<summary>Ran 9 stages in 127m 37s for $104.98</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 2m 15s | – | 0 |
| preflight_lint | 2m 29s | – | 0 |
| implement | 92m 10s | $91.53 | 0 |
| simplify_opus | 18m 35s | $10.65 | 0 |
| simplify_gpt | 7m 36s | $2.81 | 0 |
| verify | 3m 42s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **127m 37s** | **$104.98** | **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.", model="gpt-55", reasoning_effort="xhigh"]
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: fabro <fabro@anthropic.com>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
|
||
|
|
fd63f4b523
|
feat(api): expose run size buckets (#369)
## Summary Adds a stable `Run.size` API field so clients can bucket workflow runs by current best-effort billed usage without introducing a separate cost-estimation system. The field uses the `RunSize` enum and serializes as uppercase `XS`, `S`, `M`, `L`, or `XL`. ## Changes - Derives run size from terminal billed totals when available, otherwise from the existing projected stage usage while a run is still active. - Exposes `size` on `Run` in the OpenAPI contract and regenerated TypeScript client. - Preserves existing `Run.billing` behavior so live/provisional usage only affects `size`, not the nullable billing summary. ## Verification - `cargo nextest run -p fabro-types run_size` - `cargo nextest run -p fabro-store summary_size_tracks_current_projected_usage_before_terminal_conclusion` - `cargo nextest run -p fabro-api run_summary_json_matches_openapi_shape` - `cargo +nightly-2026-04-14 fmt --check --all` - `cd apps/fabro-web && bun run typecheck` - `git diff --check` - `cargo build --workspace` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 via [Codex](https://openai.com/codex) |
||
|
|
1cd5d89316
|
Runs list view: table layout, server-side sort/pagination, redesigned toolbar (#367)
## Summary Overhauls the `/runs` list view and consolidates the two runs endpoints that backed it. **API** - Removes `GET /api/v1/boards/runs`, `PaginatedBoardRunList`, and `BoardColumnDefinition`. The board view is now a pure frontend rendering. - `GET /api/v1/runs` gains `status` (repeatable `BoardColumn`), `sort` (`created_at | updated_at | status | elapsed`, default `created_at`), and `direction` (`asc | desc`, default `desc`). - `BoardColumn` enum gains `removing`; default behavior hides Removing-status runs, opt in with `?status=removing`. - `PaginationMeta` gains an optional `total: int64`; `list_runs` fills it in (free — it already filters all runs in memory before paging). **List view UI** - Renders as a real `<table>` with column headings instead of horizontal cards. - Sortable Status, Elapsed, Created, and Updated headers — click to toggle direction, click another to switch sort key (resets to desc). URL params drive `sort`/`direction`/`page`/`size`. - New pager footer with rows-per-page selector (10/25/50/100), `Page X of Y`, and first/prev/next/last icon buttons. - Toolbar redesigned into left (search + filter buttons for Time/Repo/Workflow + archived toggle) and right (column picker + view toggle) sections. Filter buttons use Headless UI `Menu` popovers; the column picker uses Headless UI `Listbox` with `multiple` for multi-select. Hidden columns persist via `?hide=...`. **Tests** - 589 server tests pass, including new coverage for status filter (single + repeated), Removing opt-in, sort × direction with `id desc` tiebreak, and status-bucket sorting. - Frontend tests updated for the matcher-based cache invalidation and the new `buildBoardColumns` signature; 435 pass (3 pre-existing `RunDetail full-height` failures unrelated to this change). ## Test plan - [ ] `cargo build --workspace` - [ ] `cargo nextest run -p fabro-server` - [ ] `cd lib/packages/fabro-api-client && bun run generate` — no diff (already regenerated and committed) - [ ] `cd apps/fabro-web && bun run typecheck && bun test` - [ ] Manual: visit `/runs` — board view still renders all columns in canonical order, Removing runs hidden, archived toggle works. - [ ] Manual: visit `/runs?view=list` — table renders with sortable headers; clicking a header updates URL; pager advances; changing rows-per-page resets to page 1; column picker hides/shows columns and round-trips via `?hide=`. - [ ] Manual: `curl '/api/v1/boards/runs'` → 404; `curl '/api/v1/runs?status=removing'` returns only removing runs; `curl '/api/v1/runs?sort=status&direction=asc'` returns runs grouped by status bucket. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7d8506cb7c
|
feat: add manual retry for failed and dead runs (#362)
## Summary
Adds a **Retry** action that creates a fresh run from a failed or dead
run's captured durable definition. The new run gets a new ID, records
`retried_from: <source_run_id>`, and is immediately queued. The source
run is left entirely unchanged.
### Plan Summary
- `retried_from: Option<RunId>` added to `RunCreatedProps`,
`RunProjection`, and `Run` (API surface); defaults to `null` for
backward compat with legacy events.
- `POST /api/v1/runs/{id}/retry` → `201` with the new run; eligible
source states are `failed` (except `reason=cancelled`) and `dead`.
- New `retry_run` workflow operation (modeled after `fork`) creates a
new run store, appends `run.created` with `retried_from` set, then
`run.submitted`. No checkpoints, sandbox state, PR links, questions, or
conclusions are copied.
- `start_run` handler refactored into a reusable `queue_run_start(state,
id, resume)` helper so retry can queue the new run through the same
path.
- Web: `canRetry` predicate, `useRetryRun` mutation, **Retry** menu item
in `ActionsMenu`, "Retried from" link in `RunSummaryPanelView`, and
navigation to the new run on success (deduplicated via
`lastProcessed.retry`).
- OpenAPI spec updated; generated Rust and TypeScript client types
regenerated.
## Key design decisions
**No runtime state is copied.** Only the durable definition fields
(`graph`, `settings`, `labels`, `git`, `manifest_blob`,
`definition_blob`, `fork_source_ref`, `parent_id`, `title`) are
forwarded to the new `RunCreated` event. Checkpoints, sandbox, billing,
PR links, and pending controls are left in the source run.
**`queue_run_start` extraction.** `start_run` was restructured to
extract a `queue_run_start(state, id, resume) -> Result<(), ApiError>`
helper, so the retry handler can reuse the exact same queueing path
without duplicating logic.
**`lastProcessed.retry` deduplication.** The UI effect that fires on
`retryMutation.data` checks `state.lastProcessed.retry === result`
before navigating, so React StrictMode double-invocation or re-renders
won't push duplicate navigations.
**Demo mode guard.** `canRetry` is gated by `!demoMode` at the call site
so the button is hidden in demo mode and never navigates to a missing
demo run.
### Fabro Details
<details>
<summary>Ran 9 stages in 65m 2s for $51.70</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 1m 53s | – | 0 |
| preflight_lint | 2m 8s | – | 0 |
| implement | 36m 51s | $36.06 | 0 |
| simplify_opus | 16m 22s | $14.46 | 0 |
| simplify_gpt | 3m 10s | $1.18 | 0 |
| verify | 3m 39s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **65m 2s** | **$51.70** | **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.", model="gpt-55", reasoning_effort="xhigh"]
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>
|
||
|
|
f81c5b96b1
|
Move agent state (todos, subagents, skills, MCP) onto StageProjection (#364)
## Summary
The `GET /runs/{id}` response now carries per-stage agent state — todos,
subagents, skills, and MCP server status — directly on each
`StageProjection`, unblocking the agent-stage sidebar without any new
endpoints.
## What changed and why
**`RunProjection.todos_by_list` removed.** The run-level map was the
only home for todos, but every list was already 1:1 with a stage (keyed
by `openai_plan:<session>` or `anthropic_tasks:<root_session>`). Moving
todos onto `StageProjection.todos: Option<TodoListProjection>`
eliminates the awkward cross-stage indirection with no loss of
expressiveness.
**Four new fields on `StageProjection`** (`todos`, `subagents`,
`skills`, `mcp_servers`) plus five new projection-side types
(`SubAgentProjection`, `SubAgentStatus`, `SkillsProjection`,
`ActivatedSkill`, `McpServerProjection`, `McpServerStatus`). All
colocated in `run_projection.rs`; no new modules. Four existing
event-payload types are reused directly (`TodoListProjection`,
`AgentSkillSummary`, `AgentSkillActivationSource`,
`AgentMcpToolSummary`) via `with_replacement(...)` in
`fabro-api/build.rs` so progenitor doesn't generate parallel `ApiFoo`
aliases.
**Reducer rerouting** (`run_state.rs`): `TodoCreated/Updated/Deleted`
now resolve the owning stage via the existing
`stage_at_stored_or_current_visit` helper and mutate `stage.todos`
directly. Eight new event arms handle
`AgentSubSpawned/Completed/Failed/Closed`,
`AgentSkillsDiscovered/Activated`, and `AgentMcpReady/Failed` using the
same `stage_at_stored_or_visit` pattern already used for other agent
events.
**Design decisions called out in the plan:**
- `SkillActivated` appends every activation (event-sourced replay
semantics); the UI can collapse if needed.
- `upsert_mcp_server` replaces by `server_name`, so a server that
recovers after a failure shows its final state.
- `SubAgentStatus` and `McpServerStatus` are projection-side enums,
intentionally distinct from the runtime per-process status types in
`fabro-agent`.
**OpenAPI + TS client** updated in lockstep: new schemas added,
`todos_by_list` removed from `RunProjection`, `bun run generate`
regenerated 20+ new model files.
### Plan Summary
- Extend `StageProjection` with `todos`, `subagents`, `skills`,
`mcp_servers` + supporting types in `run_projection.rs`
- Remove `RunProjection.todos_by_list` (no cross-stage use case)
- Reroute todo reducer handlers; add 8 new event arms for
subagent/skill/MCP events
- Wire reused types through `fabro-api/build.rs` `with_replacement`;
regenerate TS client
- Update all existing todo reducer tests to read from `stage.todos`; add
new test modules for subagent, skill, and MCP event families
- Update stale comment in `run-events.ts`
### Fabro Details
<details>
<summary>Ran 9 stages in 38m 52s for $13.29</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 16s | – | 0 |
| implement | 16m 8s | $7.80 | 0 |
| simplify_opus | 10m 34s | $3.91 | 0 |
| simplify_gpt | 3m 23s | $1.58 | 0 |
| verify | 3m 51s | – | 0 |
| fmt | 2s | – | 0 |
| **Total** | **38m 52s** | **$13.29** | **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.", model="gpt-55", reasoning_effort="xhigh"]
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>
|
||
|
|
7e6052bec3
|
Surface reasoning_effort + speed in stage badge end-to-end (#363)
## Summary
The run page stage badge showed only the model name. This PR plumbs
`reasoning_effort` and `speed` from the LLM call site all the way
through the event stream, store projection, API, and UI so the badge now
renders `gpt-5.5 · high`.
### Plan Summary
- **Event props** — `AgentSessionActivatedProps` and `StagePromptProps`
gain `reasoning_effort: Option<ReasoningEffort>` and `speed:
Option<Speed>` with `serde(default, skip_serializing_if)` for
back-compat.
- **Typed projection** — `provider_used: Option<serde_json::Value>` is
replaced by `Option<StageModelUsage>`, a proper struct in `fabro-types`
with factory methods (`from_prompt_props`,
`from_agent_session_activated`). The freeform JSON bag is gone.
- **Emission sites** — `ActivationLeaseOptions` carries the new fields;
`emit_stage_prompt()` (new shared helper) resolves
`EffectiveRequestControls` via the backend and stamps them on
`Event::Prompt`. `AgentHandler` and `PromptHandler` both call this
helper instead of building the event inline.
- **ACP path** — `AgentAcpStarted` no longer writes `provider_used`; the
canonical source is the later `AgentSessionActivated` event, which is
already emitted for ACP steering sessions. Runs without a hub
legitimately leave `provider_used` unset.
- **OpenAPI** — new `StageModelUsage` and `ReasoningEffort` schemas
replace the `object | null` bag; `build.rs` maps both to the canonical
Rust types; a new `stage_model_usage_round_trip` integration test
enforces the parity requirement.
- **UI** — `extractStageModel` (event-scanning heuristic) is deleted;
replaced by `formatStageModelUsageLabel` and `stageModelUsageTitle` that
read directly off `selectedStage.providerUsed`. `parseFanInOutcome` now
sources the reducer model from `stage.prompt` instead of
`prompt.completed`.
### Key design decisions
**No type sprawl**: `fabro_model::ReasoningEffort` and `Speed` are
reused verbatim via `with_replacement` in `build.rs` — no parallel
enums.
**ACP behavior change**: previously `AgentAcpStarted` wrote a bespoke
`provider_used` blob and a later `AgentSessionActivated` would be
ignored for ACP sessions. Now `AgentSessionActivated` is the single
write path for all modes; ACP runs that never activate a steering hub
correctly leave `provider_used = null`. The integration test (`acp.rs`)
is updated to assert the new shape, and the unit test is renamed
`agent_acp_started_alone_leaves_stage_provider_used_unset` to document
intent.
**`emit_stage_prompt` helper**: both `AgentHandler` and the existing
prompt path share one function to avoid the two call sites drifting
apart again.
### Fabro Details
<details>
<summary>Ran 9 stages in 98m 44s for $65.70</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 2m 15s | – | 0 |
| preflight_lint | 2m 30s | – | 0 |
| implement | 42m 19s | $26.85 | 0 |
| simplify_opus | 38m 3s | $35.17 | 0 |
| simplify_gpt | 9m 20s | $3.68 | 0 |
| verify | 3m 38s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **98m 44s** | **$65.70** | **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.", model="gpt-55", reasoning_effort="xhigh"]
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: Bryan Helmkamp <bhelmkamp@users.noreply.github.com>
|
||
|
|
14e2283fcd
|
feat(web): add Waterfall view to run events page
Adds a Waterfall mode to /runs/:id/events alongside the existing event log, selectable via a segmented toggle (URL-synced as ?view=, default waterfall). The waterfall renders run phases (Submitted, Queued, Initializing) derived from run.* events plus a row per stage with status-colored bars, and ticks the active stage's duration locally each second so wall_time_ms staleness doesn't freeze the display. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5e3a30c1c3
|
feat(web): show duration breakdown in run header popover
Hovering the clock chip on a run header now reveals wall-clock-since-created alongside active (inference + tools) time. Also extends formatDurationMs to render hours and days for long-running runs instead of inflating minutes. |
||
|
|
4df9e483ac
|
Merge remote-tracking branch 'origin/main' into ask-fabro-welcome-prompts | ||
|
|
a42634cc54
|
Add event-sourced todo tools for OpenAI and Anthropic profiles (#353)
## Summary
Adds a shared todo/task engine behind two model-native tool surfaces,
with all mutations persisted as individual run events and replayed into
`RunProjection`.
### Plan Summary
- **New domain types** in `fabro-types`: `TodoStatus`, `TodoListKind`,
`TodoProjection`, `TodoListProjection`, and `todos_by_list` on
`RunProjection`.
- **New run events**: `todo.created`, `todo.updated`, `todo.deleted` —
mapped through Fabro's typed event pipeline and replayed by
`RunProjectionReducer`.
- **`TodoRuntime`** (`fabro-agent`): thread-safe in-memory projection
shared across tool closures within a profile instance; each mutation
emits the corresponding agent event.
- **`update_plan`** registered only in `OpenAiProfile`: reconciles
incoming steps by exact `step` text (sha256-derived ID), emitting
create/update/delete events to match the submitted plan.
- **`TaskCreate` / `TaskUpdate` / `TaskList`** registered only in
`AnthropicProfile`: numeric task IDs per list, metadata merge with
`null`-key deletion, `status: "deleted"` routes to `todo.deleted`.
- **Session identity threading**: `ToolContext` gains `session_id`,
`root_session_id`, `tool_call_id`, and `agent_event_emitter`;
`execute_tool_calls` threads these through to `execute_one_tool`;
`Session` tracks `root_session_id` and `spawn_agent` inherits it for
subagents.
- **Scoping**: OpenAI todos scope to `openai_plan:<session_id>`
(per-session); Anthropic todos scope to
`anthropic_tasks:<root_session_id>` (shared across subagents).
- **Web invalidation**: `todo.*` events invalidate `getRunState` and the
run events list; tested in `run-events.test.tsx`.
```mermaid
graph TB
subgraph OpenAI
UP[update_plan] -->|diff by step text| TR[TodoRuntime]
end
subgraph Anthropic
TC[TaskCreate] --> TR
TU[TaskUpdate] --> TR
TL[TaskList] -->|read-only snapshot| TR
end
TR -->|emit todo.created/updated/deleted| SE[SessionBoundEmitter]
SE --> EV[AgentEvent stream]
EV --> RP[RunProjection\ntodos_by_list]
```
### Key design decisions
- **Step identity by text, not position** (`update_plan`): a
sha256-derived ID from `list_id + step` means reordering without
renaming emits an update rather than a delete+create. Duplicate step
strings are rejected with a model-visible error because text is the
identity.
- **No plan-replace event**: the engine emits only individual mutation
events; bulk replacement is expressed as a set of create/update/delete
events produced by diffing the incoming plan against the projection
snapshot.
- **`TodoRuntime` per profile instance**: tools inside a single profile
share one runtime. OpenAI subagents each have their own `session_id` so
their plans are isolated; Anthropic subagents inherit `root_session_id`
so tasks are shared — matching upstream Codex/Claude behavior.
- **`AgentEventEmitter` trait on `ToolContext`**: a narrow interface
that lets tools publish typed events without taking a dependency on the
full `Emitter`. `SessionBoundEmitter` wraps `Emitter` and stamps
`session_id` + `tool_call_id` on each event.
### Fabro Details
<details>
<summary>Ran 9 stages in 93m 23s for $56.46</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 20s | – | 0 |
| implement | 41m 30s | $38.21 | 0 |
| simplify_opus | 29m 2s | $11.78 | 0 |
| simplify_gpt | 14m 24s | $6.46 | 0 |
| verify | 3m 2s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **93m 23s** | **$56.46** | **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>
|
||
|
|
a8900c465d
|
feat: make the Ask Fabro sidebar resizable
Add a drag handle on the left edge of the docked Ask Fabro panel. The user can widen the panel up to 2x its default width and no narrower than the default. The chosen width persists across open/close. An `isResizing` flag on the layout context lets `<main>` and the steer bar drop their width transitions during a drag so the layout tracks the cursor instead of trailing it. Also bundles in-progress sidebar wiring: a collapsed tool-call summary renderer and the remark-gfm dependency for GitHub-flavored Markdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5b5e0d83fd
|
fix: render Ask Fabro session responses
Handle the flattened session SSE event envelopes emitted by the API so assistant deltas are applied to the Ask Fabro runtime instead of being ignored. |
||
|
|
95b45b5960
|
feat: wire Ask Fabro sidebar to real session API with run-control tools (#349)
## Summary
Ships the Ask Fabro sidebar on run pages end-to-end: the agent now has
live `fabro_run_interact` and `fabro_run_events` tools scoped to its
owning run, and the web sidebar talks to real session APIs instead of a
scripted adapter. The `?ask=1` prototype gate is dropped in favour of
server-reported `run.ask_fabro.available`.
## What changed and why
### Rust — run-control tools in Ask Fabro sessions (`fabro-server`,
`fabro-workflow`, `fabro-tool`)
**Tool registration** (`fabro-workflow`): `register_fabro_run_tools` is
now `pub`; a new `register_named_fabro_run_tools` variant accepts a name
allowlist so callers can register a subset without forking the catalog
loop. Unknown names are silently ignored.
**Run-scoped backend** (`fabro-tool`): `ClientBackend` gains a
`run_scope: Option<RunId>` field set via `.with_run_scope(run_id)`.
Every method checks the scope before delegating to the HTTP client,
returning an error before a network call is made. `list_store_runs`
returns a single-element vec of the owning run when scoped;
`resolve_run` rejects non-parseable selectors rather than forwarding
them.
**Session wiring** (`fabro-server`): `build_profile` now returns
`Box<dyn AgentProfile>` (mutably accessible) instead of `Arc`;
`build_agent_session` mints a same-run worker token, builds a
`ClientBackend::with_run_scope`, constructs `FabroRunToolServices`, and
calls `register_named_fabro_run_tools` for the two tools before freezing
into an `Arc`. `AppState::self_server_target()` reads the bound address
from the runtime daemon record for the loopback HTTP call.
**Approval gate**: `build_ask_fabro_tool_approval` now fast-paths
`fabro_run_interact` and `fabro_run_events` to `Ok(())`; all other tools
remain subject to the `ReadOnly` auto-approve check. File/shell tools
are still denied.
### Web — real session adapter and sidebar wiring (`fabro-web`)
**`ask-fabro-runtime.ts`** (new): a `ChatModelAdapter` that creates a
session lazily on the first turn (`sessionsApi.createRunSession`),
caches the session id in `sessionStorage` keyed by run id, and streams
turns via `streamSessionTurn`. `applyTurnEvent` maps `run.session.*` SSE
events to assistant-ui `ThreadAssistantMessagePart[]` incrementally
(text deltas, tool-call started/completed pairs). A 404 on stream clears
the cached id so the next turn starts fresh.
**`ask-fabro-sidebar.tsx`**: drops `scriptIndexRef`, `EMPTY_CHAT`, and
the scripted adapter import; accepts `runId` and `defaultModel` props;
constructs the real adapter via `createAskFabroAdapter`.
**`run-detail.tsx`**: removes `?ask=1` / `askEnabled`; reads
`run.ask_fabro.{available, default_model}` from the summary; always
renders an `AskFabroTriggerButton` (disabled with a tooltip when
unavailable); passes `runId` and `defaultModel` to `<AskFabroSidebar>`.
### Architecture
```mermaid
graph TB
Browser -->|SSE turn stream| SessionsHandler
SessionsHandler -->|spawn| AskFabroAgent
AskFabroAgent -->|fabro_run_interact\nfabro_run_events| ClientBackend
ClientBackend -->|HTTP + same-run\nworker token| RunsAPI[Runs API\n/runs/:id]
ClientBackend -->|run_scope check| ClientBackend
RunsAPI -->|403 cross-run| ClientBackend
```
### Design decisions
- **Same-run scoping is double-enforced**: the `ClientBackend` scope
check fires before the HTTP call; the worker token's run scope causes a
403 at the API layer if the check were somehow bypassed.
- **`build_profile` → `Box` not `Arc`**: the profile needs mutable
access for tool registration after construction, so the `Arc` wrapping
is deferred until registration is complete.
- **`sessionStorage` per-run**: one session is reused across sidebar
open/close cycles for the same run tab; a page reload or different run
always starts clean.
- **Mutating actions included**: `interact` exposes
start/cancel/steer/archive/answer. This is intentional per the locked
decisions; the worker-token scope prevents cross-run blast radius.
### Fabro Details
<details>
<summary>Ran 9 stages in 74m 52s for $44.11</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 11s | – | 0 |
| preflight_lint | 2m 22s | – | 0 |
| implement | 38m 37s | $32.02 | 0 |
| simplify_opus | 17m 40s | $6.55 | 0 |
| simplify_gpt | 9m 32s | $5.55 | 0 |
| verify | 3m 43s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **74m 52s** | **$44.11** | **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: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: fabro <fabro@example.com>
Co-authored-by: fabro <fabro@fabro.sh>
|
||
|
|
c6356cbd77
|
feat: improve run board, thread, and MCP create flows (#347)
## Summary This branch improves several run-management surfaces that agents and users rely on: archived runs now stay visible and ordered correctly in the board view, pair-session messages appear in the stage Thread tab, and the `fabro_run_create` MCP tool accepts the workflow-string shorthand it advertises. ## Changes - Updates the web board cache invalidation and archived-column handling so archive/unarchive actions refresh both active and archived board queries and keep archived runs in a predictable column position. - Adds pair user/system message events to stage activity parsing, Thread rendering, search, details, and DNA timeline items. - Aligns `fabro_run_create` MCP runtime deserialization and `tools/list` schema so each run entry may be either a workflow string or a full create spec object. ## Test Plan - `cargo nextest run -p fabro-tool -p fabro-mcp-server` - `cargo nextest run -p fabro-cli stdio_server_initializes_and_lists_run_tools mcp_create_string_shorthand_deserializes_before_auth mcp_create_validation_errors_happen_before_auth_or_network mcp_create_and_search_manage_real_runs_with_cli_auth` - `cargo +nightly-2026-04-14 fmt --check --all` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 via [Codex](https://openai.com/codex) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
296fbddec9
|
feat(api): add ask fabro session endpoints (#342)
## Summary Adds the run-backed API surface needed for a real Ask Fabro sidebar: run readiness metadata, detailed session projections, session-scoped event listing/attach streaming, and turn control that exposes durable turn IDs and machine-readable failures. ## What Changed - Extended the OpenAPI contract and regenerated Rust/TypeScript clients for `Run.ask_fabro`, `SessionDetail`, `SessionTurn`, paginated run sessions, session event APIs, and optional client-supplied `turn_id` values. - Updated `fabro-types` and `fabro-store` so durable `run.session.*` events project active turn state, transcript messages, and the latest owning run event sequence. - Implemented server routing for session details, `/events`, `/attach`, turn conflict headers, typed turn failure codes, and cheap run readiness decoration across run responses. - Added browser helpers for POST turn streaming and session attach SSE parsing, plus an exported generated `sessionsApi`. ## Verification - `cargo +nightly-2026-04-14 fmt --check --all` - `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` - `cargo build --workspace` - `cargo test -p fabro-types run_session_turn_failed_defaults_code_for_old_events` - `cargo test -p fabro-store run_sessions::tests` - `cargo test -p fabro-api` - `cargo test -p fabro-server --features test-support --test it api::sessions` - `cargo test -p fabro-server --features test-support --test it api::runs` - `cd apps/fabro-web && bun test app/lib/session-stream.test.ts` - `cd apps/fabro-web && bun run typecheck` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 Codex (context unknown, medium reasoning) via [Codex](https://openai.com/codex/) |
||
|
|
54bc67017e
|
feat: Replace duration/elapsed fields with wall_time_ms and StageTiming (#343)
## Summary
Replaces the ambiguous `runtime_secs`, `elapsed_secs`, and `duration_ms`
timing fields on run/stage public API surfaces with explicit
`wall_time_ms` (elapsed clock time) and a `StageTiming` value object
that also carries `inference_time_ms`, `tool_time_ms`, and
`active_time_ms`.
This is a greenfield breaking change — no compatibility shims are
preserved.
### What changed
**API shape**
- `RunBillingStage.runtime_secs` → `RunBillingStage.timing: StageTiming`
- `RunBillingTotals.runtime_secs` → `RunBillingTotals.timing:
StageTiming`
- `RunSummary.timestamps.duration_ms` / `elapsed_secs` removed; a
top-level `timing: StageTiming | null` field added
- Stage list item `duration_secs` → `wall_time_ms`
**Web app (`apps/fabro-web`)**
- `run-billing.tsx`: `liveRuntimeSecs` → `liveWallTimeMs`; live ticking
now returns milliseconds and the footer total sums `wallTimeMs` across
rows
- `stage-sidebar.ts`: `duration_secs` → `wall_time_ms` for the per-stage
duration display
- `runs.ts`: `elapsed_secs` lookup replaced with `timing.wall_time_ms`
- `formatElapsedSecs` / `formatDurationSecs` call sites replaced with
`formatDurationMs`
**Lockfile / tooling**
- `@openapitools/openapi-generator-cli@2.20.2` added as a dev dependency
to `@qltysh/fabro-api-client` to support regenerating the TypeScript
client after schema edits; several transitive deps pulled in alongside
it.
### Design notes
- **Units are now consistent**: every timing value on run/stage surfaces
is in milliseconds; the old API mixed seconds (`runtime_secs`,
`elapsed_secs`) with milliseconds (`duration_ms`).
- **Live ticking** still works correctly: the in-flight billing row
computes `now - startedAt` in ms and sums across rows for the footer,
avoiding a server round-trip during a running stage.
- **`StageTiming.active_time_ms = inference_time_ms + tool_time_ms`** —
parallel work is summed, so run active time can exceed wall time.
- Subsystem-internal `duration_ms` fields (sandbox setup, devcontainer
lifecycle, hooks) are intentionally left unchanged; only public
run/stage timing surfaces are affected.
### Fabro Details
<details>
<summary>Ran 9 stages in 115m 53s for $108.50</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 18s | – | 0 |
| implement | 80m 53s | $101.97 | 0 |
| simplify_opus | 21m 35s | $4.09 | 0 |
| simplify_gpt | 5m 1s | $2.44 | 0 |
| verify | 3m 11s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **115m 53s** | **$108.50** | **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>
|
||
|
|
4e4ab091d4
|
feat(web): add Ask Fabro sidebar and settings panels (#346)
## Summary Adds the prototype Ask Fabro docked sidebar to run detail pages behind `?ask=1`, with app-shell layout coordination so opening the sidebar shifts content instead of covering it. The branch also improves settings visibility with active run concurrency on Resources and a Project Management integrations placeholder. ## Changes - Add a shared Ask Fabro layout context so the app shell can inset main content by the docked sidebar width. - Gate the run detail Ask Fabro button and sidebar behind `?ask=1`, keeping the bottom steer/interview bar aligned while the sidebar is open. - Poll system info on the Resources page to show active runs against the scheduler limit. - Add a Project Management panel with Linear marked as coming soon. ## Verification Not run; PR opened from the existing branch without changing code. --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 (unknown context, medium reasoning) via [Codex](https://openai.com/codex) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
06ee2fea39
|
fix(web): measure active stage duration from startedAt
The left sidebar tracked when it first *observed* a running stage (Date.now() on mount) instead of the stage's actual startedAt, so the duration reset to 0s on every page load. Compute elapsed time directly from stage.startedAt via a shared elapsedSecsSince helper, dropping the runningStartRef tracking. The stage meta bar already did this correctly but with a duplicated parser; fold it onto the same helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5095873ddd
|
feat(web): add detail popovers to the run header
Hovering the run header items now reveals a popover with extra context: - Run status: failure reason and error message for failed runs; archived timestamp for archived runs (no popover otherwise) - Repository: full owner/repo name and the cloned branch - Workflow: node and edge counts plus run labels - PR: live GitHub details fetched lazily on hover — title, an open/draft/merged/closed badge, and the head -> base branch arrow Workflow node/edge counts are new: WorkflowRef now carries node_count/edge_count, computed in build_summary from the parsed graph that is already in hand there. Adds a HoverCard primitive alongside Tooltip (shared useHoverAnchor hook) for rich, viewport-aware popovers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f2efaf70fb
|
feat(web): show sandbox status dot on the run overview panel
The Sandbox cell now renders a colored status dot before the resource summary, with a tooltip explaining the state on hover. The dot reuses the data already fetched for CPU/memory, so no new API call. Falls back to the state label when resources are unavailable. Lifts the per-state display map into a shared lib/sandbox-state module so the overview panel and the dedicated sandbox page stay consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cc7d6a007f
|
feat(web): group memory sizes with thousands separators
Large GiB values rendered without separators (2173 GiB). Format the numeric part with toLocaleString so it reads 2,173 GiB. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
732d57483b
|
feat(web): round units on the resources settings page
The resources page mixed precise and rounded values (8.2%, 5.0s, 51.8 GiB). Round everything to whole units for at-a-glance reading. Add an optional fractionDigits param to formatBytesAsMemory and formatDurationMs (default 1, so sandbox memory limits and turn durations keep their decimals) and have settings-resources opt into fractionDigits 0. formatPercent now uses Math.round. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
37d6b3dbcd
|
fix(server): count whole storage tree in Fabro-managed bytes
build_disk_usage_response only summed scratch/ run dirs and logs/*.log, omitting objects/ (SlateDB + artifacts), sessions/, and vaults/ — a ~30x undercount of "Fabro managed" storage on the resources page. Measure the whole storage_dir tree for total_size_bytes so it can't drift as new subdirectories are added. Reclaimable stays a curated estimate that matches what `fabro system prune` actually frees. A residual "other" summary row keeps `fabro system df` totals consistent and surfaces as a "Database & artifacts" table row. Also add a KiB tier to formatBytesAsMemory so small storage values render human-readably instead of raw byte counts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
412f57f5ae
|
feat(web): add secrets management settings page (#327)
## What Adds a **Settings → Secrets** page so secrets can be managed from the browser, backed by the existing secrets HTTP API and generated TS client. - **`/settings/secrets`** — lists stored secrets (name, type badge, description, last-updated) and deletes them through the shared confirm dialog. - **`/settings/secrets/new`** — the create form for **token** and **file** secrets. OAuth secrets still list and delete here, but are created by provider sign-in flows, not typed by hand (matching the CLI's `secret set`). - The **Secrets** entry is added to the settings sidebar nav. ## How - `secretsApi` wired into `api-client.ts`; `useSecrets()` SWR hook + `secrets` query key. - New sibling routes `secrets` and `secrets/new` under `settings` (same pattern as `runs` / `runs/:id`). - The settings layout gains optional **handle-driven** `description` and `headerAction`. When a page declares them, the layout renders title + subheading + a vertically-centered header action button as one unified header. Other settings pages are unaffected — they fall back to the existing title-only header. ## Notes - Values are write-only: the API never returns secret values, and the UI never displays them. - Reuses existing primitives throughout (`Panel`, `Badge`, `ConfirmDialog`, `useToast`, button/input classes) — no new shared components. - Verified: `bun run typecheck` and `bun run build` pass; routes serve 200. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
40ed64c1c2
|
Add system resources settings page (#328)
## Summary Adds server-visible resource reporting and a compact Resources settings tab for CPU, memory, and the filesystem that contains Fabro storage. ## Changes - Adds `GET /api/v1/system/resources` backed by `sysinfo`, including CPU sampling, cgroup-aware memory reporting, storage filesystem matching, and Fabro-managed disk byte totals. - Extends the OpenAPI contract and regenerates the Rust and TypeScript API clients. - Adds a deterministic demo-mode resources route. - Adds `/settings/resources` with 5 second polling and panels for overview, CPU, memory, disk, and notes. - Adds server integration/unit coverage and web route/render coverage. ## Screenshot  ## Verification - `cargo build -p fabro-api` - `cd lib/packages/fabro-api-client && bun run generate` - `cargo nextest run -p fabro-server --features test-support --test it api::system` - `cargo test -p fabro-server resource_sampler::tests` - `cd apps/fabro-web && bun test` - `cd apps/fabro-web && bun run typecheck` - `cd apps/fabro-web && bun run build` - `cargo +nightly-2026-04-14 fmt --check --all` - `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` --- [](https://github.com/compound-engineering) 🤖 Generated with GPT-5 via [Codex](https://openai.com/codex) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fbe8b50a16
|
feat(server): add GET /api/v1/providers and /settings/models page (#321)
## Summary Operators had no UI surface to see which LLM providers their Fabro server has configured — provider state was only inferable indirectly via the per-model `configured` flag on `GET /api/v1/models`. This adds a dedicated **Models** settings tab backed by a new providers endpoint. - **`fabro_model::Provider`** — a public projection of the internal `CatalogProvider` that *structurally* excludes credential-bearing fields (`auth`, `extra_headers`, `billing_policy`, `agent_profile`). Reused by the generated API client via progenitor `with_replacement`, mirroring the existing `Model` pattern — no parallel API DTO. - **`GET /api/v1/providers`** — lists catalog providers with effective config and a `configured` status stamped per request from `ready_llm_provider_ids()`. Sorted by the catalog's existing `provider_order`. No write endpoints. - **`/settings/models` web page** — new route + nav entry (`CpuChipIcon`, between Integrations and Security) rendering each provider with model count, default model, configured status, and a "Get API key" link for unconfigured providers. ## Key decisions - Provider sort: reuse catalog `provider_order` (priority desc, id asc) — zero extra code. - `adapter` is hidden in the UI row (noisy for first-party providers); the OpenAPI `adapter` field is pinned to an enum matching the closed `AdapterKind` type. - `configured` reflects credential resolution **at the time of the response**, not a frozen startup snapshot — doc/spec wording corrected to match. ## Testing - `fabro-model`: `From<&CatalogProvider>` + serde `skip_serializing_if` unit tests. - `fabro-api`: `Provider` type-identity + JSON-parity tests, including the required/optional field split. - `fabro-server`: handler tests for configured vs unconfigured providers, exact `model_count`/`default_model` against catalog truth, and credential-omission (asserts internal field names *and* the injected credential value never reach the wire). - OpenAPI route conformance test covers `GET /api/v1/providers`. - `cargo build --workspace`, `fmt --check`, `clippy -D warnings` clean; 935 Rust tests pass; web `tsc` typecheck passes. - Reviewed via a 10-persona `ce:review` (autofix) — no P0/P1 in shipped code; 8 safe fixes applied. Not done: manual UI screenshots — the `apps/fabro-web` build is blocked in this environment by an unrelated missing `@assistant-ui/react` dependency. Run `bun install` in `apps/fabro-web` to verify `/settings/models` manually. ## Post-Deploy Monitoring & Validation - **What to watch:** request logs for `GET /api/v1/providers` — expect `200`s for authenticated users, `401` for unauthenticated. The handler resolves LLM credentials per request via `ready_llm_provider_ids()` (the same path the existing `list_models` handler already uses). - **Healthy signals:** `/settings/models` renders the provider list; `configured` matches each provider's actual credential state; no credential strings appear in any response body or log line. - **Failure signals / rollback trigger:** any provider object in the response containing `auth`, `extra_headers`, or a raw key/token value → roll back immediately (the projection type makes this structurally impossible, but treat any occurrence as P0). 5xx spikes on the new route. - **Validation window / owner:** first 24h after deploy, owned by the deploying engineer. Pre-existing note (not introduced here): credential resolution can refresh OAuth tokens and write the vault as a side effect of this read — shared with `list_models`; flagged for a future caching pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
581ab41d28
|
feat(web): add Overview summary panel and promote PR to header pill (#299)
## Summary Promotes the most useful per-run state into the Overview tab so users no longer have to tab-hop to read the basics of a run. - **Adds a horizontal summary panel** above the workflow graph (right column only — does not span the stages sidebar) with five columns: **Created by · Changes · Sandbox · Cost · Artifacts**. Quiet-uppercase labels (`text-[10px] uppercase tracking-[0.08em] text-fg-muted`) over regular-weight values. Skeleton loaders while queries are in flight; em dash in muted color for missing/zero data. - **Promotes the PR chip** out of the meta strip into a `SECONDARY_BUTTON_CLASS`-style pill next to the Actions menu, visible on every tab. Pill renders only when a PR exists. - Lifts `formatBytesAsMemory`, `formatCpuCores`, `formatUsdMicros` to `lib/format.ts` so the panel can reuse them. - New `RunSummaryPanel` is split into a smart wrapper (owns the SWR hooks) + a presentational `RunSummaryPanelView` (prop-driven) for clean test seams. - All 7 `Principal` kinds (user / agent / system / slack / webhook / worker / anonymous) map to glyph + label; user kind uses login-initial avatar. ## Screenshots Captured against a real local Fabro server (`fabro server start`) on demo runs — these only exercise the Created-by column (the other cells display em dashes because the demo runs have no PR / diff / billing / artifacts data). The em-dash states **are** the intended empty-state design. ### Overview tab — full page  ### Header + tabs + summary panel close-up  ### Summary panel detail  > The PR pill (mint icon + `#number` next to Actions) is unverified visually because no demo run on this server has an associated PR — but the rendering path is the same `SECONDARY_BUTTON_CLASS` markup as the Actions button and is conditioned on `run.pullRequestUrl && run.number != null`. See the [HTML prototype](https://github.com/fabro-sh/fabro/blob/feat/run-overview-summary-panel/.context/run-overview-options.html) for the locked design. ## Test plan - [x] `cd apps/fabro-web && bun run typecheck` clean (only pre-existing assistant-ui errors) - [x] `bun test` — +12 new passes, no new failures (387 pass / 5 fail / 2 errors vs baseline 375 / 6 / 3) - [x] Manual: load `/runs/<id>` against a real server, confirm panel + em dashes render correctly - [ ] Manual on a run **with** a PR: verify the pill appears next to Actions and opens the PR in a new tab - [ ] Manual on a run **with** rich data (diff / billing / sandbox resources / artifacts): verify each column populates correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ac32963538
|
feat(web): add Children tab to Run detail page (#294)
## Summary - Surfaces parent/child run relationships in the web UI as a new **Children** tab between Files Changed and Sandbox on `/runs/:id`. - Backend exposes a new `children_count` field on the `Run` summary, computed on read from the existing `RunProjectionCacheState.children_by_parent` index — accurate without an extra query. - Frontend reuses the compact-table `RunRow` from `/runs` (now exported) so the children list matches the existing list-view at a glance. - Tab always shows, with a zero state when there are no children. Refresh button (icon-only, matching the Files Changed pattern) re-fetches both the list and the parent detail so the count badge updates with the list. ## Screenshots Captured live from the running fabro server. **Populated — `Children · 2` tab active, two succeeded child rows:**  **Zero state — visiting a run that has no children:**  ## API verification ```sh # parent $ curl -s -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:32276/api/v1/runs/01KRTKP5DJJ4EV6T7QSB081Z1N \ | jq '{id, parent_id, children_count}' { "id": "01KRTKP5DJJ4EV6T7QSB081Z1N", "parent_id": null, "children_count": 2 } # child $ curl -s -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:32276/api/v1/runs/01KRTKP7VAS2J2AG73GQSAKF4G \ | jq '{id, parent_id, children_count}' { "id": "01KRTKP7VAS2J2AG73GQSAKF4G", "parent_id": "01KRTKP5DJJ4EV6T7QSB081Z1N", "children_count": 0 } # list-by-parent $ curl -s -H "Authorization: Bearer $TOKEN" \ "http://127.0.0.1:32276/api/v1/runs?parent_id=01KRTKP5DJJ4EV6T7QSB081Z1N" \ | jq '{count: (.data | length), has_more: .meta.has_more}' { "count": 2, "has_more": false } ``` ## What's in each commit | Commit | What | | --- | --- | | `2f5f4296` | `chore(api-client)`: regenerate TS client from current OpenAPI spec — catches up drift from #292's source-aware diagnostics and the session/turn shape updates that hadn't been re-run yet. Pure generator output. | | `ba16d2b3` | `feat(web)`: the actual Children tab feature. Backend `children_count` field + cache wiring, new `useChildRuns` SWR hook, exported `RunRow`/`RUNS_LIST_GRID_TEMPLATE` from `runs.tsx`, new `run-children.tsx` route, `Run.children_count` on the generated TS type. | | `de0c32c9` | `docs`: live UI screenshots for this PR. Safe to revert before merge if reviewers prefer a screenshot-free repo. | ## Reproducing the screenshots 1. `cargo build -p fabro-cli && ./target/debug/fabro server start` 2. `cd apps/fabro-web && bun run build` 3. ```sh PARENT=$(./target/debug/fabro run hello --dry-run --detach --sandbox local --json | jq -r .run_id) ./target/debug/fabro run hello --dry-run --detach --sandbox local --parent "$PARENT" ./target/debug/fabro run hello --dry-run --detach --sandbox local --parent "$PARENT" ``` 4. Open `http://127.0.0.1:<port>/runs/$PARENT/children` (populated) and a child's children tab (zero state). ## Test plan - [x] `cargo nextest run -p fabro-store -p fabro-types -p fabro-api -p fabro-server -p fabro-mcp-server` — 900+ tests pass, including new `run_summary_includes_children_count` in `fabro-store` - [x] `cd apps/fabro-web && bun run typecheck` — clean - [x] `cd apps/fabro-web && bun test` — 383/383 pass - [x] OpenAPI ↔ Rust parity (the `fabro-api` `run_summary_round_trip` test covers the new field both directions) - [x] Manual API verification via curl (above) - [x] Live UI verification (screenshots above) ## Out of scope (v1) - Real-time SSE updates of the children list (refresh button covers this). - Multi-page pagination UI (shows first page with a "more exist" footer when `has_more`). - Parent breadcrumb on the child run page (separate small change). - Tree/nesting view (flat list only). - Empty-state CTA. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7ac15b28f0
|
feat(fabro-web): port /chats/new + /chats/:id from prototype (#289)
## Summary
Ports the validated `/chats/new` and `/chats/:id` chat surface from
`docs/superpowers/prototypes/2026-05-16-chats-new/` into
`apps/fabro-web`. Client-side scripted prototype mounted inside the
existing `AppShell`; replaces `/start` as the planned new "kick off
agent work" entry point (but does not delete `/start` in this phase).
- New routes: `/chats/new` (empty-state composer) and `/chats/:chatId`
(active conversation with assistant-ui's `<Thread>`, scripted streaming
replies, markdown + tool-call rendering).
- Drives `@assistant-ui/react` + `@assistant-ui/react-ui` via
`useLocalRuntime` and a custom `ChatModelAdapter` that cycles a 6-entry
scripted reply bank.
- Tailwind v4 cascade fix: assistant-ui CSS is now imported via `@layer
assistant-ui` so v4 utilities cascade above the package's unlayered
scoped preflight. Includes a discovered Bun-specific tweak — see Notable
Deviations below.
- StrictMode-safe first-message handoff: store seeds the user message
into `seedMessages` with a `pendingResponse: true` flag, and
`chats-detail` triggers a single `runtime.thread.startRun({ parentId:
null })` then consumes the flag. Avoids the prototype's
autorespond-lost-stream race under React 19 StrictMode.
The Ask-Fabro right sidebar (also in the prototype) is **out of scope**
for this PR.
Companion spec:
[`docs/superpowers/specs/2026-05-16-chats-new-prototype-design.md`](../tree/chats-new-port/docs/superpowers/specs/2026-05-16-chats-new-prototype-design.md)
Implementation plan:
[`docs/superpowers/plans/2026-05-16-chats-new-fabro-web-port.md`](../tree/chats-new-port/docs/superpowers/plans/2026-05-16-chats-new-fabro-web-port.md)
## Screenshots
Captured from a local debug `fabro server` running this branch's binary,
signed in via GitHub.
### `/chats/new` (empty state)

### `/chats/:chatId` (active conversation)

## Files
**New** (under `apps/fabro-web/`):
- `app/lib/chats-types.ts` — `Chat` wrapper + `ChatContentPart`
discriminated union over the API client's `CompletionContentPart`
- `app/lib/chats-script.ts` — 6-entry scripted reply bank
(`CompletionMessage[]`)
- `app/lib/chats-store.tsx` — Context + `useReducer` for chat metadata,
`pendingResponse` flag, scriptIndex
- `app/lib/chats-runtime.ts` — `createScriptedAdapter` +
`toThreadMessages` boundary converter
- `app/lib/test-utils.tsx` — minimal `renderHook` shim (lifts the
duplicated `IS_REACT_ACT_ENVIRONMENT` + dep-warning silencing pattern
out of `install-app.test.tsx`)
-
`app/components/chats/{tool-fallback,composer-chips,custom-composer}.tsx`
- `app/routes/{chats-layout,chats-new,chats-detail}.tsx`
- Tests: `chats-store.test.tsx` (5), `chats-runtime.test.ts` (4),
`chats-router.test.tsx` (3)
**Modified:**
- `package.json` — adds `@assistant-ui/{react,react-ui,react-markdown}`
(pinned exactly to versions verified in the prototype)
- `app/app.css` — `@layer` declaration + assistant-ui CSS imports into
`layer(assistant-ui)` + `.fabro-chat` `--aui-*` variable overrides
mapping to the Fabro palette
- `app/root.tsx` — removed `import "./app.css"` (see Notable Deviations)
- `app/router.tsx` — wires the chats routes under the AppShell tree
## Notable deviations from the plan
Two intentional deviations, both explained in their commit bodies:
1. **`apps/fabro-web/app/root.tsx` no longer imports `./app.css`.**
Bun's CSS bundler (used by `Bun.build` on `entry.tsx`) rejects
spec-valid `@layer name, name;` ordering between `@import` rules, even
though Tailwind's CLI accepts it. The CSS is built standalone by the
Tailwind CLI step in `scripts/build.ts` and linked from
`index.template.html`, so dropping the JS-side import bypasses Bun's
parser without any runtime change. A safety-net comment at the top of
`app.css` warns future engineers against re-adding the import. Commit:
`c37690be9`.
2. **`!` non-null assertions removed** in two places where the verbatim
prototype copy violated the global CLAUDE.md rule banning `!` in
production code: `chats-script.ts` now uses a typed `FALLBACK_REPLY` and
`??` coalescing; `composer-chips.tsx` lifts the first option of each
chip into a `DEFAULT_*` constant. `chats-runtime.test.ts`'s `for await`
drain loops were also replaced with `Array.fromAsync(...)` per the
no-loops-in-tests rule. Commits: `ace6ac6d4`, `652ad97af`.
## Test plan
- [x] `cd apps/fabro-web && bun run typecheck` — clean
- [x] `bun test` — 383 pass / 0 fail (12 new tests for chats)
- [x] `cd apps/fabro-web && bun run build` — succeeds; assistant-ui CSS
bundled into `dist/assets/app.css`
- [x] **Manual browser smoke test** — debug `fabro` binary running this
branch served `/chats/new` and `/chats/seed_email` correctly inside the
real AppShell with GitHub-OAuth auth (screenshots above).
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4f5e3b78f8
|
refactor: remove compatibility shims (#281)
## Summary Simplifies the greenfield PR/run schema surface by collapsing alias-only type shims and removing legacy compatibility paths that kept old wire shapes and workflow names alive. ## Changes - Use canonical `Run`, `PullRequestLink`, `PullRequestResponse`, `BoardColumn`, `WorkflowSettings`, SWR `Key`, and `SteerRunRequest` names directly across Rust and web code. - Remove legacy PR/event deserialization compatibility for old PR records and command output fields, with tests updated to reject stale wire shapes. - Drop obsolete workflow aliases for `agent_loop`, `one_shot`, `codergen_mode`, and `stack.child_dotfile`, then update docs and tests to the current names. ## Verification - `git diff --check` - `cargo +nightly-2026-04-14 fmt --check --all` - `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` - `cargo nextest run -p fabro-types -p fabro-api -p fabro-client -p fabro-store -p fabro-server -p fabro-workflow -p fabro-cli` - `cd apps/fabro-web && bun run typecheck` - `cd apps/fabro-web && bun test` --- [](https://github.com/EveryInc/compound-engineering-plugin) Generated with GPT-5 via [Codex](https://openai.com/codex) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d09e6cde33
|
feat(pr): support GitHub pull request associations (#270)
## Summary
Adds event-sourced pull request association management for runs while
preserving Fabro-created PR creation. A run can now store a current
GitHub PR association, replace it by linking another GitHub PR URL, and
remove it through an unlink event.
## What Changed
- Added `pull_request.linked` and `pull_request.unlinked` events,
projection replay support, and optional PR metadata fields in shared
pull request records.
- Added API, server, and client support for `PUT
/runs/{id}/pull_request` and `DELETE /runs/{id}/pull_request`; linking
accepts GitHub PR URLs, infers owner/repo/number, and captures live
GitHub title and branch metadata when available.
- Added `fabro pr link` and `fabro pr unlink`, updated `fabro pr view`,
and kept create/merge/close behavior guarded to GitHub PRs with usable
coordinates.
- Updated web UI rendering and internal event docs so stored PR links
display cleanly when live GitHub details are unavailable.
## Testing
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`
- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-types -p fabro-store -p fabro-server -p
fabro-cli`
- `bun run typecheck` in `lib/packages/fabro-api-client`
- `bun run typecheck` in `apps/fabro-web`
- `bun test` in `apps/fabro-web`
Refs https://github.com/fabro-sh/fabro/issues/235
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Haroldo Olivieri <6575718+haroldolivieri@users.noreply.github.com>
|
||
|
|
1cfe9419f5
|
refactor(llm): simplify catalog cleanup paths (#261)
## Summary This is a cleanup pass over the configurable LLM provider/catalog work from issue #210. It addresses reuse, quality, and efficiency findings from the phase 0-9 review without changing the public provider settings contract. Notable changes: - skip LLM client initialization during run preflight when the workflow has no LLM nodes - make preflight provider checks use alias-aware `Client::has_provider` - resolve `run.model.fallbacks` through the catalog instead of the old empty-key bridge - paginate `/models` before cloning returned rows - share label parsing, provider default-adapter lookup, enum expected-value formatting, and billing token formatting helpers - use catalog provider display names for OpenAI-compatible agent profiles - align process-env configured-provider discovery with `EnvCredentialSource` ## Verification - `cargo check -p fabro-config -p fabro-model -p fabro-auth -p fabro-agent -p fabro-workflow -p fabro-server -p fabro-cli` - `cargo nextest run -p fabro-config parse_labels_keeps_key_value_pairs` - `cargo nextest run -p fabro-workflow resolve_fallback_chain_resolves` - `cargo nextest run -p fabro-auth configured_providers` - `cargo nextest run -p fabro-server list_models` - `cargo +nightly-2026-04-14 fmt --check --all` - `cargo +nightly-2026-04-14 clippy -p fabro-model -p fabro-auth -p fabro-workflow -p fabro-server -p fabro-agent -p fabro-config -p fabro-cli --all-targets -- -D warnings` - `cd apps/fabro-web && bun test app/routes/run-billing.test.tsx` - `cd apps/fabro-web && bun run typecheck` - `git diff --check` --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4fa4716015
|
refactor(core): simplify reviewed run cleanup
Reuse shared frontend formatting and SSE dedupe helpers, tighten typed sandbox handling, remove obsolete run DTOs, and collapse auth-session revoke into a single store operation. |
||
|
|
cccb557281
|
feat(api): unify public run shape
Return canonical Run payloads across run list, board, create, and lifecycle endpoints. Move archive state out of RunStatus and into lifecycle metadata, split sandbox runtime from planned sandbox data, and separate static pull request records from live pull request details. Regenerate the TypeScript API client and migrate web, CLI, server, store, workflow, and API tests to the new contract. |
||
|
|
d2623662e0
|
feat(web): build unified auth sessions page
Render /profile/sessions from the new GET /api/v1/auth/sessions API. The page shows the current browser session and active CLI sessions in one list, with revoke buttons gated by the backend-supplied revocable field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
acec32cea9
|
feat(web): build live events page in settings
Replaces the /settings/live-events placeholder with a working page that streams server-wide events from /api/v1/attach. Shares the leader-owned cross-tab EventSource so additional tabs subscribe without opening parallel connections. The page keeps an in-memory ring buffer (newest first, max 1,000) with id or run_id:seq dedupe and resets on remount; live-only by design, nothing is replayed on connect or persisted in the browser. Reuses the existing event-debug filters, search, and details panel, and links each row's run_id to /runs/:id. The category filter is the static set of DebugCategory values so "All types" always matches. Settings layout is now fullHeight-aware so the events page can fill the viewport alongside the sub-nav. DebugEventDetailsPanel's event prop is broadened to a shared EventDisplayPayload shape so it accepts both EventEnvelope and the live payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7b832c74a8
|
feat(web): add Services tab to sandbox page
Lists backend-discovered TCP services for a run's sandbox between the Terminal and Filesystem tabs. Previewable ports open a signed Daytona URL in a new browser tab; the rest render as Unavailable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8bde7d77f5
|
feat(web): add Daytona VNC mode to Sandbox tab
Adds a third right-column mode (Terminal | Filesystem | VNC) gated to Daytona sandboxes. The panel POSTs /api/v1/runs/:id/sandbox/vnc, embeds the signed noVNC preview URL in an iframe with clipboard + fullscreen allowed, and renders distinct states for unsupported provider (Docker hides the tab entirely), 409 startup failure (recoverable, "Try again"), and 404/501 (non-recoverable). A reconnect button refetches the signed URL when it expires. Also fixes a Filesystem regression: the previous "skip first effect" ref guard meant @pierre/trees never received its first resetPaths call when the listing transitioned from empty to populated, leaving the tree stuck on the initial empty model. Now the model is always synced via resetPaths whenever the listing changes; verified live against a Daytona /workspace + / listing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cc5c505487
|
feat(web): add read-only Filesystem mode to Sandbox tab
Adds a Filesystem right-column mode alongside Terminal in /runs/:id/sandbox, selectable via ?mode=filesystem. The persistent SandboxDetails left column stays visible in both modes. Browses the run sandbox via the existing list/get file endpoints, previews text files with @pierre/diffs, and falls back to download-only for binary, oversized, and unreadable files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f73a1bb8f7
|
feat(web): add Sandbox tab and route to run detail
Adds /runs/:id/sandbox between Files Changed and Terminal, gated by the same sandbox-presence check as Terminal. The route fetches SandboxDetails through a new useRunSandboxDetails SWR hook and renders provider-neutral panels for Overview, Resources, Labels, and Timestamps. Null fields render as muted em dashes. |
||
|
|
b6ebe9ca50
|
feat(web): add specialized stage renderers for non-agent handlers
Replace the raw Debug fallback for human, conditional, parallel, parallel.fan_in, stack.manager_loop, and wait stages with purpose-built views — Q&A transcript, decision card, children grid, fan-in results with reducer transcript, cycle summary, and live waiter clock. Add a generic Summary card as the new default for any unknown handler so future StageHandler additions get a usable view for free. Debug tab remains as the escape hatch on every handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
010828ae7a
|
Add run commit diff picker | ||
|
|
447b94da6d
|
feat(run-files): add sandbox diff scopes
Add committed, uncommitted, and all scope handling for run files with source reporting for sandbox and final patch responses. Wire the run files page to persist scope in the URL and cache each scope independently. |
||
|
|
7b013cef7f
|
feat(web): inline-edit run titles in the run header
Click the title (or its hover-revealed pencil) to swap it for an input;
Enter or blur saves via PATCH /runs/{id}, Esc reverts. Trims whitespace,
no-ops blank or unchanged values, and surfaces server validation errors
through the existing toast system. The board and breadcrumb refresh from
the same SWR cache after a successful save.
|
||
|
|
697dc1294f
|
feat(runs): support explicit run titles
Persist resolved run titles on creation, expose title update events, and add the run title PATCH API. Regenerate API clients and refresh web/server invalidation so title changes are reflected across run detail and board views. |
||
|
|
1a43cf5abc
|
feat(api): expose stage handlers on run stages
Populate RunStage.handler from workflow graph metadata and use it for the run stages renderer instead of inferring from activity events. |
||
|
|
4d33daf14e
|
fix(web): render prompt.completed in stage Transcript
Prompt-shape stages (prompt, fan_in) only emit `prompt.completed` for their response, so the Transcript tab showed the input but never the output. Add the event to STAGE_ACTIVITY_EVENT_TYPES and to the eventsToActivity reducer, suppressing it when a prior agent.message already streamed the same content (agent stages). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |