mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-20 00:11:34 +00:00
Replace fabro's hand-written agent loop with pebble's `CodingAgent` and delete the `fabro-agent` crate. Workflow: `PebbleBackend` builds one agent per stage over `RunSandbox`, binds the stage's hooks as tool middleware, the interviewer as the human-input provider, and a durable `EventSink` that writes every agent event through the run event log before the agent goes on. Full-fidelity threads continue across stages through `export`/`resume_from_export`. Model failover takes the session record after the failed prompt and continues it on the next route with `ResumeMode::UseModel`, so no tool effect repeats. The steering hub targets pebble's control handle, with a steering lease holding completion open while a human is paired. Events: `EventBody::Agent` carries pebble's `CodingAgentEvent` envelope; the per-variant bodies, the transcript projection, and the fabro-only context-window, tool-summary, and skill types are gone in favor of pebble's. The OpenAPI schemas, generated Rust and TypeScript clients, and web readers follow. Ask Fabro: the session runs a `CodingAgent` under a read-only permission policy and a system prompt transform. Its conversation lives in a new `run_session_records` table and resumes on the recorded model with the event cursor advanced past the run log. `fabro exec` builds the same agent over a local sandbox with pebble's permission middleware and an interactive approval service. The catalog fills in `metadata.agent.profile` for operator providers that declare none, so pebble's lookup is the one resolution path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
84 lines
2.6 KiB
Rust
84 lines
2.6 KiB
Rust
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
use fabro_auth::test_support;
|
|
use fabro_hooks::{
|
|
HookContext, HookDecision, HookDefinition, HookEvent, HookExecutionContext, HookRunner,
|
|
HookSettings, InterpString,
|
|
};
|
|
use fabro_llm::credentials::CredentialProvider;
|
|
use fabro_llm::lithos_catalog::Catalog;
|
|
use fabro_sandbox::{RunSandbox, local_sandbox};
|
|
use fabro_types::RunId;
|
|
use tokio::fs;
|
|
|
|
fn test_llm_source() -> Arc<dyn CredentialProvider> {
|
|
test_support::vault_only_credential_source()
|
|
}
|
|
|
|
fn test_catalog() -> Arc<Catalog> {
|
|
Arc::new(fabro_llm::default_catalog())
|
|
}
|
|
|
|
async fn test_sandbox() -> Arc<RunSandbox> {
|
|
Arc::new(
|
|
local_sandbox(std::env::current_dir().expect("test process should have a cwd"))
|
|
.await
|
|
.expect("local sandbox should be created"),
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn host_command_hook_uses_host_workdir_not_sandbox_workdir() {
|
|
let host_work_dir =
|
|
std::env::temp_dir().join(format!("fabro-host-hook-cwd-{}", std::process::id()));
|
|
let _ = fs::remove_dir_all(&host_work_dir).await;
|
|
fs::create_dir_all(&host_work_dir)
|
|
.await
|
|
.expect("test should create host hook cwd");
|
|
let container_only_work_dir = Path::new("/workspace/fabro-host-hook-repro-missing");
|
|
assert!(
|
|
!container_only_work_dir.exists(),
|
|
"reproduction requires a container-only cwd that does not exist on the host"
|
|
);
|
|
|
|
let runner = HookRunner::new(
|
|
HookSettings {
|
|
hooks: vec![HookDefinition {
|
|
name: Some("host-marker".to_string()),
|
|
event: HookEvent::RunStart,
|
|
command: Some(InterpString::parse("printf ran > marker.txt")),
|
|
hook_type: None,
|
|
matcher: None,
|
|
blocking: Some(true),
|
|
timeout_ms: Some(5000),
|
|
sandbox: Some(false),
|
|
}],
|
|
},
|
|
test_llm_source(),
|
|
test_catalog(),
|
|
);
|
|
let context = HookContext::new(
|
|
HookEvent::RunStart,
|
|
RunId::new(),
|
|
"host-hook-cwd".to_string(),
|
|
);
|
|
|
|
let decision = runner
|
|
.run(&context, test_sandbox().await, HookExecutionContext {
|
|
host_source_dir: Some(host_work_dir.clone()),
|
|
sandbox_work_dir: Some(container_only_work_dir.to_path_buf()),
|
|
})
|
|
.await;
|
|
|
|
assert_eq!(decision, HookDecision::Proceed);
|
|
assert_eq!(
|
|
fs::read_to_string(host_work_dir.join("marker.txt"))
|
|
.await
|
|
.expect("host hook should create marker file"),
|
|
"ran"
|
|
);
|
|
fs::remove_dir_all(&host_work_dir)
|
|
.await
|
|
.expect("test should clean up host hook cwd");
|
|
}
|