Commit graph

5068 commits

Author SHA1 Message Date
Scott Werner
a9f28828a6 Adapt workflow target selection to sandbox provider kinds 2026-09-12 11:48:21 -06:00
Scott Werner
321a190315 Share one supervised process runner between server and CLI Git
The CLI's native Git runner and the server's run_git_plan each hand-rolled
the same mechanics: kill-on-drop, a wall-clock timeout, output capture,
and (only in the CLI) process-group teardown, bounded capture, and
cancellation. Add fabro_proc::SupervisedCommand, which owns stdio, the
process group, the timeout, cooperative cancellation, and bounded
capture, and put both runners on it. The server gains group teardown on
timeout, so helpers a stuck clone or fetch spawned no longer outlive it;
the CLI keeps discarding output on failure and gains nothing but less
code. The hardened -c overrides become one named list in the CLI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
7c97ba1b70 Move the run-driven remote workflow test to cmd/run.rs
remote_workflow_run_starts_once_create_leaves_submitted_and_failures_do_not_refetch
lived in cmd/create.rs but drove fabro run in four of its five
iterations and asserted the start call, which is fabro run's contract.
Split it: cmd/create.rs keeps the single create invocation that must
leave the run submitted without starting it, cmd/run.rs owns the
run-driven success and failure iterations, and the workflow and remote
repository fixtures move to the shared command test support module.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
f15972f8f2 Keep Ctrl-C owned for the whole native Git command phase
owned() polled tokio's ctrl_c() only for the duration of one Git
acquisition. On Unix that listener permanently replaces the default
SIGINT disposition, so once acquisition finished nothing handled Ctrl-C
and fabro create, fabro run --detach, and fabro run before attach
silently ignored it while waiting on the server. Introduce an
Interruption handle that the command entry points create from the run
arguments: it installs a listener only when --workflow-git or
--target-git is in play, guards create (and start for fabro run) as one
phase, and tracks owned Git tasks so interruption waits for their
cleanup before returning. attach keeps installing its own listener.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
39694ec5e8 Acquire the remote workflow before observing a local Git target
For --workflow-git selections, target resolution ran before the remote
workflow ref was verified to exist. On Docker and Daytona environments a
path target that is a GitHub checkout is observed via
observe_git_run_target, which may silently push the attached branch, so
a typo in --workflow-ref produced a remote side effect with no run
created. Resolve the remote workflow after parent and environment
validation but before target observation, restoring the pre-existing
workflow-then-target order, and cover it with a caller checkout whose
unpushed branch must stay unpublished.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
0998f60624 Contain the selected workflow file instead of pre-walking the checkout
Remote acquisition walked the entire depth-1 checkout and failed on any
symlink that dangled or resolved outside the root, even when the link
was nowhere near the selected workflow. Submodule-style dangling links
and links into the host are common in workflow repositories and made
--workflow-git fail where the same commit collected fine locally. The
bundler already root-checks every file it opens; the only unchecked
reads were the selected TOML (or a graph selector's sibling TOML) during
location resolution. Check those in collect_workflow_versions and drop
the O(repo) walk. walkdir stays a dev-dependency for the dump tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
bf41ad8a24 Classify workflow refs once when parsing --workflow-ref
RemoteWorkflowRevision::parse already enforced which ref namespaces a
value may name, but RefCandidates re-derived the branch/tag split from
the string and treated everything under refs/ that was not refs/heads/
as a tag, relying on an invariant checked in another file. Parse now
yields Branch, Tag, or Name variants and resolution matches on them
directly, replacing the Option/Option candidate encoding and its
impossible (None, None) input state.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
3163e71236 Reject unusable remote default branches with a --target-branch hint
Target resolution accepted any remote default HEAD that passed the ref
selector grammar, then failed inside GitRunTarget::validate with a
generic branch-grammar error when the default branch was something like
heads/main or tags/release. Validate the default branch as a working
branch name up front and point the user at --target-branch, since they
passed no branch at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
bba5e2b20b Run native Git metadata lookups from an owned scratch repository
ls-remote ran in the caller's working directory while fetch, cat-file,
checkout, and rev-parse ran inside the temporary checkout, so the lookup
honored repository-local config (url.*.insteadOf, credential.*, http.*,
core.sshCommand) that the fetch never saw, and a broken .git in the
caller's directory failed the lookup outright. Initialize the scratch
repository first and run every command from it, so all steps see the
same configuration. Target resolution uses a short-lived scratch
repository of its own and still creates no checkout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
d87f94bead Only kill the Git process group when a command fails
The native Git runner SIGKILLed its child's process group after every
command, including successful ones. Git spawns credential-cache--daemon
into the same group, so each ls-remote or fetch destroyed the cache it
had just warmed and every later command re-ran the full helper chain.
Kill the group only on timeout, cancellation, or failure, and drop the
redundant kill/wait on an already-reaped child.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
e99d3cebe8 Disable fsmonitor during native Git workflow checkout
The hardened -c list for CLI-owned Git acquisition disabled hooks, LFS
filters, submodules, and maintenance but omitted core.fsmonitor, so a
user's global fsmonitor hook (or the builtin daemon) still ran during the
temporary checkout. Match the sandbox's hardening and cover it in the
hooks/filters test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
b0ed545092 Simplify CLI workflow source and run target selection
Remove validation that ran twice on the same inputs: clap already
enforces the flag co-occurrence rules, and the native Git layer no
longer re-checks selectors, branch names, refs, and commit SHAs that
selection parsing already validated. Remote selector shape rules now
delegate to the shared WorkflowPath validator.

Reuse fabro_proc for the process-group kill and liveness probe instead
of calling nix directly, dropping the extra nix features. Fold the
duplicated branch/tag candidate derivation into one RefCandidates type,
label each Git command explicitly instead of inferring it from argv,
hoist the duplicated workflow resolver call in create_run, and merge the
two directory target arms now that the default is just the caller path.

Share the run-argument parser and workflow/commit fixtures across the
unit tests through a test_support module, drop an integration test that
duplicated one cell of the cross-product test, and make the malformed
slug vectors assert the clap rejection they exercise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 11:48:21 -06:00
Scott Werner
0e0d986634 Select CLI workflow sources and run targets independently 2026-09-12 11:48:21 -06:00
Scott Werner
2c81e81e5a
Merge pull request #832 from fabro-sh/codex/run-intent-run-create
Migrate fabro_run_create to the RunIntent API
2026-09-12 13:38:01 -04:00
Bryan Helmkamp
d407c42636
Merge pull request #855 from fabro-sh/pebble-pin-main
Pin pebble to main after lithoscomputer/pebble#10
2026-09-12 11:31:24 -06:00
Scott Werner
21a5e5b86f Simplify run creation to registered workflow versions 2026-09-12 11:04:15 -06:00
Scott Werner
1e8e1c9a30 Fix child target inheritance and inline workflow path validation 2026-09-12 10:59:07 -06:00
Scott Werner
6f72d369bf Document that fabro_run_create does not apply user run defaults
Tool-created runs no longer layer the caller's ~/.fabro/settings.toml
[run] defaults or project and machine run settings; only the values in
the request are transmitted, matching fabro run. The PR body stated this
but the public MCP and child-run docs did not, so callers relying on an
auto_approve or model default would see runs pause for approval or use
the default model without explanation. Note the behavior in both pages
and point at the explicit spec fields to use instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:42 -06:00
Scott Werner
b413b9b29c Inherit the parent branch, not its pinned commit, for child run targets
A child created without a target copied the parent's full Git target,
including the sha admitted for the parent. Clone-based providers never
fall back to branch HEAD, so a child created after the parent pushed new
commits was checked out at the parent's starting commit and never saw
the work it was meant to review or continue.

Inherit the repository and branch only, so the child resolves the
branch's current remote HEAD at admission; the parent's pinned commit and
tag stay on the parent. Callers that want a pinned child pass an explicit
target. Folder and none targets are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:42 -06:00
Scott Werner
5e79285a43 Honor the workflow's configured run.scm repository in target derivation
The replaced manifest builder resolved the run's repository identity from
the workflow's run.scm settings before falling back to the checkout's
origin. The new standalone derivation always used the checkout's origin,
so a fork checkout of a workflow that names its upstream repository
silently targeted the fork and pushed there.

Read the run.scm layer from the resolved workflow.toml and project.toml
(or from the inline workflow.toml bytes) and pass it through both the CLI
and the standalone run-tool adapter. When the configured repository is
not the checkout's origin, nothing can be proven about it, so derivation
now fails with a message naming that mismatch instead of the generic
"push the commit" hint.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:42 -06:00
Scott Werner
20f3766e75 Derive standalone run-tool targets from the selected environment
Standalone fabro_run_create ignored the environment's provider and always
produced a Git target or failed, so a Local environment with no explicit
target was rejected by admission and a directory without Git metadata
hard-failed, while fabro run derived a folder target and a none target
for the same inputs.

Move the CLI's provider-aware derivation into fabro-manifest as a shared
helper with a typed error, and have the standalone adapter look up the
selected environment and call it. The helper also distinguishes a failed
remote query from an unpublished commit, so an offline ls-remote no
longer reports "push the commit and try again" when the branch is
already on the origin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:42 -06:00
Scott Werner
f5811b47f3 Derive the fabro_run_create spec schema from the struct
The MCP tool schema for the object-form create spec was a hand-written
literal that had to be kept in step with the deny_unknown_fields struct
by hand, and only the target field had a parity test. A field added to
the struct deserialized fine but stayed invisible to clients because the
advertised schema forbade it.

Derive JsonSchema for CreateRunSpec so the field list and
additionalProperties come from the struct, keep hand-written schemas only
for the two custom-deserialized types (the workflow source union and the
run target union), and extend the parity test to validate a fully
populated spec against the advertised schema.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:21 -06:00
Scott Werner
001ed111fb Reject colliding inline workflow paths during tool validation
Two inline file paths that differ only by case, or a file that is also
an ancestor directory of another, used to surface as platform-dependent
low-level I/O errors naming a private temporary directory, and the
case-only case succeeded on Linux while failing on macOS. Validate both
shapes in fabro_run_create input validation so callers get a clear
message before any staging or registration happens.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:21 -06:00
Scott Werner
fefc236ab9 Collect inline workflows at their exact entrypoint
Inline workflow sources were routed through the checkout-selector
collector, which rewrites any extensionless relative path to a
.fabro/workflows/<name>/workflow.toml lookup. A supplied entrypoint such
as "review" therefore failed with "workflow was not found" even though
its bytes were in the file map.

Add a dedicated inline collector in fabro-manifest that treats the
entrypoint as an exact key, checks the file paths for filesystem
collisions before staging anything, and stages the bytes in a private
temporary root only for the duration of collection. The server adapter
now delegates to it instead of staging files itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:21 -06:00
Scott Werner
da0b4c259a Return 404 when a worker's originating run is missing
The worker folder-target guard opened a run reader and mapped every
failure, including a run that no longer exists, to HTTP 500 with an
error log. Load the projection through the store's lookup instead so a
missing run is a 404 with its own error code, and run the check after
environment selection so ordinary environment errors are reported
first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:58:05 -06:00
Scott Werner
b12eb8d84e Harden run-tool target and workflow resolution 2026-09-12 10:58:05 -06:00
Scott Werner
c2d6dc4a92 Align run-tool creation with server admission 2026-09-12 10:57:46 -06:00
Scott Werner
b38279fc44 Apply cleanup review fixes to the run-create path
Deduplicate shared-filesystem capability checks and simplify workflow-source dispatch and types. Move Git observation and local package collection onto spawn_blocking, and flush inline workflow files before collection.

Simplify validated source and input types, derive inline size-limit messages from shared constants, add target schema-parity coverage, and remove dead producer pass-through parameters.
2026-09-12 10:57:46 -06:00
Scott Werner
45741a3e6e Fix RunIntent producer CI failures 2026-09-12 10:57:46 -06:00
Scott Werner
c67c60eeba Create run tools from immutable workflow versions 2026-09-12 10:57:22 -06:00
Bryan Helmkamp
e51644b8d0
Pin pebble to main after lithoscomputer/pebble#10
Pebble main `222d17f` merges the sandbox-driver re-pin fabro was
pointing at by branch commit. Same tree as `9ec23d0`, so this is a
lockfile-only change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:54:59 -06:00
Scott Werner
bfcf82e367
Merge pull request #851 from fabro-sh/codex/workflow-version-create-tool
Add fabro_workflow_version_create to agent tools and MCP
2026-09-12 12:54:19 -04:00
Scott Werner
d611ef2bb1 Port workflow version creation to Pebble native tools 2026-09-12 10:23:34 -06:00
Scott Werner
ec81170c8a Share workflow packaging results and validation across callers 2026-09-12 10:15:42 -06:00
Scott Werner
410651d781 Bound workflow packaging depth and reject invalid supplied configs 2026-09-12 10:15:29 -06:00
Scott Werner
709d15f908 Keep supplied workflow source out of packaging failure logs
The packager logged the full packaging error chain at WARN. That chain
embeds caller-supplied workflow and prompt source: the graph parser's
diagnostic includes the unparsed remainder and the TOML parser prints
the offending line. The logging strategy prohibits user file contents
in tracing events at every level, and this adapter runs inside
`fabro mcp` and run workers at the default filter.

Log the collector error's own path-only message at DEBUG, since a
malformed request is an expected input error, together with the
entrypoint and file count. Wrap the blocking-task join error with
`context` instead of interpolating it. A test installs a TRACE-level
subscriber around the blocking path and checks that the fixture's
source marker, which the full chain does contain, never reaches the
log.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
97a7bb06e5 Pin sibling config resolution to the supplied file keys
Supplied files are staged on the host filesystem and WorkflowLocation
probes the fixed sibling name `workflow.toml` there. A request that
supplied `Workflow.toml` beside its graph therefore attached the config
on a case-insensitive host (and then failed the not-supplied check),
while the identical request on ext4 registered a version with no
config. The outcome of a content-addressed registration depended on
the server's filesystem.

After collection, every version is checked against the supplied map:
when no exact sibling `workflow.toml` was supplied, no supplied key may
alias that name under the same case and normalization rules the tool
already applies to supplied keys. A supplied sibling config still
attaches only to the graph it selects, matching checkouts, since
several graphs may share one directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
9bc6bf4227 Surface path-only packaging failures to the tool caller
Every packaging failure collapsed into one generic message, so an LLM
caller that omitted a child workflow, referenced a prompt with the
wrong case, or exceeded the canonical size limit could not tell what to
fix. The collector's error type already separates variants whose
messages carry only paths and counts from the ones whose sources quote
supplied content.

collect_supplied_workflow_versions now returns the typed collector
error, with new variants for a referenced file missing from the
package root, a collected file the caller did not supply, and staging
I/O failures. The bundler reports missing files with their
package-relative path so the collector can recognize them. The packager
renders the full cause chain for path-only variants and stops at the
last path-only level, plus a hint, for graph, TOML, and template
failures whose diagnostics quote source.

The tool-side raw byte total remains a cheap lower bound; the canonical
limit now surfaces with its own message instead of the generic one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
e6eb59537a Move the supplied-content packager next to its collector
ServerWorkflowVersionPackager was a pure adapter over
fabro_manifest::collect_supplied_workflow_versions that touched no
server state, yet it lived in fabro-server and was imported from there
by the standalone MCP server and the CLI run worker. fabro-manifest can
depend on fabro-tool without a cycle, so the adapter now lives beside
the collector as SuppliedWorkflowVersionPackager and fabro-server no
longer exports a non-server module for it.

The adapter also cloned every version's file map out of a closure it
already owned. CollectedWorkflowClosure::into_versions hands the
versions over by value inside the blocking task instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
44dccfa3d2 Require version config to be workflow.toml beside its graph
WorkflowLocation dispatches any `.toml` path to the config loader, so a
supplied entrypoint such as `sub/run.toml` was accepted, its graph
became the version entrypoint, and the config file was registered under
its own name. Runtime only reads WorkflowVersion::config_path(), the
fixed sibling `workflow.toml`, so the version's goal, environment, and
Dockerfile settings were silently dropped on every run.

In workflow-version projection, reject a config whose collected path is
not the graph's sibling `workflow.toml`. This applies to every caller
that packages versions, including `fabro run <dir>/other.toml`, which
previously registered the config and then ignored it; failing at
packaging replaces a silent drop. Manifest bundling for the legacy run
path does not project versions and is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
70b1090f58 Drop the duplicate-key guard that no tool route can reach
FabroWorkflowVersionCreateParams deserialized `files` through a
duplicate-rejecting map, but both production routes (rmcp Parameters
and the native LLM tool dispatch) deserialize from an already-parsed
serde_json::Value in which duplicate keys have collapsed last-wins. The
only test that exercised the guard used serde_json::from_str, the one
entry point production never uses, so the safeguard was misleading.

Remove the attribute and its byte-level test, and drop the
deserialize_unique_map export that existed only for it. The canonical
WorkflowVersion wire type keeps its own duplicate-key rejection, which
does run on the byte-level HTTP route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
4b9db26027 Keep portable path validation out of the checkout collector
validate_workflow_source_paths ran inside workflow_files for every
collected version, including the pre-existing checkout callers behind
automation materialization and `fabro run`. A repository on a
case-sensitive filesystem whose graph legitimately references two paths
that differ only by case or Unicode normalization packaged before this
branch and would have started failing.

The check is also redundant for the supplied-content path that
motivated it: the tool request validates the full key set before
staging, and the supplied collector confines collected keys to that
set. Remove it from the collector so existing callers are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
6a951652a7 Report path collisions in a deterministic order
The refactored ancestor-collision loop iterated the HashMap of folded
keys, so when a version contained more than one ancestor collision the
reported pair depended on the hasher seed. The same request could
produce different 422 bodies from POST /workflow-versions on repeated
submissions.

Collect the input into a Vec and walk it in order for the ancestor
pass, matching the previous behavior, and add a test with two
collisions that runs the check repeatedly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
b998f88cde Normalize workflow paths before case folding
validate_workflow_source_paths folded case before applying NFC, but
case folding is not closed under canonical equivalence: a decomposed
sequence and its precomposed form can fold to different strings. Two
supplied paths that a normalization-insensitive filesystem treats as
one entry therefore passed the collision check, and staging silently
overwrote one file with the other.

Apply NFC first, then fold, then normalize again, and add the Greek
pair that reproduced the gap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
111e737d53 Reject escaping workflow references before resolving them
The pre-resolution containment check in the version bundler was a
no-op: ManifestPath::from_absolute happily returns a `..`-prefixed path
for locations outside the package root, so an escaping
stack.child_workflow reference reached WorkflowLocation resolution,
which probes and parses config files on the host before the real
containment check in read_package_file ran. The request still failed,
but the TOML parser's diagnostic quoted the host file.

Check that the normalized reference stays under the package root before
resolving it, and extend the supplied-workflow test to plant malformed
host files that any parser would quote.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
bafdd880f5 Simplify workflow version registration tool layering
Move supplied-content packaging into fabro-manifest beside the checkout
collector, and narrow the injected seam to a packager that returns the
dependency-ordered closure so ClientBackend registers versions with the
client it already owns.

Validate the tool input once through a ValidatedWorkflowVersionCreate
newtype, matching the other tools, instead of re-validating at three
layers. Reuse the fabro-types unique-map deserializer and the shared
"not available" error helper, derive budget messages from the limit
constants, and render the tool result through the shared summary+JSON
path used by sibling tools.

Share one extension dispatch between WorkflowLocation::resolve and
from_exact_path, compute the bundler's normalized reference once, key
path-collision checks by a Cow so the canonical exact check no longer
allocates, and log the full packaging error chain before returning the
curated tool message. Replace the hand-rolled axum test server with
httpmock and declare the new unicode dependencies at the workspace.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:15:29 -06:00
Scott Werner
d5dec0fffb Add content-based workflow version registration tools 2026-09-12 10:15:19 -06:00
Bryan Helmkamp
a8cc458151
Merge pull request #852 from fabro-sh/pebble-agent-loop
Run the agent loop on pebble's CodingAgent
2026-09-12 10:04:05 -06:00
Bryan Helmkamp
39d013d50f
Drop the CI step that ran fabro-agent's twin-mode ignored suites
The step's filter named `fabro-agent` and `fabro-llm`. This branch removes
`fabro-agent`, and `fabro-llm` has no ignored tests, so the step ran nothing
and nextest exited 4 on the empty selection. The agent loop's tests run in
the ordinary suite now and pebble's own suite covers the loop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 09:51:00 -06:00
Bryan Helmkamp
957fc97c5c
Merge origin/main into pebble-agent-loop
Main merged the sandbox-driver adoption (#849) in a later form than this
branch was stacked on: the driver's own exec types replace fabro-sandbox's,
shell quoting moved to fabro-util, the sandbox lifecycle collapsed, and the
driver's events are stored as run events. This branch had deleted
`fabro-agent` and put the coding agent, the environment adapter, and the
steering hub on pebble.

The resolution takes main's sandbox API and re-applies pebble on top: the
`RunSandbox` `Environment` adapter moves to `pebble_environment.rs` (main's
`environment.rs` is the sandbox spec) and runs commands through `ExecSpec`
and `ExecControls`, feeding pebble's output sink from the driver's; the
driver-era `sandbox.*` names leave the known-event list, as on main, so a
stored event with that name and no driver shape is `Unknown` rather than an
error; `program_exit_code` matches pebble's non-exhaustive termination; the
Docker and Daytona smokes use main's constructor and credentials; the
remaining `fabro_agent` paths point at fabro-sandbox.

Pebble's `mcp` feature pins sandbox-driver, and the preview-url trait
objects only cross when both sides name one revision, so pebble moved to
main's `a92c0db6` (lithoscomputer/pebble#10) and fabro pins that pebble
revision until it lands on pebble main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 09:40:00 -06:00