mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
parent
57def34882
commit
a523a883fb
6 changed files with 585 additions and 10 deletions
File diff suppressed because one or more lines are too long
233
nodes/simplify_gpt/prompt.md
Normal file
233
nodes/simplify_gpt/prompt.md
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
Goal: # Unified WorktreeSandbox
|
||||
|
||||
## Context
|
||||
|
||||
Worktree management is currently split across two locations with duplicated logic:
|
||||
|
||||
1. **Parallel branches** (`parallel.rs`): Inline git commands in `ParallelHandler::execute()`, a thin `WorktreeSandbox` decorator, and separate local/remote code paths
|
||||
2. **Top-level CLI run** (`run.rs`): A `setup_worktree()` function using synchronous git helpers, with the worktree path fed into a plain `LocalSandbox`
|
||||
|
||||
The goal is a single `WorktreeSandbox` type that wraps any `Arc<dyn Sandbox>`, manages the worktree lifecycle in `initialize()`/`cleanup()`, and eliminates the local/remote branching.
|
||||
|
||||
## Plan
|
||||
|
||||
### Step 1: Create `WorktreeSandbox` in fabro-sandbox
|
||||
|
||||
**New file:** `lib/crates/fabro-sandbox/src/worktree.rs`
|
||||
|
||||
Define:
|
||||
|
||||
```rust
|
||||
pub enum WorktreeEvent {
|
||||
BranchCreated { branch: String, sha: String },
|
||||
WorktreeAdded { path: String, branch: String },
|
||||
WorktreeRemoved { path: String },
|
||||
Reset { sha: String },
|
||||
}
|
||||
|
||||
pub type WorktreeEventCallback = Arc<dyn Fn(WorktreeEvent) + Send + Sync>;
|
||||
|
||||
pub struct WorktreeConfig {
|
||||
pub branch_name: String,
|
||||
pub base_sha: String,
|
||||
pub worktree_path: String,
|
||||
/// Skip branch creation and reset (for resume, where branch already exists).
|
||||
pub skip_branch_creation: bool,
|
||||
}
|
||||
|
||||
pub struct WorktreeSandbox {
|
||||
inner: Arc<dyn Sandbox>,
|
||||
config: WorktreeConfig,
|
||||
event_callback: Option<WorktreeEventCallback>,
|
||||
}
|
||||
```
|
||||
|
||||
**Constructor + getters:** `new(inner, config)`, `set_event_callback()`, `branch_name()`, `base_sha()`, `worktree_path()`
|
||||
|
||||
**`initialize()`:**
|
||||
1. If `!skip_branch_creation`: `git branch --force {branch_name} {base_sha}` via `inner.exec_command()`, emit `BranchCreated`
|
||||
2. `git worktree remove --force {path}` (best-effort), then `git worktree add {path} {branch}`, emit `WorktreeAdded`
|
||||
3. If `!skip_branch_creation`: `git reset --hard {base_sha}` in worktree dir, emit `Reset`
|
||||
|
||||
Does NOT call `inner.initialize()` — the inner sandbox's lifecycle is managed separately.
|
||||
|
||||
**`cleanup()`:** `git worktree remove --force {path}`, emit `WorktreeRemoved`. Does NOT call `inner.cleanup()`.
|
||||
|
||||
**`working_directory()`:** Returns `config.worktree_path`.
|
||||
|
||||
**`exec_command()`:** Defaults `working_dir` to `config.worktree_path` when `None`, delegates to inner.
|
||||
|
||||
**All other Sandbox methods:** Delegate to inner. Must be a manual `impl Sandbox` block (can't use `delegate_sandbox!` since it generates `initialize`/`cleanup`/`working_directory`/`exec_command` which we need to override).
|
||||
|
||||
All interpolated values in git commands use `shell_quote()`.
|
||||
|
||||
### Step 2: Register module and re-exports
|
||||
|
||||
- `lib/crates/fabro-sandbox/src/lib.rs`: Add `pub mod worktree;` and `pub use worktree::WorktreeSandbox;`
|
||||
- `lib/crates/fabro-agent/src/sandbox.rs`: Add re-export of `WorktreeSandbox`
|
||||
|
||||
### Step 3: Unit tests for WorktreeSandbox
|
||||
|
||||
In `worktree.rs` `#[cfg(test)]` module, using `MockSandbox`:
|
||||
|
||||
- `initialize()` issues correct git commands (branch, worktree remove, worktree add, reset) and emits events
|
||||
- `skip_branch_creation` skips branch + reset, only does worktree add
|
||||
- `cleanup()` issues `worktree remove` and emits `WorktreeRemoved`
|
||||
- `working_directory()` returns worktree path
|
||||
- `exec_command()` with `None` working_dir defaults to worktree path
|
||||
- `exec_command()` with explicit working_dir passes it through
|
||||
- `initialize()` propagates errors on non-zero exit
|
||||
|
||||
**MockSandbox enhancement:** Add `captured_commands: Mutex<Vec<String>>` field to `test_support.rs` to capture the sequence of `exec_command` calls (current `captured_command` only stores the last one). Append to vec in `exec_command()` impl.
|
||||
|
||||
### Step 4: Refactor parallel.rs
|
||||
|
||||
- **Remove** the private `WorktreeSandbox` struct (lines 28-126) and `use fabro_agent::LocalSandbox`
|
||||
- **Replace** the inline git setup loop (lines 361-450) with:
|
||||
- Construct `WorktreeConfig` with branch name, base SHA, worktree path
|
||||
- Create `WorktreeSandbox::new(Arc::clone(&services.sandbox), config)`
|
||||
- Wire event callback to bridge `WorktreeEvent` → `WorkflowRunEvent`
|
||||
- Call `initialize().await`
|
||||
- This eliminates the `if services.sandbox.is_remote()` branch (lines 442-449) — `WorktreeSandbox` works the same for any inner sandbox
|
||||
- **Cleanup loop** (lines 659-668): Keep calling `git_remove_worktree()` on the parent sandbox (the `WorktreeSandbox` Arc is consumed by the spawned task and dropped). Alternatively, could store the sandbox Arc in `BranchResult` and call `.cleanup()`, but the current approach is simpler.
|
||||
|
||||
### Step 5: Refactor run.rs — new runs
|
||||
|
||||
Replace `setup_worktree()` call (lines 830-845) + separate `LocalSandbox` construction with:
|
||||
|
||||
```
|
||||
if workdir_strategy == LocalWorktree:
|
||||
base_sha = git::head_sha()
|
||||
branch_name = "fabro/run/{run_id}"
|
||||
inner = Arc::new(LocalSandbox::new(original_cwd))
|
||||
wt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig { ... })
|
||||
wt_sandbox.set_event_callback(bridge to WorkflowRunEvent)
|
||||
wt_sandbox.initialize().await
|
||||
std::env::set_current_dir(&worktree_path) // stays in CLI, not in sandbox
|
||||
sandbox = Arc::new(wt_sandbox)
|
||||
// store base_sha, branch_name for RunConfig
|
||||
```
|
||||
|
||||
**Delete** the `setup_worktree()` function (lines 1696-1714) — its logic is absorbed above.
|
||||
|
||||
`std::env::set_current_dir()` stays in `run.rs` — it's a process-global side effect that belongs to the CLI.
|
||||
|
||||
### Step 6: Refactor run.rs — resume (run_from_branch)
|
||||
|
||||
Replace worktree re-attachment (lines 1810-1822) with:
|
||||
|
||||
```
|
||||
inner = Arc::new(LocalSandbox::new(original_cwd))
|
||||
wt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig {
|
||||
branch_name: run_branch,
|
||||
base_sha: base_sha.unwrap_or_default(),
|
||||
worktree_path: wt_str,
|
||||
skip_branch_creation: true, // branch already exists
|
||||
})
|
||||
wt_sandbox.initialize().await
|
||||
std::env::set_current_dir(&wt)
|
||||
```
|
||||
|
||||
### Step 7: Verify
|
||||
|
||||
- `cargo build --workspace`
|
||||
- `cargo test --workspace`
|
||||
- `cargo clippy --workspace -- -D warnings`
|
||||
- Manual: `fabro run` with worktree mode enabled on a local workflow
|
||||
- Manual: `fabro run --run-branch` to test resume path
|
||||
|
||||
## Files to modify
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `lib/crates/fabro-sandbox/src/worktree.rs` | **New** — WorktreeSandbox, WorktreeConfig, WorktreeEvent, impl Sandbox, tests |
|
||||
| `lib/crates/fabro-sandbox/src/lib.rs` | Add module + re-export |
|
||||
| `lib/crates/fabro-sandbox/src/test_support.rs` | Add `captured_commands: Mutex<Vec<String>>` to MockSandbox |
|
||||
| `lib/crates/fabro-agent/src/sandbox.rs` | Add WorktreeSandbox re-export |
|
||||
| `lib/crates/fabro-workflows/src/handler/parallel.rs` | Remove old WorktreeSandbox, use new one |
|
||||
| `lib/crates/fabro-cli/src/commands/run.rs` | Replace setup_worktree + run_from_branch worktree logic |
|
||||
|
||||
## Functions that become removable
|
||||
|
||||
| Function | Location | Reason |
|
||||
|---|---|---|
|
||||
| `setup_worktree()` | `run.rs:1696` | Logic absorbed into WorktreeSandbox |
|
||||
| Old `WorktreeSandbox` struct | `parallel.rs:28-126` | Replaced by shared WorktreeSandbox |
|
||||
|
||||
Engine git helpers (`git_add_worktree`, `git_remove_worktree`, etc. in `engine.rs`) stay — still used by parallel cleanup and potentially other callers. Sync git helpers in `git.rs` also stay.
|
||||
|
||||
|
||||
## Completed stages
|
||||
- **toolchain**: success
|
||||
- Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`
|
||||
- Stdout:
|
||||
```
|
||||
cargo 1.94.0 (85eff7c80 2026-01-15)
|
||||
```
|
||||
- Stderr: (empty)
|
||||
- **preflight_compile**: success
|
||||
- Script: `cargo check -q --workspace 2>&1`
|
||||
- Stdout: (empty)
|
||||
- Stderr: (empty)
|
||||
- **preflight_lint**: success
|
||||
- Script: `cargo clippy -q --workspace -- -D warnings 2>&1`
|
||||
- Stdout: (empty)
|
||||
- Stderr: (empty)
|
||||
- **implement**: success
|
||||
- Model: claude-sonnet-4-6, 190.9k tokens in / 105.0k out
|
||||
- Files: /home/daytona/workspace/lib/crates/fabro-agent/src/lib.rs, /home/daytona/workspace/lib/crates/fabro-agent/src/sandbox.rs, /home/daytona/workspace/lib/crates/fabro-cli/src/commands/run.rs, /home/daytona/workspace/lib/crates/fabro-sandbox/src/lib.rs, /home/daytona/workspace/lib/crates/fabro-sandbox/src/test_support.rs, /home/daytona/workspace/lib/crates/fabro-sandbox/src/worktree.rs, /home/daytona/workspace/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||
- **simplify_opus**: success
|
||||
- Model: claude-sonnet-4-6, 84.8k tokens in / 30.2k out
|
||||
- Files: /home/daytona/workspace/lib/crates/fabro-cli/src/commands/run.rs, /home/daytona/workspace/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||
|
||||
|
||||
# Simplify: Code Review and Cleanup
|
||||
|
||||
Review all changed files for reuse, quality, and efficiency. Fix any issues found.
|
||||
|
||||
## Phase 1: Identify Changes
|
||||
|
||||
Run git diff (or git diff HEAD if there are staged changes) to see what changed. If there are no git changes, review the most recently modified files that the user mentioned or that you edited earlier in this conversation.
|
||||
|
||||
## Phase 2: Launch Three Review Agents in Parallel
|
||||
|
||||
Use the Agent tool to launch all three agents concurrently in a single message. Pass each agent the full diff so it has the complete context.
|
||||
|
||||
### Agent 1: Code Reuse Review
|
||||
|
||||
For each change:
|
||||
|
||||
1. Search for existing utilities and helpers that could replace newly written code. Use Grep to find similar patterns elsewhere in the codebase — common locations are utility directories, shared modules, and files adjacent to the changed ones.
|
||||
2. Flag any new function that duplicates existing functionality. Suggest the existing function to use instead.
|
||||
3. Flag any inline logic that could use an existing utility — hand-rolled string manipulation, manual path handling, custom environment checks, ad-hoc type guards, and similar patterns are common candidates.
|
||||
|
||||
Note: This is a greenfield app, so focus on maximizing simplicity and don't worry about changing things to achieve it.
|
||||
|
||||
### Agent 2: Code Quality Review
|
||||
|
||||
Review the same changes for hacky patterns:
|
||||
|
||||
1. Redundant state: state that duplicates existing state, cached values that could be derived, observers/effects that could be direct calls
|
||||
2. Parameter sprawl: adding new parameters to a function instead of generalizing or restructuring existing ones
|
||||
3. Copy-paste with slight variation: near-duplicate code blocks that should be unified with a shared abstraction
|
||||
4. Leaky abstractions: exposing internal details that should be encapsulated, or breaking existing abstraction boundaries
|
||||
5. Stringly-typed code: using raw strings where constants, enums (string unions), or branded types already exist in the codebase
|
||||
|
||||
Note: This is a greenfield app, so be aggressive in optimizing quality.
|
||||
|
||||
### Agent 3: Efficiency Review
|
||||
|
||||
Review the same changes for efficiency:
|
||||
|
||||
1. Unnecessary work: redundant computations, repeated file reads, duplicate network/API calls, N+1 patterns
|
||||
2. Missed concurrency: independent operations run sequentially when they could run in parallel
|
||||
3. Hot-path bloat: new blocking work added to startup or per-request/per-render hot paths
|
||||
4. Unnecessary existence checks: pre-checking file/resource existence before operating (TOCTOU anti-pattern) — operate directly and handle the error
|
||||
5. Memory: unbounded data structures, missing cleanup, event listener leaks
|
||||
6. Overly broad operations: reading entire files when only a portion is needed, loading all items when filtering for one
|
||||
|
||||
## Phase 3: Fix Issues
|
||||
|
||||
Wait for all three agents to complete. Aggregate their findings and fix each issue directly. If a finding is a false positive or not worth addressing, note it and move on — do not argue with the finding, just skip it.
|
||||
|
||||
When done, briefly summarize what was fixed (or confirm the code was already clean).
|
||||
5
nodes/simplify_gpt/provider_used.json
Normal file
5
nodes/simplify_gpt/provider_used.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "anthropic",
|
||||
"model": "claude-sonnet-4-6"
|
||||
}
|
||||
17
nodes/simplify_gpt/response.md
Normal file
17
nodes/simplify_gpt/response.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
All clean. Here's a summary of what was fixed:
|
||||
|
||||
## Changes Made
|
||||
|
||||
### Fix 1: Eliminated `worktree_event_callback` duplication (Code Reuse)
|
||||
|
||||
The function was defined identically in both `parallel.rs` and `run.rs`. Moved it as `EventEmitter::worktree_callback(self: Arc<Self>) -> WorktreeEventCallback` in `fabro-workflows/src/event.rs` — the natural home since it bridges `WorktreeEvent` (sandbox layer) → `WorkflowRunEvent` (workflows layer) via the emitter.
|
||||
|
||||
Both call sites now use `Arc::clone(&emitter).worktree_callback()`. The `WorktreeEvent` and `WorktreeEventCallback` imports were removed from `parallel.rs` and `run.rs`.
|
||||
|
||||
### Fix 2: Removed no-op `git reset --hard` from `WorktreeSandbox::initialize()` (Efficiency)
|
||||
|
||||
After `git branch --force {branch} {sha}` (step 1) and `git worktree add {path} {branch}` (step 3), the worktree is already checked out at exactly `sha` — `git reset --hard {sha}` was resetting a fresh worktree to the commit it was already at. This saved one git invocation (a process spawn locally, or an SSH round-trip on remote) per worktree initialization.
|
||||
|
||||
Removed `WorktreeEvent::Reset` from the enum since it was only emitted from the now-deleted reset step. `WorkflowRunEvent::GitReset` (the serialized protocol event) was left untouched for historical log compatibility.
|
||||
|
||||
Updated 3 affected tests: renamed the events assertion test, removed `initialize_reset_uses_worktree_path_as_working_dir`, and updated command-count assertions from 4 → 3.
|
||||
6
nodes/simplify_gpt/status.json
Normal file
6
nodes/simplify_gpt/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"status": "success",
|
||||
"notes": "Stage completed: simplify_gpt",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-03-20T02:03:03.155181+00:00"
|
||||
}
|
||||
283
nodes/simplify_opus/diff.patch
Normal file
283
nodes/simplify_opus/diff.patch
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
diff --git a/lib/crates/fabro-cli/src/commands/run.rs b/lib/crates/fabro-cli/src/commands/run.rs
|
||||
index 6f76725f..dfe34fa9 100644
|
||||
--- a/lib/crates/fabro-cli/src/commands/run.rs
|
||||
+++ b/lib/crates/fabro-cli/src/commands/run.rs
|
||||
@@ -9,7 +9,7 @@ use chrono::{Local, Utc};
|
||||
use clap::{Args, ValueEnum};
|
||||
use fabro_agent::{
|
||||
DockerSandbox, DockerSandboxConfig, LocalSandbox, Sandbox, WorktreeConfig, WorktreeEvent,
|
||||
- WorktreeSandbox,
|
||||
+ WorktreeEventCallback, WorktreeSandbox,
|
||||
};
|
||||
use fabro_config::run::{RunDefaults, WorkflowRunConfig};
|
||||
use fabro_config::{project as project_config, run as run_config, sandbox as sandbox_config};
|
||||
@@ -423,6 +423,34 @@ struct CostAccumulator {
|
||||
has_pricing: bool,
|
||||
}
|
||||
|
||||
+/// Build a callback that forwards [`WorktreeEvent`]s as workflow run events on `emitter`.
|
||||
+fn worktree_event_callback(emitter: Arc<EventEmitter>) -> WorktreeEventCallback {
|
||||
+ Arc::new(move |event| match event {
|
||||
+ WorktreeEvent::BranchCreated { branch, sha } => {
|
||||
+ emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitBranch { branch, sha });
|
||||
+ }
|
||||
+ WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||
+ emitter
|
||||
+ .emit(&fabro_workflows::event::WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||
+ }
|
||||
+ WorktreeEvent::WorktreeRemoved { path } => {
|
||||
+ emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitWorktreeRemove { path });
|
||||
+ }
|
||||
+ WorktreeEvent::Reset { sha } => {
|
||||
+ emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitReset { sha });
|
||||
+ }
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+/// Create a [`LocalSandbox`] wired to emit [`WorkflowRunEvent::Sandbox`] events.
|
||||
+fn local_sandbox_with_callback(cwd: PathBuf, emitter: Arc<EventEmitter>) -> Arc<dyn Sandbox> {
|
||||
+ let mut env = LocalSandbox::new(cwd);
|
||||
+ env.set_event_callback(Arc::new(move |event| {
|
||||
+ emitter.emit(&fabro_workflows::event::WorkflowRunEvent::Sandbox { event });
|
||||
+ }));
|
||||
+ Arc::new(env)
|
||||
+}
|
||||
+
|
||||
/// Execute a full workflow run.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -1125,57 +1153,26 @@ pub async fn run_command(
|
||||
Arc::new(env)
|
||||
}
|
||||
SandboxProvider::Local => {
|
||||
- if worktree_base_sha.is_some() {
|
||||
+ if let (Some(base_sha), Some(branch_name), Some(wt_path)) = (
|
||||
+ worktree_base_sha.as_ref(),
|
||||
+ worktree_branch.as_ref(),
|
||||
+ worktree_path.as_ref(),
|
||||
+ ) {
|
||||
// Set up a WorktreeSandbox for git-isolated local execution.
|
||||
- let base_sha = worktree_base_sha.clone().unwrap();
|
||||
- let branch_name = worktree_branch.clone().unwrap();
|
||||
- let wt_path = worktree_path.clone().unwrap();
|
||||
- let wt_path_str = wt_path.to_string_lossy().to_string();
|
||||
-
|
||||
- let mut inner = LocalSandbox::new(original_cwd.clone());
|
||||
- let emitter_inner = Arc::clone(&emitter);
|
||||
- inner.set_event_callback(Arc::new(move |event| {
|
||||
- emitter_inner
|
||||
- .emit(&fabro_workflows::event::WorkflowRunEvent::Sandbox { event });
|
||||
- }));
|
||||
-
|
||||
+ let wt_path_str = wt_path.to_string_lossy().into_owned();
|
||||
+ let inner = local_sandbox_with_callback(original_cwd.clone(), Arc::clone(&emitter));
|
||||
let wt_config = WorktreeConfig {
|
||||
branch_name: branch_name.clone(),
|
||||
base_sha: base_sha.clone(),
|
||||
- worktree_path: wt_path_str.clone(),
|
||||
+ worktree_path: wt_path_str,
|
||||
skip_branch_creation: false,
|
||||
};
|
||||
- let mut wt_sandbox = WorktreeSandbox::new(Arc::new(inner), wt_config);
|
||||
- let emitter_wt = Arc::clone(&emitter);
|
||||
- wt_sandbox.set_event_callback(Arc::new(move |event| match event {
|
||||
- WorktreeEvent::BranchCreated { branch, sha } => {
|
||||
- emitter_wt.emit(&fabro_workflows::event::WorkflowRunEvent::GitBranch {
|
||||
- branch,
|
||||
- sha,
|
||||
- });
|
||||
- }
|
||||
- WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||
- emitter_wt.emit(
|
||||
- &fabro_workflows::event::WorkflowRunEvent::GitWorktreeAdd {
|
||||
- path,
|
||||
- branch,
|
||||
- },
|
||||
- );
|
||||
- }
|
||||
- WorktreeEvent::WorktreeRemoved { path } => {
|
||||
- emitter_wt.emit(
|
||||
- &fabro_workflows::event::WorkflowRunEvent::GitWorktreeRemove { path },
|
||||
- );
|
||||
- }
|
||||
- WorktreeEvent::Reset { sha } => {
|
||||
- emitter_wt
|
||||
- .emit(&fabro_workflows::event::WorkflowRunEvent::GitReset { sha });
|
||||
- }
|
||||
- }));
|
||||
+ let mut wt_sandbox = WorktreeSandbox::new(inner, wt_config);
|
||||
+ wt_sandbox.set_event_callback(worktree_event_callback(Arc::clone(&emitter)));
|
||||
|
||||
match wt_sandbox.initialize().await {
|
||||
Ok(()) => {
|
||||
- std::env::set_current_dir(&wt_path)?;
|
||||
+ std::env::set_current_dir(wt_path)?;
|
||||
Arc::new(wt_sandbox) as Arc<dyn Sandbox>
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -1187,22 +1184,11 @@ pub async fn run_command(
|
||||
worktree_path = None;
|
||||
worktree_branch = None;
|
||||
worktree_base_sha = None;
|
||||
- let mut env = LocalSandbox::new(cwd.clone());
|
||||
- let emitter_cb = Arc::clone(&emitter);
|
||||
- env.set_event_callback(Arc::new(move |event| {
|
||||
- emitter_cb
|
||||
- .emit(&fabro_workflows::event::WorkflowRunEvent::Sandbox { event });
|
||||
- }));
|
||||
- Arc::new(env) as Arc<dyn Sandbox>
|
||||
+ local_sandbox_with_callback(cwd.clone(), Arc::clone(&emitter))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
- let mut env = LocalSandbox::new(cwd.clone());
|
||||
- let emitter_cb = Arc::clone(&emitter);
|
||||
- env.set_event_callback(Arc::new(move |event| {
|
||||
- emitter_cb.emit(&fabro_workflows::event::WorkflowRunEvent::Sandbox { event });
|
||||
- }));
|
||||
- Arc::new(env)
|
||||
+ local_sandbox_with_callback(cwd.clone(), Arc::clone(&emitter))
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1870,48 +1856,17 @@ async fn run_from_branch(
|
||||
SandboxProvider::Local | SandboxProvider::Docker => {
|
||||
// Re-attach worktree to the existing run branch via WorktreeSandbox.
|
||||
let wt = run_dir.join("worktree");
|
||||
- let wt_str = wt.to_string_lossy().to_string();
|
||||
-
|
||||
- let mut inner = fabro_agent::LocalSandbox::new(original_cwd.clone());
|
||||
- let emitter_inner = Arc::clone(&emitter);
|
||||
- inner.set_event_callback(Arc::new(move |event| {
|
||||
- emitter_inner
|
||||
- .emit(&fabro_workflows::event::WorkflowRunEvent::Sandbox { event });
|
||||
- }));
|
||||
+ let wt_str = wt.to_string_lossy().into_owned();
|
||||
|
||||
+ let inner = local_sandbox_with_callback(original_cwd.clone(), Arc::clone(&emitter));
|
||||
let wt_config = WorktreeConfig {
|
||||
branch_name: run_branch.to_string(),
|
||||
base_sha: base_sha.clone().unwrap_or_default(),
|
||||
worktree_path: wt_str.clone(),
|
||||
skip_branch_creation: true, // branch already exists on resume
|
||||
};
|
||||
- let mut wt_sandbox = WorktreeSandbox::new(Arc::new(inner), wt_config);
|
||||
- let emitter_wt = Arc::clone(&emitter);
|
||||
- wt_sandbox.set_event_callback(Arc::new(move |event| match event {
|
||||
- WorktreeEvent::BranchCreated { branch, sha } => {
|
||||
- emitter_wt.emit(&fabro_workflows::event::WorkflowRunEvent::GitBranch {
|
||||
- branch,
|
||||
- sha,
|
||||
- });
|
||||
- }
|
||||
- WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||
- emitter_wt.emit(
|
||||
- &fabro_workflows::event::WorkflowRunEvent::GitWorktreeAdd {
|
||||
- path,
|
||||
- branch,
|
||||
- },
|
||||
- );
|
||||
- }
|
||||
- WorktreeEvent::WorktreeRemoved { path } => {
|
||||
- emitter_wt.emit(
|
||||
- &fabro_workflows::event::WorkflowRunEvent::GitWorktreeRemove { path },
|
||||
- );
|
||||
- }
|
||||
- WorktreeEvent::Reset { sha } => {
|
||||
- emitter_wt
|
||||
- .emit(&fabro_workflows::event::WorkflowRunEvent::GitReset { sha });
|
||||
- }
|
||||
- }));
|
||||
+ let mut wt_sandbox = WorktreeSandbox::new(inner, wt_config);
|
||||
+ wt_sandbox.set_event_callback(worktree_event_callback(Arc::clone(&emitter)));
|
||||
|
||||
wt_sandbox.initialize().await.map_err(|e| {
|
||||
anyhow::anyhow!("failed to attach worktree to {run_branch}: {e}")
|
||||
diff --git a/lib/crates/fabro-workflows/src/handler/parallel.rs b/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||
index 36db9f35..5ff62054 100644
|
||||
--- a/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||
+++ b/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||
@@ -3,14 +3,14 @@ use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use async_trait::async_trait;
|
||||
-use fabro_agent::{Sandbox, WorktreeConfig, WorktreeEvent, WorktreeSandbox};
|
||||
+use fabro_agent::{Sandbox, WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
use crate::context::keys;
|
||||
use crate::context::Context;
|
||||
use crate::engine::set_hook_node;
|
||||
use crate::error::FabroError;
|
||||
-use crate::event::WorkflowRunEvent;
|
||||
+use crate::event::{EventEmitter, WorkflowRunEvent};
|
||||
use crate::millis_u64;
|
||||
use crate::outcome::{Outcome, StageStatus};
|
||||
use fabro_graphviz::graph::{Graph, Node};
|
||||
@@ -91,6 +91,24 @@ fn parse_error_policy(raw: &str) -> ErrorPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
+/// Bridge a [`WorktreeEvent`] to the corresponding workflow run event on `emitter`.
|
||||
+fn worktree_event_callback(emitter: Arc<EventEmitter>) -> WorktreeEventCallback {
|
||||
+ Arc::new(move |event| match event {
|
||||
+ WorktreeEvent::BranchCreated { branch, sha } => {
|
||||
+ emitter.emit(&WorkflowRunEvent::GitBranch { branch, sha });
|
||||
+ }
|
||||
+ WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||
+ emitter.emit(&WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||
+ }
|
||||
+ WorktreeEvent::WorktreeRemoved { path } => {
|
||||
+ emitter.emit(&WorkflowRunEvent::GitWorktreeRemove { path });
|
||||
+ }
|
||||
+ WorktreeEvent::Reset { sha } => {
|
||||
+ emitter.emit(&WorkflowRunEvent::GitReset { sha });
|
||||
+ }
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
struct BranchResult {
|
||||
id: String,
|
||||
outcome: Outcome,
|
||||
@@ -283,7 +301,7 @@ impl Handler for ParallelHandler {
|
||||
.join(branch_key)
|
||||
.join("worktree")
|
||||
.to_string_lossy()
|
||||
- .to_string()
|
||||
+ .into_owned()
|
||||
};
|
||||
tracing::debug!(branch = %branch_name, path = %wt_path_str, "Creating worktree for parallel branch");
|
||||
|
||||
@@ -295,21 +313,8 @@ impl Handler for ParallelHandler {
|
||||
skip_branch_creation: false,
|
||||
};
|
||||
let mut wt_sandbox = WorktreeSandbox::new(Arc::clone(&services.sandbox), wt_config);
|
||||
- let emitter_wt = Arc::clone(&services.emitter);
|
||||
- wt_sandbox.set_event_callback(Arc::new(move |event| match event {
|
||||
- WorktreeEvent::BranchCreated { branch, sha } => {
|
||||
- emitter_wt.emit(&WorkflowRunEvent::GitBranch { branch, sha });
|
||||
- }
|
||||
- WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||
- emitter_wt.emit(&WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||
- }
|
||||
- WorktreeEvent::WorktreeRemoved { path } => {
|
||||
- emitter_wt.emit(&WorkflowRunEvent::GitWorktreeRemove { path });
|
||||
- }
|
||||
- WorktreeEvent::Reset { sha } => {
|
||||
- emitter_wt.emit(&WorkflowRunEvent::GitReset { sha });
|
||||
- }
|
||||
- }));
|
||||
+ wt_sandbox
|
||||
+ .set_event_callback(worktree_event_callback(Arc::clone(&services.emitter)));
|
||||
wt_sandbox
|
||||
.initialize()
|
||||
.await
|
||||
@@ -532,7 +537,7 @@ impl Handler for ParallelHandler {
|
||||
// Clean up worktrees first
|
||||
for result in &results {
|
||||
if let Some(ref wt_path) = result.worktree_path {
|
||||
- let wt_str = wt_path.to_string_lossy().to_string();
|
||||
+ let wt_str = wt_path.to_string_lossy().into_owned();
|
||||
crate::engine::git_remove_worktree(&*services.sandbox, &wt_str).await;
|
||||
services
|
||||
.emitter
|
||||
Loading…
Add table
Reference in a new issue