Skip to repository content1225 lines · 50.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:05:19.059Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
omega_front_door.rs
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