Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:05:19.059Z 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_front_door.rs

1225 lines · 50.4 KB · rust
1//! The typed core of Omega's chat-first front door.
2//!
3//! Omega Agent is a router. It owns routing, disclosure, and receipts, and it
4//! owns no execution. This crate holds the parts of the front door that are
5//! decisions rather than pixels: where a fresh launch lands, what a thread
6//! says about the executor that did the work, and which gestures are allowed
7//! to start engine-lane authority.
8//!
9//! It is deliberately a leaf. It depends on nothing, so the laws below can be
10//! checked in a second without building GPUI, and so the router packets
11//! (omega#77, omega#78) can take this vocabulary without taking the UI crate.
12//!
13//! Product contract: `specs/omega/omega-agent.product-spec.md` revision 1 in
14//! the openagents repository, admitted by the owner on 2026-07-25.
15
16pub mod router;
17mod send_during_turn;
18
19pub use send_during_turn::{
20    QueueItemState, Quiescence, SendCommand, SendDisposition, SendFallback, SteerCapability,
21    SteerRefusal, disposition, may_promote,
22};
23
24pub use router::{
25    EngineLane, EngineReadiness, EngineUnreachable, ExecutorPin, LaneState, RESERVED_RECORD_CHARACTERS,
26    RouteDecision, RouteInputs, RouteReason, lane_ref_is_recordable, route, select_lane,
27};
28
29// -------------------------------------------------------------------------
30// Where a fresh launch lands
31// -------------------------------------------------------------------------
32
33/// The surface Omega shows when a window opens with nothing to restore.
34///
35/// Upstream Zed calls `Editor::new_file` here, so the first thing a new user
36/// meets is an empty untitled buffer. Omega's front door is the agent instead:
37/// the window opens on the New Agent Thread surface with the composer focused,
38/// and typing starts a thread. `OMEGA-DELTA-0019`.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum LaunchSurface {
41    /// The New Agent Thread surface, composer focused. Omega's front door.
42    NewAgentThread,
43    /// An empty untitled buffer. Upstream Zed's answer, kept only for the
44    /// startup behaviours that explicitly ask for a file-first window.
45    EmptyBuffer,
46    /// Nothing: the launchpad startup behaviour opens no content at all, and
47    /// overriding it would be Omega ignoring a setting the user set.
48    Nothing,
49}
50
51/// The `restore_on_startup` values that reach the no-restorable-session path.
52///
53/// Mirrors `settings::RestoreOnStartupBehavior` without depending on it. The
54/// mapping below is the whole reason this is a type and not a bool: the
55/// front door replaces the *empty buffer*, and must not replace a deliberate
56/// launchpad choice.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum RestoreOnStartup {
59    /// The user asked for the launchpad. Omega opens no content.
60    Launchpad,
61    /// Anything else. Upstream opens an empty buffer here; Omega opens the
62    /// front door.
63    Other,
64}
65
66/// Decide what a window with no restorable session opens on.
67///
68/// This is the single rule behind `OMEGA-DELTA-0019`, kept here so it can be
69/// tested without a window.
70#[must_use]
71pub fn launch_surface(restore_on_startup: RestoreOnStartup) -> LaunchSurface {
72    match restore_on_startup {
73        RestoreOnStartup::Launchpad => LaunchSurface::Nothing,
74        RestoreOnStartup::Other => LaunchSurface::NewAgentThread,
75    }
76}
77
78// -------------------------------------------------------------------------
79// Executor disclosure
80// -------------------------------------------------------------------------
81
82/// The three admitted executor classes.
83///
84/// ProductSpec `OMEGA-AGENT-AC-04` fixes the set at exactly three. A fourth
85/// class needs a new spec revision, which is why this is a closed enum and not
86/// a string.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum ExecutorClass {
89    /// The inherited native agent loop in `crates/agent`.
90    NativeLoop,
91    /// An external ACP agent reached through `crates/agent_servers`.
92    ExternalAcp,
93    /// An `omega-effectd` engine lane. Full Auto and Agent Computer.
94    EngineLane,
95}
96
97impl ExecutorClass {
98    /// The stable wire token for this class.
99    ///
100    /// Persisted and compared. Never shown to a user on its own — the user
101    /// sees [`ExecutorDisclosure::label`], which is derived.
102    #[must_use]
103    pub const fn token(self) -> &'static str {
104        match self {
105            Self::NativeLoop => "native_loop",
106            Self::ExternalAcp => "external_acp",
107            Self::EngineLane => "engine_lane",
108        }
109    }
110
111    /// Every admitted class, in declaration order.
112    #[must_use]
113    pub const fn all() -> &'static [Self] {
114        &[Self::NativeLoop, Self::ExternalAcp, Self::EngineLane]
115    }
116}
117
118/// What a thread says about the executor that did its work.
119///
120/// **This is a record, not a label.** The owner admitted the Omega Agent shape
121/// on 2026-07-25 on the recorded condition that the first-party agent does not
122/// sign with its own principal *and* that disclosure is stored as a typed
123/// record that a label renders. That condition is the only reason the identity
124/// choice stays cheap to reverse: moving to a signing principal later then
125/// needs a signer, not a rewrite of every stored thread record.
126///
127/// So [`label`](Self::label) is a function of the fields, and there is no
128/// field to put a rendered label in. omega#76 fixed this shape; omega#77
129/// populates it from live threads and renders the line.
130///
131/// omega#82 tightened how that is checked. The old check dumped the struct and
132/// failed if the word `label` appeared in it — a **denylist**, which passes for
133/// a field called `line`, `text`, `summary`, or `rendered`. The check now
134/// asserts the declared fields **exactly** against
135/// [`EXECUTOR_DISCLOSURE_FIELDS`], so any new field at all is a deliberate edit
136/// to a list that says why the shape is closed.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct ExecutorDisclosure {
139    /// Which of the three admitted classes ran the work.
140    pub class: ExecutorClass,
141    /// The executor's own identifier, as the executor reports it.
142    pub agent_id: String,
143    /// The model provider that served the turn, where the executor reports
144    /// one.
145    ///
146    /// `None` is *not disclosed*, and is different from an empty string, which
147    /// is a bug. `AcpConnection` does not implement
148    /// `AgentConnection::model_selector`, so an external ACP agent such as
149    /// `codex-acp` genuinely does not tell Omega which model served a turn.
150    /// Before omega#77 this was a `String`, which left only two options for
151    /// that case — fabricate a model, or fail [`is_coherent`](Self::is_coherent)
152    /// on every external thread. Saying "not disclosed" is the honest third.
153    pub provider: Option<String>,
154    /// The model that served the turn, where the executor reports one. See
155    /// [`provider`](Self::provider) for why this is optional.
156    pub model: Option<String>,
157    /// The engine run this thread is bound to, where one applies.
158    ///
159    /// `Some` only for [`ExecutorClass::EngineLane`]: a native or ACP turn has
160    /// no run authority to reference.
161    pub run_ref: Option<String>,
162    /// Why Omega Agent routed the thread here, where it routed the thread.
163    ///
164    /// A typed part, added by omega#78 — not a caption. `None` means this
165    /// thread was not routed by the router: a thread restored from before
166    /// `OMEGA-DELTA-0029`, or one opened directly on an executor. Saying "not
167    /// routed" is different from claiming a reason nobody recorded, and the
168    /// difference matters most for the fallback reasons, which are the ones a
169    /// user needs to see.
170    ///
171    /// This is the field that makes an engine-down fallback *visible*. A
172    /// fallback the user cannot see is the same defect class as a handoff with
173    /// no system note.
174    pub route: Option<RouteReason>,
175}
176
177impl ExecutorDisclosure {
178    /// Render the disclosure line for a thread.
179    ///
180    /// Derived on every call. Nothing stores the output.
181    ///
182    /// An undisclosed model is *said*, not skipped. A line that quietly
183    /// dropped the model segment would read as a complete disclosure, and the
184    /// reader would have no way to tell "Omega did not ask" from "the executor
185    /// would not say".
186    #[must_use]
187    pub fn label(&self) -> String {
188        let model = match (&self.provider, &self.model) {
189            (Some(provider), Some(model)) => format!("{provider}/{model}"),
190            (Some(provider), None) => format!("{provider}/model not disclosed"),
191            (None, Some(model)) => format!("provider not disclosed/{model}"),
192            (None, None) => "model not disclosed".to_owned(),
193        };
194        let mut line = format!("{} · {} · {model}", self.class.token(), self.agent_id);
195        if let Some(run_ref) = &self.run_ref {
196            line.push_str(" · ");
197            line.push_str(run_ref);
198        }
199        // omega#78. An unrouted thread says nothing here rather than claiming a
200        // reason nobody recorded; a routed one always says why, including — and
201        // especially — when a pin could not be honoured.
202        if let Some(route) = self.route {
203            line.push_str(" · routed: ");
204            line.push_str(route.phrase());
205        }
206        line
207    }
208
209    /// Whether this record is internally consistent.
210    ///
211    /// A run reference on a native turn means something mislabelled a routed
212    /// result, which `OMEGA-AGENT-AC-05` exists to prevent. An identifier that
213    /// is present but empty means something built the record out of a missing
214    /// value instead of leaving it absent, so it stays incoherent.
215    ///
216    /// omega#78 adds two clauses about the route. A record that says a pin
217    /// could not be honoured while showing an engine lane is claiming both that
218    /// the router fell back and that it did not; and a record that says the
219    /// thread was unpinned while showing an engine lane is owner gate 8 broken,
220    /// because nothing but a human pin may reach Full Auto authority.
221    #[must_use]
222    pub fn is_coherent(&self) -> bool {
223        let present_and_named =
224            |value: &Option<String>| value.as_ref().is_none_or(|value| !value.is_empty());
225        let route_agrees_with_the_class = match self.route {
226            Some(route) if route.is_fallback() || route == RouteReason::UnpinnedDefault => {
227                self.class == ExecutorClass::NativeLoop
228            }
229            Some(_) | None => true,
230        };
231        !self.agent_id.is_empty()
232            && present_and_named(&self.provider)
233            && present_and_named(&self.model)
234            && route_agrees_with_the_class
235            && match self.class {
236                ExecutorClass::EngineLane => self.run_ref.is_some(),
237                ExecutorClass::NativeLoop | ExecutorClass::ExternalAcp => self.run_ref.is_none(),
238            }
239    }
240}
241
242/// Every field [`ExecutorDisclosure`] is allowed to have, in declaration order.
243///
244/// Asserted **exactly** against the struct's own source by
245/// `the_disclosure_record_holds_no_rendered_label`. The point of an exact list
246/// rather than a denylist is that a denylist only catches the names its author
247/// thought of: a `label` field fails a `contains("label")` check, and `line`,
248/// `text`, `summary`, `rendered`, and `caption` all sail through it. The
249/// binding condition of the owner's 2026-07-25 identity decision is that
250/// disclosure is a *record a label renders*, and a stored rendering under any
251/// name breaks it, not just one spelled `label`.
252pub const EXECUTOR_DISCLOSURE_FIELDS: &[&str] = &[
253    "class", "agent_id", "provider", "model", "run_ref", "route",
254];
255
256// -------------------------------------------------------------------------
257// Where a session was reached from
258// -------------------------------------------------------------------------
259
260/// How a session reached Omega Agent. omega#82.
261///
262/// **This is not an [`ExecutorClass`].** `ExecutorClass` answers *who ran the
263/// work*; this answers *who asked*. Serving Omega Agent over ACP to an external
264/// host changes the second and nothing about the first: the turn is still
265/// executed by the native loop, an external ACP agent, or an engine lane, and
266/// which one is a fact about the route decision rather than about the socket
267/// the request arrived on.
268///
269/// Reusing [`ExecutorClass::ExternalAcp`] for a served session would be
270/// actively wrong rather than merely imprecise. It would make a served
271/// session's disclosure say *an external ACP agent did this work* when Omega
272/// did, and it would make one token mean two opposite things depending on which
273/// side of the socket the reader is standing on — Omega-as-client reaching out
274/// to a foreign agent, and Omega-as-agent being reached by a foreign host. A
275/// disclosure record whose meaning depends on the reader's vantage point is not
276/// a disclosure record. A fourth `ExecutorClass` variant is wrong for the same
277/// reason: there is no fourth *executor*, and `ServedOverAcp` would be an
278/// ingress fact wearing an execution field's clothes.
279///
280/// So `OMEGA-AGENT-AC-04` stands unrevised at exactly three executor classes,
281/// and ingress is modelled here instead. That also keeps the identity decision
282/// cheap to reverse in the direction that matters: an origin record can be
283/// added and removed without rewriting a single stored thread record, whereas
284/// re-pointing `ExternalAcp` would retroactively change what every record
285/// already written meant.
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub enum Ingress {
288    /// The session was started inside Omega, by a person at the front door.
289    InApp,
290    /// The session was reached over the loopback ACP server by an external
291    /// host. `OMEGA-DELTA-0041`.
292    LoopbackAcp,
293}
294
295impl Ingress {
296    /// Every admitted ingress, in declaration order.
297    #[must_use]
298    pub const fn all() -> &'static [Self] {
299        &[Self::InApp, Self::LoopbackAcp]
300    }
301
302    /// The stable wire token for this ingress.
303    #[must_use]
304    pub const fn token(self) -> &'static str {
305        match self {
306            Self::InApp => "in_app",
307            Self::LoopbackAcp => "loopback_acp",
308        }
309    }
310}
311
312/// What a session says about where it was reached from.
313///
314/// A record, on the same terms as [`ExecutorDisclosure`]: [`label`](Self::label)
315/// is derived on every call and nothing stores the output, so there is no field
316/// here to put a rendered sentence in either.
317#[derive(Debug, Clone, PartialEq, Eq)]
318pub struct SessionOrigin {
319    /// How the session was reached.
320    pub ingress: Ingress,
321    /// The name the external host gave for itself, where one did.
322    ///
323    /// `None` is *not disclosed*. An in-app session has no host to name, and a
324    /// served session whose client sent no `clientInfo` genuinely did not say.
325    pub host_name: Option<String>,
326    /// The version the external host gave for itself, where one did.
327    pub host_version: Option<String>,
328    /// Whether the host authenticated.
329    ///
330    /// Always `false` for [`Ingress::LoopbackAcp`], and
331    /// [`is_coherent`](Self::is_coherent) enforces it: the served surface
332    /// declares an empty `authMethods` list, so a served session claiming an
333    /// authenticated host is claiming a credential no one could have presented.
334    pub authenticated: bool,
335}
336
337impl SessionOrigin {
338    /// The origin of a session a person started inside Omega.
339    #[must_use]
340    pub const fn in_app() -> Self {
341        Self {
342            ingress: Ingress::InApp,
343            host_name: None,
344            host_version: None,
345            authenticated: false,
346        }
347    }
348
349    /// The origin of a session an external host reached over the loopback ACP
350    /// server.
351    #[must_use]
352    pub const fn loopback_acp(host_name: Option<String>, host_version: Option<String>) -> Self {
353        Self {
354            ingress: Ingress::LoopbackAcp,
355            host_name,
356            host_version,
357            authenticated: false,
358        }
359    }
360
361    /// Render the origin line for a session.
362    ///
363    /// Derived on every call. Nothing stores the output. An undisclosed host is
364    /// *said*, for the same reason [`ExecutorDisclosure::label`] says an
365    /// undisclosed model: a line that quietly dropped the host would read as a
366    /// complete origin.
367    #[must_use]
368    pub fn label(&self) -> String {
369        let host = match (&self.host_name, &self.host_version) {
370            (Some(name), Some(version)) => format!("{name} {version}"),
371            (Some(name), None) => format!("{name}, version not disclosed"),
372            (None, _) => "host not disclosed".to_owned(),
373        };
374        match self.ingress {
375            Ingress::InApp => "in_app".to_owned(),
376            Ingress::LoopbackAcp => {
377                format!("loopback_acp · {host} · unauthenticated")
378            }
379        }
380    }
381
382    /// Whether this record is internally consistent.
383    #[must_use]
384    pub fn is_coherent(&self) -> bool {
385        let present_and_named =
386            |value: &Option<String>| value.as_ref().is_none_or(|value| !value.is_empty());
387        let named_host_matches_the_ingress = match self.ingress {
388            // An in-app session has no external host, so naming one means the
389            // record was built from the wrong session.
390            Ingress::InApp => self.host_name.is_none() && self.host_version.is_none(),
391            Ingress::LoopbackAcp => true,
392        };
393        // The served surface offers no authentication method at all, so an
394        // authenticated served session is a credential nobody could present.
395        let unauthenticated_where_it_must_be =
396            self.ingress != Ingress::LoopbackAcp || !self.authenticated;
397        present_and_named(&self.host_name)
398            && present_and_named(&self.host_version)
399            && named_host_matches_the_ingress
400            && unauthenticated_where_it_must_be
401    }
402}
403
404/// Every field [`SessionOrigin`] is allowed to have, in declaration order.
405///
406/// Asserted exactly, for the reason [`EXECUTOR_DISCLOSURE_FIELDS`] gives.
407pub const SESSION_ORIGIN_FIELDS: &[&str] =
408    &["ingress", "host_name", "host_version", "authenticated"];
409
410/// The `pub` field names a struct declares, read from source text.
411///
412/// A lexical scan rather than reflection, because Rust has none: the check that
413/// matters is "what does this struct declare", and the source is where that is
414/// written. Starts at `pub struct {name} {` and stops at the first line that is
415/// exactly `}`, which is where rustfmt puts a struct's closing brace.
416#[must_use]
417pub fn declared_struct_fields(source: &str, struct_name: &str) -> Vec<String> {
418    let opening = format!("pub struct {struct_name} {{");
419    let mut fields = Vec::new();
420    let mut inside = false;
421    for line in source.lines() {
422        if !inside {
423            inside = line.trim_start().starts_with(&opening);
424            continue;
425        }
426        if line == "}" {
427            break;
428        }
429        let trimmed = line.trim_start();
430        let Some(rest) = trimmed.strip_prefix("pub ") else {
431            continue;
432        };
433        if let Some((name, _)) = rest.split_once(':') {
434            fields.push(name.trim().to_owned());
435        }
436    }
437    fields
438}
439
440// -------------------------------------------------------------------------
441// Who may start Full Auto
442// -------------------------------------------------------------------------
443
444/// Every gesture that may start Full Auto authority.
445///
446/// Owner gate 8, as restated for the fold: *no model-initiated path can start
447/// Full Auto authority; only an explicit human action can, wherever that
448/// action lives.* Folding Full Auto into chat moves where the action lives. It
449/// does not add a new way to reach it.
450///
451/// Every variant here is a pointer device or keyboard gesture on a control the
452/// user can see. There is deliberately no variant for a tool call, a slash
453/// command, a restored draft, an agent turn, or a composer mode flag — and
454/// `origins_are_all_human_gestures` fails if one appears, because the set is
455/// asserted against a written allowlist rather than merely being short today.
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457pub enum LaunchOrigin {
458    /// The Full Auto entry in the agent panel's new-thread menu.
459    NewThreadMenuItem,
460    /// The `full_auto_panel::OpenLauncher` action, dispatched from a keymap or
461    /// the command palette. Retained across the fold so no existing user
462    /// keybinding stops working.
463    OpenLauncherAction,
464    /// The `+` button on the run monitor rail.
465    RunMonitorNewRun,
466    /// The "New run" button on a finished run's surface.
467    RunSurfaceNewRun,
468}
469
470impl LaunchOrigin {
471    /// Every admitted origin, in declaration order.
472    #[must_use]
473    pub const fn all() -> &'static [Self] {
474        &[
475            Self::NewThreadMenuItem,
476            Self::OpenLauncherAction,
477            Self::RunMonitorNewRun,
478            Self::RunSurfaceNewRun,
479        ]
480    }
481
482    /// The stable token for this origin, recorded with the run.
483    #[must_use]
484    pub const fn token(self) -> &'static str {
485        match self {
486            Self::NewThreadMenuItem => "new_thread_menu_item",
487            Self::OpenLauncherAction => "open_launcher_action",
488            Self::RunMonitorNewRun => "run_monitor_new_run",
489            Self::RunSurfaceNewRun => "run_surface_new_run",
490        }
491    }
492}
493
494/// Reaching the launch surface is not starting a run.
495///
496/// Every [`LaunchOrigin`] opens the Full Auto launch surface with an unsent
497/// draft. Starting the run is a second, separate human act on the "Start Full
498/// Auto" button. Two gestures, both human, is the whole guard — and it is the
499/// reason the fold does not need a mode flag on the composer.
500#[must_use]
501pub const fn origin_starts_a_run(_origin: LaunchOrigin) -> bool {
502    false
503}
504
505// -------------------------------------------------------------------------
506// Who may pin an executor
507// -------------------------------------------------------------------------
508
509/// Every gesture that may set a thread's executor pin.
510///
511/// A pin is the only way a thread reaches anything but the native loop, and
512/// [`ExecutorClass::EngineLane`] *is* Full Auto authority. So owner gate 8 —
513/// *no model-initiated path can start Full Auto authority; only an explicit
514/// human action can* — reaches the pin as directly as it reaches the Start
515/// button. omega#76 rejected a composer mode flag for Full Auto because a
516/// boolean the send path reads can be set by a slash command, a restored
517/// draft, or a model-authored insertion. A pin is the same construct wearing a
518/// different name, and it gets the same treatment.
519///
520/// The mechanism is a *required argument*, not a convention. `pin_session` and
521/// `pin_next_session` in `crates/agent_ui/src/omega_router.rs` take a
522/// `PinGesture`, so there is no way to set a pin without naming the gesture
523/// that set it, and `omega_deltas` asserts every call site passes a literal
524/// variant rather than a value it was handed. A tool call, a slash command, a
525/// restored draft, an agent turn, and a composer mode flag each have no
526/// variant here, and `pin_gestures_are_all_human_gestures` fails if one
527/// appears.
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529pub enum PinGesture {
530    /// A click on an entry of the executor pin menu, on the thread's own
531    /// disclosure line.
532    ExecutorPinMenuItem,
533    /// A click on the "unpin" entry of the same menu, clearing the pin.
534    ExecutorPinCleared,
535}
536
537impl PinGesture {
538    /// Every admitted gesture, in declaration order.
539    #[must_use]
540    pub const fn all() -> &'static [Self] {
541        &[Self::ExecutorPinMenuItem, Self::ExecutorPinCleared]
542    }
543
544    /// The stable token this gesture is logged under.
545    #[must_use]
546    pub const fn token(self) -> &'static str {
547        match self {
548            Self::ExecutorPinMenuItem => "executor_pin_menu_item",
549            Self::ExecutorPinCleared => "executor_pin_cleared",
550        }
551    }
552}
553
554// -------------------------------------------------------------------------
555// The affordance ledger
556// -------------------------------------------------------------------------
557
558/// Where a Full Auto panel affordance lives after the fold.
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560pub enum FoldedHome {
561    /// The same view, rendered by the agent panel instead of by a dock panel.
562    /// Nothing about the control changed except its parent.
563    AgentPanelFullAutoSurface,
564    /// Withdrawn, with the reason. Read the reason before assuming it was an
565    /// oversight.
566    Withdrawn(&'static str),
567}
568
569/// One interactive affordance of the Full Auto panel, and its home after the
570/// fold.
571#[derive(Debug, Clone, Copy, PartialEq, Eq)]
572pub struct Affordance {
573    /// The GPUI element id, exactly as written in
574    /// `crates/full_auto_ui/src/`.
575    pub element_id: &'static str,
576    /// What the control does, in the user's terms.
577    pub does: &'static str,
578    /// Where it lives now.
579    pub home: FoldedHome,
580}
581
582/// Every interactive affordance the Full Auto panel expressed before the fold.
583///
584/// The owner asked for the panel to be folded into the Omega chat UI. "Folded"
585/// is not "reduced", so this table exists to make a loss impossible to commit
586/// by accident: `every_full_auto_affordance_is_mapped` scans
587/// `crates/full_auto_ui/src/` for element ids and fails if one is missing here
588/// or listed here and gone from the source.
589///
590/// If you are adding a control to the Full Auto surface and this test failed:
591/// add a row. That is the whole fix, and the row is the record of where your
592/// control lives after the fold.
593pub const FULL_AUTO_AFFORDANCES: &[Affordance] = &[
594    Affordance {
595        element_id: "full-auto-panel",
596        does: "The launch and run surface root, and its focus target.",
597        home: FoldedHome::AgentPanelFullAutoSurface,
598    },
599    Affordance {
600        element_id: "full-auto-openagents-connect",
601        does: "Connect or reconnect the OpenAgents Sync account.",
602        home: FoldedHome::AgentPanelFullAutoSurface,
603    },
604    Affordance {
605        element_id: "full-auto-openagents-disconnect",
606        does: "Revoke the OpenAgents Sync account credentials.",
607        home: FoldedHome::AgentPanelFullAutoSurface,
608    },
609    Affordance {
610        element_id: "full-auto-provider-account",
611        does: "One connected provider account: readiness, quota, lane, ref.",
612        home: FoldedHome::AgentPanelFullAutoSurface,
613    },
614    Affordance {
615        element_id: "full-auto-advanced-toggle",
616        does: "Reveal title, done condition, and turn cap on the draft.",
617        home: FoldedHome::AgentPanelFullAutoSurface,
618    },
619    Affordance {
620        element_id: "full-auto-start",
621        does: "Start the run. The only path to engine-lane run authority.",
622        home: FoldedHome::AgentPanelFullAutoSurface,
623    },
624    Affordance {
625        element_id: "full-auto-cancel",
626        does: "Clear the draft without starting anything.",
627        home: FoldedHome::AgentPanelFullAutoSurface,
628    },
629    Affordance {
630        element_id: "full-auto-pause",
631        does: "Pause a running run.",
632        home: FoldedHome::AgentPanelFullAutoSurface,
633    },
634    Affordance {
635        element_id: "full-auto-resume",
636        does: "Resume a paused run.",
637        home: FoldedHome::AgentPanelFullAutoSurface,
638    },
639    Affordance {
640        element_id: "full-auto-handoff",
641        does: "Hand a paused run to the other local lane.",
642        home: FoldedHome::AgentPanelFullAutoSurface,
643    },
644    Affordance {
645        element_id: "full-auto-retry",
646        does: "Retry a stalled run whose recovery action is retry_now.",
647        home: FoldedHome::AgentPanelFullAutoSurface,
648    },
649    Affordance {
650        element_id: "full-auto-stop",
651        does: "Stop a non-terminal run.",
652        home: FoldedHome::AgentPanelFullAutoSurface,
653    },
654    Affordance {
655        element_id: "full-auto-new",
656        does: "Return to the launch surface from a run.",
657        home: FoldedHome::AgentPanelFullAutoSurface,
658    },
659    Affordance {
660        element_id: "full-auto-evidence-chain",
661        does: "The host-verified evidence chain for the active run.",
662        home: FoldedHome::AgentPanelFullAutoSurface,
663    },
664    Affordance {
665        element_id: "full-auto-monitor",
666        does: "The concurrent run monitor rail.",
667        home: FoldedHome::AgentPanelFullAutoSurface,
668    },
669    Affordance {
670        element_id: "full-auto-monitor-new",
671        does: "Start a new Full Auto draft from the monitor rail.",
672        home: FoldedHome::AgentPanelFullAutoSurface,
673    },
674    Affordance {
675        element_id: "full-auto-run-row",
676        does: "Open one run from the monitor rail.",
677        home: FoldedHome::AgentPanelFullAutoSurface,
678    },
679];
680
681/// What the fold costs, stated rather than discovered.
682///
683/// Every *control* survives the fold — the ledger above proves that
684/// mechanically, because the same views render under a new parent. Two
685/// capabilities of the panel were not controls, and they do not survive. They
686/// are recorded here because the owner asked for a fold, not a reduction, and
687/// a reduction that nobody wrote down is indistinguishable from a bug.
688pub const FOLD_COSTS: &[&str] = &[
689    "Independent dock placement. Full Auto had its own DockPosition::Right and \
690     its own 520px default width, so it could sit opposite the agent panel. It \
691     now inherits the agent panel's dock and size.",
692    "Simultaneous full detail. A separate dock panel could show a run's full \
693     detail while the agent panel showed a chat thread. One panel shows one \
694     surface at a time, so watching a run in full while typing in a thread is \
695     no longer possible. Active runs still render on the front door beneath \
696     the composer, and the monitor rail still lists them, so noticing a run is \
697     preserved; reading one in full alongside a thread is not.",
698];
699
700// -------------------------------------------------------------------------
701// Checks
702// -------------------------------------------------------------------------
703
704/// Repository-root-relative path resolution.
705///
706/// `CARGO_MANIFEST_DIR` is `crates/omega_front_door`, so the root is two
707/// levels up.
708#[must_use]
709pub fn repository_path(relative: &str) -> std::path::PathBuf {
710    std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
711        .join("../..")
712        .join(relative)
713}
714
715/// Every `"full-auto-…"` string literal in a source file.
716///
717/// GPUI element ids are string literals, so this is a lexical scan. It is
718/// deliberately generous: it will pick up a `"full-auto-…"` literal that is
719/// not an element id, which fails the ledger check and forces a human to look.
720/// The opposite error — missing a real control — is the one that would let a
721/// capability disappear silently.
722#[must_use]
723pub fn full_auto_element_ids(source: &str) -> std::collections::BTreeSet<String> {
724    let mut ids = std::collections::BTreeSet::new();
725    let bytes = source.as_bytes();
726    let needle = b"\"full-auto";
727    let mut index = 0;
728    while index + needle.len() <= bytes.len() {
729        if &bytes[index..index + needle.len()] == needle {
730            let start = index + 1;
731            if let Some(offset) = source[start..].find('"') {
732                ids.insert(source[start..start + offset].to_owned());
733                index = start + offset + 1;
734                continue;
735            }
736        }
737        index += 1;
738    }
739    ids
740}
741
742#[cfg(test)]
743mod tests {
744    use super::*;
745
746    /// The front door replaces the empty buffer and nothing else.
747    #[test]
748    fn a_fresh_launch_lands_on_the_new_agent_thread_surface() {
749        assert_eq!(
750            launch_surface(RestoreOnStartup::Other),
751            LaunchSurface::NewAgentThread,
752            "a window with nothing to restore must open on the front door, \
753             not on upstream Zed's empty untitled buffer"
754        );
755    }
756
757    /// Overriding a deliberate launchpad choice would be Omega ignoring a
758    /// setting, which is a different bug from the one the front door fixes.
759    #[test]
760    fn the_launchpad_startup_behaviour_still_wins() {
761        assert_eq!(
762            launch_surface(RestoreOnStartup::Launchpad),
763            LaunchSurface::Nothing
764        );
765        assert_ne!(
766            launch_surface(RestoreOnStartup::Launchpad),
767            LaunchSurface::EmptyBuffer,
768            "the launchpad path never opened a buffer and must not start"
769        );
770    }
771
772    /// The disclosure is a record. A label is derived from it.
773    ///
774    /// The failure this guards is subtle: storing the rendered line would make
775    /// the owner's reversible identity decision irreversible, because moving to
776    /// a signing principal would then need every stored thread rewritten.
777    #[test]
778    fn disclosure_renders_from_its_fields() {
779        let disclosure = ExecutorDisclosure {
780            class: ExecutorClass::EngineLane,
781            agent_id: "codex-local".into(),
782            provider: Some("google".into()),
783            model: Some("gemini-3.6-flash".into()),
784            run_ref: Some("run.abc".into()),
785            route: None,
786        };
787        assert_eq!(
788            disclosure.label(),
789            "engine_lane · codex-local · google/gemini-3.6-flash · run.abc"
790        );
791
792        // Change one field and the rendered line follows, which is only true
793        // because nothing cached it.
794        let mut moved = disclosure;
795        moved.class = ExecutorClass::NativeLoop;
796        moved.run_ref = None;
797        assert_eq!(
798            moved.label(),
799            "native_loop · codex-local · google/gemini-3.6-flash"
800        );
801    }
802
803    /// An executor that does not report a model is disclosed as not reporting
804    /// one. omega#77.
805    ///
806    /// The failure this guards is a line that reads as complete while a part
807    /// of it is missing: `external_acp · codex-acp` alone gives the reader no
808    /// way to tell a fully disclosed thread from a partly disclosed one.
809    #[test]
810    fn an_undisclosed_model_is_said_rather_than_skipped() {
811        let disclosure = ExecutorDisclosure {
812            class: ExecutorClass::ExternalAcp,
813            agent_id: "codex-acp".into(),
814            provider: None,
815            model: None,
816            run_ref: None,
817            route: None,
818        };
819        assert!(disclosure.is_coherent());
820        assert_eq!(
821            disclosure.label(),
822            "external_acp · codex-acp · model not disclosed"
823        );
824
825        let half_known = ExecutorDisclosure {
826            provider: Some("openai".into()),
827            ..disclosure
828        };
829        assert_eq!(
830            half_known.label(),
831            "external_acp · codex-acp · openai/model not disclosed"
832        );
833    }
834
835    /// A struct with no field to hold a rendered label cannot accidentally
836    /// grow one without this test's author noticing.
837    ///
838    /// omega#82 replaced a denylist with an **exact** field list. The old check
839    /// dumped the struct and failed if the word `label` appeared; a field named
840    /// `line`, `text`, `summary`, `rendered`, or `caption` passed it, and every
841    /// one of those is the same defect. The list is the whole check now: a new
842    /// field of any name fails until someone edits `EXECUTOR_DISCLOSURE_FIELDS`
843    /// on purpose.
844    #[test]
845    fn the_disclosure_record_holds_no_rendered_label() {
846        let source = std::fs::read_to_string(repository_path(
847            "crates/omega_front_door/src/omega_front_door.rs",
848        ))
849        .expect("this crate's source is readable");
850        let declared = declared_struct_fields(&source, "ExecutorDisclosure");
851        assert_eq!(
852            declared, EXECUTOR_DISCLOSURE_FIELDS,
853            "ExecutorDisclosure's declared fields moved. The owner admitted \
854             the non-signing identity choice on the condition that disclosure \
855             stays a typed record a label renders, so a field holding a \
856             rendered line — under any name, not only `label` — breaks it. If \
857             this is a deliberate shape change, edit \
858             EXECUTOR_DISCLOSURE_FIELDS and say why."
859        );
860
861        // The scan reaching nothing would make the assertion above vacuous in
862        // one direction, so the list is also checked to be non-empty and to
863        // name a field the struct genuinely round-trips.
864        assert!(!declared.is_empty(), "the field scan reached no fields");
865        let disclosure = ExecutorDisclosure {
866            class: ExecutorClass::NativeLoop,
867            agent_id: "a".into(),
868            provider: Some("p".into()),
869            model: Some("m".into()),
870            run_ref: None,
871            route: None,
872        };
873        let dumped = format!("{disclosure:?}");
874        for field in EXECUTOR_DISCLOSURE_FIELDS {
875            assert!(
876                dumped.contains(field),
877                "EXECUTOR_DISCLOSURE_FIELDS names {field}, which the struct \
878                 does not carry: {dumped}"
879            );
880        }
881    }
882
883    /// The field scan finds real fields and stops at the struct it was asked
884    /// about.
885    ///
886    /// A scan that silently matched nothing would make the exactness check
887    /// above pass for a struct that had grown a rendered line.
888    #[test]
889    fn the_field_scan_reads_a_struct_and_stops_at_its_brace() {
890        let source = "\
891pub struct Wanted {
892    /// doc
893    pub first: String,
894    pub second: Option<u8>,
895    private: bool,
896}
897
898pub struct Other {
899    pub third: String,
900}
901";
902        assert_eq!(
903            declared_struct_fields(source, "Wanted"),
904            ["first".to_owned(), "second".to_owned()],
905            "the scan must read public fields and must not walk into the \
906             next struct"
907        );
908        assert!(declared_struct_fields(source, "Missing").is_empty());
909    }
910
911    /// omega#82. Ingress is a separate record from the executor class, and the
912    /// executor set stays closed at three.
913    #[test]
914    fn reaching_over_acp_is_an_origin_and_not_an_executor() {
915        // The whole design call, as an assertion: no executor class names a
916        // socket direction, so a served session cannot claim an external agent
917        // did work Omega did.
918        for class in ExecutorClass::all() {
919            assert!(
920                !class.token().contains("served") && !class.token().contains("ingress"),
921                "{} names an ingress fact in an execution field",
922                class.token()
923            );
924        }
925        assert_eq!(ExecutorClass::all().len(), 3);
926
927        let origin = SessionOrigin::loopback_acp(Some("Zed".into()), Some("1.12.0".into()));
928        assert!(origin.is_coherent());
929        assert_eq!(origin.label(), "loopback_acp · Zed 1.12.0 · unauthenticated");
930        assert_eq!(origin.ingress.token(), "loopback_acp");
931
932        let quiet = SessionOrigin::loopback_acp(None, None);
933        assert!(quiet.is_coherent());
934        assert_eq!(
935            quiet.label(),
936            "loopback_acp · host not disclosed · unauthenticated"
937        );
938    }
939
940    /// omega#82. A served session cannot claim it authenticated, and an in-app
941    /// session cannot claim an external host.
942    #[test]
943    fn a_served_origin_cannot_claim_a_credential_nobody_could_present() {
944        let claiming = SessionOrigin {
945            authenticated: true,
946            ..SessionOrigin::loopback_acp(Some("Zed".into()), None)
947        };
948        assert!(
949            !claiming.is_coherent(),
950            "the served surface offers no auth method, so an authenticated \
951             served session is a credential that could not have been presented"
952        );
953
954        let borrowed_host = SessionOrigin {
955            host_name: Some("Zed".into()),
956            ..SessionOrigin::in_app()
957        };
958        assert!(!borrowed_host.is_coherent());
959
960        let blank_host = SessionOrigin::loopback_acp(Some(String::new()), None);
961        assert!(
962            !blank_host.is_coherent(),
963            "absent means not disclosed; empty means something built the \
964             record out of a missing value and lost the distinction"
965        );
966    }
967
968    /// The origin record holds no rendered line either, on the same terms.
969    #[test]
970    fn the_origin_record_holds_no_rendered_label() {
971        let source = std::fs::read_to_string(repository_path(
972            "crates/omega_front_door/src/omega_front_door.rs",
973        ))
974        .expect("this crate's source is readable");
975        assert_eq!(
976            declared_struct_fields(&source, "SessionOrigin"),
977            SESSION_ORIGIN_FIELDS,
978            "SessionOrigin's declared fields moved. Ingress is disclosed the \
979             same way execution is: a typed record a label renders."
980        );
981    }
982
983    /// A run reference on a native turn is a routed result wearing the wrong
984    /// name.
985    #[test]
986    fn only_an_engine_lane_carries_a_run_reference() {
987        let base = ExecutorDisclosure {
988            class: ExecutorClass::EngineLane,
989            agent_id: "codex-local".into(),
990            provider: Some("google".into()),
991            model: Some("gemini-3.6-flash".into()),
992            run_ref: Some("run.abc".into()),
993            route: None,
994        };
995        assert!(base.is_coherent());
996
997        let mut engine_without_run = base.clone();
998        engine_without_run.run_ref = None;
999        assert!(!engine_without_run.is_coherent());
1000
1001        let mut native_with_run = base.clone();
1002        native_with_run.class = ExecutorClass::NativeLoop;
1003        assert!(!native_with_run.is_coherent());
1004
1005        // A present-but-empty identifier stays incoherent after omega#77 made
1006        // these optional. Absent means "not disclosed"; empty means something
1007        // built the record out of a missing value and lost the distinction.
1008        let mut blank_model = base.clone();
1009        blank_model.model = Some(String::new());
1010        assert!(!blank_model.is_coherent());
1011
1012        let mut blank_provider = base;
1013        blank_provider.provider = Some(String::new());
1014        assert!(!blank_provider.is_coherent());
1015    }
1016
1017    /// omega#78. A thread routed by a fallback says so on its own line.
1018    ///
1019    /// This is the "prove it is disclosed rather than silent" half of the
1020    /// engine-down exit property. The record carries the typed reason; the line
1021    /// renders it; neither stores a sentence.
1022    #[test]
1023    fn an_engine_down_fallback_is_visible_on_the_disclosure_line() {
1024        let decision = route(
1025            &RouteInputs::native_only()
1026                .with_engine(EngineReadiness::Unreachable(EngineUnreachable::NotRunning))
1027                .pinned(ExecutorPin::on_lane("claude-local")),
1028        );
1029        assert_eq!(decision.chosen, ExecutorClass::NativeLoop);
1030
1031        let disclosure = ExecutorDisclosure {
1032            class: decision.chosen,
1033            agent_id: "omega-agent".into(),
1034            provider: Some("anthropic".into()),
1035            model: Some("claude-opus-5".into()),
1036            run_ref: None,
1037            route: Some(decision.disclosed_route()),
1038        };
1039        assert!(disclosure.is_coherent());
1040
1041        let line = disclosure.label();
1042        assert!(
1043            line.contains("engine unreachable, fell back to the native loop"),
1044            "the thread ran somewhere the user did not pin and the line does \
1045             not say so: {line}"
1046        );
1047
1048        // The same record with the route dropped renders a line that reads as
1049        // an ordinary native thread. That silence is the defect.
1050        let silent = ExecutorDisclosure {
1051            route: None,
1052            ..disclosure
1053        };
1054        assert!(!silent.label().contains("fell back"));
1055    }
1056
1057    /// omega#78. A record cannot say both "the pin was not honoured" and "an
1058    /// engine lane ran it", and cannot say an unpinned thread reached Full Auto
1059    /// authority.
1060    #[test]
1061    fn a_route_that_contradicts_the_executor_is_incoherent() {
1062        let engine_thread = ExecutorDisclosure {
1063            class: ExecutorClass::EngineLane,
1064            agent_id: "codex-local".into(),
1065            provider: None,
1066            model: None,
1067            run_ref: Some("run.abc".into()),
1068            route: Some(RouteReason::PinHonored),
1069        };
1070        assert!(engine_thread.is_coherent());
1071
1072        for contradiction in [
1073            RouteReason::EngineUnreachable,
1074            RouteReason::EngineAtCapacity,
1075            RouteReason::EngineHasNoReadyLane,
1076            RouteReason::PinnedLaneUnavailable,
1077            RouteReason::ExternalAcpUnavailable,
1078            RouteReason::UnrecordableLane,
1079            // Owner gate 8: nothing unpinned reaches an engine lane.
1080            RouteReason::UnpinnedDefault,
1081        ] {
1082            let lying = ExecutorDisclosure {
1083                route: Some(contradiction),
1084                ..engine_thread.clone()
1085            };
1086            assert!(
1087                !lying.is_coherent(),
1088                "{} on an engine-lane thread passed coherence",
1089                contradiction.token()
1090            );
1091        }
1092    }
1093
1094    /// `OMEGA-AGENT-AC-04` fixes the executor set at exactly three.
1095    #[test]
1096    fn the_admitted_executor_set_is_exactly_three() {
1097        let tokens: Vec<&str> = ExecutorClass::all().iter().map(|c| c.token()).collect();
1098        assert_eq!(tokens, ["native_loop", "external_acp", "engine_lane"]);
1099    }
1100
1101    /// Owner gate 8. The allowlist is written out so that adding an origin is
1102    /// a deliberate edit to a test that says why the list is closed.
1103    #[test]
1104    fn origins_are_all_human_gestures() {
1105        let tokens: Vec<&str> = LaunchOrigin::all().iter().map(|o| o.token()).collect();
1106        assert_eq!(
1107            tokens,
1108            [
1109                "new_thread_menu_item",
1110                "open_launcher_action",
1111                "run_monitor_new_run",
1112                "run_surface_new_run",
1113            ],
1114            "every Full Auto launch origin must be a visible control a person \
1115             operates. A tool call, a slash command, a restored draft, an agent \
1116             turn, or a composer mode flag is not one. If you are adding an \
1117             origin, prove it is a human gesture before you edit this list."
1118        );
1119    }
1120
1121    /// Owner gate 8, at the pin. An engine lane is Full Auto authority and a
1122    /// pin is the only door to one, so the set of gestures that may set a pin
1123    /// is closed for the same reason the set of launch origins is.
1124    #[test]
1125    fn pin_gestures_are_all_human_gestures() {
1126        let tokens: Vec<&str> = PinGesture::all().iter().map(|g| g.token()).collect();
1127        assert_eq!(
1128            tokens,
1129            ["executor_pin_menu_item", "executor_pin_cleared"],
1130            "every executor pin gesture must be a visible control a person \
1131             operates. A tool call, a slash command, a restored draft, an \
1132             agent turn, or a composer mode flag is not one — and a pin \
1133             reaches engine-lane authority, so admitting one of those here is \
1134             owner gate 8 broken through a door nobody flagged. If you are \
1135             adding a gesture, prove it is a human one before you edit this \
1136             list."
1137        );
1138    }
1139
1140    /// Reaching the launch surface is not starting a run.
1141    #[test]
1142    fn no_origin_starts_a_run_by_itself() {
1143        for origin in LaunchOrigin::all() {
1144            assert!(
1145                !origin_starts_a_run(*origin),
1146                "{} reached the launch surface and started a run in one \
1147                 gesture; the Start button is the second human act",
1148                origin.token()
1149            );
1150        }
1151    }
1152
1153    /// The fold carries every control, and the ledger proves it against the
1154    /// source rather than against its author's memory.
1155    #[test]
1156    fn every_full_auto_affordance_is_mapped() {
1157        let directory = repository_path("crates/full_auto_ui/src");
1158        let mut found = std::collections::BTreeSet::new();
1159        for entry in std::fs::read_dir(&directory).expect("full_auto_ui sources are readable") {
1160            let path = entry.expect("directory entry").path();
1161            if path.extension().and_then(|e| e.to_str()) != Some("rs") {
1162                continue;
1163            }
1164            let source = std::fs::read_to_string(&path).expect("source is readable");
1165            found.extend(full_auto_element_ids(&source));
1166        }
1167
1168        assert!(
1169            !found.is_empty(),
1170            "the element-id scan found nothing in {}; the ledger check would \
1171             be vacuous",
1172            directory.display()
1173        );
1174
1175        let mapped: std::collections::BTreeSet<String> = FULL_AUTO_AFFORDANCES
1176            .iter()
1177            .map(|a| a.element_id.to_owned())
1178            .collect();
1179
1180        let unmapped: Vec<&String> = found.difference(&mapped).collect();
1181        assert!(
1182            unmapped.is_empty(),
1183            "Full Auto controls with no entry in FULL_AUTO_AFFORDANCES: \
1184             {unmapped:?}. The owner asked for Full Auto to be folded into the \
1185             chat UI, not reduced. Add a row saying where your control lives \
1186             after the fold."
1187        );
1188
1189        let stale: Vec<&String> = mapped.difference(&found).collect();
1190        assert!(
1191            stale.is_empty(),
1192            "FULL_AUTO_AFFORDANCES maps controls that no longer exist: \
1193             {stale:?}. A ledger that outlives its source stops being evidence."
1194        );
1195    }
1196
1197    /// A scan that reaches nothing passes every check it is asked to make.
1198    #[test]
1199    fn the_element_id_scan_reaches_real_ids() {
1200        let ids = full_auto_element_ids(
1201            r#"Button::new("full-auto-start", "Start").id(("full-auto-run-row", index))"#,
1202        );
1203        assert_eq!(ids.len(), 2);
1204        assert!(ids.contains("full-auto-start"));
1205        assert!(ids.contains("full-auto-run-row"));
1206
1207        // An unterminated literal must not swallow the rest of the file.
1208        assert!(full_auto_element_ids("\"full-auto-start").is_empty());
1209    }
1210
1211    /// The costs are stated, not discovered.
1212    #[test]
1213    fn the_fold_costs_are_written_down() {
1214        assert_eq!(
1215            FOLD_COSTS.len(),
1216            2,
1217            "FOLD_COSTS is the honest half of the ledger. If the fold now costs \
1218             more or less, say so here."
1219        );
1220        for cost in FOLD_COSTS {
1221            assert!(cost.len() > 80, "a cost stated in a phrase is not stated");
1222        }
1223    }
1224}
1225
Served at tenant.openagents/omega Member data and write actions are omitted.