The /runs/:id/children tab now gets server-side pagination, sortable
columns, column picker, search/time/archived filters, and bulk
archive/unarchive/delete — same affordances as the main runs list view.
Preferences persist to localStorage under a dedicated key so they don't
collide with the /runs page.
Repo and Workflow filter buttons are intentionally omitted (children
typically share these with the parent), but those columns remain visible
for the cases where workflows fan out across repos.
- New childRunsListPreferences in components/runs-list/preferences.ts
- run-children.tsx fetches via useRunsPage({parentId, ...}) with all
list controls wired up
- Empty state retains the existing "Learn about parent links" CTA
- useChildRuns + queryKeys.runs.children removed (replaced by the
generalized useRunsPage)
- useRetryRun broadcasts via mutateRunListCaches now that the dedicated
children cache key is gone
- Revert board-cache children matcher added in the previous commit
(no longer needed)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the inline runs list view (pagination, sorting, column picker,
selection, bulk actions) from runs.tsx into a self-contained module at
components/runs-list/ so it can be reused by the Children sub-tab and
future run-list surfaces. No behavior change to /runs.
- RunsListView now takes an emptyState slot (Runs page passes RunsLandingEmpty)
- useRunsPage accepts an optional parentId for non-page run lists
- runListCacheMatchers also matches ["runs","children",...] so bulk
archive/delete invalidate children caches automatically
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pending approval is the only action a user can take to unblock a run,
so the Approve action shouldn't be buried in the Actions dropdown.
- Run detail header: render a primary teal "Approve" button beside the
Actions menu when approval is pending; remove the duplicate menu item.
- Board view (/runs): surface `pendingApproval` on RunItem and render
an inline Approve button on cards in the Pending column.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resume attempts must not inherit the prior terminal conclusion. Leaving it in the projection made attach and summaries treat a live resumed run as already failed.
Cancelled runs are now eligible for retry alongside other failed and
dead runs. A user who cancels a run and then changes their mind no
longer has to manually re-create it from scratch.
- ensure_retryable drops the FailureReason::Cancelled rejection arm
- canRetry simplifies to failed || dead (still gated by !archived)
- OpenAPI Retry Run description no longer lists cancelled as ineligible
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align Fabro's built-in OpenAI GPT-5 catalog defaults with Codex's 272k context window so Codex-backed runs compact before provider rejection. Direct API users can still raise the window through catalog overrides.
- Loosen vertical spacing between tool rows (space-y-1 → space-y-1.5).
- Add `title={tool.description}` so hovering a tool surfaces the
model-facing description without re-adding inline description text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #384 unconditionally dropped TODO events from child sessions to stop
a subagent's update_plan from overwriting the root agent's plan in
StageProjection.todos. But when the root never emits a plan and a
subagent owns the only one (e.g. delegating gpt-5.5 stages), every TODO
event was filtered, the projection stayed None, and the web sidebar
hid its Todos section.
Narrow the guard: a child OpenAI plan event is now rejected only when
it would replace an existing list with a different list_id. Empty
slots and same-list continuations pass through, so a subagent plan
projects when nothing else owns the slot, and a later root plan still
takes over via the existing list_id replacement path.
Regression test mirrors the failing run (implement@1 on
01KSDXK5DJ61CFCK9YSDR8AETQ). The PR #384 canary still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the "Full Access" / permission badge section; Tools now conveys
the same surface area more directly.
- Reorder so Tools sits at the bottom (after MCPs).
- Strip each tool row to just a used/not-used indicator and the tool
name — no descriptions, source labels, or category badges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /settings/sandboxes route surfaces the local, docker, and daytona
runtime sandbox providers using existing useServerSettings(). Mirrors
the /settings/models pattern: enabled providers shown first, disabled
hidden behind a progressive-disclosure toggle. Disabled Daytona row
links to add the DAYTONA_API_KEY secret.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gives each of the four empty-state suggestions a Heroicon to aid
scannability: warning triangle for "Surface errors", bolt for
"Analyze performance", map for "Review key decisions", and lightbulb
for "Suggest improvements".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
This improves Slack support from setup through day-to-day operator
visibility. The server now resolves Slack credentials through the same
`process env -> server.env` path as other server secrets and logs
whether Slack is enabled or which credential variables are missing.
Run lifecycle notifications now use a structured Block Kit layout
instead of one dense markdown section. Messages get a header, workflow
summary, metadata fields, optional failure details, optional PR
metadata, and an Open in Fabro context link.
The public docs now explain local `server.env` setup, Docker/process-env
setup, expected startup logs, lifecycle smoke testing, terminal
`run.failed` semantics, and the current route-based
`[run.notifications]` format.
## Validation
- `cargo test -p fabro-slack`
- `cargo check -p fabro-server`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo dev docs check`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
Removes the standalone `agent.context_window.snapshot` event and instead
attaches the context-window projection directly to `agent.message`. This
eliminates the async provider token-count API calls that the old
approach required, and simplifies the event log to a single event type
carrying all post-response agent data.
## What Changed and Why
**Before:** After each LLM turn, the agent emitted a separate
`agent.context_window.snapshot` event — first a local estimate, then
potentially a second one after an async `count_input_tokens` call
resolved (or after response usage arrived). This required fingerprint
deduplication state, a `close_token` to cancel in-flight counts, and
frontend handling for the extra event type.
**After:** The `AgentEvent::AssistantMessage` variant carries an
`Option<StageContextWindowProjection>`. The projection is computed
locally at request-build time and then refined using response token
usage when available (`ResponseUsageScaledBreakdown`), or kept as a
`LocalEstimate` when response usage is absent. No provider API calls are
made.
### Plan Summary
- **Task 1:** Added `context_window:
Option<StageContextWindowProjection>` to `AgentMessageProps` (Rust types
+ OpenAPI), removed `AgentContextWindowSnapshotProps` and
`EventBody::AgentContextWindowSnapshot`.
- **Task 2:** Removed the spawned `count_input_tokens` task,
`close_token`, fingerprint sets, and both snapshot-emit methods from
`Session`. Added `context_window_from_response_usage` to
`context_window.rs`; `BuiltRequest` now holds the local projection
instead of the tool list.
- **Task 3:** Workflow conversion copies `context_window` from
`AgentEvent::AssistantMessage` into `AgentMessageProps`; store reducer
reads it from `AgentMessage` instead of the removed snapshot variant and
stamps `event_seq`.
- **Task 4:** GET endpoint tests updated to seed data via
`agent.message` with embedded context-window; endpoint behavior
unchanged.
- **Task 5:** Frontend constant and tests for
`agent.context_window.snapshot` removed; `agent.message` already
invalidates `stageContextWindow` through existing stage-activity
handling. TypeScript client regenerated with the new `AgentMessageProps`
model.
### Key Design Decisions
- **No provider token-count API calls** during normal execution —
context-window accuracy relies on local estimates scaled by response
usage, which is always available for successful turns.
- **Failed-before-response turns** emit no context-window data
(`context_window: None`), matching the old behavior where a snapshot
would have been emitted but response-usage scaling would never arrive.
- `BuiltRequest` drops the `tools` field (only needed for the
now-removed snapshot emission path); the local projection is computed at
build time and stored directly.
### Fabro Details
<details>
<summary>Ran 8 stages in 60m 3s for $55.78</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 1s | – | 0 |
| preflight_lint | 2m 16s | – | 0 |
| implement | 30m 39s | $44.82 | 0 |
| simplify_opus | 10m 48s | $4.03 | 0 |
| simplify_gpt | 5m 1s | $6.93 | 0 |
| verify | 8m 47s | – | 0 |
| **Total** | **60m 3s** | **$55.78** | **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>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
## Summary
Adds support for MCP servers that use the SSE-based HTTP transport,
including Playwright MCP.
Fabro already supports stdio and Streamable HTTP MCP servers. Some MCP
servers still expose the SSE transport shape where the client opens an
SSE stream, receives an `endpoint` event, and sends JSON-RPC requests
back to that endpoint. This PR adds an explicit `protocol = "sse"`
option while keeping Streamable HTTP as the default.
## What Changed
- Added `McpHttpProtocol` with `streamable_http` as the default and
`sse` as an opt-in protocol.
- Added an SSE MCP client transport implementation.
- Wired HTTP MCP setup to choose Streamable HTTP or SSE based on config.
- Added `protocol = "sse"` support for both `http` and `sandbox` MCP
entries.
- Updated sandbox MCP resolution so SSE sandbox servers connect through
the preview `/sse` path.
- Documented `protocol = "sse"` for Playwright MCP.
- Added an integration test covering SSE initialize, tool listing, and
tool calls.
## Example
```toml
[run.agent.mcps.playwright]
type = "sandbox"
protocol = "sse"
command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless", "--browser", "chromium"]
port = 3100
startup_timeout = "60s"
tool_timeout = "2m"
```
## Compatibility
Existing MCP configs are unchanged because `protocol` defaults to
`streamable_http`.
## Validation
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo nextest run -p fabro-mcp`
- `cargo check -p fabro-agent -p fabro-workflow -p fabro-config`
---------
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking a column header on /runs?view=list silently did nothing.
handleSortClick called updateParam three times in a row; each call
cloned a fresh URLSearchParams from the same closure-captured
searchParams and invoked setSearchParams independently. React Router's
setSearchParams calls don't merge in a single tick, so only the last
one's params landed in the URL -- the sort change was overwritten by
the trailing page-reset. setPageSize had the same shape and was also
quietly losing the size change.
Replace the per-key URL mutator with a reducer-shaped
updatePreferences((prev) => next) that operates on the typed
RunsWorkspacePreferences model. URL and localStorage are derived from
the same next object via the existing converters, and each handler
makes exactly one call -- so concurrent-update races are
structurally impossible. Use setSearchParams((prev) => ...) so the
updater reads the latest committed URL params instead of a closure.
Add page to RunsWorkspacePreferences so the model describes the full
URL view state; strip it before persisting to localStorage since page
is ephemeral. Rename persistRunsWorkspaceSearchParams to
persistRunsWorkspacePreferences to match what it now consumes. Guard
the hydration useEffect with a useRef so it runs only on mount.
Add a regression test that clicks a SortHeader and asserts the URL
gains sort=status while preserving view=list&archived=1, and that a
second click toggles direction=asc.
The local server wasn't setting git_root, and wasn't defaulting to the
workspace working directory, so it didn't find project-specific skills.
---------
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Fixes the flaky `attach_json_errors_without_prompting_for_human_input`
snapshot by moving elapsed JSON duration redaction into the shared
`fabro-test` snapshot filters. `duration_ms`, `wall_time_ms`,
`inference_time_ms`, `tool_time_ms`, and `active_time_ms` now use one
common normalization path, and the attach, wait, and events integration
snapshots use that shared helper instead of hand-rolled per-test
regexes.
This keeps snapshots focused on event shape and command behavior rather
than exact runtime timing, while leaving exact timing relationships to
direct assertions in lower-level tests.
Verified with `cargo nextest run -p fabro-test`, `cargo nextest run -p
fabro-cli --test it cmd::attach`, `cargo nextest run -p fabro-cli --test
it cmd::wait`, `cargo nextest run -p fabro-cli --test it cmd::events`,
`cargo +nightly-2026-04-14 fmt --check --all`, and `cargo
+nightly-2026-04-14 clippy -p fabro-test -p fabro-cli --test it -- -D
warnings`.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (context unknown, reasoning effort unknown) via
[Codex](https://openai.com/codex)
- Add /automations/new with Basics/Source/Goal/Triggers panels and a
kebab-case Slug auto-derived from Name until the user edits it
- Wire the "Create Automation" button on /automations to the new page
- Move individual automation URL from /automations/<slug> to
/automation/<slug>; /automations and /automations/new are unchanged
- Refresh /settings/secrets/new to use the Panel + Row layout pattern:
breadcrumb header, one field per row, plain footer, no inside-Panel
stacked form
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lifecycle status pill next to the run title now hides when the run
is in the initializing column, mirroring the board view behavior. This
removes the duplicate "Initializing" / "STARTING" indicators that
appeared together on the row during startup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Connect menu (Preview / SSH) on the run detail header was wired up
only in demo mode and the items were never connected to real actions.
Drop the dead UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Pending, Skipped, and Cancelled status pills used `text-fg-muted`
(#4B5768) on `bg-overlay-strong`, producing ~1.7:1 contrast against the
panel — well below WCAG AA. Switch to `text-fg-3` (#A8B5C5) for ~6:1
while keeping the subdued look that distinguishes these states from
active/result tones.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reuses the StagePopover from the stages sidebar so graph nodes reveal
the same handler, timing, model, and status-specific detail on hover.
The graph is server-rendered SVG injected via innerHTML, so listeners
are attached imperatively alongside the existing click handlers; the
popover is portal-positioned via the shared hoverCardStyle helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hovering a stage row reveals handler, timing, model, and status-specific
detail — failure reason for failed/retrying, notes for skipped/partial,
tokens and files-touched for succeeded. Lazy-fetches per-stage events
on first hover via the existing useRunStageEvents hook; HoverCard gains
an openDelay so a cursor sweep doesn't trigger fetches for every row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two icon buttons on the stage events toolbar, to the right of the
model usage label: copy all loaded events as pretty-printed JSON, or
download them as JSONL. Both read from the existing SWR cache, so no
new API surface — for in-flight stages they're a snapshot of what the
client has fetched so far.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the native browser title tooltip on the model chip with a
structured HoverCard listing provider, model, reasoning, and speed.
Shorten the chip label to `model[effort]` (e.g. `gpt-5.5[xhigh]`).
Search expands on focus with a width transition and collapses on blur
when empty. Ghost icon style when collapsed; full input styling slides
in on expand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On /settings/models, unconfigured providers now offer "Add secret →"
alongside "Get API key →", deep-linking to /settings/secrets/new with
the expected vault secret name prefilled. Driven by a new
`expected_secret_name` field on the Provider API, derived from the
first vault credential in the catalog so the suggestion stays in sync
with the catalog instead of being hardcoded on the frontend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the existing summary.size (XS/S/M/L/XL) as a compact bold
badge to the right of the last-event chip, with a tooltip showing
the underlying billed cost. Hidden when no billing data is available
so we don't show a misleading "XS" for runs with zero cost.