Commit graph

516 commits

Author SHA1 Message Date
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
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
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
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
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
Bryan Helmkamp
a81eb09e78
feat(llm): add catalog controls and speed billing (#249)
## Summary

This PR advances the catalog-driven LLM work from fabro-sh/fabro#210 by
making the resolved model catalog the source of truth for provider
registration, request control validation, and billing identity. Runs now
preserve canonical provider/model/speed identity through pricing and API
responses instead of collapsing billing around provider API aliases or
model IDs alone.

## What Changed

- Register LLM provider adapters from the resolved catalog, including
custom OpenAI-compatible providers and their credential resolution
paths.
- Validate effective model request controls, including run-level
defaults and node overrides, before dispatching LLM requests.
- Add catalog-aware billing lookup that prices canonical `ModelRef`
values, uses base model costs for standard speed, applies per-speed cost
overrides, and returns an unknown estimate instead of silently billing
zero for unsupported combinations.
- Move Anthropic Opus fast-mode pricing into the built-in catalog for
`claude-opus-4-6` and `claude-opus-4-7`.
- Thread the injected catalog and effective speed controls through
workflow billing, including API-mode and CLI-mode handlers.
- Update billing APIs, server aggregation, generated clients, and the
web billing view to expose provider/model/speed billing identity and
keep standard and fast usage in separate rows.

## Notes for Review

Billing lookup intentionally uses canonical catalog model IDs. Provider
`api_id` substitution remains limited to provider request construction,
so aliases can be used on the wire without changing billing identity.
Event conversion paths that do not have catalog access now preserve
token counts with a null dollar estimate rather than falling back to the
bootstrap catalog.

## Verification

- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `ulimit -n 4096 && cargo nextest run -p fabro-model -p fabro-workflow
-p fabro-server -p fabro-api -p fabro-cli --no-fail-fast`
- `ulimit -n 4096 && cargo nextest run --workspace --no-fail-fast`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun test`
- `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)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:12:16 -04:00
Bryan Helmkamp
a33d17c88d
feat(run): add managed branch controls (#243)
## Summary

Adds run-level controls for clone behavior, managed run branch
setup/pushes, and metadata branch writes/pushes so workflows can opt out
of Fabro-managed Git behavior without relying on provider-specific
`skip_clone` settings. This closes fabro-sh/fabro#240.

## What Changed

- Introduced `[run.clone]`, `[run.run_branch]`, and `[run.meta_branch]`
settings with defaults that preserve current behavior.
- Removed user-facing `skip_clone` from Docker/Daytona config while
mapping the new run-level clone setting into the internal sandbox
runtime options.
- Gated run branch setup/push, metadata branch writer creation/push, and
PR branch output on the new settings.
- Enforced invalid combinations: pull requests require an enabled pushed
run branch, and disabling the run branch also disables metadata branch
behavior.
- Updated OpenAPI, the generated TypeScript API client, frontend fixture
data, and docs for the new configuration shape.

## Testing

- `cargo nextest run -p fabro-config -p fabro-types -p fabro-workflow -p
fabro-server`
- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cd lib/packages/fabro-api-client && bun run typecheck`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun test`
- `cargo build --workspace`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo insta pending-snapshots`
- `git diff --check`

## Post-Deploy Monitoring & Validation

- Validation window: first 24 hours after release; owner: release
owner/on-call engineer.
- Log queries/search terms: `run_branch`, `meta_branch`,
`clone.enabled`, `skip_clone`, `pull request requires an enabled pushed
run branch`, `metadata branch`.
- Healthy signals: runs without custom branch config continue creating
and pushing run/meta branches; runs with `[run.clone] enabled = false`
start provider sandboxes without cloning; runs with branch pushes
disabled complete without Git push errors.
- Failure signals: increased run startup failures for Docker/Daytona,
unexpected PR creation conflicts, missing metadata for default-config
runs, or validation errors for configurations that previously used
default settings.
- Mitigation trigger: if default-config runs stop producing expected
branch/metadata artifacts or sandbox startup failures increase, roll
back the release or temporarily restore previous defaults while
investigating the run-level setting resolution path.

---

[![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-12 12:00:30 -04:00
Bryan Helmkamp
2d36ef63cc
ci(web): isolate Bun test files
Some checks failed
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) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
2026-05-11 00:20:42 -04:00
Bryan Helmkamp
4fa4716015
refactor(core): simplify reviewed run cleanup
Reuse shared frontend formatting and SSE dedupe helpers, tighten typed sandbox handling, remove obsolete run DTOs, and collapse auth-session revoke into a single store operation.
2026-05-10 22:53:20 -04:00
Bryan Helmkamp
cccb557281
feat(api): unify public run shape
Return canonical Run payloads across run list, board, create, and lifecycle endpoints. Move archive state out of RunStatus and into lifecycle metadata, split sandbox runtime from planned sandbox data, and separate static pull request records from live pull request details.

Regenerate the TypeScript API client and migrate web, CLI, server, store, workflow, and API tests to the new contract.
2026-05-10 20:48:55 -04:00
Bryan Helmkamp
92ce72da6e
refactor(web): simplify profile sessions layout
Split the unified list into separate Browser and CLI panels and drop
provider, login, kind/current badges, and user agent. Each panel shows
just the timestamps that matter, with revoke gated to CLI sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:32:19 -04:00
Bryan Helmkamp
d2623662e0
feat(web): build unified auth sessions page
Render /profile/sessions from the new GET /api/v1/auth/sessions API.
The page shows the current browser session and active CLI sessions in one
list, with revoke buttons gated by the backend-supplied revocable field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:30:00 -04:00
Bryan Helmkamp
acec32cea9
feat(web): build live events page in settings
Replaces the /settings/live-events placeholder with a working page that
streams server-wide events from /api/v1/attach. Shares the leader-owned
cross-tab EventSource so additional tabs subscribe without opening
parallel connections.

The page keeps an in-memory ring buffer (newest first, max 1,000) with
id or run_id:seq dedupe and resets on remount; live-only by design,
nothing is replayed on connect or persisted in the browser. Reuses the
existing event-debug filters, search, and details panel, and links each
row's run_id to /runs/:id. The category filter is the static set of
DebugCategory values so "All types" always matches.

Settings layout is now fullHeight-aware so the events page can fill the
viewport alongside the sub-nav. DebugEventDetailsPanel's event prop is
broadened to a shared EventDisplayPayload shape so it accepts both
EventEnvelope and the live payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:05:10 -04:00
Bryan Helmkamp
5209d05623
refactor(sandbox): unify run sandbox identity
Replace the separate sandbox record shape with a typed RunSandbox model shared by projections, API responses, and generated clients. The public contract now uses SandboxProvider plus a non-null id and working_directory, and removes sandbox identifier/name leakage.
2026-05-10 13:16:57 -04:00
Bryan Helmkamp
3acfa9eb93
feat(web): add profile sub-navigation with Sessions
Wraps /profile in a left-sidebar layout. Existing profile page becomes
the index; adds a placeholder /profile/sessions. Renames the identity
rows from "IdP issuer"/"IdP subject" to "Issuer"/"Subject".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:40:50 -04:00
Bryan Helmkamp
47f581cc1d
fix(server): improve sandbox service discovery
Fall back to procfs when ss is unavailable, report the discovery source in API metadata, and surface the sandbox install tip in the services UI. Previewable services are ordered first for clearer service selection.
2026-05-10 12:40:38 -04:00
Bryan Helmkamp
a74ae2e7f8
feat(web): add settings sub-navigation with Live Events
Wraps /settings in a left-sidebar layout. Existing settings page becomes
the index; adds a placeholder /settings/live-events. The layout owns the
section h1 and hides the app-shell header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:20:42 -04:00
Bryan Helmkamp
7b832c74a8
feat(web): add Services tab to sandbox page
Lists backend-discovered TCP services for a run's sandbox between the
Terminal and Filesystem tabs. Previewable ports open a signed Daytona
URL in a new browser tab; the rest render as Unavailable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:56:05 -04:00
Bryan Helmkamp
1f6965386c
refactor(web): rename Workflows tab to Automations
Repurpose the /workflows tab to manage Automations (e.g., scheduled
workflows). The underlying Workflow domain entity, API types, and
"workflow runs" are unchanged — this is a web-surface rename only:
URLs (/workflows -> /automations), route file names, default-export
component names, page titles, breadcrumbs, and the visible UI labels
on these pages (Create Automation, Search automations..., Run automation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:47:46 -04:00
Bryan Helmkamp
d0f3676d88
fix(web): hide filesystem directory sentinels 2026-05-10 10:41:02 -04:00
Bryan Helmkamp
f607068a99
fix(web): wrap full-screen terminal route in ToastProvider
TerminalView calls useToast() unconditionally, so the chromeless route
crashed with "useToast must be used within a ToastProvider" because the
route lives outside the AppShell that normally provides it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:32:11 -04:00
Bryan Helmkamp
e4093a2e61
feat(web): virtualize sandbox file preview and handle empty files
Wraps the Pierre File renderer in WorkerPoolContextProvider + Virtualizer
so long file previews scroll efficiently and reuse the shared highlighter
worker pool. Adds a content-aware sandbox cacheKey so previews don't
re-highlight unchanged content. Empty files now render an "Empty file"
state instead of mounting an empty File component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:25:08 -04:00
Bryan Helmkamp
ee3ef3a3b2
feat(web): add "Open in new tab" button to embedded terminal toolbar
Mirrors the affordance on the VNC panel. The button links to
/runs/:id/terminal, which renders the chromeless full-screen TerminalView
in a new browser tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:23:42 -04:00
Bryan Helmkamp
cb316936e3
feat(web): add chromeless TerminalView and full-screen terminal route
New `/runs/:id/terminal` route renders a bare TerminalView at viewport
size, opened outside the AppShell so there is no nav, sidebar, or run
detail tabs around it. TerminalView gains a `chromeless` prop that hides
the toolbar and decorative wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:23:11 -04:00
Bryan Helmkamp
5ac9a188c6
refactor(web): drop Start nav tab and reuse sparkles for Workflows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:22:18 -04:00
Bryan Helmkamp
04b37ed1d3
refactor(web): collapse sandbox mode toggle into panel header rows
Previously the right column rendered the mode toggle on its own line
above each panel's status/action row, leaving an awkward double-row
header. Now each panel accepts a `leading` slot and hosts the toggle
inline with its existing status pill / breadcrumbs and action buttons,
so Terminal/Filesystem/VNC each occupy a single tight header row.

Also lands a quiet DEFAULT_DIR change in the filesystem panel from
/workspace to /, matching how non-clone sandboxes (and Daytona's image
layout) actually expose the working tree, plus matching test updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:01:19 -04:00
Bryan Helmkamp
8bde7d77f5
feat(web): add Daytona VNC mode to Sandbox tab
Adds a third right-column mode (Terminal | Filesystem | VNC) gated to
Daytona sandboxes. The panel POSTs /api/v1/runs/:id/sandbox/vnc, embeds
the signed noVNC preview URL in an iframe with clipboard + fullscreen
allowed, and renders distinct states for unsupported provider (Docker
hides the tab entirely), 409 startup failure (recoverable, "Try again"),
and 404/501 (non-recoverable). A reconnect button refetches the signed
URL when it expires.

Also fixes a Filesystem regression: the previous "skip first effect"
ref guard meant @pierre/trees never received its first resetPaths call
when the listing transitioned from empty to populated, leaving the tree
stuck on the initial empty model. Now the model is always synced via
resetPaths whenever the listing changes; verified live against a
Daytona /workspace + / listing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:21:35 -04:00
Bryan Helmkamp
cc5c505487
feat(web): add read-only Filesystem mode to Sandbox tab
Adds a Filesystem right-column mode alongside Terminal in /runs/:id/sandbox,
selectable via ?mode=filesystem. The persistent SandboxDetails left column
stays visible in both modes. Browses the run sandbox via the existing
list/get file endpoints, previews text files with @pierre/diffs, and falls
back to download-only for binary, oversized, and unreadable files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 03:48:56 -04:00
Bryan Helmkamp
91a3f552e4
fix(web): widen gap between sandbox state label and native state
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:11:58 -04:00
Bryan Helmkamp
45c7f2d59f
fix(web): inline sandbox native state next to status label
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:11:28 -04:00
Bryan Helmkamp
2600eb905d
feat(web): merge Terminal into Sandbox tab as side-by-side panes
Combines the run detail Sandbox and Terminal tabs into one. Sandbox
details sit in a narrow left column and the terminal fills the wider
right column, separated by a vertical divider that runs from the tab
nav down to the steer bar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:10:51 -04:00
Bryan Helmkamp
3b2c1fa3c4
fix(web): align command empty-state padding with populated layout
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:10:19 -04:00
Bryan Helmkamp
f73a1bb8f7
feat(web): add Sandbox tab and route to run detail
Adds /runs/:id/sandbox between Files Changed and Terminal, gated by
the same sandbox-presence check as Terminal. The route fetches
SandboxDetails through a new useRunSandboxDetails SWR hook and renders
provider-neutral panels for Overview, Resources, Labels, and
Timestamps. Null fields render as muted em dashes.
2026-05-09 21:48:31 -04:00
Bryan Helmkamp
02d494c268
feat(sandbox,server,web): add sandbox_details inspection and unify SandboxResources
Adds fabro_sandbox::sandbox_details, a control-plane inspection function
that maps Local, Docker, and Daytona providers into a shared
SandboxDetails record (state, image, resources, labels, timestamps).

To avoid type sprawl, the demo board's SandboxResources is unified with
the new control-plane shape (cpu_cores: f64, memory_bytes: u64,
disk_bytes: u64). The runs board chip in apps/fabro-web converts
memory_bytes back to GB for display.
2026-05-09 21:40:19 -04:00
Bryan Helmkamp
74862bdbaf
feat(web): add Thread DNA strip to agent stage tabs
Renders a Gantt-style strip below the Thread toolbar on agent and prompt
stages only. Bar position encodes start time and width encodes duration,
so empty stretches in the strip are real dead time (idle waits, sandbox
spin-up, time waiting on a human steer). Categories collapse to five
colors: system/agent/tool/user/interrupt. Tool/command bars use their
explicit durationMs; tool groups span first child's start to last
child's end so dead time between grouped tools is honored; assistant
bars span the gap from the previous activity's end to the message ts as
an approximation of "thinking" time; system/steer/interrupt are 4 px
instant markers. Hover shows kind/label/elapsed/duration in a portaled
popover; click selects the matching turn or tool group and shares state
with the existing event list and side panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:10:53 -04:00
Bryan Helmkamp
4f202303bc
fix(web): hide retry on non-recoverable terminal errors
Server-typed errors like "Local sandboxes do not support embedded
terminals" won't change on retry, so the ErrorState now omits the
"Try again" button for them. WebSocket connection failures stay
recoverable and keep the retry affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:21:58 -04:00
Bryan Helmkamp
85a2cdda64
fix(web): show terminal errors as a centered ErrorState panel
Replaces the stray red caption + empty terminal frame with the shared
ErrorState card so errors like "Local sandboxes do not support embedded
terminals" land in a clear, retry-able panel instead of floating above a
misleading blank terminal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:21:09 -04:00
Bryan Helmkamp
06277c79f5
fix(web): balance top and bottom padding in stage renderer area
The outer scroll container had pt-2 with pb-[calc(1.5rem+dock)], so
content sat ~8px below the tab toolbar but ~24px above the dock —
visibly off. Each new renderer also re-applied its own pt-2, doubling
the top spacing. Bump the outer pt to pt-6 so it matches the 24px
baseline bottom, and drop the duplicated pt-2 from every renderer
wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:33:13 -04:00
Bryan Helmkamp
8b4442096d
refactor(web): polish stage renderer designs after visual review
Tighten the meta bar (drop redundant labels, fold timestamp into the
duration tooltip), elevate the wait card to a centered hero clock,
strengthen the conditional view with the actual chosen edge and
condition expression sourced from run-level edge.selected events,
upgrade the fan-in selected card with a trophy badge and gradient, and
redesign the parallel stat strip with toned numbers and a duration
column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:16:27 -04:00
Bryan Helmkamp
80d3965670
feat(web): add DNA strip timeline to debug events view
Renders a full-width strip below the Debug toolbar with one bar per
event placed by elapsed time. Hover shows category/name/elapsed;
click opens the event in the side panel. Collapses event categories
to five colors (agent/command/lifecycle/human/system) and shows the
event count persistently on Debug so the strip's density has a number
next to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:51:35 -04:00
Bryan Helmkamp
b6ebe9ca50
feat(web): add specialized stage renderers for non-agent handlers
Replace the raw Debug fallback for human, conditional, parallel,
parallel.fan_in, stack.manager_loop, and wait stages with purpose-built
views — Q&A transcript, decision card, children grid, fan-in results
with reducer transcript, cycle summary, and live waiter clock. Add a
generic Summary card as the new default for any unknown handler so
future StageHandler additions get a usable view for free. Debug tab
remains as the escape hatch on every handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:35:19 -04:00
Bryan Helmkamp
7e9edcf229
fix(web): show sandbox id in terminal status 2026-05-09 17:21:33 -04:00
Bryan Helmkamp
fb577b1fcb
fix(web): add bottom padding inside terminal frame
Give the terminal wrapper pb-3 so the active console no longer hugs
the bottom edge of the bordered frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:12:47 -04:00
Bryan Helmkamp
61ab94195e
feat(sandbox): copy Docker exec access command 2026-05-09 17:05:46 -04:00
Bryan Helmkamp
160c21c8b1
fix(web): rename run "Diff" tab back to "Files Changed"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:53:47 -04:00
Bryan Helmkamp
872c81bdc7
fix(web): stop terminal bottom row from being clipped
The xterm.js renderer cell height is fontSize (13) × lineHeight (1.45)
= 18.85px — non-integer. FitAddon proposes N rows where
N × cellHeight <= available height, but sub-pixel rounding in the
renderer (and font-load timing on first paint) lets the Nth row
extend past the wrapper's content edge, so the last visible line
gets half-clipped.

Reserve one row of buffer in the fit calculation (proposed.rows - 1)
so xterm is never asked to render right up to the bottom edge. The
re-fit on document.fonts.ready stays in place; sendResize now reads
terminal.cols/rows directly so the server sees the same dimensions
xterm actually uses.

Trade-off: ~18px less visible terminal area. The alternative is
switching to an integer cell height (e.g. lineHeight 18/13), which
we can revisit if the lost row matters.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:53:33 -04:00
Bryan Helmkamp
01f33e6d50
refactor(web): polish terminal tab chrome and theme
- Replace heading + status badge with a single status pill that folds in the sandbox provider
- Make Reconnect an icon-only button with tooltip; keep SSH labeled
- Darken the terminal canvas and brighten the ANSI palette for higher contrast
- Trim the static gap above the steer-bar dock from 0.5rem to 0.25rem

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:41:49 -04:00
Bryan Helmkamp
0de07bbfec
refactor(web): rename Files Changed tab to Diff and reorder before Terminal
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:41:41 -04:00
Bryan Helmkamp
0f30150264
fix(web): show empty state on Billing tab when no models were used
Runs with only non-billable stages rendered as a header + empty body
+ all-dashes "Total" row, which looked broken. Show the existing
EmptyState panel ("No model usage") instead, keeping the original
"No stages yet" message for runs that haven't started executing.

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