Commit graph

3454 commits

Author SHA1 Message Date
Bryan Helmkamp
34dc99963a
feat(web): add search and event-type filter to run stages tab
Mirrors the toolbar pattern from the run logs view so users can narrow
down stage events by kind (System, Agent, Tool, Command) or by free-text
search across event content. Filter state persists across stage
selection; the open detail panel still resets per stage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:00:16 -07:00
Bryan Helmkamp
e6e6c0b5f8
feat(web): pretty-print and syntax-highlight tool input/result JSON
Replace the plain CodeBlock for tool input and result with a JsonBlock
that parses the value, re-stringifies with indent 2, and applies a
small regex-based syntax highlighter (keys, strings, numbers,
booleans, null get distinct theme colors). Non-JSON results — file
contents from Read, error strings — fall through to plain text when
JSON.parse fails. No new dependencies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:49:24 -07:00
Bryan Helmkamp
036ee0472f
feat(web): render markdown for system and agent event details
Bring back the Marked-based renderer for system prompt and assistant
message bodies in the event details panel so headings, lists, inline
code, and fenced blocks render as formatted prose instead of a single
preformatted block. Tool input/result and command scripts continue to
render as fixed-width code since they're JSON/shell. Same URL/HTML
sanitization policy as the prior markdown integration: protocol-
relative and non-http(s)/mailto links are rewritten to empty hrefs,
and raw HTML tokens are dropped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:46:13 -07:00
Bryan Helmkamp
de9656dfb7
fix(web): propagate definite height through full-height layout chain
The full-height shell used min-h-dvh, which only sets a minimum and
leaves height: auto. CSS percentage heights (h-full) don't resolve
against an auto parent, so every descendant that relied on h-full
collapsed to its content size — leaving the run stages column
separator, events list, and detail panel ending mid-page instead of
reaching the window bottom.

Switch the shell to h-dvh, make the run-detail outlet wrapper a flex
column, and replace h-full with flex-1 on the run-stages and
run-files roots so they grow via flex sizing within the column. The
height chain is now: shell h-dvh → main flex-1 → layout div h-full →
run-detail h-full → outlet wrapper flex-1 flex-col → page root flex-1
→ children fill via flex stretch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:43:03 -07:00
Bryan Helmkamp
f7fd30f36c
refactor(web): inline event details panel so events list contracts
Replaces the fixed-position overlay panel with an in-flow flex sibling.
The panel now starts under the tab bar (not over the navbar) and the
events list contracts via flex-1 to make room for it instead of being
covered. Uses self-stretch on the panel wrapper so its height
propagates reliably; an inner absolute container right-anchored at
w-[28rem] gives the slide-in-from-right reveal as the wrapper width
animates from 0 to 28rem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:31:08 -07:00
Bryan Helmkamp
9d2c83d904
refactor(web): redesign run stages tab as a row list with detail panel
Replaces the per-event SystemBlock / AssistantBlock / ToolBlock /
CommandBlock layout with a flat list of three-column rows (label pill,
truncated summary, elapsed time from run start) and a slide-out detail
panel that opens on row click. Tool names are humanized (read_file →
"Read", shell → "Bash", etc.). The vertical column separator now
extends to the actual window bottom via an absolutely positioned line
that bleeds 1.5rem past its flex parent's bottom edge into the layout's
bottom padding, sidestepping a calc(100% + 3rem) approach that wasn't
resolving reliably on a flex-1 ancestor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:58:11 -07:00
Bryan Helmkamp
86cf3f9ee6
fix(server): unblock shutdown with active SSE streams
Signal server cancellation before worker cleanup, wire long-lived SSE streams to the shutdown token, and backstop HTTP drain after five seconds so open browser streams cannot keep the listener alive indefinitely.
2026-05-07 10:55:32 -07:00
Bryan Helmkamp
59e8f2d30b
style(web): remove stage label/duration header from run stages tab
The stage label and ticking duration were a redundant repeat of the
sidebar's selected entry. Drop the sticky header (and the now-unused
RunningStageDuration helper) so the right column focuses on the stage
activity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:30:52 -07:00
Bryan Helmkamp
fd66797635
style(web): add full-height vertical separator on run stages tab
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:29:41 -07:00
Bryan Helmkamp
ac0492a617
feat: track last_event_at on runs and show it in the run header
Add a `last_event_at` timestamp to RunProjection (set in apply_event so
every event ticks the field) and surface it through RunSummary and the
RunListItem board response. Backed by an OpenAPI extension so both the
Rust and TypeScript clients pick up the new optional field.

In the web UI, the run-detail header gains a "Last activity Xm ago"
badge next to the elapsed-time chip, driven by a 30-second ticker so the
relative time stays current between event refreshes.

The fabro-server tests.rs hunk is incidental rustfmt drift surfaced by
running `cargo fmt --all` over the workspace; including it keeps CI's
fmt-check green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:15:04 -07:00
Bryan Helmkamp
4be52897ec
feat(web): filter runs list by workflow
Adds an "All workflows" dropdown to the /runs toolbar between the
repo filter and the "Show archived" toggle, applied client-side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:06:57 -07:00
Bryan Helmkamp
08bac56388
feat(web): filter runs list by created time
Adds an "All time / Today / Last hour / Last day / Last 7 days /
Last 30 days" dropdown to the /runs toolbar, applied client-side
alongside the existing search and repo filters in both Board and
List views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:05:27 -07:00
Bryan Helmkamp
7ae9cceb71
style(web): loosen spacing on run header metadata row
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:02:02 -07:00
Bryan Helmkamp
e4fbe4482a
feat(web): icon + workflow in run header metadata
Prefix each run header chip with its icon (folder for repo, stack for
workflow, clock for elapsed) and surface the workflow name alongside
the repo so it's discoverable from the detail header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:57:49 -07:00
Bryan Helmkamp
c8336a250e
feat: show archived runs on the runs page
Extend GET /api/v1/boards/runs with include_archived=true (matching the
existing flag on listRuns), add an Archived BoardColumn that the server
appends only when the flag is set, and surface a "Show archived" toggle
on /runs that flips between request shapes. Default behavior is unchanged
— archived runs stay hidden.

Server: list_board_runs now takes ListRunsParams; board_column maps
RunStatus::Archived to BoardColumn::Archived; board_columns(include_archived)
appends the column conditionally. Two new handler tests cover the default
and flag-on paths.

Web: useBoardsRuns(includeArchived) keys requests so SWR refetches on
toggle; columnStatuses + columnStatusDisplay + columnStyles get an
"archived" entry; buildSkeletonColumns filters by the flag so the loading
state matches the eventual response. Two new buildBoardColumns tests cover
both column shapes.

Touched generated TS client files include unrelated whitespace drift from
openapi-generator-cli; including them keeps the working tree consistent
with what `bun run generate` produces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:36:04 -07:00
Bryan Helmkamp
9b10f7d4fb
feat(web): collapse run header actions into a dropdown menu
Replace the row of contextual buttons (Steer / Cancel / Archive / Unarchive
/ Preview) on the run detail page with a single Actions dropdown. Each
action's pending label ("Archiving…", "Cancelling…", etc.) now appears on
its menu item; the trigger shows a spinner while any mutation is in flight
and disables itself to prevent stacked calls. The menu is hidden entirely
when no actions apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:35:46 -07:00
Bryan Helmkamp
0d23d885a1
feat(web): filter and search run logs
Add a toolbar with a multi-select level filter (TRACE/DEBUG/INFO/WARN/ERROR)
and a typeahead search to /runs/{id}/logs. Filtering is record-aware so
multi-line entries (stack traces, indented continuations) stay together with
their parent log line. Combine the panel header into a single row with
filters on the left and size + copy on the right; round byte sizes to whole
units.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:35:39 -07:00
Bryan Helmkamp
dd28dc7a1f
feat(web): add column-level "Archive all" action to runs page
Adds a three-dot kebab menu to each runs board column header (in both
column and list view). The menu exposes a single "Archive all" action
that fans out individual archive POSTs for every archivable run in the
column via Promise.allSettled, then revalidates the board. A toast
summarizes full success, partial failure, or total failure. The menu
is hidden when a column has no archivable runs.

Tracked for a future single-POST API in #226.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:29:46 -07:00
Bryan Helmkamp
5334278074
fix(cli): keep run logs free of ANSI
Give the colored TTY layer a distinct span field formatter so its ANSI span cache cannot leak into plain per-run disk logs.
2026-05-07 07:28:25 -07:00
Bryan Helmkamp
56fc79d55e
fix(cli): parse colored run span fields
Strip ANSI styling from stored span fields before extracting the run id so foreground TTY logs include run_id even when color is enabled.
2026-05-07 07:11:08 -07:00
Bryan Helmkamp
5ab3e3f132
fix(cli): show run id in foreground logs 2026-05-06 15:18:59 -07:00
Bryan Helmkamp
aa56b67096
fix(server): preserve worker terminal color env
Keep non-secret terminal color controls in the worker subprocess environment so inherited stdout logs use the same color decision as the foreground server.
2026-05-06 15:54:00 -04:00
Bryan Helmkamp
f536bf2404
feat(cli): polish foreground TTY logs
Add a compact formatter for interactive foreground stdout while preserving the plain tracing format for piped output and file logs.
2026-05-06 15:31:25 -04:00
Bryan Helmkamp
ce481ca154
fix(cli): preserve API error details
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
Route async API failures through the body-preserving classifier and add run-create context so CLI output keeps server response details in the cause chain.
2026-05-06 14:03:12 -04:00
Bryan Helmkamp
3bfb012fab
feat(cli): split run events and raw logs
Make fabro events the event-stream command and repurpose fabro logs for the per-run worker tracing log returned by the server.
2026-05-06 13:29:35 -04:00
Bryan Helmkamp
c39ff666ed
fix(server): default foreground logs to stdout
Keep daemon and hidden serve logs on the file destination by default, while making foreground server commands stream logs to the terminal unless the server config explicitly selects file logging.
2026-05-06 12:41:52 -04:00
Bryan Helmkamp
be084c1944
refactor(dev): decouple CLI reference generation
Expose the CLI reference renderer through a hidden fabro subcommand so fabro-dev can refresh docs without linking fabro-cli. Gate the fabro-dev binary behind the dev feature and update the cargo dev alias to opt into it explicitly.
2026-05-06 12:31:02 -04:00
Bryan Helmkamp
45bebb1fd8
fix(dev): avoid repeated cargo dev rebuilds
Scrub Cargo build-script environment from nested cargo commands so cargo dev does not poison fingerprints, and preserve unchanged SPA asset files while refreshing embedded assets.
2026-05-06 11:35:03 -04:00
Bryan Helmkamp
67ad1f520f
fix(web): debounce dev watcher and ignore non-source events
macOS recursive fs.watch fires multiple events per logical save and emits
spurious "bubble" events for sibling directories. With the previous slow
~10s tailwind step those re-fires were absorbed between rebuilds; with
~60ms rebuilds the watcher entered a continuous-rebuild loop instead.

- Coalesce events with a 75ms debounce window so one save fires one
  rebuild even when the editor produces several FS events.
- Filter to source-relevant extensions (ts/tsx/css/html/images/fonts);
  ignore .DS_Store, .tsbuildinfo, swap files, and the extensionless
  bubble events (e.g. "rename images") that were the dominant source
  of the loop.
- Optional FABRO_BUILD_DEBUG=1 logs which path queued or skipped each
  rebuild for future diagnosis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:01:06 -04:00
Bryan Helmkamp
f4cfa50bc4
fix(server): make --watch-web honest and fast
In --watch-web dev mode the server silently fell back to the embedded SPA
snapshot whenever the disk dist/ was missing or partial, so edits to the
web app appeared not to take effect with no error anywhere. This change
makes the dev loop visible and quick:

- static_files plumbs a dev_disk_only flag from RouterOptions.watch_web
  into the fallback handler. When set, embedded fallback is skipped and
  a miss returns 503 with a "build in progress" auto-refresh page.
- The web build script writes each rebuild into apps/fabro-web/.dist-builds/<id>/
  and atomically replaces the dist symlink via rename(2), so requests
  never observe a partially-populated dist tree.
- Tailwind is invoked through node_modules/.bin/tailwindcss directly
  instead of bunx, removing a per-rebuild bun add @latest --force round-
  trip and dropping rebuild time from ~10s to ~250ms.
- load_asset no longer falls through to the workspace dist/ when an
  explicit asset_root is provided, restoring test isolation when a
  real dev build is sitting next door.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 10:36:01 -04:00
Bryan Helmkamp
e05a04cb55
test(cli): refresh help-text snapshots for backtick-wrapped placeholders
Commit 258e46e95 wrapped angle-bracket placeholders in backticks
without updating the inline snapshots, breaking CI on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:29:53 -04:00
Bryan Helmkamp
258e46e954
fix(cli): wrap angle-bracket placeholders in backticks in help text
Bare `<slug>` and `<run-id>:<path>` in clap help text generate doc
table cells where MDX parses the placeholders as JSX tags and fails
the Mintlify build. Wrap them in backticks so the generated table
cells route them through inline code spans where MDX leaves them
alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 07:39:09 -04:00
Bryan Helmkamp
9b7c1907eb
docs: sync public docs to recent runtime changes 2026-05-06 07:39:09 -04:00
Bryan Helmkamp
6836aebc0c
docs(changelog): refresh recent product changes 2026-05-06 07:39:09 -04:00
Bryan Helmkamp
ac931a8838
docs: refresh CLI reference for system repair
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 07:28:40 -04:00
Bryan Helmkamp
5a9f568e46
refactor: simplify token plumbing and parallelize read_many_files
- Replace mint_github_token's hand-rolled Pat/Installation/App match with
  GitHubCredentials::resolve_bearer_token, removing a near-duplicate of
  the same logic already in run_metadata::mint_token.
- Parallelize read_many_files via futures::future::join_all so the tool
  actually reads concurrently — previously serial despite the name.
- Replace .expect() on the post-refresh GitHubTokenSource cache with a
  proper anyhow error so a refresh edge case can't panic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 07:15:18 -04:00
Bryan Helmkamp
f1c247bc0f
fix(github): refresh installation tokens during workflows
Split PATs from installation access tokens so static configuration cannot accidentally store expiring ghs_* credentials. Workflow command and API agent stages now resolve GITHUB_TOKEN lazily from a refreshable source, while CLI agent stages surface their launch-time refresh limitation.
2026-05-06 07:15:18 -04:00
Bryan Helmkamp
d2e6f09780
refactor(system): simplify repair-runs flow and rm --force
Mark SystemRepairRunsResponse and SystemRepairRunIssue fields required so
generated Rust/TS types stop forcing Some(...) wrapping on the producer
and defensive .unwrap_or("-") on consumers. Collapse the two-arm dispatch
in fabro rm --force into a single resolve_target step + shared
delete/account block, eliminating ~20 lines of duplicated error handling.
Loosen the brittle "no events" assertion to a substring check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 07:15:18 -04:00
Bryan Helmkamp
b64352dccd
chore: plan 2026-05-06 07:15:18 -04:00
Bryan Helmkamp
10f5eac1d2
chore: add gh-list workflow 2026-05-06 07:15:18 -04:00
Bryan Helmkamp
6e159fa9d3
fix(system): expose unreadable run repair flow 2026-05-06 07:15:18 -04:00
fabro-releases[bot]
408b5ab79d Bump version to 0.225.0-nightly.0 2026-05-06 10:02:14 +00:00
Bryan Helmkamp
603a64810c
fix(web): virtualize run file diffs consistently
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
Always route non-empty Files Changed views through Pierre's Virtualizer and worker pool, with full-height layout propagation and stable per-file cache keys. Copy Pierre worker assets during the web build so the static worker URL resolves in production.
2026-05-05 16:41:39 -04:00
Bryan Helmkamp
bd82366e6b
fix(docs): point docs.json to renamed stage events endpoint (#217)
## Summary
- `docs.json` referenced `GET /api/v1/runs/{id}/stages/{stageId}/turns`,
but that operation was renamed to `/events` in `fabro-api.yaml` between
the last passing and first failing Mintlify deploy.
- Mintlify could not resolve the operation under the API Reference tab
and reported `Failed to fetch OpenAPI file for anchor or tab`, failing
every docs deploy on `main` since commit `e40dc7d9a`.

## Verification
- Cross-checked every operation page reference in
`docs/public/docs.json` against operations defined in
`docs/public/api-reference/fabro-api.yaml`; all references now resolve.

## Test plan
- [ ] Mintlify Deployment check turns green on this PR
- [ ] After merge, https://docs.fabro.sh updates and the API Reference >
Run Internals group shows the renamed `List Stage Events` endpoint

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:10:25 -04:00
fabro-sh-0530[bot]
79f89165f6
Wire end-to-end steering for running agents (#209)
## Summary
This makes the advertised mid-run steering path real: users can send
append or interrupt steering messages through the API, CLI, and web UI,
and the worker delivers them to live API-mode agent sessions or buffers
them for the next session. The change adds the control protocol, session
interrupt machinery, workflow hub, server route/OpenAPI/client updates,
and UI feedback needed for the whole path.

### Plan Summary
- Add `SteerKind`/`run.steer` wire protocol and `POST /runs/{id}/steer`
- Deliver steers through subprocess JSONL or the in-process
`SteeringHub`
- Support append and interrupt behavior in agent sessions, with bounded
buffering and events
- Expose steering in the CLI/web UI and surface SSE toasts

## Flow

```mermaid
flowchart TB
  UI["CLI / Web UI"] --> API["POST /runs/{id}/steer"]
  API -->|"subprocess transport"| Control["Worker control JSONL"]
  API -->|"in-process transport"| Hub["SteeringHub"]
  Control --> Hub
  Hub -->|"active API sessions"| Session["SessionControlHandle"]
  Hub -->|"no active session"| Pending["Pending buffer"]
  Pending -->|"first future API session"| Session
  Session --> Agent["Session round loop"]
  Agent --> Events["RunEvent stream"]
  Events --> UI
```

## What changed and why

- Agent sessions now expose a lightweight `SessionControlHandle`, drain
steering at the top of each round, and use a replaceable round
cancellation token for interrupts. LLM waits are cancelled promptly,
while tool execution observes cancellation cooperatively so every
committed `tool_use` still gets a matching `tool_result`.
- `SteeringHub` owns active API session registration, broadcast
delivery, pending buffering, FIFO queue caps, and steering
lifecycle/drop events. A completion coordinator closes the
final-response race without introducing a workflow dependency into the
agent crate.
- The server route replaces the 501 stub, validates run state and
best-effort CLI-only steerability, and forwards through either
subprocess control JSONL or the in-process hub. OpenAPI and generated
clients now include the request type.
- The CLI and web UI can send append or interrupt steers. Run detail and
board views open the new composer, and shared SSE subscriptions now
support per-subscriber event callbacks so invalidation and steering
toasts can coexist on one EventSource.

## Review notes

- Steering actors stay on top-level `RunEvent.actor`; event props only
carry steering kind/drop metadata.
- Buffered steers replay as append messages to the first API session
that registers after an empty-active period. Per-stage targeting remains
out of scope.
- CLI-mode agent stages are still not steerable; the server returns a
best-effort 409 when all active agent stages are CLI-mode, while the
worker hub remains the authoritative safety net.
- No persistence or schema migration is required; active and pending
steering state is in memory.
- New tests focus on protocol round-trips, hub buffering/bounds, session
steering-loop behavior, SSE fanout, and basic server rejection paths.

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

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:34:16 -04:00
fabro-sh-0530[bot]
e40dc7d9ad
Move GitHub token permissions to [run.integrations.github.permissions] (#215)
## Summary

Token scopes describe what *a run* is authorized to do, not server
identity. Today they live under
`[server.integrations.github.permissions]`, which can't be overridden by
`workflow.toml` / `project.toml` (server keys are stripped from
per-workflow layers) — so projects and workflows can't tighten or relax
permissions despite the docs already advertising a per-run config. This
PR moves them under `[run.integrations.github.permissions]`, where the
standard layer-merge (workflow > project > user > defaults) Just Works.
Greenfield, no migration shim.

## What changed

- **New layer/resolved types** in `fabro-config` and `fabro-types`:
`RunIntegrationsLayer`, `RunIntegrationsGithubLayer`, and resolved
counterparts. `permissions` becomes a flat `HashMap<String,
InterpString>` post-resolve; empty = no token requested.
- **Server schema**: `permissions` removed from `GithubIntegrationLayer`
/ `GithubIntegrationSettings`. `deny_unknown_fields` rejects the stale
path.
- **Bundled `workflow.toml` parsing** (`run_manifest.rs`): now goes
through `SettingsLayer` via the new `parse_run_layer_from_settings_toml`
helper, so stale `[server.integrations.github.permissions]` errors
instead of being silently dropped by the old `toml::Table` lift-out.
- **Consumers updated**: server preflight, run launch path, and the CLI
worker (`runner.rs`) all read run-level permissions. CLI worker
previously hardcoded `HashMap::new()` — runs launched via the local CLI
path were getting no `GITHUB_TOKEN` regardless of TOML.
- **Shared helpers** on `RunIntegrationsGithubSettings`:
`is_token_requested()` and `resolve_permissions(lookup)` so server and
CLI don't drift.
- **OpenAPI + TS client** regenerated; new `RunIntegrationsSettings` /
`RunIntegrationsGithubSettings` schemas added, `permissions` removed
from `GithubIntegrationSettings`.
- **Repo workflows + docs** rewritten to the new path. Docs gain a
security-model note (boundary = installation grants; no Fabro-side cap).

## Key design decision: hand-rolled `Combine` for
`RunIntegrationsGithubLayer`

`ReplaceMap`'s "empty inherits from below" semantics (`maps.rs:76-80`)
are wrong here — we want `permissions = {}` in a higher layer to act as
an explicit clear. So the layer field is `Option<HashMap<...>>` with
hand-rolled `Combine`:

| Higher layer | Lower layer | Result |
|---|---|---|
| `None` | anything | lower (inherit) |
| `Some(map)` | anything | `Some(map)` (full replace, including
`Some({})` = clear) |

Not derived: the blanket `Option<T: Combine>` impl would recurse into
the inner `HashMap` and reintroduce empty-fallback. Documented inline in
`layers/run.rs`.

`InterpString` is preserved through resolve and only flattened to
`String` at the start-services boundary, matching the existing pattern.

### Plan Summary

- New `[run.integrations.github.permissions]` layer + resolved types;
remove from server side.
- Hand-rolled `Combine` so empty-wins-as-clear; no change to
`ReplaceMap` semantics for other consumers.
- Strict `SettingsLayer` parse for bundled `workflow.toml` so stale
schema errors loudly.
- Both server and CLI worker paths read run-level permissions via shared
helpers.
- OpenAPI + TS client regenerated; parity test added.
- Repo workflow TOMLs and `integrations/github.mdx` rewritten.


### Fabro Details

<details>
<summary>Ran 0 stages in 61m 23s for $53.41</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| **Total** | **61m 23s** | **$53.41** | **0** |

</details>

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

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

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

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

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

```

</details>

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

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 15:33:31 -04:00
Bryan Helmkamp
f3784e2e7f
fix(store): remove unused projection cache wrapper 2026-05-05 14:05:09 -04:00
Bryan Helmkamp
5fe9ce8816
cache run projections 2026-05-05 13:55:11 -04:00
Bryan Helmkamp
4661c0fbf4
fix(llm): omit Anthropic thinking for forced tools 2026-05-05 13:02:29 -04:00
Bryan Helmkamp
dc5602580b
refactor(store): collapse list_runs onto list_runs_with_projection
Implement list_runs in terms of list_runs_with_projection and drop the
now-unused RunDatabase::build_summary wrapper. Removes the duplicated
catalog-iteration loop and sort key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:28:59 -04:00