Commit graph

3513 commits

Author SHA1 Message Date
Bryan Helmkamp
7f81beb63c
docs(changelog): refresh recent product changes
Add dated changelog entries for the recent user-facing changes and advance the changelog watermark.
2026-05-18 14:42:54 -04:00
fabro-releases[bot]
a9ef40d9e4 Bump version to 0.237.0-nightly.1 2026-05-18 17:33:39 +00:00
Bryan Helmkamp
29b7cc0de0
feat(workflow): enforce strict api/acp backends (#307)
## Summary

This PR makes agent execution a strict two-backend contract: API-backed
stages use Fabro-owned model/provider auth, while ACP-backed stages
launch a user-supplied stdio process that owns its own auth and tools.
That removes the legacy CLI backend and prevents ACP execution from
accidentally resolving or forwarding provider credentials.

## Changes

- Replaces the old `api`/`cli`/`acp` backend model with `AgentBackend {
api, acp }`, with `backend=\"cli\"` rejected and migrated toward
explicit ACP process configuration.
- Splits ACP process configuration into `acp.command` for shell command
strings and `acp.config` for JSON stdio configs, while rejecting legacy
`acp_command`.
- Restricts ACP to `agent` nodes and rejects API-only attributes such as
`model`, `provider`, `reasoning_effort`, `max_tokens`, and `speed` on
ACP nodes.
- Deletes the workflow CLI runtime, CLI credential resolver surface, CLI
live smoke tests, and `agent.cli.*` event handling.
- Updates ACP events and projections to report process identity
(`command`, optional `config_name`) rather than provider/model metadata.
- Updates import/stylesheet propagation, CLI workflow smoke coverage,
server steering tests, and web model extraction for the new
event/backend contract.

## Validation

- `cargo check -p fabro-auth -p fabro-acp -p fabro-workflow -p fabro-cli
--all-targets`
- `cargo nextest run -p fabro-auth -p fabro-acp -p fabro-validate -p
fabro-store -p fabro-workflow --lib`
- `cargo nextest run -p fabro-acp`
- `cargo nextest run -p fabro-cli --test it
workflow::acp::acp_backend_workflow`
- `cargo nextest run -p fabro-workflow --test it
codergen_without_backend_simulated`
- `cargo nextest run -p fabro-workflow --test it
import_e2e_through_engine`
- `cargo nextest run -p fabro-workflow --test it stylesheet_application`
- `cargo nextest run -p fabro-server
steer_with_active_acp_stage_returns_non_steerable_conflict`
- `cargo nextest run -p fabro-server
active_acp_stage_marker_clears_on_terminal_paths`
- `cargo nextest run -p fabro-types
agent_backend_accepts_only_api_and_acp`
- `cd apps/fabro-web && bun test app/routes/run-stages.test.ts`
- `cd apps/fabro-web && bun run typecheck`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: Peter Bell <4843+PeterBell@users.noreply.github.com>
2026-05-18 13:20:56 -04:00
Bryan Helmkamp
c6547f126a
fix(web): raise timeout for build tests that run a real vite build (#310)
## Summary
- The TypeScript CI on main started failing because two tests in
`apps/fabro-web/scripts/build.test.ts` shell out to a real production
Vite build and were hitting Bun's default 5000ms test timeout.
- When the test timed out, Bun killed the build subprocess (SIGTERM →
exit 143), reported as `build failed with code 143`.
- Raise the per-test timeout to 60s on the two build-running tests so CI
variance no longer kills the build.

Failing run:
https://github.com/fabro-sh/fabro/actions/runs/26042062252/job/76556114190

## Test plan
- [x] `cd apps/fabro-web && bun test scripts/build.test.ts` passes
locally
- [ ] TypeScript CI passes on the PR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 12:52:18 -04:00
Bryan Helmkamp
cd74013d06
refactor(auth): split credential sources and vault schemas (#306)
## Summary

Compared with `origin/main`, this PR splits credential storage and
credential references into explicit types. Vault secrets now distinguish
`token`, `oauth`, and `file` payloads, while runtime/model configuration
points to credentials through explicit `env:<NAME>` and `vault:<NAME>`
source refs.

## Changes

- Replaces the old `environment`/`credential` secret schema vocabulary
with `token`/`oauth`/`file` across OpenAPI, Rust API tests, generated
TypeScript models, CLI/docs references, and the changelog.
- Updates auth resolution, refresh, provider strategies, workflow LLM
handling, server diagnostics, install flows, run manifests, and secret
handlers to consume typed vault entries and explicit credential sources.
- Updates provider catalog TOMLs and config parsing so provider auth and
extra headers use `vault` refs instead of ambiguous `credential` refs.
- Updates CLI install/login/run/secret paths and integration tests to
write and read the new credential shapes.
- Removes the temporary legacy vault migration and empty-vault fallback,
then centralizes provider vault secret-name lookup and Codex API
credential shaping.

## Verification

- `cargo +nightly-2026-04-14 fmt --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-auth -p fabro-model -p
fabro-config -p fabro-vault -p fabro-server -p fabro-cli --all-targets
-- -D warnings`
- `ulimit -n 4096 && cargo nextest run -p fabro-auth -p fabro-model -p
fabro-config -p fabro-vault -p fabro-server -p fabro-cli` (`1938`
passed, `35` skipped)
2026-05-18 11:07:42 -04:00
David Bock
6a86ced77c
fix(server): persist manifest metadata names (#302)
When Fabro creates a detached run through the server, the resulting run
metadata should still read like something a human can trust at a glance.
Before this change, those runs could persist with
`settings.project.name` and `settings.workflow.name` left `null` even
though Fabro already had enough local context to infer them. That made
`inspect` output look half-populated and made it harder to tell whether
the saved run state was complete.

This fixes that trust gap in the server-backed manifest flow.

## Summary
- backfill missing manifest-backed project and workflow names during
server run preparation
- prefer explicit `[workflow].name` from bundled `workflow.toml`, then
fall back to graph name or workflow slug
- cover both manifest preparation and persisted run-state behavior with
server tests

## Testing
- cargo test -p fabro-server
prepare_manifest_backfills_missing_project_and_workflow_names --
--nocapture
- cargo test -p fabro-server
prepare_manifest_preserves_explicit_project_and_workflow_names --
--nocapture
- cargo test -p fabro-server
create_run_persists_backfilled_project_and_workflow_names -- --nocapture

---------

Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-05-18 08:31:23 -04:00
fabro-releases[bot]
f24cb05972 Bump version to 0.237.0-nightly.0 2026-05-18 10:45:02 +00:00
Aleksi Asikainen
492aba7fff
fix: MiniJinja can't find partials (#301)
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Fixes an issue where use of MiniJinja
[`include`](https://jinja.palletsprojects.com/en/stable/templates/#include)
control structure (`{% include "filename.ext" %}`) causes a render error
`template not found: tried to include non-existing template
"filename.ext"`

### Example broken diagram
``` dot
digraph ValidatePlan {
    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    test_inline_prompt [label="moo" prompt="{% include 'test.tpl.md' %}"]
                                          // ^^^^^^^^^^^^^^^^^^^^^^^^^
    start -> test_inline_prompt -> exit
}
```

### Fix
The core issue was that template rendering knew the source name for
diagnostics, but did not have a loader rooted at the prompt/goal file
location. Includes therefore failed even when the included file existed
next to the rendered file. The fix adds optional loader support to
`fabro-template`, then wires workflow rendering to the existing
`FileResolver` so includes resolve relative to the file currently being
rendered.

For `fabro validate`, there was a second manifest-specific problem:
validation runs through a bundled manifest, and the manifest builder
only bundled explicit `prompt.md` / `goal.md` files, not static
MiniJinja `include` dependencies inside those files. The manifest
builder now scans prompt/goal template text for literal `{% include
"file" %}` / `{% include 'file' %}` references and bundles those files
too. Missing or unsafe include names are left for MiniJinja/runtime
validation rather than expanding scope.

(For clarity: The fix does not support variables or arrays in
`include`.)
2026-05-17 22:15:45 -04:00
Bryan Helmkamp
302e2445b4
refactor(model): move provider facts into catalog (#298)
## Summary

Moves provider-specific facts out of `AdapterKind` metadata and into
provider catalog data, leaving adapters responsible for runtime protocol
behavior. This makes providers that share an adapter mostly TOML-driven
while still surfacing adapter construction failures during readiness
checks.

## What Changed

- Provider TOML now owns auth mode, API-key/header policy, billing
policy, agent profile, base URLs/env overrides, extra headers, and probe
markers.
- Auth, install, config, diagnostics, and server flows resolve provider
credentials from catalog auth config, including API-key, header-only,
and no-auth providers.
- LLM client registration now reports adapter construction failures,
validates final adapter requests before HTTP dispatch, and preserves
custom primary auth headers.
- Billing and docs now use provider-owned billing policy instead of
adapter metadata, and the old adapter metadata surface is removed.

## Reviewer Notes

OpenAI-compatible `base_url` validation now happens during
adapter/client registration rather than catalog build. That keeps
catalog parsing adapter-agnostic while still letting readiness and model
listing reflect providers that cannot register.

## Verification

- `cargo check -p fabro-model -p fabro-auth -p fabro-llm -p fabro-server
-p fabro-cli`
- `cargo nextest run -p fabro-llm -- adapter_registry`
- `cargo nextest run -p fabro-model -- catalog`
- `cargo nextest run -p fabro-auth -- api_key`
- `cargo nextest run -p fabro-server -- install`
- `cargo +nightly-2026-04-14 fmt --check --all`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](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>
2026-05-17 20:59:08 -04:00
Bryan Helmkamp
93452001a1
feat(api): require typed PermissionLevel on session create (#300)
## Summary

- `POST /api/v1/sessions` now requires `permissions` as a typed enum
(`read-only` | `read-write` | `full`) instead of accepting an optional
plain string.
- Removes the silent fallback at `sessions.rs:906-911` where unknown
values (e.g. `"readonly"`) were coerced to `read-write` — a real
security footgun: a client trying to lock the agent down would get write
access instead.
- Invalid or missing values are now rejected by axum's `Json` extractor
with `422 Unprocessable Entity`.

## Approach

- New `PermissionLevel` OpenAPI schema (`type: string, enum: [...]`).
- Moves `PermissionLevel` from `fabro_agent::cli` to
`fabro_types::session` so `fabro-api` can `with_replacement` it without
a circular dep. `fabro_agent::cli::PermissionLevel` remains as a `pub
use` re-export so existing call sites keep working.
- `SessionRecord.permissions` becomes required and non-nullable for
coherence — every created session has a concrete level.
- `build_tool_approval` in the server takes `PermissionLevel` directly;
the string-match fallback is deleted.
- CLI's `session_permissions` returns a concrete `PermissionLevel`
(defaults to `read-write` when neither flag nor settings provide one)
and is sent explicitly on every request.

## Scope notes

Confirmed out of scope and not addressed here:
- Mid-session model/permission switching
- Interactive tool approval / HITL

## Breaking change

The `permissions` field is now required on `CreateSessionRequest` and
non-nullable on `SessionRecord`. Existing on-disk session records
persisted with `"permissions": null` will fail to deserialize.
Acceptable per project policy (no migration); local dev users may need
to clear `~/.fabro/storage/sessions/` once.

## Test plan

- [x] `cargo build --workspace`
- [x] `cargo nextest run -p fabro-api` — 125/125 (includes new
`permission_level_round_trip` parity tests)
- [x] `cargo nextest run -p fabro-server` — 554/554 (includes new 422
tests for missing + invalid permissions)
- [x] `cargo nextest run -p fabro-cli` — 892/892
- [x] `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- [x] `cargo +nightly-2026-04-14 fmt --check --all`
- [x] `bun run generate` on `fabro-api-client` — emits typed
`PermissionLevel` union and required field on `CreateSessionRequest`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:15:20 -04:00
Bryan Helmkamp
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

![Overview tab](https://files.catbox.moe/2idmv5.png)

### Header + tabs + summary panel close-up

![Header and panel](https://files.catbox.moe/bugbmy.png)

### Summary panel detail

![Summary panel](https://files.catbox.moe/4sbcl0.png)

> 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>
2026-05-17 16:52:48 -04:00
Bryan Helmkamp
73ebb7d28b
feat(mcp): support run parent relationships (#295)
## Summary

- Add parent metadata (`parent_id`, `children_count`) to Fabro MCP run
summaries, search summaries, and created-run results.
- Allow MCP clients to create child runs, search direct children, and
link or unlink an existing run's parent through the existing run tools.
- Update MCP docs and tool descriptions for the parent-aware
create/search/interact behavior.

## Test Plan

- [x] `cargo +nightly-2026-04-14 fmt --check --all`
- [x] `cargo nextest run -p fabro-mcp-server`
- [x] `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:26:18 -04:00
Bryan Helmkamp
d1fd6d3abf
fix(web): widen Children tab and hide zero count badge
The Children sub-tab was missing `wide: true` on its route handle, so
the Run detail nav narrowed to max-w-5xl only on this tab. Hide the
count badge when the value is zero to reduce visual noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:18:20 -04:00
Bryan Helmkamp
8d1b5f6c23
feat(mcp): add interrupt action to fabro_run_interact (#296)
## Summary

- Adds a standalone `interrupt` action to the `fabro_run_interact` MCP
tool, closing a parity gap with the HTTP API (`POST
/api/v1/runs/{id}/interrupt`).
- Lets MCP callers cancel an API-mode agent's current LLM round and park
it in `SteeringHub`'s `waiting_for_steer` state without committing to
follow-up text in the same call.
- Dispatches through the existing `Client::interrupt_run`; no client or
server-side changes.

## Why not just use `message` with `interrupt: true`?

Combined interrupt+steer remains the right choice when you want to
redirect the agent. Bare interrupt is for "pause the agent while I
decide what to say next." The variant's doc comment steers callers
toward `message` or `cancel` as the usual options, since a bare
interrupt with no follow-up leaves the run idle indefinitely.

## Test plan

- [x] `cargo nextest run -p fabro-mcp-server` — 13/13 pass, including
new `interrupt_action_requires_only_run_id` unit test
- [x] `cargo nextest run -p fabro-cli -E 'test(/mcp_/)'` — 27/27 pass,
including extended
`mcp_interact_actions_resolve_selector_and_call_expected_endpoints` E2E
(mocks `POST /runs/{id}/interrupt`, asserts the tool hits it)
- [x] `cargo +nightly-2026-04-14 clippy -p fabro-mcp-server -p fabro-cli
--all-targets -- -D warnings` — clean
- [x] `cargo +nightly-2026-04-14 fmt --check` on touched crates — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:12:32 -04:00
Bryan Helmkamp
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:**

![Children tab
populated](https://raw.githubusercontent.com/fabro-sh/fabro/feature/run-children-tab/.github/assets/children-tab/populated.png)

**Zero state — visiting a run that has no children:**

![Children tab zero
state](https://raw.githubusercontent.com/fabro-sh/fabro/feature/run-children-tab/.github/assets/children-tab/zero-state.png)

## 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>
2026-05-17 10:21:40 -04:00
fabro-releases[bot]
19b84777c7 Bump version to 0.236.0-nightly.0 2026-05-17 09:19:05 +00:00
Bryan Helmkamp
3afc95abdd
test(server): isolate storage in streaming_session_turn_updates_runtime_context test (#293)
## Summary

- Fixes the flaky nightly release-pipeline failure observed on tag
`v0.235.0-nightly.1` ([run
25975240763](https://github.com/fabro-sh/fabro/actions/runs/25975240763/job/76354137058)).
-
`streaming_session_turn_updates_runtime_context_without_copying_prior_history_to_turn`
was using the default test server settings, which means every parallel
test shares the default session storage directory
(`$HOME/.fabro/storage`).
- `session_store::write_json` writes via `fs::write`, which truncates
the file before writing. A concurrent reader from another test's
`AppState` session lookup can observe the empty file mid-write and fail
to deserialize. The deserialization error bubbled up as a `turn.failed`
SSE event carrying `Serialization error: EOF while parsing a value at
line 1 column 0`.
- Apply the same isolation pattern used in `e9387bf62` for
`interrupt_active_session_turn_cancels_runtime_and_persists_interrupted`:
give this test its own storage root under `std::env::temp_dir()`.

## Test plan

- [x] `cargo nextest run -p fabro-server -- streaming_session_turn`
passes locally
- [x] `cargo nextest run -p fabro-server` (full suite, 552 tests) passes
locally
- [x] `cargo +nightly-2026-04-14 clippy -p fabro-server --all-targets --
-D warnings` clean
- [ ] CI green

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 05:06:57 -04:00
fabro-releases[bot]
0f5285209f Bump version to 0.235.0-nightly.1
Some checks failed
Rust / Test (macOS) (push) Has been cancelled
Rust / Format (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Generated Docs (push) Has been cancelled
Rust / Test (Linux) (push) Has been cancelled
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
2026-05-16 22:59:47 +00:00
Bryan Helmkamp
2ba04be181
feat(template): add source-aware diagnostics (#292)
## Summary

Template failures from `fabro run` and structural warnings from `fabro
validate` now preserve source provenance through rendering, workflow
transforms, API serialization, and CLI display. Diagnostics can point at
the actual workflow, import, or prompt file with node/attribute context
instead of surfacing MiniJinja's generic `<string>` source.

## What Changed

- Added named MiniJinja render APIs plus miette-aware `TemplateError`
metadata for source names, source text, spans, and labels.
- Reworked workflow template expansion so inline attributes, imported
workflows, and `@prompt` files render with file and owner context.
- Split strict run behavior from structural validate behavior: run-start
still hard-fails on missing inputs, while validate emits source-aware
warnings and continues linting.
- Extended validation diagnostics through Rust structs, OpenAPI, server
DTO mapping, and CLI rendering with optional source path, line, column,
span, and related metadata.
- Added regression coverage across template rendering, workflow
transforms, CLI output, and the server validate endpoint.

## Verification

- `cargo nextest run -p fabro-template`
- `ulimit -n 4096 && cargo nextest run -p fabro-workflow --no-fail-fast`
- `cargo nextest run -p fabro-cli
bare_fabro_with_unbound_inputs_validates_structurally_with_warning
run_rejects_unbound_template_inputs_before_creating_remote_run`
- `cargo nextest run -p fabro-server
validate_endpoint_returns_template_source_coordinates`
- `cargo build -p fabro-api`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: Aleksi Asikainen <1086393+salieri@users.noreply.github.com>
2026-05-16 18:47:37 -04:00
Bryan Helmkamp
e9387bf622
test(server): isolate storage in interrupt_active_session test
The test was failing in CI because parallel tests share the default
session storage directory ($HOME/.fabro/storage). Every AppState build
runs `recover_stale_running_state`, which scans that directory and
re-marks any in-flight Running turn as Interrupted. When another test
built its AppState while this test's turn was running, the recovery
clobbered the turn before the interrupt request arrived, causing a 409
"Turn is already terminal" response.

Give the test its own storage root and wait for `turn.assistant_text_start`
so the agent is committed to the in-flight LLM call before interrupting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:11:10 -04:00
Bryan Helmkamp
b9497517a5
feat(llm): support agent profile overrides (#291)
## Summary

Adds catalog-level `agent_profile` overrides so custom providers and
individual models can choose Anthropic, OpenAI, or Gemini agent behavior
independently from their adapter default. The effective precedence is
model override, then provider override, then adapter metadata.

## What Changed

- Added typed provider/model `agent_profile` settings in `fabro-config`
and `fabro-model`, with serde/strum support for `anthropic`, `openai`,
and `gemini`.
- Centralized effective profile resolution in the catalog, including
provider alias canonicalization and a guard against unrelated model
overrides leaking across providers.
- Updated run startup, API sessions, CLI/ACP backends, prompt
project-memory discovery, and standalone agent startup to use the
resolved catalog profile.
- Documented provider-level and model-level `agent_profile`
configuration in the public model and user configuration docs.

No OpenAPI or model-list response shape changes are included.

## Validation

- `cargo nextest run -p fabro-model -p fabro-config -p fabro-workflow -p
fabro-agent` passed: 1826 passed, 125 skipped.
- `cargo +nightly-2026-04-14 fmt --check --all` passed.
- `cargo +nightly-2026-04-14 clippy -p fabro-model -p fabro-config -p
fabro-workflow -p fabro-agent --all-targets -- -D warnings` passed.
- `git diff --check` passed.
- `cargo nextest list -p fabro-dev` confirmed there is no docs-options
reference test target to run.

## Post-Deploy Monitoring & Validation

Watch workflow and agent-session logs for provider/model resolution
errors, unexpected project-memory file selection, or CLI/ACP launch
command mismatches on custom catalog providers. Healthy signal: custom
provider/model runs start normally and use the intended profile-specific
behavior. Failure trigger: repeated `Provider ... is not configured`
errors, missing expected project memory, or profile-specific agent
startup failures after configuring `agent_profile`. Mitigation is to
remove the override from config or revert this PR. Validation window:
first deploy cycle after merge; owner: release/on-call engineer.

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 17:40:59 -04:00
Bryan Helmkamp
2b53917759
fix(validate): treat undefined template vars in @file prompts as warnings (#290)
## Summary

`fabro validate` had inconsistent behavior for undefined template
variables depending on whether the prompt was inline or loaded via an
`@file` reference. Inline `{{ inputs.foo }}` produced a warning and
validation passed; the same expression inside a `@file`-imported prompt
produced a hard validation error.

Fixes #286.

## Root cause

Two template-rendering passes with different strictness, applied to
disjoint inputs:

1. **DOT-source pass**
(`lib/crates/fabro-workflow/src/operations/create.rs`) honored
`RenderMode::Structural` for `fabro validate` — undefined variables
downgraded to a `Severity::Warning` diagnostic, then lenient render
finished the job.
2. **Per-attribute pass**
(`lib/crates/fabro-workflow/src/transforms/variable_expansion.rs`)
inside `TemplateTransform` was always strict and had no `RenderMode`
awareness. Because `FileInliningTransform` runs *before*
`TemplateTransform`, expressions inside `@file` content only ever
encountered the strict pass.

## Fix

- Plumb `RenderMode` through `TransformOptions` into
`TemplateTransform`.
- In `RenderMode::Structural`, the transform catches
`TemplateError::UndefinedVariable` per attribute, emits a warning
diagnostic, and falls back to `render_lenient`.
- Diagnostics flow through a new `Transformed.diagnostics` field into
`Validated` alongside lint output.
- Diagnostics now include `node_id` when the undefined variable was
found inside a node attribute, which is more useful than the previous
"at line 1" location.
- `RenderMode` and the shared `template_undefined_variable_diagnostic`
helper moved to `pipeline/types.rs` so the transform layer can reach
them without a circular dep.

Strict mode (`fabro run`, preflight) is unchanged — undefined inputs
still hard-fail before a run is created.

## Behavior

Illustrative output shapes (variable names and line numbers depend on
the fixture):

Inline prompt (unchanged):
```
warning: undefined template variable `inputs.<name>` at line <n> (template_undefined_variable)
Validation: OK
```

`@file`-imported prompt (previously a hard error, now matches inline —
node-attributed instead of line-attributed):
```
warning [node: <id>]: undefined template variable `inputs.<name>` in node `<id>` (template_undefined_variable)
Validation: OK
```

## Test plan

- [x] `cargo nextest run --workspace` — 5773/5773 passing
- [x] `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings` clean
- [x] `cargo +nightly-2026-04-14 fmt --check --all` clean
- [x] New regression test
`bare_fabro_with_unbound_inputs_in_imported_prompt_validates_structurally_with_warning`
in `lib/crates/fabro-cli/tests/it/cmd/validate.rs` against new fixture
`test/templated_unbound_imported/`
- [x] Existing
`bare_fabro_with_unbound_inputs_validates_structurally_with_warning` and
`strict_render_hard_fails_on_unbound_inputs` still pass — verifies
inline structural and run-start strict behavior are both preserved
- [x] Manual reproduction of the exact inputs from the issue now
succeeds with a warning

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Aleksi Asikainen <1086393+salieri@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:31:22 -04:00
Bryan Helmkamp
56c7f627c4
feat(session): add server-backed agent sessions (#278)
## Summary

Adds the first server-backed Fabro agent session slice: persistent
session records, durable turn/event storage, HTTP session APIs, SSE turn
streaming, generated clients, and a new `fabro session -p <prompt>` CLI
path.

## What Changed

- Adds shared session IDs, records, statuses, event envelopes, and
message DTOs in `fabro-types`, with OpenAPI replacements in `fabro-api`.
- Renames the agent runtime transcript item from `Turn` to `Message` and
adds conversion between runtime history and persisted `SessionMessage`
records.
- Introduces a file-backed `SessionStore` for session metadata, turns,
full transcripts, and append-only events under local storage.
- Wires server session routes for create/list/read/update/delete, turn
submission, event replay, interrupt requests, and session-scoped tools.
- Implements streamed turn execution with durable events persisted
before SSE broadcast, active-turn conflict handling, local same-machine
`working_dir` validation, and noninteractive permission denials.
- Adds `fabro-client` helpers and the `fabro session -p` command, plus
regenerated TypeScript API client files.

## Notes

V1 intentionally keeps session execution local to same-machine server
targets. Remote clone-backed session sandboxes, interactive REPL/TUI
behavior, warm session pooling, and real tool discovery for
`/sessions/{id}/tools` remain follow-up work.

## Verification

- `cargo build --workspace`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo test -p fabro-store session_store_contract_tests --lib`
- `cargo test -p fabro-agent
history::tests::session_message_roundtrip_preserves_runtime_history
--lib`
- `cargo test -p fabro-server 'session_' --lib`
- `cargo test -p fabro-server --features test-support --test it
openapi_conformance -- --nocapture`
- `cargo test -p fabro-cli --test it cmd::session:: -- --nocapture`
- `cd lib/packages/fabro-api-client && bun run typecheck`
- `git diff --check`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 17:25:05 -04:00
Bryan Helmkamp
350c029d97
chore: mark generated fabro-api-client as linguist-generated
Stops github-code-quality (CodeQL) from flagging template artifacts in
the openapi-generator output (unused imports, ASI inconsistencies),
and collapses these files in PR diffs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:17:42 -04:00
Bryan Helmkamp
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-new empty
state](https://github.com/fabro-sh/fabro/raw/chats-new-port/docs/superpowers/prototypes/2026-05-16-chats-new/screenshots/chats-new-v4.png)

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

![chats-detail active
chat](https://github.com/fabro-sh/fabro/raw/chats-new-port/docs/superpowers/prototypes/2026-05-16-chats-new/screenshots/chats-detail-v4.png)

## 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>
2026-05-16 17:10:16 -04:00
Bryan Helmkamp
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`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](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>
2026-05-16 15:29:25 -04:00
Bryan Helmkamp
0f1cf4da5c
feat(cli): wire run parent commands (#288)
## Summary
Add CLI support for run parent relationships now that the server API can
store them. This lets users create child runs, filter children, inspect
parent metadata, and link or unlink parents without dropping to raw API
calls.

## What Changed
- Added top-level `fabro parent link` and `fabro parent unlink` commands
with selector resolution, text output, and JSON summaries.
- Added `--parent` to `fabro run`, `fabro create`, and `fabro ps`;
create/run send `parent_id` in manifests and `ps` uses server-side
parent filtering.
- Surfaced `parent_id` in `ps --json` and `inspect`, with a conditional
`PARENT` column for unfiltered tables.
- Extended `fabro-client` parent-link APIs and
`list_store_runs(parent_id)`.

## Test Plan
- `cargo nextest run -p fabro-cli`
- `cargo nextest run -p fabro-client`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-cli -p fabro-client
--all-targets -- -D warnings`
- `cargo insta pending-snapshots`
- `git diff --check`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 15:15:28 -04:00
Bryan Helmkamp
2296ba6ea8
feat(runs): add parent run links (#271)
## Summary

Adds orchestration-only parent links between runs without merging them
into fork or rewind lineage. Runs can now be created under a parent,
linked to a different parent, or unlinked through event-sourced
mutations that rebuild summaries and projections from the run event
stream.

## Changes

- Adds optional `parent_id` to run manifests, public run summaries, run
projections, `run.created`, OpenAPI, and the generated TypeScript API
client.
- Adds `PUT /api/v1/runs/{id}/parent` and `DELETE
/api/v1/runs/{id}/parent` for mutable parent links across any run state,
including terminal or archived runs.
- Records `run.parent.linked` and `run.parent.unlinked` events with
actor metadata and previous/current parent IDs.
- Validates parent changes in the API path: parent must exist for new
links, self-parenting is rejected, cycles are rejected, and same-parent
or already-root operations are idempotent no-ops.
- Adds `parent_id` filtering to run listing while preserving dangling
historical parent references after parent deletion.

## Validation

- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo build -p fabro-api`
- `cargo check -p fabro-workflow -p fabro-store -p fabro-server`
- `cargo nextest run -p fabro-types -p fabro-store`
- `cargo nextest run -p fabro-server --features test-support
create_run_can_set_parent_and_list_children
link_relink_and_unlink_parent_are_idempotent
parent_link_validation_rejects_missing_self_and_cycles
deleting_parent_leaves_child_parent_id_as_historical_reference`
- `cargo nextest run -p fabro-api
run_summary_json_matches_openapi_shape`
- `cd lib/packages/fabro-api-client && bun run typecheck`

Known unrelated broad-suite blocker: `cargo nextest run -p fabro-server
--features test-support get_graph_returns_svg` currently returns 500
because the render subprocess emits test-harness output instead of SVG.

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, medium reasoning) via
[Codex](https://openai.com/codex)
2026-05-16 13:57:33 -04:00
Bryan Helmkamp
ae55bded81
fix(sandbox): clone Daytona repos under /home/daytona/repos (#285)
## Summary

Daytona's default snapshot runs as the `daytona` user (uid 1001), which
lacks write permission on `/`. With `run.clone.enabled = true`, sandbox
init failed at `fs.create_folder("/repos", ...)` with HTTP 400, before
the first workflow stage could run:

```
sandbox.git.failed error="Failed to create Daytona repos root" causes=["HTTP 400"]
run.failed
```

Root cause: the Daytona provider was using Docker's root-level `/repos`
layout. Docker works because its containers run as root; Daytona's
default sandbox user does not.

**Fix:** move `REPOS_ROOT` for Daytona to `/home/daytona/repos`,
alongside the existing `/home/daytona/workspace`. The path is writable
by the default sandbox user, the symlink layout is unchanged
(`/home/daytona/workspace/<repo>` →
`/home/daytona/repos/<owner>/<repo>`),
and Docker keeps its existing `/repos` path.

**Bonus — better error diagnostics.** A new `wrap_fs_error(operation,
path, error)` helper in the Daytona provider:

- includes the attempted path in the message (was just "Failed to create
  Daytona repos root" with no indication of which path);
- classifies HTTP 400 as a likely permission issue and points at
  snapshot configuration;
- classifies HTTP 401/403 as an API key permissions issue;
- preserves the underlying `DaytonaError` in the source chain
  (per `docs/internal/error-handling-strategy.md` — verified by walking
  `Error::source()` in the regression test).

So if this class of failure recurs (custom snapshot, future path
changes, ...) the user gets:

> Failed to create Daytona repos root '/home/daytona/repos' failed
> (HTTP 400). This usually means the sandbox user lacks write permission
> on the parent directory. If you're using a custom Daytona snapshot,
> ensure the sandbox user can write to '/home/daytona/repos', or use a
> path under the user's home directory (e.g. /home/daytona/...).

instead of:

> Failed to create Daytona repos root
> HTTP 400

## Test plan

- [x] `cargo build --workspace`
- [x] `cargo nextest run -p fabro-sandbox --features daytona` — 142/142
pass
- [x] `cargo nextest run -p fabro-types -p fabro-workflow` — 1365/1365
pass
- [x] New unit test `wrap_fs_error_classifies_http_400_and_403` —
asserts
      top-level message contains path + hint AND walks the source chain
      to prove `DaytonaError::Api { status_code: 400, .. }` is preserved
- [x] `cargo +nightly-2026-04-14 fmt --check --all`
- [x] `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- [x] **Live regression**: `daytona_clone_layout_live_smoke` against the
      default `daytona-medium` snapshot — failed with `Failed to create
      Daytona repos root / HTTP 400` before the change; passes
      end-to-end after (provisions sandbox → clones repo → verifies
      symlink + HEAD match in 2.5s)

## Related

- Closes #284 (thanks @jessmartin for the report, diagnosis, and
proposed fix)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Jess Martin <27258+jessmartin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 13:47:54 -04:00
Bryan Helmkamp
64fd4de393
docs: offer issue-based contribution path alongside PRs
Add a third option for contributors who'd rather not write the code
themselves: file an issue and a maintainer will implement it with
co-author credit on the landing commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 13:40:23 -04:00
Bryan Helmkamp
66519ee12a
feat(errors): add structured failure diagnostics (#277)
## Summary
- Make `FailureDetail` the canonical rich diagnostic shape for stage and
terminal failures, with terminal `RunFailure` carrying `{ reason, detail
}`.
- Preserve cause chains and move process stdout/stderr diagnostics into
sanitized `exec_output_tail` instead of embedding them in messages or
causes.
- Update ACP error plumbing, CLI/server/store rendering, OpenAPI, and
the generated TypeScript API client for the nested failure contract.

Closes #273

## Test Plan
- `cargo nextest run -p fabro-types -p fabro-core -p fabro-acp -p
fabro-api -p fabro-store -p fabro-server -p fabro-workflow -p fabro-cli
--no-fail-fast -E 'not test(/returns_svg/)' --status-level fail
--final-status-level fail`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `bun run typecheck` in `lib/packages/fabro-api-client`
- `bun run typecheck` in `apps/fabro-web`
2026-05-16 13:25:07 -04:00
Bryan Helmkamp
87950295bd
refactor(llm): split provider identity from adapters (#280)
## Summary

This PR separates provider identity from adapter behavior across the LLM
stack. Provider IDs now represent catalog rows and provider metadata,
while adapter/profile routing owns protocol behavior for Anthropic,
OpenAI, Gemini, and OpenAI-compatible providers.

## Changes

- Replace the shared `fabro_model::Provider` enum with open-ended
`ProviderId` catalog identity and typed `AdapterKind` metadata.
- Route auth, CLI, ACP, workflow, memory selection, profile
construction, and LLM client registration through catalog provider rows
instead of provider-ID fallbacks.
- Move API-key URL/header/env metadata into provider catalog/auth flows
and require configured provider rows for credential-backed clients.
- Simplify billing to `algorithm`-tagged OpenAI, Anthropic, and Gemini
shapes; OpenAI-compatible adapters bill through the OpenAI algorithm.
- Remove greenfield compatibility paths for old provider aliases, legacy
provider-tagged billing JSON, and the `openai_compatible`
pseudo-provider env fallback.
- Update fixtures and tests to exercise catalog-driven
Kimi/Zai/Minimax/Inception/custom OpenAI-compatible routing.

## Validation

- `cargo test --no-run -p fabro-model -p fabro-auth -p fabro-agent -p
fabro-workflow -p fabro-server -p fabro-llm -p fabro-api -p fabro-cli -p
fabro-store -p fabro-static`
- `cargo nextest run -p fabro-model -p fabro-auth -p fabro-agent -p
fabro-workflow -p fabro-server --no-fail-fast`
- `cargo nextest run -p fabro-llm -p fabro-api -p fabro-cli -p
fabro-store -p fabro-static --no-fail-fast`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 13:13:41 -04:00
Bryan Helmkamp
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

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](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>
2026-05-16 12:47:27 -04:00
David Bock
b0847180b6
Fix custom provider resolution in exec and model list (#276)
## Summary
- allow `fabro exec` to use configured custom provider IDs from the
resolved LLM catalog
- route direct exec sessions through the same catalog-aware
provider/profile resolution used by workflow runs
- stop `fabro-client::list_models` from rejecting non-built-in provider
filters client-side
- update CLI snapshots and add regression tests for custom-provider exec
and model listing

## Repro
With a configured provider like:

```toml
[llm.providers.bedrock]
adapter = "openai_compatible"
base_url = "https://.../v1"

[cli.exec.model]
provider = "bedrock"
name = "bedrock-claude-sonnet-4-6"
```

these paths diverged:

- `fabro run ... --model bedrock-claude-sonnet-4-6` worked
- `fabro model list` showed `bedrock-*` models
- `fabro exec "..."` failed with `unknown provider: bedrock`
- `fabro model test --provider bedrock` failed with the same client-side
error

## Root cause
There were two separate built-in-only assumptions:

1. `fabro-agent` direct CLI paths parsed provider strings into the
built-in `Provider` enum and built a default catalog, so configured
provider IDs from `settings.toml` were invisible.
2. `fabro-client::list_models()` parsed the optional provider filter
into the same built-in enum before calling the server, so custom
provider filters never reached the API.

## Validation
- `cargo check -p fabro-cli -p fabro-agent -p fabro-client`
- `cargo test -p fabro-agent
resolve_provider_accepts_custom_catalog_provider -- --nocapture`
- `cargo test -p fabro-client list_models_allows_custom_provider_filters
-- --nocapture`
- `cargo test -p fabro-cli
exec_accepts_configured_custom_provider_from_settings -- --nocapture`
- `cargo test -p fabro-cli list_invalid_provider_errors -- --nocapture`
- `cargo test -p fabro-cli help -- --nocapture`

---------

Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-05-16 10:49:14 -04:00
Bryan Helmkamp
f790a47da4
feat(sandbox): surface provider links and network policy (#275)
## Summary
- Add provider dashboard URL reporting to `SandboxDetails`, including
Daytona dashboard links and a Sandbox tab provider link.
- Extend `SandboxDetails` with required provider-neutral public network
policy for egress and ingress allow/block rules.
- Populate local, Docker, and Daytona network policies from provider
details when Fabro can assert them, otherwise default to explicit
`unknown` policy.
- Update OpenAPI, Rust API replacements, generated TypeScript client
models, server/API tests, and the Sandbox tab `Network` panel.

## Notes
- This reports policy only; it does not probe live connectivity.
- The network model intentionally excludes ports, previews, IP
addresses, DNS, routes, Docker network IDs, and service discovery.
- Older persisted/API JSON still deserializes through the Rust serde
default for `network`.

## Verification
- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-types -p fabro-api -p fabro-sandbox -p
fabro-server sandbox_details`
- `cargo test -p fabro-sandbox details --features docker,daytona`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cd apps/fabro-web && bun test app/routes/run-sandbox.test.tsx`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`
- `cd lib/packages/fabro-api-client && bun run typecheck`
- `git diff --check`

## Post-Deploy Monitoring & Validation
- Log queries/search terms: `Failed to inspect Docker container`,
`Daytona sandbox is not initialized`, `missing runtime metadata`, `GET
/api/v1/runs/*/sandbox`, `Sandbox unavailable`.
- Metrics/dashboards to watch: API 5xx/error rate for `GET
/api/v1/runs/{id}/sandbox`, frontend error reporting for the Sandbox
tab, and provider reconnect/inspect failures.
- Expected healthy signals: Sandbox tab loads with Overview, Resources,
Network, Labels, and Timestamps; Daytona sandboxes show provider links;
local/ambiguous network policy shows `Unknown`; Docker `network_mode =
none` shows `Blocked`.
- Failure signals and rollback trigger: sandbox details deserialization
errors, missing `network` crashes, sustained sandbox endpoint 5xx
increase, or blank Sandbox tab after deploy. Roll back this PR or hide
the Network panel if API/client shape issues appear.
- Validation window and owner: first 24 hours after deploy, release
owner/on-call.

## Compound Engineering
- Implemented with OpenAI Codex CLI on GPT-5.
2026-05-16 10:16:18 -04:00
Bryan Helmkamp
be993cb608
feat(server): expose health check at /api/v1/health (#279)
## Summary

- Mount the existing `/health` handler at `/api/v1/health` so callers
using a uniform `/api/v1` base no longer have to special-case the root
path. The root `/health` route is unchanged and remains the canonical
probe target.
- Add the new path to the OpenAPI spec (`operationId: getApiHealth`,
`Discovery` tag, reusing `HealthResponse`), and regenerate the
TypeScript client so `DiscoveryApi.getApiHealth()` is exposed alongside
`getHealth()`.
- Split the old `moved_routes_not_at_root_of_api_prefix` test into a
focused `api_v1_root_is_not_routed` and a new
`health_responds_at_versioned_path` that asserts `200` +
`{"status":"ok"}` under the versioned prefix.

## Test plan

- [x] `cargo build --workspace` (verifies the OpenAPI spec regenerates
cleanly via `fabro-api` build.rs)
- [x] `cargo nextest run -p fabro-server` (545 tests pass, including
OpenAPI conformance and the new routing assertions)
- [x] `cd lib/packages/fabro-api-client && bun run generate`
(regenerated client exposes `getApiHealth`)
- [ ] Manual: `fabro server start` then `curl -s
http://localhost:<port>/api/v1/health` and `curl -s
http://localhost:<port>/health` both return `{"status":"ok"}`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:37:00 -04:00
Bryan Helmkamp
d52a2ccbe2
feat: add opt-in LiteLLM TOML provider (#269)
## Summary
- Add a disabled built-in `litellm` provider fragment backed by the
OpenAI-compatible adapter and local proxy defaults.
- Document how to enable LiteLLM in `settings.toml`, configure
credentials, and declare explicit LiteLLM-routed models.
- Register the LiteLLM integration page and cross-link it from the model
and settings docs.

## Validation
- `cargo test -p fabro-model`
- `cargo test -p fabro-config`
- `jq empty docs/public/docs.json`
- `rg -n 'aliases = \["openai_compatible",
"openai-compatible"\]|llm\.discovery|FABRO_LITELLM|litellm_api_key_env|x-litellm-'
lib/crates/fabro-model/src/catalog/providers/litellm.toml
docs/public/integrations/litellm.mdx
docs/public/core-concepts/models.mdx
docs/public/reference/user-configuration.mdx` returned no matches

---------

Co-authored-by: Mark Ferraz <mferraz@netwoven.com>
2026-05-16 08:36:31 -04:00
Bryan Helmkamp
9768651b52
feat(model): add opt-in Ollama catalog provider (#268)
## Summary

Adds Ollama as a disabled-by-default built-in catalog provider backed
entirely by provider TOML. Enabling `[llm.providers.ollama] enabled =
true` exposes the bundled `qwen3-coder` sample model through the
existing OpenAI-compatible adapter, while other local Ollama models
still require explicit model blocks until fabro-sh/fabro#267 adds
discovery.

The docs now show the opt-in setting and note that local users can set
`OLLAMA_API_KEY=ollama` for Ollama's OpenAI-compatible endpoint.

## Verification

- `cargo nextest run -p fabro-model`
- `cargo nextest run -p fabro-cli cmd::model`
- `cargo +nightly-2026-04-14 fmt --check --all`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: roALAB1 <233429779+roALAB1@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:18:21 -04:00
fabro-releases[bot]
54c0bb4ef1 Bump version to 0.235.0-nightly.0 2026-05-16 09:45:35 +00:00
Bryan Helmkamp
9027f1cc67
docs(tutorials): clarify what sub-workflows share with the parent run
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
The sub-workflow tutorial implied a child workflow was an "entirely
separate" engine with isolated logs, but the child reuses the parent's
run ID and emits into the same event stream. Reframe the section as
"What's shared and what's isolated" and correct the encapsulation
bullet to scope the isolation to checkpoints and artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:03:53 -04:00
Bryan Helmkamp
ba27bcdd57
docs(github): note GITHUB_TOKEN enables gh CLI in sandbox
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:14:22 -04:00
Bryan Helmkamp
4071908b1d
docs(workflows): document file-based workflow imports
Adds a Defining Workflows page covering the import placeholder
syntax, node ID prefixing, the imported-file contract, default
attribute and class propagation, retry_target remapping, templating
behavior, nested imports, empty-import bypass, and the import_error
validation surface.
2026-05-15 08:02:31 -04:00
fabro-releases[bot]
6f656c8f2b Bump version to 0.234.0-nightly.0 2026-05-15 10:07:14 +00:00
Bryan Helmkamp
32f100cbe7
feat(install): make LLM setup optional in web installer and CLI (#265)
Some checks failed
Rust / Clippy (push) Waiting to run
Rust / Format (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
## Summary

Makes LLM setup explicitly skippable in both the web installer and
`fabro install`, without making omission accidental. A skipped LLM step
lets install complete with zero LLM credentials; later LLM-dependent
workflows keep using the existing provider-not-configured behavior.
`fabro doctor` is intentionally unchanged.

Plan: `docs/superpowers/plans/2026-05-14-optional-llm-install.md`

## Key changes

**Server + API**
- `PUT /install/llm` now accepts `{"providers":[]}` as "LLM step
completed, skipped" — the empty-list rejection is removed; per-provider
validation for non-empty lists is retained.
- OpenAPI: dropped `minItems: 1` from
`InstallLlmProvidersInput.providers`, updated schema descriptions so
empty = skipped and `llm: null` = incomplete. TypeScript client
regenerated.
- `/install/finish` still requires the LLM step to be present, but
tolerates zero credentials — it writes settings, runtime auth secrets,
and GitHub secrets normally and writes no LLM vault entries.

**Web installer**
- New "Skip LLM setup" secondary action on the LLM step (via a
`secondaryAction` prop on `StepPanel`) that records an empty provider
list and advances to GitHub.
- Review screen shows `LLM providers: Skipped` (step completed, empty)
vs `Not configured` (step never completed), via a new
`describeLlmSummary` helper.
- Continue with no API keys still shows the existing validation error —
skipping is only reachable through the explicit skip action.

**CLI**
- Interactive `fabro install` asks "Configure LLM providers now?"
(default yes) before provider selection; declining returns an empty
selection and continues to GitHub.
- Hidden non-interactive `--skip-llm` flag, mutually exclusive with
`--llm-provider` / `--llm-api-key-stdin` / `--llm-api-key-env` via clap
`conflicts_with_all`. Missing LLM flags are still validation errors
unless `--skip-llm` is present. Non-interactive usage text updated with
a skip example.

## Code review

Ran a 12-reviewer `ce:review` pass (correctness, testing,
maintainability, project-standards, agent-native, learnings, security,
api-contract, reliability, adversarial, cli-readiness,
kieran-typescript). No P0/P1 findings; agent-native parity PASS. Applied
fixes in `40a29c591`:
- Re-entrancy guard on `runStepSubmit` so a fast double-click on "Skip
LLM setup" can't fire two requests.
- `validate()` only suggests `--skip-llm` in the missing-provider error
when no credential flag is set (it conflicts with those flags).
- Added tests: all three `--skip-llm` conflict arms, the review screen's
"Not configured" branch, and the skip-button failure path.

One advisory finding left as report-only: an empty `PUT /install/llm`
overwrites previously-saved credentials if a user navigates Back and
clicks Skip — judged acceptable since the button is explicitly labeled
and clicking it is deliberate.

## Testing

- `cargo nextest run -p fabro-server -p fabro-cli -p fabro-install` —
1521 passed
- `cargo build -p fabro-api`, `cargo fmt --check`, `cargo clippy`
(changed crates) — clean
- `bun test` (install-app) — 14 passed; `bun run typecheck` — clean
- New coverage: server accepts empty providers + session shows `llm`
complete with `providers:[]`; finish with skipped LLM persists no LLM
vault credentials but keeps GitHub secrets; web skip button PUTs
`providers:[]` and navigates to GitHub; review renders Skipped / Not
configured; CLI `--skip-llm` requires `--non-interactive`, conflicts
with all credential flags, `validate()` succeeds with `--skip-llm`,
usage text documents `--skip-llm`.

Not added (out of plan scope): an automated test for the interactive
`InstallInputSource` skip branch — `InteractiveInstallInputSource` is
TTY-coupled and has no existing tests; the non-interactive `--skip-llm`
path is fully covered.

## Post-Deploy Monitoring & Validation

This change is install-time only; there is no continuous runtime impact.
Validate during the next install/release smoke:

- **Web installer:** run a fresh browser install, click "Skip LLM setup"
on the LLM step, confirm it advances to GitHub and the review screen
reads `LLM providers: Skipped`. Finish the install and confirm the
server restarts into normal mode with no LLM credentials in the vault
(`secrets.json` has no credential entries) and
GitHub/server/object-store/sandbox settings written normally.
- **CLI:** run `fabro install --non-interactive --skip-llm
--github-strategy token --github-username <user>` and confirm it
completes; run interactive `fabro install` and confirm declining
"Configure LLM providers now?" continues to GitHub.
- **Healthy signals:** install completes (web `/install/finish` → 202;
CLI exits 0), server boots in normal mode, `fabro doctor` runs and
reports no LLM providers configured (expected, unchanged behavior).
- **Failure signals / rollback trigger:** install fails to finish,
server fails to boot after a skipped install, or `/install/finish`
rejects a completed-but-empty LLM step. Rollback = revert this PR;
install behavior returns to requiring at least one LLM provider.
- **Validation window/owner:** next install smoke / release
verification, owned by whoever runs the release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:34:39 -04:00
Bryan Helmkamp
869b94c0c4
feat(model): add Venice catalog provider (#266)
## Summary

Adds Venice as a catalog-only built-in OpenAI-compatible provider. The
preparatory catalog/test work is already merged in #264, so this PR is
intentionally limited to the provider TOML.

## Changes

- Adds `lib/crates/fabro-model/src/catalog/providers/venice.toml`.
- Registers provider ID `venice` with alias `venice-ai`,
OpenAI-compatible base URL, and `credential:venice` / `VENICE_API_KEY`
credential lookup.
- Adds the two initial Venice-owned models and pricing metadata.

## Verification

- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo nextest run -p fabro-model`
- `cargo nextest run -p fabro-cli cmd::model`
- `cargo nextest run --workspace --status-level slow --profile ci`
- `cargo insta pending-snapshots`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, medium reasoning) via
[Codex](https://openai.com/codex)

---------

Co-authored-by: Jesse <606+jesseproudman@users.noreply.github.com>
2026-05-14 21:59:04 -04:00
Bryan Helmkamp
78941a2b84
test(model): prepare catalog-only providers (#264)
## Summary

Prepares the model catalog tests for catalog-only built-in providers so
a future provider can be added with just its catalog TOML.

## Changes

- Replaces the closed-enum round-trip guardrail with a catalog metadata
guardrail, allowing built-in TOML providers that do not have `Provider`
enum variants.
- Makes the all-model `fabro model` CLI tests assert stable table
structure instead of snapshotting every built-in catalog row.
- Renames synthetic custom-provider and missing-provider fixtures away
from provider names that can become real catalog entries.

## Verification

- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo nextest run -p fabro-model -p fabro-auth -p fabro-llm -p
fabro-server -p fabro-workflow -p fabro-config`
- `cargo nextest run -p fabro-cli cmd::model`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, medium reasoning) via
[Codex](https://openai.com/codex)

---------

Co-authored-by: Jesse <606+jesseproudman@users.noreply.github.com>
2026-05-14 21:51:18 -04:00
Bryan Helmkamp
e7e4fb5ca1
Add Daytona volume mount passthrough (#263)
## Summary

This adds run-configuration support for mounting existing Daytona
volumes into Fabro-managed Daytona sandboxes.

Concretely, this PR:

- adds `[[run.sandbox.daytona.volumes]]` with `volume_id`, `mount_path`,
and optional `subpath`
- resolves that config through the layer/settings/runtime pipeline
- forwards configured mounts to `daytona_sdk::SandboxBaseParams.volumes`
when creating the sandbox
- documents the configuration surface in the Daytona environment and run
configuration docs

## Motivation

Daytona already supports attaching volumes when a sandbox is created,
but Fabro currently owns that sandbox creation call. That means users
cannot attach a pre-created Daytona volume to a Fabro-managed sandbox
from run config.

The intended use is persistent, provider-owned state such as agent
credentials, caches, datasets, or other files that should survive
ephemeral sandbox lifecycles.

## Scope

This is intentionally a narrow passthrough. Fabro does not create,
delete, list, wait on, or otherwise manage Daytona volume lifecycle.
Users create the volume in Daytona first, then reference its `volume_id`
from Fabro run config.

`volumes` defaults to an empty list in resolved settings for backwards
compatibility with existing serialized settings.

## Testing

- `cargo test -p fabro-server
runtime_daytona_config_preserves_volume_mounts`
- `cargo test -p fabro-config resolves_daytona_volume_mounts`
- `cargo test -p fabro-sandbox --features daytona volume_mounts`
- `cargo test -p fabro-workflow
runtime_daytona_config_preserves_volume_mounts`
- `cargo check -p fabro-server`

---

_Re-opened from #262 (originally by @kimprobably) to land a rustfmt fix
— the original PR came from an org-owned fork, which blocks maintainer
pushes. Branch is now on the base repo. Original commit preserved; one
additional commit fixes rustfmt formatting._

Co-authored-by: Tim Keen <tim@keen.digital>
2026-05-14 10:54:43 -04:00
Bryan Helmkamp
c0fe29390a
feat(sandbox): prepare clone layout for multi-repo runs (#250)
## Summary

- Clone primary GitHub repos into provider-owned `/repos/{owner}/{repo}`
paths for Docker and Daytona sandboxes.
- Keep user/agent execution rooted at the workspace symlink, e.g.
`/workspace/{repo}` or `/home/daytona/workspace/{repo}`.
- Persist optional runtime layout metadata (`workspace_root`,
`repos_root`, `primary_repo_path`, `primary_repo_link`) through events,
projections, OpenAPI, Rust API tests, and the TS client.
- Preserve empty workspace behavior and reconnect from stored
`working_directory` for existing run records.

## Verification

- `cargo nextest run -p fabro-sandbox --features docker,daytona`
- `cargo nextest run -p fabro-workflow`
- `cargo nextest run -p fabro-server`
- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-api run_sandbox_json_matches_openapi_shape
sandbox_details_json_matches_openapi_shape`
- `cd lib/packages/fabro-api-client && bun run typecheck`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `git diff --check`

## Notes

- Added ignored live smoke tests for Docker and Daytona layout
validation; they require real provider credentials/runtime.
2026-05-14 09:38:20 -04:00
Bryan Helmkamp
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>
2026-05-14 09:35:42 -04:00
fabro-releases[bot]
1f531a9b9b Bump version to 0.233.0-nightly.0 2026-05-14 10:01:03 +00:00