diff --git a/apps/fabro-web/app/lib/mutations.ts b/apps/fabro-web/app/lib/mutations.ts
index bb6f706c3..b85ecfacd 100644
--- a/apps/fabro-web/app/lib/mutations.ts
+++ b/apps/fabro-web/app/lib/mutations.ts
@@ -172,4 +172,4 @@ export function useLoginDevToken() {
return response.json() as Promise<{ ok: boolean }>;
},
);
-}
\ No newline at end of file
+}
diff --git a/apps/fabro-web/app/lib/query-keys.ts b/apps/fabro-web/app/lib/query-keys.ts
index 47c3c676f..439e297f5 100644
--- a/apps/fabro-web/app/lib/query-keys.ts
+++ b/apps/fabro-web/app/lib/query-keys.ts
@@ -76,4 +76,4 @@ export const queryKeys = {
settings: {
server: () => "/api/v1/settings",
},
-};
\ No newline at end of file
+};
diff --git a/apps/fabro-web/app/lib/run-events.ts b/apps/fabro-web/app/lib/run-events.ts
index 2884f1eee..981d1cc0c 100644
--- a/apps/fabro-web/app/lib/run-events.ts
+++ b/apps/fabro-web/app/lib/run-events.ts
@@ -41,8 +41,11 @@ const INTERVIEW_EVENTS = new Set([
"interview.interrupted",
]);
const STEERING_EVENTS = new Set([
- "steering.received",
- "steering.applied",
+ "agent.steering.injected",
+ "agent.steering.attached",
+ "agent.steering.detached",
+ "agent.steer.buffered",
+ "agent.steer.dropped",
]);
export function queryKeysForRunEvent(
@@ -153,4 +156,4 @@ export function useRunEvents(runId: string | undefined) {
if (!runId) return;
return subscribeToRunEvents(runId, mutate as MutateFn);
}, [mutate, runId]);
-}
\ No newline at end of file
+}
diff --git a/apps/fabro-web/app/routes/runs.tsx b/apps/fabro-web/app/routes/runs.tsx
index b83b7e1c3..0d0aa5720 100644
--- a/apps/fabro-web/app/routes/runs.tsx
+++ b/apps/fabro-web/app/routes/runs.tsx
@@ -794,7 +794,7 @@ export default function Runs() {
{filteredColumns.map((col) => (
-
+
))}
@@ -869,6 +869,9 @@ export default function Runs() {
>
)}
+ {steerRunId && (
+ setSteerRunId(null)} />
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml
index 22ad033ae..c5e8908c0 100644
--- a/docs/public/api-reference/fabro-api.yaml
+++ b/docs/public/api-reference/fabro-api.yaml
@@ -8395,4 +8395,4 @@ components:
login:
type: string
description: User's login identifier (e.g. GitHub username).
- example: octocat
\ No newline at end of file
+ example: octocat
diff --git a/lib/crates/fabro-agent/src/lib.rs b/lib/crates/fabro-agent/src/lib.rs
index b5d60da27..d3a71e352 100644
--- a/lib/crates/fabro-agent/src/lib.rs
+++ b/lib/crates/fabro-agent/src/lib.rs
@@ -62,4 +62,4 @@ pub use types::{AgentEvent, CompletionCoordinator, SessionEvent, SessionState, T
unreachable_pub,
reason = "Test support stays crate-visible for cross-module unit tests."
)]
-pub(crate) mod test_support;
\ No newline at end of file
+pub(crate) mod test_support;
diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs
index c99e65204..40c59d394 100644
--- a/lib/crates/fabro-agent/src/session.rs
+++ b/lib/crates/fabro-agent/src/session.rs
@@ -422,22 +422,12 @@ impl Session {
}
pub fn steer(&self, message: String) {
- self.steering_queue
- .lock()
- .expect("steering queue lock poisoned")
- .push_back((message, SteerKind::Append, None));
+ self.control_handle().steer(message, None);
}
/// Push an interrupt steer and cancel the current round token.
pub fn interrupt_with(&self, text: String, actor: Option) {
- self.steering_queue
- .lock()
- .expect("steering queue lock poisoned")
- .push_back((text, SteerKind::Interrupt, actor));
- self.round_token
- .read()
- .expect("round token lock poisoned")
- .cancel();
+ self.control_handle().interrupt_with(text, actor);
}
/// Returns a lightweight handle for external steering control.
@@ -734,16 +724,11 @@ impl Session {
// Reset round token if it was cancelled (steer interrupt)
{
- let needs_reset = self
+ let mut guard = self
.round_token
- .read()
- .expect("round token lock poisoned")
- .is_cancelled();
- if needs_reset {
- let mut guard = self
- .round_token
- .write()
- .expect("round token lock poisoned");
+ .write()
+ .expect("round token lock poisoned");
+ if guard.is_cancelled() {
*guard = CancellationToken::new();
}
}
@@ -1079,10 +1064,9 @@ impl Session {
.expect("steering queue lock poisoned")
.drain(..)
.collect();
- for (msg, kind, actor) in messages {
- let text = msg.clone();
+ for (text, kind, actor) in messages {
self.history.push(Turn::Steering {
- content: msg,
+ content: text.clone(),
timestamp: SystemTime::now(),
});
self.event_emitter
diff --git a/lib/crates/fabro-agent/src/types.rs b/lib/crates/fabro-agent/src/types.rs
index 7c7562a00..67187784f 100644
--- a/lib/crates/fabro-agent/src/types.rs
+++ b/lib/crates/fabro-agent/src/types.rs
@@ -803,4 +803,4 @@ mod tests {
_ => panic!("expected McpServerFailed variant"),
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-agent/tests/it/parity_matrix.rs b/lib/crates/fabro-agent/tests/it/parity_matrix.rs
index 4c5792def..3c9aa043c 100644
--- a/lib/crates/fabro-agent/tests/it/parity_matrix.rs
+++ b/lib/crates/fabro-agent/tests/it/parity_matrix.rs
@@ -1024,4 +1024,4 @@ async fn scenario_provider_specific_editing(session: &mut Session, dir: &Path) {
content.contains("goodbye"),
"Expected 'goodbye' in target.rs, got: {content}"
);
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs
index 2aefb65b6..e9e7bc661 100644
--- a/lib/crates/fabro-cli/src/args.rs
+++ b/lib/crates/fabro-cli/src/args.rs
@@ -1556,4 +1556,4 @@ pub(crate) enum ProviderCommand {
pub(crate) struct CompletionArgs {
/// Shell to generate completions for
pub shell: clap_complete::Shell,
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-cli/src/commands/mod.rs b/lib/crates/fabro-cli/src/commands/mod.rs
index 513eee73b..323e10d8a 100644
--- a/lib/crates/fabro-cli/src/commands/mod.rs
+++ b/lib/crates/fabro-cli/src/commands/mod.rs
@@ -24,4 +24,4 @@ pub(crate) mod uninstall;
pub(crate) mod upgrade;
pub(crate) mod validate;
pub(crate) mod version;
-pub(crate) mod workflow;
\ No newline at end of file
+pub(crate) mod workflow;
diff --git a/lib/crates/fabro-cli/src/commands/run/runner.rs b/lib/crates/fabro-cli/src/commands/run/runner.rs
index 1f200b182..a82afbcaf 100644
--- a/lib/crates/fabro-cli/src/commands/run/runner.rs
+++ b/lib/crates/fabro-cli/src/commands/run/runner.rs
@@ -975,4 +975,4 @@ mod tests {
assert!(credential.contains("vault-key"));
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-cli/src/commands/steer.rs b/lib/crates/fabro-cli/src/commands/steer.rs
index 5ed0a49e7..c378c9d13 100644
--- a/lib/crates/fabro-cli/src/commands/steer.rs
+++ b/lib/crates/fabro-cli/src/commands/steer.rs
@@ -34,4 +34,4 @@ pub(crate) async fn execute(args: SteerArgs) -> Result<()> {
println!("Steer delivered.");
Ok(())
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-cli/src/main.rs b/lib/crates/fabro-cli/src/main.rs
index b2ca29137..012cb0e2c 100644
--- a/lib/crates/fabro-cli/src/main.rs
+++ b/lib/crates/fabro-cli/src/main.rs
@@ -1324,4 +1324,4 @@ destination = "{destination}"
"should fail when both --quiet and --verbose"
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-client/src/client.rs b/lib/crates/fabro-client/src/client.rs
index 648cd53ef..db8a3042e 100644
--- a/lib/crates/fabro-client/src/client.rs
+++ b/lib/crates/fabro-client/src/client.rs
@@ -1950,4 +1950,4 @@ mod tests {
"hint should only fire on unstructured 404s, got: {message}"
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-interview/src/control_protocol.rs b/lib/crates/fabro-interview/src/control_protocol.rs
index 03ff72d97..d10624757 100644
--- a/lib/crates/fabro-interview/src/control_protocol.rs
+++ b/lib/crates/fabro-interview/src/control_protocol.rs
@@ -175,4 +175,4 @@ mod tests {
assert!(json.contains(r#""type":"run.steer""#));
assert!(json.contains(r#""kind":"interrupt""#));
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-interview/src/lib.rs b/lib/crates/fabro-interview/src/lib.rs
index 214432377..8c1eded8e 100644
--- a/lib/crates/fabro-interview/src/lib.rs
+++ b/lib/crates/fabro-interview/src/lib.rs
@@ -415,4 +415,4 @@ mod tests {
let answer = ask.await.unwrap().answer;
assert_eq!(answer.value, AnswerValue::Yes);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs
index 43b9a1426..b3732d826 100644
--- a/lib/crates/fabro-server/src/server.rs
+++ b/lib/crates/fabro-server/src/server.rs
@@ -3241,4 +3241,4 @@ async fn reject_if_archived(state: &AppState, run_id: &RunId) -> Option EventBody {
AgentEvent::SteeringInjected { text, kind, .. } => {
EventBody::AgentSteeringInjected(fabro_types::AgentSteeringInjectedProps {
text: text.clone(),
- kind: match kind {
- ::fabro_types::SteerKind::Append => "append".to_string(),
- ::fabro_types::SteerKind::Interrupt => "interrupt".to_string(),
- },
+ kind: kind.to_string(),
visit: *visit,
})
}
@@ -720,10 +717,7 @@ fn event_body_from_event(event: &Event) -> EventBody {
}
Event::SteerBuffered { kind, .. } => {
EventBody::AgentSteerBuffered(fabro_types::AgentSteerBufferedProps {
- kind: match kind {
- ::fabro_types::SteerKind::Append => "append".to_string(),
- ::fabro_types::SteerKind::Interrupt => "interrupt".to_string(),
- },
+ kind: kind.to_string(),
})
}
Event::SteerDropped { count, reason, .. } => {
@@ -1881,4 +1875,4 @@ mod tests {
let actor = stored.actor.as_ref().expect("actor set");
assert_eq!(actor, &user_principal("alice"));
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/event/events.rs b/lib/crates/fabro-workflow/src/event/events.rs
index e888f56a4..ba8b16e8c 100644
--- a/lib/crates/fabro-workflow/src/event/events.rs
+++ b/lib/crates/fabro-workflow/src/event/events.rs
@@ -1397,4 +1397,4 @@ impl Event {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/event/names.rs b/lib/crates/fabro-workflow/src/event/names.rs
index 28658c860..62160586b 100644
--- a/lib/crates/fabro-workflow/src/event/names.rs
+++ b/lib/crates/fabro-workflow/src/event/names.rs
@@ -192,4 +192,4 @@ mod tests {
"run.unarchived"
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/event/stored_fields.rs b/lib/crates/fabro-workflow/src/event/stored_fields.rs
index ad75501c2..fe581db0f 100644
--- a/lib/crates/fabro-workflow/src/event/stored_fields.rs
+++ b/lib/crates/fabro-workflow/src/event/stored_fields.rs
@@ -225,4 +225,4 @@ fn agent_actor_for_event(
AgentEvent::SteeringInjected { actor, .. } => actor.clone(),
_ => None,
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/handler/llm/api.rs b/lib/crates/fabro-workflow/src/handler/llm/api.rs
index 0348a15a5..09ed49707 100644
--- a/lib/crates/fabro-workflow/src/handler/llm/api.rs
+++ b/lib/crates/fabro-workflow/src/handler/llm/api.rs
@@ -504,14 +504,7 @@ impl CodergenBackend for AgentApiBackend {
// Register with steering hub for steer delivery
let stage_id = stage_scope.stage_id();
let _steering_guard = if let Some(ref hub) = self.steering_hub {
- let control_handle = session.control_handle();
- hub.register(&stage_id, &control_handle);
- let coordinator = SteeringCompletionCoordinator {
- hub: Arc::clone(hub),
- stage_id: stage_id.clone(),
- handle: control_handle,
- };
- session.set_completion_coordinator(Arc::new(coordinator));
+ register_session_steering(hub, &stage_id, &mut session);
Some(SteeringGuard {
hub: Arc::clone(hub),
stage_id: stage_id.clone(),
@@ -575,14 +568,7 @@ impl CodergenBackend for AgentApiBackend {
// Re-register with steering hub for the new session
if let Some(ref hub) = self.steering_hub {
- let control_handle = session.control_handle();
- hub.register(&stage_id, &control_handle);
- let coordinator = SteeringCompletionCoordinator {
- hub: Arc::clone(hub),
- stage_id: stage_id.clone(),
- handle: control_handle,
- };
- session.set_completion_coordinator(Arc::new(coordinator));
+ register_session_steering(hub, &stage_id, &mut session);
}
// Re-subscribe to forward events + track files from the new session
@@ -677,6 +663,23 @@ impl CodergenBackend for AgentApiBackend {
}
}
+/// Register a session with the steering hub, setting up the completion
+/// coordinator so the close-the-door protocol works.
+fn register_session_steering(
+ hub: &Arc,
+ stage_id: &StageId,
+ session: &mut Session,
+) {
+ let control_handle = session.control_handle();
+ hub.register(stage_id, &control_handle);
+ let coordinator = SteeringCompletionCoordinator {
+ hub: Arc::clone(hub),
+ stage_id: stage_id.clone(),
+ handle: control_handle,
+ };
+ session.set_completion_coordinator(Arc::new(coordinator));
+}
+
/// RAII guard that unregisters a session from the steering hub on drop.
struct SteeringGuard {
hub: Arc,
diff --git a/lib/crates/fabro-workflow/src/lib.rs b/lib/crates/fabro-workflow/src/lib.rs
index 571489401..4e84d35c6 100644
--- a/lib/crates/fabro-workflow/src/lib.rs
+++ b/lib/crates/fabro-workflow/src/lib.rs
@@ -158,4 +158,4 @@ pub mod steering_hub;
pub mod test_support;
#[doc(hidden)]
pub mod transforms;
-pub mod workflow_bundle;
\ No newline at end of file
+pub mod workflow_bundle;
diff --git a/lib/crates/fabro-workflow/src/lifecycle/git.rs b/lib/crates/fabro-workflow/src/lifecycle/git.rs
index 23b2e8d9d..e8f3e2ef4 100644
--- a/lib/crates/fabro-workflow/src/lifecycle/git.rs
+++ b/lib/crates/fabro-workflow/src/lifecycle/git.rs
@@ -1063,4 +1063,4 @@ mod tests {
Ok(None)
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs
index a9da2a9fa..0e062d176 100644
--- a/lib/crates/fabro-workflow/src/operations/start.rs
+++ b/lib/crates/fabro-workflow/src/operations/start.rs
@@ -1498,4 +1498,4 @@ mod tests {
result = result.as_ref().map(|_| "Ok"),
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs
index 9bb7bb34e..d8a04e65c 100644
--- a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs
@@ -1163,4 +1163,4 @@ async fn run_with_lifecycle_emits_initialize_and_setup_events() {
assert!(sandbox_idx < setup_idx);
assert!(setup_idx < run_started_idx);
assert!(run_started_idx < run_running_idx);
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/pipeline/finalize.rs b/lib/crates/fabro-workflow/src/pipeline/finalize.rs
index 8ff179374..9e04846c9 100644
--- a/lib/crates/fabro-workflow/src/pipeline/finalize.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/finalize.rs
@@ -1089,4 +1089,4 @@ mod tests {
Ok(None)
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs
index 73db715a0..10fc07527 100644
--- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs
@@ -1388,4 +1388,4 @@ mod tests {
assert!(matches!(result, Err(Error::Cancelled)));
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/pipeline/retro.rs b/lib/crates/fabro-workflow/src/pipeline/retro.rs
index e9d94a98c..f2646a959 100644
--- a/lib/crates/fabro-workflow/src/pipeline/retro.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/retro.rs
@@ -444,4 +444,4 @@ mod tests {
assert!(retro_completed_properties.get("retro").is_some());
assert_eq!(retro_completed_properties["retro"]["smoothness"], "smooth");
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/pipeline/types.rs b/lib/crates/fabro-workflow/src/pipeline/types.rs
index def89daf2..b4baa16ea 100644
--- a/lib/crates/fabro-workflow/src/pipeline/types.rs
+++ b/lib/crates/fabro-workflow/src/pipeline/types.rs
@@ -352,4 +352,4 @@ pub struct PullRequestOptions {
pub github_app: Option,
pub origin_url: Option,
pub model: String,
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/services.rs b/lib/crates/fabro-workflow/src/services.rs
index dcbe57131..f3e640c99 100644
--- a/lib/crates/fabro-workflow/src/services.rs
+++ b/lib/crates/fabro-workflow/src/services.rs
@@ -277,4 +277,4 @@ mod tests {
.is_empty()
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/steering_hub.rs b/lib/crates/fabro-workflow/src/steering_hub.rs
index 5c9e77ef7..6dc91e267 100644
--- a/lib/crates/fabro-workflow/src/steering_hub.rs
+++ b/lib/crates/fabro-workflow/src/steering_hub.rs
@@ -259,4 +259,4 @@ mod tests {
assert_eq!(q.len(), 1);
assert_eq!(q[0].1, SteerKind::Interrupt);
}
-}
\ No newline at end of file
+}
diff --git a/lib/crates/fabro-workflow/src/test_support.rs b/lib/crates/fabro-workflow/src/test_support.rs
index 3148600bc..1d44945ae 100644
--- a/lib/crates/fabro-workflow/src/test_support.rs
+++ b/lib/crates/fabro-workflow/src/test_support.rs
@@ -523,4 +523,4 @@ impl WorkflowRunner {
))
.await
}
-}
\ No newline at end of file
+}