Commit graph

84 commits

Author SHA1 Message Date
Scott Werner
49fb160108 Use async reads during CLI intent preparation 2026-09-02 13:44:33 -04:00
Scott Werner
e3cbc31ca1 Harden CLI run target identity 2026-09-02 12:59:04 -04:00
Scott Werner
ccfd23104d Harden CLI run intent creation 2026-09-01 17:08:34 -04:00
Scott Werner
694d1981ff Simplify the CLI run-intent create path
Quality pass over the intent-producer changes, no behavior changes
intended:

- Move the TOML->JSON scalar conversion into fabro-types as
  toml_scalar_to_json_value, next to its inverse, with typed errors and
  round-trip tests; the CLI now calls the shared helper.
- Reuse goal_layer_from_args for --goal/--goal-file resolution instead
  of a second copy of the exclusivity check and cwd anchoring.
- Delete the dead run_manifest_args helper and the test that kept it
  compiling; preflight_manifest_args is the remaining real builder.
- Make run_target_for_environment a pure (provider, cwd) -> target
  mapping using is_clone_based(), warning at the call site, and default
  the environment id from DEFAULT_ENVIRONMENT_ID instead of a literal.
- Resolve the parent run and retrieve the environment concurrently.
- Drop the ResolvedCommandSettings pass-through struct and the
  duplicated parse-error mapping in the project settings presence read.
- Share the environment/workflow-version/git test mocks from the cmd
  test support module instead of three per-file copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:14:34 -04:00
Scott Werner
afe1133878 Fix CLI RunIntent producer CI failures 2026-09-01 16:14:34 -04:00
Scott Werner
2507a0075f Create CLI runs from immutable workflow intents 2026-09-01 16:14:34 -04:00
Bryan Helmkamp
e5046d8b1b
Refactor MCP config argument handling 2026-08-26 09:01:37 -04:00
Bryan Helmkamp
24165b10f5
Add --name to fabro mcp config and fabro mcp init
Both commands always registered the MCP client entry under the fixed
`mcpServers` key `fabro`, so users could not register separate Fabro
servers (for example production and testing) without editing the client
JSON by hand.

`--name <NAME>` now selects the `mcpServers` key. It defaults to `fabro`
for backward compatibility and rejects empty values. `fabro mcp init`
upserts only the named entry and preserves entries with other names, so
reusing a name updates that entry in place.

Closes #808

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 07:34:06 -04:00
Bryan Helmkamp
32d6be7ea5
refactor(model): simplify model test plumbing
Review cleanups for the tools/reasoning-effort model test change:

- Extract a shared parse_query_enum helper in the models handler in
  place of two copy-pasted parse-or-400 match blocks.
- Collapse the duplicated basic-probe pipeline in fabro-llm behind a
  single basic_probe core; name the shared EXPANDED_MAX_TOKENS budget.
- Pass &ModelTestArgs to test_models_via_server instead of threading
  five of its fields positionally.
- Dedupe the two forwarding CLI integration tests behind a helper.
- Derive clap::ValueEnum for ReasoningEffort behind a feature-gated
  clap dep (same pattern as MergeStrategy in fabro-types) so --help,
  cli.mdx, and error output list effort values from the enum instead
  of a hand-written list that drifts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TDdjG18d2AHh7mFWXFkBLn
2026-08-25 14:49:34 -04:00
Bryan Helmkamp
5ead0145b9
feat(model): separate tool and reasoning tests 2026-08-25 13:36:51 -04:00
Bryan Helmkamp
c042b9abdc
Merge remote-tracking branch 'origin/main' into feature/bounded-agent-tool-output
# Conflicts:
#	lib/components/fabro-sandbox/src/clone_source.rs
2026-08-24 14:08:04 -04:00
Bryan Helmkamp
401acb6cdf
Record tool output byte counts 2026-08-24 12:46:27 -04:00
Scott Werner
b51b80e16e Preserve run creation error context 2026-08-24 12:22:38 -04:00
Scott Werner
fc822ab9b0 Fix CLI RunSpec test fixtures 2026-08-24 12:17:47 -04:00
Bryan Helmkamp
45e06d2a6e
Merge pull request #775 from fabro-sh/claude/additional-github-repositories
Additional GitHub repository access
2026-08-21 18:55:31 -04:00
Bryan Helmkamp
d8edd410f3
feat(workflow): bridge git and gh to the shared token
Carry the resolved GitHub integration (permissions plus declared
additional repositories) as one value from run materialization into
workflow startup, and make the sandbox environment reach every declared
repository through the single managed GITHUB_TOKEN.

- `StartServices.github_permissions` becomes
  `github_integration: ResolvedGithubIntegration`; CLI and server
  workers build it with `resolve_integration()` after interpolation and
  pass it through `SandboxEnvSpec` as one unit.
- `build_sandbox_env` constructs the validated
  `GitHubRepositoryAccess` and scopes the App token source to the whole
  effective set. Missing credentials or a missing origin are hard
  initialization errors when additional repositories are declared;
  legacy permissions-only configuration keeps its best-effort behavior.
- When additional repositories are declared, initialization eagerly
  resolves each repository's App installation (naming any repository
  the App cannot see) and the token itself, so an inaccessible declared
  repository fails before the first workflow stage.
- A new `git_bridge` module injects secret-free `GIT_CONFIG_*` entries
  into the stage environment: a github.com credential helper that reads
  `$GITHUB_TOKEN` at invocation time, per-repository SSH-to-HTTPS
  `insteadOf` rewrites, and `GIT_TERMINAL_PROMPT=0`. Entries append
  after a valid user-provided Git config overlay and fail clearly on a
  malformed one. Contract tests drive the installed git binary against
  local fixtures for the rewrite, credential, prefix-collision, and
  overlay-preservation behaviors.
- The long-running ACP notice now says all declared repository access
  expires together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:47:31 -04:00
Bryan Helmkamp
f2047ad9a9
feat(config): add validated additional github repositories
Add `additional_repositories` to `[run.integrations.github]`: a list of
full `owner/repository` slugs, beyond the implicit run origin, that the
minted GITHUB_TOKEN must cover.

- `GitHubRepositorySlug` gains FromStr, Display, string serde, and
  case-insensitive Eq/Ord/Hash identity while preserving the submitted
  spelling for display and serialization.
- The config layer keeps raw strings; the higher-precedence list
  replaces the lower one wholesale, with `[]` as an explicit clear,
  resolving independently from the `permissions` map.
- Resolution validates each entry with indexed error paths: slug
  grammar, case-insensitive duplicates, one shared owner, the
  499-repository cap, and a required `contents = "read"|"write"`
  permission (templated values are re-checked at the runtime boundary).
- `RunIntegrationsGithubSettings` resolves permissions and repositories
  together through `resolve_integration()` so consumers cannot pick up
  one without the other; the field is omitted from serialization when
  empty, keeping single-repository settings byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:15:46 -04:00
Bryan Helmkamp
7de3b409ed
Request Packages read permission in the GitHub App manifest
Fabro can mint a scoped sandbox GITHUB_TOKEN via
[run.integrations.github.permissions], but apps registered through the
manifest flow could not grant packages = "read" because the manifest
never requested it. Add Packages (read-only) so freshly registered apps
can download private GitHub Packages (for example npm registry
dependencies) inside sandboxes, mirroring how GitHub Actions workflows
use their built-in GITHUB_TOKEN for registry reads.

Existing apps still need the permission added manually in the app's
settings, as the docs already describe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:12:32 -04:00
Scott Werner
27fd48c603 Persist workflow version lineage on runs 2026-08-21 12:39:02 -04:00
Bryan Helmkamp
ca6d9a46da
Merge remote-tracking branch 'origin/main' into fix/redaction-corrupts-executable-spec
# Conflicts:
#	lib/components/fabro-dump/src/lib.rs
#	lib/components/fabro-store/src/run_state.rs
#	lib/components/fabro-store/tests/serializable_projection.rs
#	lib/components/fabro-workflow/src/billing_rollup.rs
#	lib/components/fabro-workflow/src/run_lookup.rs
#	lib/components/fabro-workflow/src/runtime_store.rs
#	lib/foundation/fabro-api/tests/run_projection_round_trip.rs
#	lib/foundation/fabro-test/src/lib.rs
#	lib/foundation/fabro-types/src/run.rs
#	lib/foundation/fabro-types/src/run_event/run.rs
#	lib/foundation/fabro-types/src/run_projection.rs
#	lib/foundation/fabro-types/tests/run_spec_methods.rs
2026-08-20 20:42:20 -04:00
Scott Werner
46d4a1e5c8 Inline the blob_hash_from_response alias
It was a one-line passthrough to parse_blob_ref with a single caller,
leaving two names for the same operation; every other consumer calls
parse_blob_ref directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 13:56:19 -04:00
Scott Werner
f1c00a167e Rename blob read parameters from id to blob_hash
Finish the blob-hash vocabulary unification at the defining signatures:
RunStoreBackend::read_blob, RunStoreHandle, LocalRunStoreBackend, the
HTTP backend impl, RunDatabase::read_blob, and BlobStore::read/exists
all said `id`, which kept re-teaching the old vocabulary at every impl
site and inlay hint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 13:56:19 -04:00
Scott Werner
bf4265e1b8 Unify blob hash vocabulary 2026-08-17 13:56:19 -04:00
Scott Werner
62ed7cb8a2 Rename RunBlobId to BlobHash 2026-08-12 11:33:41 -04:00
Bryan Helmkamp
3421c4f06f
Keep the executable run spec out of reach of event redaction
Two root-cause fixes for the sandbox failure where an inline Dockerfile
came back from the store as `ARG REDACTED` and the Daytona snapshot
build died on the unset variable.

Entropy redaction measures values, not assignment pairs. The detector
matched `NAME=value` as one token, so an uppercase name merged its
charset into a pure-hex value (which alone can never exceed 4.0 bits)
and pushed the pair over the 4.5-bit threshold — then replaced the
whole pair, destroying the name. `find_entropy_regions` now strips an
identifier-shaped `NAME=` prefix before measuring and redacts only the
value, matching the gitleaks layer's `key=REDACTED` shape.

Execution no longer reads redacted content. Every stored event passes
through the redaction sink, and `load_from_store` rehydrated the
worker's RunSpec from the projection folded from those events — so a
redactor false positive silently rewrote the spec the sandbox builds
from (and changed its snapshot identity). The creation path now writes
the exact spec bytes to the content-addressed blob store and records
`spec_blob` on run.created; `load_from_store` loads the spec from the
blob, keeping the event stream authoritative for run identity,
provenance, and event-recorded blob ids. Retry and fork carry the
source run's `spec_blob` forward, so derived runs stop inheriting the
redacted copy. Runs created before the blob existed fall back to the
folded spec.

The projection and every API surface keep serving the redacted fold;
blobs were already stored unredacted (the workflow bundle carries the
same bytes), so this adds no new exposure at rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 21:44:46 -04:00
Bryan Helmkamp
6dfe1c49d2
Merge remote-tracking branch 'origin/main' into feat/async-pr-create
# Conflicts:
#	lib/foundation/fabro-api/src/lib.rs
#	lib/foundation/fabro-client/src/client.rs
2026-08-04 15:04:24 -04:00
Bryan Helmkamp
5c6289df80
Simplify async pull request creation
Structural cleanup of the durable pull request creation feature, from a
three-agent review (reuse, quality, efficiency) of the branch:

- Move the supervisor out of handler/ into server/pull_request_supervisor.rs,
  collapse its double bookkeeping into one task-id map, and fold the five
  copy-pasted failure arms into attempt_pull_request_creation.
- Tag pull_request.failed events with the creation id they resolve, so a
  publish-stage failure can never fail an unrelated explicit creation. The
  reducer gains PullRequestCreation::succeed/fail transition methods.
- Scan pending creations through a narrow projection-cache accessor instead
  of materializing every run summary, raise the scan interval to 30s (notify
  covers the live path), and cap retries for runs whose worker cannot even
  record a failure.
- Answer "creation already pending" POSTs before taking the per-run create
  lock, which a worker can hold for the whole creation.
- Replace the hand-rolled per-run lock map with fabro_store::KeyedMutex.
- Reuse cheap Arc'd projections (cached_run_projection) on the poll endpoint
  and in the worker instead of deep-cloning run summaries and diffs.
- Merge ExistingPullRequest into fabro_github::CreatedPullRequest and
  extract one reconcile_existing_pull_request helper for both call sites.
- Give the client poll loop a 15-minute deadline; document that Retry-After
  and the poll interval are the same constant.
- Resolve a wedged pending creation (run already has a pull request) as a
  durable failure instead of skipping it forever.
- Tests: shared wait_for_pull_request_creation helper, a pinned generation-
  failure assertion, and a new pipeline test proving reconciliation adopts
  an existing PR without an LLM call or create request.

Verified: cargo build --workspace, cargo nextest run --workspace (7,767
passed), nightly clippy -D warnings, fmt --check, insta (no pending), bun
typecheck in fabro-api-client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 14:51:29 -04:00
Christophe de Carvalho
8b25001985 fix(cli): raise doctor health check timeout to 1s
250ms was too aggressive: a server that was reachable but slightly slow
to answer /health made `fabro doctor` report a failed health check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:43:42 +01:00
Scott Werner
be91a5ef89 Remove run IDs from create manifest producers 2026-08-01 11:47:11 -04:00
Scott Werner
3a558b225e Remove client-selected run IDs from the CLI 2026-08-01 11:47:11 -04:00
Bryan Helmkamp
f212594875
Merge pull request #694 from fabro-sh/feat/reusable-subagent-sessions
Reuse completed subagent sessions
2026-08-01 10:21:41 -04:00
Bryan Helmkamp
4b732287e5
Merge pull request #676 from fabro-sh/remove-run-agent-permissions
Remove nonfunctional run agent permissions setting
2026-08-01 10:18:45 -04:00
Bryan Helmkamp
ef9606e6ec
Reuse PermissionLevel and fix stale run spec snapshots
AgentPermissions duplicated fabro_types::PermissionLevel: same variants,
same kebab-case wire form, same crate. PermissionLevel is strictly richer
(Hash, strum, clap::ValueEnum) and is already the with_replacement target
for the OpenAPI PermissionLevel schema, whose values are identical to the
AgentPermissions schema this branch deletes.

Delete AgentPermissions and type the [cli.exec.agent] permissions setting
as PermissionLevel. This drops the adapter match in `fabro exec` and the
`as AgentPermissionLevel` alias that existed only to tell the two names
apart. The TOML wire form is unchanged.

Removing run.agent.permissions also changed the serialized run spec, but
two fabro-cli inline snapshots still carried "permissions": null. They
failed on this branch and passed on main. Accept the updated snapshots.

Also tighten the removed-setting test to assert the exact unknown-field
message, rename its module to run_agent now that it covers more than
fabro_tools, and drop three doc references to the removed setting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:09:44 -04:00
Bryan Helmkamp
1bb70adafd
refactor: simplify subagent session reuse
Review pass over the reuse change. No intended behavior changes.

- share one definition of the initial generation from fabro-types instead
  of three copies across fabro-types, fabro-agent, and the supervisor
- give each child one SubAgentHandle instead of threading the supervisor's
  state, callback, and notification sender through five functions, and
  collapse the repeated signal-then-drain pairs into publish()
- move `reusable` inside SubAgentStatus::Finished so a closed agent can no
  longer be marked reusable
- clear the lifecycle draining flag with an RAII guard, so one panicking
  callback cannot silence every later lifecycle event
- tear down a session that failed to initialize right away rather than
  holding it and its sandbox until the parent closes the agent
- look agents up through SupervisorState::agent/agent_mut instead of five
  copies of the same not-found error
- drop the unreachable cleanup_started branch and the test-only emit_event
  whose only caller was its own test
- render subagent starts from one ProgressEvent and one display method,
  deriving the spawn/turn distinction from the generation
- set projected subagent status through one helper instead of four
  identical reducer arms
- drive the generation-pinned wait test through spawn/send_input rather
  than hand-writing private supervisor state

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:09:07 -04:00
Bryan Helmkamp
cc590f6f97
Merge remote-tracking branch 'origin/main' into fix/modal-provider-catalog
Resolve the model catalog table conflict in docs/public/core-concepts/models.mdx
by keeping both changes: this branch's `kimi` -> `moonshot` provider rename for
the Kimi rows, and main's new DeepSeek V4 rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:32:19 -04:00
Bryan Helmkamp
24cd22d793
Merge pull request #703 from fabro-sh/fix/mcp-restart-after-upgrade-697
Fix stale MCP servers after upgrades
2026-08-01 09:26:26 -04:00
Bryan Helmkamp
14cf4f3e94
Simplify MCP executable monitoring
Exit the process from `main` for every command instead of returning. The
`mcp start` command parks Tokio's stdin reader on a read only the MCP host
can end, so dropping the runtime waits forever. Exiting in `main` also keeps
the CLI telemetry event, which the previous exit inside the MCP command
skipped.

That removes the reason for the `McpServerExit` enum, whose only job was to
carry an implementation detail out to the CLI so it could exit.

Watch the executable through its device and inode on Unix. That is a
complete file identity, so the length and modification time no longer add
anything. Drop the PATH scan: `current_exe` reports the symlink itself on
macOS, so it detects a Homebrew relink without it. This also drops the
`fabro-static` dependency and a clippy suppression.

Bound the shutdown wait after an upgrade is detected. The transport closes
by writing to a stdout the host may already have stopped reading, which
could hang the exit the change is supposed to trigger.

Log a warning when upgrade detection cannot start, rather than disabling it
silently.

Share one spawn helper between the two raw stdio tests, and link the test
executable instead of copying 200 MB of binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:10:28 -04:00
Scott Werner
42dcb410f2 Remove obsolete manifest Docker image argument 2026-08-01 09:03:10 -04:00
Bryan Helmkamp
f868734f27
Fix stale MCP servers after upgrades 2026-07-31 13:18:12 -04:00
Release Repro
f613821bfb
feat(llm): add direct DeepSeek provider 2026-07-31 13:10:08 -04:00
Bryan Helmkamp
24f9ac6bd1
refactor(model): rename Kimi provider to Moonshot 2026-07-31 09:00:52 -04:00
Bryan Helmkamp
8b5106902c
feat: reuse completed subagent sessions 2026-07-31 08:19:10 -04:00
Bryan Helmkamp
0f2b66565a
Address artifact review feedback 2026-07-29 16:37:59 -04:00
Bryan Helmkamp
5f040d3107
Fix artifacts from repeated stage visits 2026-07-29 14:27:37 -04:00
Scott Werner
1575cc174f Stop requesting workflow write permission 2026-07-29 13:19:55 -04:00
Bryan Helmkamp
de7bb61ef5
Remove nonfunctional run agent permissions setting 2026-07-29 10:23:00 -04:00
Bryan Helmkamp
a369ea7fc4
Merge remote-tracking branch 'origin/main' into feat/for-each-item-injection
# Conflicts:
#	apps/fabro-web/app/components/stage-renderers/parallel-children.tsx
2026-07-28 20:03:25 -04:00
Bryan Helmkamp
17b3223558
Neutralize runtime for_each labels before display
Addresses three Copilot review comments on #653.

`item_label` comes from a model or a workflow author, and it reaches the
terminal through the CLI progress display. A label could carry ANSI
escapes, newlines, or bidi overrides and rewrite what the operator sees.
It could also be whitespace-only, giving a branch a blank identity.

Add `text::sanitize_display_label`: strip ANSI sequences, drop control
and bidi-reordering characters, trim, and elide past 80 characters.
Return an empty string when nothing printable survives so callers fall
back to an identity they control.

Apply it where the label is created, so events, the store, and the web
UI all get a clean value instead of each consumer having to remember.
`parallel_branch_display` sanitizes again, because a run recorded before
this commit still has raw labels in its event log.

`emit_branch_retrying` now sets `stage.retrying`'s `index` from the
branch stage's execution ordinal, matching the envelope `stage_id` and
the meaning every other emitter gives that field. The branch's position
in the fan-out is already on `parallel.branch.started`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:52:59 -04:00
Bryan Helmkamp
9d828a8688
Merge remote-tracking branch 'origin/main' into refactor/remove-env-interpolation
# Conflicts:
#	lib/components/fabro-workflow/src/pipeline/pull_request.rs
2026-07-28 17:44:03 -04:00
Bryan Helmkamp
6226c8c517
fix: address env interpolation review findings
Restore the documented SDK env credential facade without reintroducing run fallback behavior. Fail closed on GitHub permission resolution, require worker storage at the CLI boundary, and align interpolation names and generated docs.
2026-07-28 17:30:50 -04:00