Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:08:33.413Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

omega_effectd.rs

758 lines · 30.1 KB · rust
1//! Omega Rust supervisor for packaged `@openagentsinc/omega-effectd`.
2//!
3//! Authority: OpenAgentsInc/omega#21 (`OMEGA-FA-02`).
4//! Durable run truth stays in omega-effectd on disk. GPUI is not run authority.
5
6mod issue31_nostr;
7mod issue31_provider_handoff;
8mod nostr_websocket_relay;
9mod openagents_binding;
10mod openagents_session;
11mod protocol;
12mod sarah_conversation;
13mod supervisor;
14
15use std::{rc::Rc, sync::Arc};
16
17use anyhow::{Result, anyhow};
18use gpui::{App, Global};
19use smol::lock::Mutex as AsyncMutex;
20
21pub use openagents_binding::{
22    BINDING_RECORD_SCHEMA, BindingEvent, BindingProjection, BindingState,
23    OPENAGENTS_BINDING_CREDENTIAL_KEY, OPENAGENTS_OMEGA_CLIENT_ID, OWNER_SCOPE_REFUSED_MESSAGE,
24    OpenAgentsBinding, apply_binding_transition, binding_record_path, default_binding_data_root,
25    init_openagents_binding, openagents_binding, try_openagents_binding,
26};
27
28pub use issue31_nostr::*;
29pub use issue31_provider_handoff::*;
30pub use nostr_websocket_relay::WebSocketRelayAdapter;
31pub use openagents_session::{
32    OpenAgentsSession, OpenAgentsSessionPhase, VerifiedOpenAgentsSession, init_openagents_session,
33    openagents_session,
34};
35
36pub use protocol::{
37    HealthResult, HostMethod, HostRequestFrame, HostResponseError, HostResponseErrorCode,
38    HostResponseFrame, InitializeResult, PROTOCOL_SCHEMA, PROTOCOL_VERSION, ProtocolError,
39    ProtocolErrorCode, RunSnapshot, SERVICE_VERSION,
40};
41pub use sarah_conversation::{
42    BootstrapResult, ConversationIdentity, GapState, InterruptTurnResult, MockRelayAdapter,
43    RelayTransport, RoomSnapshotResult, RoomStateEvent, SARAH_EVENT_ROOM_EVENT,
44    SARAH_EVENT_ROOM_STATE, SARAH_FRAMED_METHODS, SARAH_METHOD_BOOTSTRAP,
45    SARAH_METHOD_DEVICE_GRANTS, SARAH_METHOD_INTERRUPT_TURN, SARAH_METHOD_READMIT_DEVICE,
46    SARAH_METHOD_RENEW_DEVICE_GRANT, SARAH_METHOD_REVOKE_DEVICE_GRANT,
47    SARAH_METHOD_ROOM_SNAPSHOT, SARAH_METHOD_SEND_MESSAGE,
48    SARAH_METHOD_SESSION_STATUS, Issue31HostProjectionDocuments, Issue31HostProjectionRequest,
49    Issue31HostProjectionSource, Issue31ProviderRosterSource,
50    SarahConversationClient, SarahConversationConfig,
51    SarahConversationError, SendMessageResult, SessionStatusResult, SigningIdentity,
52    asserts_no_khala_sync_client,
53};
54pub use supervisor::{
55    AttentionDecision, MAX_FRAME_BYTES, OmegaEffectdCommand, OmegaEffectdHostFuture,
56    OmegaEffectdHostHandler, OmegaEffectdSupervisor, OmegaEffectdSupervisorOptions,
57    SupervisorError, default_options, fixture_command, resolve_effectd_command,
58};
59
60pub type SharedOmegaEffectdSupervisor = Rc<AsyncMutex<OmegaEffectdSupervisor>>;
61
62enum OmegaEffectdRuntime {
63    Available(SharedOmegaEffectdSupervisor),
64    Unavailable(Arc<str>),
65}
66
67impl Global for OmegaEffectdRuntime {}
68
69pub fn init(cx: &mut App) {
70    init_with_host_handler(None, cx);
71}
72
73pub fn init_with_host_handler(handler: Option<OmegaEffectdHostHandler>, cx: &mut App) {
74    // OMEGA-SW-01: binding is independent of effectd packaging availability.
75    init_openagents_binding(cx);
76
77    if cx.has_global::<OmegaEffectdRuntime>() {
78        return;
79    }
80
81    start_served_acp_surface();
82
83    let runtime = match std::env::current_exe()
84        .map_err(anyhow::Error::from)
85        .and_then(|executable| {
86            resolve_effectd_command(
87                std::env::var_os("OPENAGENTS_OMEGA_EFFECTD_BIN").as_deref(),
88                &executable,
89            )
90        }) {
91        Ok(command) => {
92            let mut supervisor = OmegaEffectdSupervisor::new(default_options(
93                paths::data_dir().join("openagents"),
94                command,
95            ));
96            if let Some(handler) = handler {
97                supervisor.set_host_handler(handler);
98            }
99            OmegaEffectdRuntime::Available(Rc::new(AsyncMutex::new(supervisor)))
100        }
101        Err(error) => OmegaEffectdRuntime::Unavailable(error.to_string().into()),
102    };
103    cx.set_global(runtime);
104}
105
106/// `OMEGA-DELTA-0041`, omega#82. Serve Omega Agent over ACP, if the flag says
107/// so.
108///
109/// The supervisor layer owns this, not GPUI. The socket itself lives in
110/// `crates/omega_acp_server`, which depends on no part of GPUI, and this is its
111/// only production call site — `crates/omega_deltas` fails if a second one
112/// appears in a UI crate. Omega's own windows never open a listener.
113///
114/// Off unless `OMEGA_ACP_SERVER` is exactly `1`, so the shipped default binds
115/// nothing at all and this is a no-op in every normal launch.
116fn start_served_acp_surface() {
117    match omega_acp_server::start_if_enabled() {
118        omega_acp_server::StartOutcome::NotStarted(reason) => {
119            log::debug!(
120                "OMEGA-DELTA-0041: Omega Agent is not served over ACP ({})",
121                reason.token()
122            );
123        }
124        omega_acp_server::StartOutcome::Listening(address) => {
125            log::info!(
126                "OMEGA-DELTA-0041: Omega Agent is served over ACP on {address} \
127                 (loopback, unauthenticated, read-only)"
128            );
129        }
130        omega_acp_server::StartOutcome::Failed(error) => {
131            log::error!(
132                "OMEGA-DELTA-0041: the served ACP surface was asked for and \
133                 could not listen: {error}"
134            );
135        }
136    }
137}
138
139pub fn shared_supervisor(cx: &App) -> Result<SharedOmegaEffectdSupervisor> {
140    match cx.try_global::<OmegaEffectdRuntime>() {
141        Some(OmegaEffectdRuntime::Available(supervisor)) => Ok(supervisor.clone()),
142        Some(OmegaEffectdRuntime::Unavailable(message)) => Err(anyhow!(message.to_string())),
143        None => Err(anyhow!("omega-effectd runtime was not initialized")),
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::ffi::OsStr;
150    use std::path::PathBuf;
151    use std::rc::Rc;
152    use std::sync::{Arc, Mutex};
153    use std::time::Duration;
154
155    use serde_json::json;
156    use tempfile::tempdir;
157
158    use super::*;
159
160    fn fixture_path() -> PathBuf {
161        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/fake_effectd.mjs")
162    }
163
164    #[test]
165    fn missing_packaged_component_fails_closed_without_fixture_fallback() {
166        let root = tempdir().expect("tempdir");
167        let executable = root.path().join("Omega.app/Contents/MacOS/omega");
168        std::fs::create_dir_all(executable.parent().expect("executable parent"))
169            .expect("create app executable directory");
170
171        let error = resolve_effectd_command(None, &executable).expect_err("component is absent");
172        let message = error.to_string();
173        assert!(message.contains("packaged omega-effectd component is unavailable"));
174        assert!(!message.contains("fake_effectd"));
175        assert!(!message.contains("openagents/packages"));
176    }
177
178    #[test]
179    fn resolver_accepts_only_explicit_or_packaged_component_paths() {
180        let root = tempdir().expect("tempdir");
181        let executable = root.path().join("Omega.app/Contents/MacOS/omega");
182        let component = root
183            .path()
184            .join("Omega.app/Contents/Resources/omega-effectd/bin/omega-effectd");
185        std::fs::create_dir_all(component.parent().expect("component parent"))
186            .expect("create component directory");
187        std::fs::write(&component, "#!/bin/sh\n").expect("write component");
188
189        let packaged = resolve_effectd_command(None, &executable).expect("packaged component");
190        assert_eq!(packaged.program, component);
191        assert!(packaged.args.is_empty());
192
193        let explicit = root.path().join("explicit-effectd");
194        std::fs::write(&explicit, "#!/bin/sh\n").expect("write explicit component");
195        let overridden = resolve_effectd_command(Some(explicit.as_os_str()), &executable)
196            .expect("explicit component");
197        assert_eq!(overridden.program, explicit);
198
199        let missing = root.path().join("missing-effectd");
200        assert!(
201            resolve_effectd_command(Some(OsStr::new(&missing)), &executable).is_err(),
202            "an explicit missing component must not fall back"
203        );
204    }
205
206    #[test]
207    fn oversized_response_frame_fails_closed_and_stops_child() {
208        smol::block_on(async {
209            let root = tempdir().expect("tempdir");
210            let mut command = fixture_command(&fixture_path());
211            command.args.push("--oversized-health-response".into());
212            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
213                data_root: root.path().to_path_buf(),
214                command,
215                initial_generation: 1,
216                request_timeout: Duration::from_secs(5),
217            });
218
219            supervisor.start().await.expect("start");
220            let error = supervisor.health().await.expect_err("oversized frame");
221            assert!(error.to_string().contains("response frame exceeds"));
222            let stopped = supervisor.health().await.expect_err("child was torn down");
223            assert!(stopped.to_string().contains("not started"));
224        });
225    }
226
227    #[test]
228    fn host_requests_are_multiplexed_with_matching_generation() {
229        smol::block_on(async {
230            let root = tempdir().expect("tempdir");
231            let mut command = fixture_command(&fixture_path());
232            command.args.push("--host-request-health".into());
233            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
234                data_root: root.path().to_path_buf(),
235                command,
236                initial_generation: 7,
237                request_timeout: Duration::from_secs(5),
238            });
239            let observed = Arc::new(Mutex::new(None));
240            supervisor.set_host_handler(Rc::new({
241                let observed = observed.clone();
242                move |request| {
243                    let observed = observed.clone();
244                    Box::pin(async move {
245                        *observed.lock().expect("observed request lock") = Some(request.clone());
246                        Ok(json!({ "workspaceRef": "workspace.omega.supervised" }))
247                    })
248                }
249            }));
250
251            supervisor.start().await.expect("start");
252            supervisor.health().await.expect("health");
253            let request = observed
254                .lock()
255                .expect("observed request lock")
256                .clone()
257                .expect("host request");
258            assert_eq!(request.generation, 7);
259            assert_eq!(request.method, HostMethod::ResolveWorkspace);
260        });
261    }
262
263    #[test]
264    fn missing_host_authority_returns_typed_unavailable_response() {
265        smol::block_on(async {
266            let root = tempdir().expect("tempdir");
267            let mut command = fixture_command(&fixture_path());
268            command
269                .args
270                .push("--unavailable-host-request-health".into());
271            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
272                data_root: root.path().to_path_buf(),
273                command,
274                initial_generation: 1,
275                request_timeout: Duration::from_secs(5),
276            });
277
278            supervisor.start().await.expect("start");
279            supervisor
280                .health()
281                .await
282                .expect("typed unavailable response");
283        });
284    }
285
286    #[test]
287    fn host_authority_timeout_returns_unavailable_without_parking_request() {
288        smol::block_on(async {
289            let root = tempdir().expect("tempdir");
290            let mut command = fixture_command(&fixture_path());
291            command
292                .args
293                .push("--unavailable-host-request-health".into());
294            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
295                data_root: root.path().to_path_buf(),
296                command,
297                initial_generation: 1,
298                request_timeout: Duration::from_secs(5),
299            });
300            supervisor.set_host_request_timeout(Duration::from_millis(10));
301            supervisor.set_host_handler(Rc::new(|_| {
302                Box::pin(futures::future::pending::<
303                    std::result::Result<serde_json::Value, HostResponseError>,
304                >())
305            }));
306
307            supervisor.start().await.expect("start");
308            supervisor.health().await.expect("host timeout response");
309        });
310    }
311
312    #[test]
313    fn stale_host_request_gets_generation_matched_rejection() {
314        smol::block_on(async {
315            let root = tempdir().expect("tempdir");
316            let mut command = fixture_command(&fixture_path());
317            command.args.push("--stale-host-request-health".into());
318            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
319                data_root: root.path().to_path_buf(),
320                command,
321                initial_generation: 2,
322                request_timeout: Duration::from_secs(5),
323            });
324
325            supervisor.start().await.expect("start");
326            supervisor.health().await.expect("stale host rejection");
327        });
328    }
329
330    #[test]
331    fn stale_service_response_fails_closed_and_stops_child() {
332        smol::block_on(async {
333            let root = tempdir().expect("tempdir");
334            let mut command = fixture_command(&fixture_path());
335            command.args.push("--stale-health-response".into());
336            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
337                data_root: root.path().to_path_buf(),
338                command,
339                initial_generation: 2,
340                request_timeout: Duration::from_secs(5),
341            });
342
343            supervisor.start().await.expect("start");
344            let error = supervisor
345                .health()
346                .await
347                .expect_err("stale service response");
348            assert!(error.to_string().contains("stale generation"));
349            assert!(supervisor.health().await.is_err(), "child must be stopped");
350        });
351    }
352
353    #[test]
354    fn oversized_host_response_fails_closed_and_stops_child() {
355        smol::block_on(async {
356            let root = tempdir().expect("tempdir");
357            let mut command = fixture_command(&fixture_path());
358            command.args.push("--host-request-health".into());
359            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
360                data_root: root.path().to_path_buf(),
361                command,
362                initial_generation: 1,
363                request_timeout: Duration::from_secs(5),
364            });
365            supervisor.set_host_handler(Rc::new(|_| {
366                Box::pin(async {
367                    Ok(json!({
368                        "workspaceRef": "x".repeat(MAX_FRAME_BYTES),
369                    }))
370                })
371            }));
372
373            supervisor.start().await.expect("start");
374            let error = supervisor
375                .health()
376                .await
377                .expect_err("oversized host response");
378            assert!(error.to_string().contains("host response frame exceeds"));
379            assert!(supervisor.health().await.is_err(), "child must be stopped");
380        });
381    }
382
383    #[test]
384    fn start_health_restart_stop_and_generation_fence() {
385        smol::block_on(async {
386            let root = tempdir().unwrap();
387            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
388                data_root: root.path().to_path_buf(),
389                command: fixture_command(&fixture_path()),
390                initial_generation: 1,
391                request_timeout: Duration::from_secs(5),
392            });
393
394            let init = supervisor.start().await.expect("start");
395            assert_eq!(init.generation, 1);
396            assert_eq!(init.schema, PROTOCOL_SCHEMA);
397
398            let health = supervisor.health().await.expect("health");
399            assert_eq!(health.status, "running");
400            assert_eq!(health.generation, 1);
401
402            // Persist a run through the fixture file API, then restart.
403            let runs_path = root.path().join("full-auto").join("runs.json");
404            std::fs::create_dir_all(runs_path.parent().unwrap()).unwrap();
405            std::fs::write(
406                &runs_path,
407                serde_json::to_string_pretty(&json!({
408                    "schema": "openagents.desktop.full_auto_run_registry.v1",
409                    "runs": [{
410                        "runRef": "run.full-auto.recovery",
411                        "threadRef": null,
412                        "title": "Recovery proof",
413                        "state": "paused",
414                        "updatedAt": "2026-07-24T00:00:00.000Z"
415                    }]
416                }))
417                .unwrap(),
418            )
419            .unwrap();
420
421            let restarted = supervisor.restart().await.expect("restart");
422            assert_eq!(restarted.generation, 2);
423
424            let runs = supervisor.list_runs().await.expect("list after restart");
425            assert!(
426                runs.iter().any(|run| {
427                    run.run_ref == "run.full-auto.recovery" && run.title == "Recovery proof"
428                }),
429                "durable disk truth must survive restart: {runs:?}"
430            );
431
432            // Stale generation must be refused by the child; supervisor tracks current gen.
433            assert_eq!(supervisor.generation(), 2);
434            supervisor.stop().await.expect("stop");
435        });
436    }
437
438    #[test]
439    fn fa07_control_matrix_and_native_join_survive_restart() {
440        smol::block_on(async {
441            let root = tempdir().unwrap();
442            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
443                data_root: root.path().to_path_buf(),
444                command: fixture_command(&fixture_path()),
445                initial_generation: 1,
446                request_timeout: Duration::from_secs(5),
447            });
448
449            supervisor.start().await.expect("start");
450            let started = supervisor
451                .start_run(json!({
452                    "workspaceRef": "workspace.omega.supervised",
453                    "title": "FA-07 control matrix",
454                    "objective": "Prove pause resume stop and native join.",
455                    "doneCondition": "Controls complete.",
456                    "turnCap": 8,
457                    "projectRef": "project.fa07",
458                    "worktreeRef": "worktree.fa07",
459                    "gitHead": "deadbeef"
460                }))
461                .await
462                .expect("start_run");
463            let run_ref = started
464                .get("runRef")
465                .and_then(|v| v.as_str())
466                .expect("runRef")
467                .to_string();
468            assert_eq!(
469                started
470                    .pointer("/nativeEvidence/projectRef")
471                    .and_then(|v| v.as_str()),
472                Some("project.fa07")
473            );
474
475            let paused = supervisor.pause_run(&run_ref).await.expect("pause");
476            assert_eq!(paused.get("state").and_then(|v| v.as_str()), Some("paused"));
477            let handed_off = supervisor
478                .handoff_run(&run_ref, "claude-local")
479                .await
480                .expect("handoff");
481            assert_eq!(
482                handed_off.get("lane").and_then(|v| v.as_str()),
483                Some("claude-local")
484            );
485            let resumed = supervisor.resume_run(&run_ref).await.expect("resume");
486            assert_eq!(
487                resumed.get("state").and_then(|v| v.as_str()),
488                Some("running")
489            );
490
491            let binding = supervisor
492                .get_native_binding(&run_ref)
493                .await
494                .expect("binding");
495            assert_eq!(
496                binding.get("projectRef").and_then(|v| v.as_str()),
497                Some("project.fa07")
498            );
499            let assessment = supervisor
500                .assess_native_boundary(&run_ref)
501                .await
502                .expect("assessment");
503            assert_eq!(assessment.get("ok").and_then(|v| v.as_bool()), Some(true));
504
505            let sync = supervisor.get_sync_status().await.expect("sync");
506            assert_eq!(
507                sync.get("publishBlocksDispatch").and_then(|v| v.as_bool()),
508                Some(false)
509            );
510
511            supervisor.restart().await.expect("restart");
512            let after = supervisor
513                .get_run(&run_ref)
514                .await
515                .expect("get after restart");
516            assert_eq!(
517                after.get("runRef").and_then(|v| v.as_str()),
518                Some(run_ref.as_str())
519            );
520            assert_eq!(
521                after
522                    .pointer("/nativeEvidence/worktreeRef")
523                    .and_then(|v| v.as_str()),
524                Some("worktree.fa07")
525            );
526
527            let stopped = supervisor.stop_run(&run_ref).await.expect("stop");
528            assert_eq!(
529                stopped.get("state").and_then(|v| v.as_str()),
530                Some("stopped")
531            );
532            supervisor.stop().await.expect("supervisor stop");
533        });
534    }
535
536    #[test]
537    fn ac01_agent_computer_session_survives_restart_without_bearer() {
538        smol::block_on(async {
539            let root = tempdir().unwrap();
540            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
541                data_root: root.path().to_path_buf(),
542                command: fixture_command(&fixture_path()),
543                initial_generation: 1,
544                request_timeout: Duration::from_secs(5),
545            });
546
547            supervisor.start().await.expect("start");
548            let session = supervisor
549                .start_agent_computer_session(json!({
550                    "bearerToken": "secret-fixture-token",
551                    "controlPlaneBaseUrl": "https://openagents.com",
552                    "repoRef": "OpenAgentsInc/openagents",
553                    "objective": "Fixture Agent Computer launch",
554                    "adapter": "codex",
555                    "lane": "cloud-gcp",
556                }))
557                .await
558                .expect("start agent computer");
559            let session_ref = session
560                .get("sessionRef")
561                .and_then(|v| v.as_str())
562                .expect("sessionRef")
563                .to_string();
564            assert_eq!(
565                session.get("environment").and_then(|v| v.as_str()),
566                Some("openagents_cloud")
567            );
568
569            let refreshed = supervisor
570                .refresh_agent_computer_session("secret-fixture-token", &session_ref)
571                .await
572                .expect("refresh");
573            assert_eq!(
574                refreshed.get("state").and_then(|v| v.as_str()),
575                Some("running")
576            );
577
578            let turn = supervisor
579                .run_agent_computer_turn(json!({
580                    "bearerToken": "secret-fixture-token",
581                    "controlPlaneBaseUrl": "https://openagents.com",
582                    "repoRef": "OpenAgentsInc/openagents",
583                    "objective": "Fixture Agent Computer turn",
584                }))
585                .await
586                .expect("run turn");
587            assert_eq!(
588                turn.get("finishReason").and_then(|v| v.as_str()),
589                Some("stop")
590            );
591            assert_eq!(
592                turn.pointer("/session/state").and_then(|v| v.as_str()),
593                Some("completed")
594            );
595
596            supervisor.restart().await.expect("restart");
597            let listed = supervisor
598                .list_agent_computer_sessions()
599                .await
600                .expect("list after restart");
601            let sessions = listed
602                .get("sessions")
603                .and_then(|v| v.as_array())
604                .expect("sessions array");
605            assert!(
606                sessions.iter().any(|row| {
607                    row.get("sessionRef").and_then(|v| v.as_str()) == Some(session_ref.as_str())
608                }),
609                "agent computer session must survive restart: {listed}"
610            );
611
612            let disk =
613                std::fs::read_to_string(root.path().join("agent-computer").join("sessions.json"))
614                    .expect("sessions disk");
615            assert!(!disk.contains("secret-fixture-token"));
616            assert!(!disk.contains("Fixture Agent Computer"));
617
618            supervisor.stop().await.expect("supervisor stop");
619        });
620    }
621
622    #[test]
623    fn sarah_nr06_conversation_methods_via_fixture() {
624        smol::block_on(async {
625            let root = tempdir().expect("tempdir");
626            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
627                data_root: root.path().to_path_buf(),
628                command: fixture_command(&fixture_path()),
629                initial_generation: 1,
630                request_timeout: Duration::from_secs(5),
631            });
632            supervisor.start().await.expect("start");
633
634            let status = supervisor
635                .sarah_session_status()
636                .await
637                .expect("session status");
638            assert_eq!(status.get("signedIn").and_then(|v| v.as_bool()), Some(true));
639            let encoded = status.to_string();
640            assert!(!encoded.contains("token"));
641            assert!(!encoded.contains("bearer"));
642
643            let boot = supervisor.sarah_bootstrap().await.expect("bootstrap");
644            assert_eq!(
645                boot.get("principalRef").and_then(|v| v.as_str()),
646                Some("principal.sarah")
647            );
648            let conversation_ref = boot
649                .get("conversationRef")
650                .and_then(|v| v.as_str())
651                .expect("conversationRef")
652                .to_string();
653            assert!(conversation_ref.starts_with("sarah."));
654
655            let sent = supervisor
656                .sarah_send_message("hello from fixture", "idempotency.fixture.send.1")
657                .await
658                .expect("send");
659            assert_eq!(sent.get("accepted").and_then(|v| v.as_bool()), Some(true));
660            let turn_ref = sent
661                .get("turnRef")
662                .and_then(|v| v.as_str())
663                .expect("turnRef")
664                .to_string();
665
666            let snap = supervisor
667                .sarah_room_snapshot(Some(json!({ "limit": 10 })))
668                .await
669                .expect("snapshot");
670            assert_eq!(
671                snap.get("conversationRef").and_then(|v| v.as_str()),
672                Some(conversation_ref.as_str())
673            );
674            assert!(
675                snap.pointer("/transcript/gapState")
676                    .and_then(|v| v.as_str())
677                    .is_some()
678            );
679            assert!(
680                snap.pointer("/transcript/cursor")
681                    .and_then(|v| v.as_str())
682                    .is_some()
683            );
684
685            let interrupt = supervisor
686                .sarah_interrupt_turn(&turn_ref, "idempotency.fixture.interrupt.1")
687                .await
688                .expect("interrupt");
689            assert_eq!(
690                interrupt.get("pending").and_then(|v| v.as_bool()),
691                Some(true)
692            );
693            assert_eq!(
694                interrupt.get("status").and_then(|v| v.as_str()),
695                Some("pending")
696            );
697
698            // OMEGA-SW-02 cut: no Khala Sync client on this lane.
699            assert!(asserts_no_khala_sync_client());
700            supervisor.stop().await.expect("stop");
701        });
702    }
703
704    #[test]
705    fn attention_decisions_are_typed_and_deduplicated() {
706        smol::block_on(async {
707            let root = tempdir().expect("tempdir");
708            let runs_path = root.path().join("full-auto").join("runs.json");
709            std::fs::create_dir_all(
710                runs_path
711                    .parent()
712                    .expect("runs path should have a parent directory"),
713            )
714            .expect("create runs directory");
715            std::fs::write(
716                &runs_path,
717                serde_json::to_string_pretty(&json!({
718                    "schema": "openagents.desktop.full_auto_run_registry.v1",
719                    "runs": [{
720                        "runRef": "run.full-auto.attention",
721                        "threadRef": null,
722                        "title": "SECRET_OBJECTIVE /Users/owner/private",
723                        "state": "stalled",
724                        "stallCause": "dispatch_overdue",
725                        "updatedAt": "2026-07-24T00:00:00.000Z"
726                    }]
727                }))
728                .expect("encode fixture runs"),
729            )
730            .expect("write fixture runs");
731
732            let mut supervisor = OmegaEffectdSupervisor::new(OmegaEffectdSupervisorOptions {
733                data_root: root.path().to_path_buf(),
734                command: fixture_command(&fixture_path()),
735                initial_generation: 1,
736                request_timeout: Duration::from_secs(5),
737            });
738            supervisor.start().await.expect("start");
739
740            let decision = supervisor
741                .decide_attention("run.full-auto.attention", true, None)
742                .await
743                .expect("attention decision")
744                .expect("stalled run should produce a decision");
745            assert!(decision.notify);
746            assert_eq!(decision.title, "Full Auto stalled");
747            assert!(decision.body.contains("SECRET_OBJECTIVE"));
748
749            let duplicate = supervisor
750                .decide_attention("run.full-auto.attention", true, Some(&decision.dedup_key))
751                .await
752                .expect("deduplicated attention decision");
753            assert!(duplicate.is_none());
754            supervisor.stop().await.expect("stop");
755        });
756    }
757}
758
Served at tenant.openagents/omega Member data and write actions are omitted.