Skip to content

Commit 978f58a

Browse files
committed
fix(remote): register lifecycle rollback port for mobile controllers
1 parent e485fcf commit 978f58a

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

‎src/crates/assembly/core/AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ writer ownership, unknown persisted fields, legacy metadata-only sessions, and
271271
both state/metadata projections on failure. These fixtures cover local storage
272272
and remote routing contracts, not live SSH/relay/peer/dispatch connections.
273273

274+
Remote session host rollback port wiring:
275+
276+
`cargo test --locked -p openbitfun-core --no-default-features --features remote-connect --lib remote_session_host_registers_targeted_rollback_port`.
277+
274278
Configuration persistence, account settings import, backup restore, legacy
275279
field/deletion compatibility, local-change notifications, and save/reload/model
276280
concurrency regressions have feature-free fixtures:

‎src/crates/assembly/core/src/service_agent_runtime.rs‎

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3052,7 +3052,19 @@ impl CoreRemoteSessionRuntimeHost {
30523052
pub(crate) fn new() -> Result<Self, String> {
30533053
let coordinator = get_global_coordinator()
30543054
.ok_or_else(|| "Desktop session system not ready".to_string())?;
3055-
let runtime = CoreServiceAgentRuntime::agent_runtime(coordinator.clone())?;
3055+
let scheduler = get_global_scheduler()
3056+
.ok_or_else(|| "Dialog scheduler is not initialized".to_string())?;
3057+
Self::from_owners(coordinator, scheduler)
3058+
}
3059+
3060+
fn from_owners(
3061+
coordinator: Arc<ConversationCoordinator>,
3062+
scheduler: Arc<DialogScheduler>,
3063+
) -> Result<Self, String> {
3064+
let runtime = CoreServiceAgentRuntime::agent_runtime_with_lifecycle_delivery(
3065+
coordinator.clone(),
3066+
scheduler,
3067+
)?;
30563068
Ok(Self {
30573069
coordinator,
30583070
runtime,
@@ -3921,6 +3933,89 @@ mod tests {
39213933
};
39223934
use crate::OpenBitFunError;
39233935

3936+
#[tokio::test]
3937+
async fn remote_session_host_registers_targeted_rollback_port() {
3938+
use crate::agentic::events::{EventQueue, EventQueueConfig, EventRouter};
3939+
use crate::agentic::execution::{
3940+
ExecutionEngine, ExecutionEngineConfig, RoundExecutor, StreamProcessor,
3941+
};
3942+
use crate::agentic::persistence::PersistenceManager;
3943+
use crate::agentic::session::{ContextCompressor, SessionContextStore, SessionManager};
3944+
use crate::agentic::tools::{registry::ToolRegistry, ToolPipeline, ToolStateManager};
3945+
3946+
let directory = tempfile::tempdir().expect("runtime fixture");
3947+
let paths = Arc::new(
3948+
crate::infrastructure::PathManager::with_user_root_for_tests(
3949+
directory.path().join("user-root"),
3950+
),
3951+
);
3952+
let sessions = Arc::new(SessionManager::new(
3953+
Arc::new(SessionContextStore::new()),
3954+
Arc::new(PersistenceManager::new(paths).expect("persistence manager")),
3955+
Default::default(),
3956+
));
3957+
let events = Arc::new(EventQueue::new(EventQueueConfig::default()));
3958+
let tools = Arc::new(ToolPipeline::new(
3959+
Arc::new(tokio::sync::RwLock::new(ToolRegistry::new())),
3960+
Arc::new(ToolStateManager::new(events.clone())),
3961+
None,
3962+
));
3963+
let engine = Arc::new(ExecutionEngine::new(
3964+
Arc::new(RoundExecutor::new(
3965+
Arc::new(StreamProcessor::new(events.clone())),
3966+
events.clone(),
3967+
tools.clone(),
3968+
)),
3969+
events.clone(),
3970+
sessions.clone(),
3971+
Arc::new(ContextCompressor::new()),
3972+
ExecutionEngineConfig::default(),
3973+
));
3974+
let coordinator = Arc::new(ConversationCoordinator::new(
3975+
sessions.clone(),
3976+
engine,
3977+
tools,
3978+
events,
3979+
Arc::new(EventRouter::new()),
3980+
Arc::new(
3981+
crate::runtime_ownership::CoreRuntimeOwnership::embedded_with_facts(
3982+
directory.path().join("ownership"),
3983+
"openbitfun".to_string(),
3984+
"test",
3985+
),
3986+
),
3987+
));
3988+
let scheduler = DialogScheduler::new(coordinator.clone(), sessions);
3989+
let host = CoreRemoteSessionRuntimeHost::from_owners(coordinator, scheduler)
3990+
.expect("remote session host");
3991+
3992+
// Invalid identity must reach the registered revert provider's validation.
3993+
let error = host
3994+
.runtime
3995+
.rollback_session_to_turn(AgentSessionRollbackToTurnRequest {
3996+
workspace_path: String::new(),
3997+
workspace_id: None,
3998+
workspace_hostname: None,
3999+
session_id: "../invalid-session".to_string(),
4000+
target_turn_id: "turn-1".to_string(),
4001+
require_idle: true,
4002+
expected_storage_turn_index: None,
4003+
expected_catalog_revision: None,
4004+
remote_connection_id: None,
4005+
remote_ssh_host: None,
4006+
})
4007+
.await
4008+
.expect_err("invalid session identity");
4009+
assert!(
4010+
matches!(
4011+
error,
4012+
openbitfun_agent_runtime::sdk::RuntimeError::Port(ref port)
4013+
if port.kind == openbitfun_runtime_ports::PortErrorKind::InvalidRequest
4014+
),
4015+
"expected revert provider validation, got {error}"
4016+
);
4017+
}
4018+
39244019
#[test]
39254020
fn local_workspace_marker_is_not_remote_routing_authority() {
39264021
use crate::service::workspace::WorkspaceKind;

0 commit comments

Comments
 (0)