Skip to repository content
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T00:57:32.952Z 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.gitRevision diff
c886698b → 7123aca2diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs
index ab65edda25..946a87f2ef 100644
--- a/crates/acp_thread/src/acp_thread.rs
+++ b/crates/acp_thread/src/acp_thread.rs
@@ -2225,6 +2225,15 @@ pub enum LoadError {
22252225 status: ExitStatus,
22262226 stderr: Option<SharedString>,
22272227 },
2228+ /// The agent no longer holds the session this thread names.
2229+ ///
2230+ /// Omega's thread record outlives the external agent's session store, so
2231+ /// reopening an old thread after the agent has forgotten it is ordinary
2232+ /// rather than exceptional. It surfaced as "Failed to Launch: Resource not
2233+ /// found: <uuid>", which names a thing the reader cannot act on and reads
2234+ /// like the agent failed to start. Nothing failed to launch: the
2235+ /// conversation is gone and a new one is the way forward.
2236+ SessionGone,
22282237 Other(SharedString),
22292238 }
22302239
@@ -2243,6 +2252,10 @@ impl Display for LoadError {
22432252 }
22442253 LoadError::FailedToInstall(msg) => write!(f, "Failed to install: {msg}"),
22452254 LoadError::Exited { status, .. } => write!(f, "Server exited with status {status}"),
2255+ LoadError::SessionGone => write!(
2256+ f,
2257+ "the agent no longer has this conversation; start a new thread to keep going"
2258+ ),
22462259 LoadError::Other(msg) => write!(f, "{msg}"),
22472260 }
22482261 }
diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs
index 4fbb6e3408..9d432d0895 100644
--- a/crates/agent_servers/src/acp.rs
+++ b/crates/agent_servers/src/acp.rs
@@ -1743,7 +1743,7 @@ impl AgentConnection for AcpConnection {
17431743 )
17441744 .block_task()
17451745 .await
1746- .map_err(map_acp_error)?;
1746+ .map_err(map_session_open_error)?;
17471747 Ok(SessionConfigResponse {
17481748 modes: response.modes,
17491749 config_options: response.config_options,
@@ -1788,7 +1788,7 @@ impl AgentConnection for AcpConnection {
17881788 )
17891789 .block_task()
17901790 .await
1791- .map_err(map_acp_error)?;
1791+ .map_err(map_session_open_error)?;
17921792 Ok(SessionConfigResponse {
17931793 modes: response.modes,
17941794 config_options: response.config_options,
@@ -2074,6 +2074,24 @@ fn map_acp_error(err: acp::Error) -> anyhow::Error {
20742074 }
20752075 }
20762076
2077+/// Map an error from a `session/load` or `session/resume` call.
2078+///
2079+/// `ResourceNotFound` from these two calls means one specific thing: the agent
2080+/// does not have the session we named. Omega keeps its own thread record, and
2081+/// that record outlives the agent's session store, so this is an ordinary end
2082+/// of life rather than a failure to start. Left as a generic error it rendered
2083+/// as "Failed to Launch: Resource not found: <uuid>" — a title that blames
2084+/// startup and a body that names an identifier the reader cannot act on.
2085+///
2086+/// Only the session-scoped calls get this treatment. `ResourceNotFound` from a
2087+/// file read means a missing file and must keep saying so.
2088+fn map_session_open_error(err: acp::Error) -> anyhow::Error {
2089+ if err.code == acp::ErrorCode::ResourceNotFound {
2090+ return anyhow!(LoadError::SessionGone);
2091+ }
2092+ map_acp_error(err)
2093+}
2094+
20772095 #[cfg(any(test, feature = "test-support"))]
20782096 pub mod test_support {
20792097 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@@ -5098,3 +5116,47 @@ fn handle_wait_for_terminal_exit(
50985116 })
50995117 .detach();
51005118 }
5119+
5120+#[cfg(test)]
5121+mod session_open_error_tests {
5122+ use super::*;
5123+
5124+ /// A vanished session must not read as a failure to start.
5125+ ///
5126+ /// This shipped as "Failed to Launch: Resource not found: <uuid>" — a
5127+ /// title that blames startup for something startup did not do, and a body
5128+ /// naming an identifier the reader cannot act on.
5129+ #[test]
5130+ fn a_missing_session_is_reported_as_gone_not_as_a_launch_failure() {
5131+ let mapped = map_session_open_error(acp::Error::resource_not_found(Some(
5132+ "1297dcfa-027c-422c-9b56-acb526144c93".into(),
5133+ )));
5134+ let load_error = mapped
5135+ .downcast_ref::<LoadError>()
5136+ .expect("a missing session must map to a LoadError");
5137+ assert!(
5138+ matches!(load_error, LoadError::SessionGone),
5139+ "expected SessionGone, got {load_error:?}"
5140+ );
5141+ let rendered = load_error.to_string();
5142+ assert!(
5143+ !rendered.contains("1297dcfa"),
5144+ "the session id is not actionable and must not be shown: {rendered}"
5145+ );
5146+ assert!(
5147+ rendered.contains("new thread"),
5148+ "the reader needs the way forward: {rendered}"
5149+ );
5150+ }
5151+
5152+ /// Only the session-scoped calls get this treatment. `ResourceNotFound`
5153+ /// from a file read still means a missing file.
5154+ #[test]
5155+ fn other_errors_are_left_alone() {
5156+ let mapped = map_session_open_error(acp::Error::internal_error());
5157+ assert!(
5158+ mapped.downcast_ref::<LoadError>().is_none(),
5159+ "an unrelated error must not be rewritten into SessionGone"
5160+ );
5161+ }
5162+}
diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs
index ad2c1e9fb4..1e8fb8e90c 100644
--- a/crates/agent_ui/src/conversation_view.rs
+++ b/crates/agent_ui/src/conversation_view.rs
@@ -1533,6 +1533,8 @@ impl ConversationView {
15331533 format!("Failed to Install {}", self.agent.agent_id()).into()
15341534 }
15351535 LoadError::Exited { .. } => format!("{} Exited", self.agent.agent_id()).into(),
1536+ // Deliberately not "Error Loading <agent>": the agent loaded.
1537+ LoadError::SessionGone => "Conversation No Longer Available".into(),
15361538 LoadError::Other(_) => format!("Error Loading {}", self.agent.agent_id()).into(),
15371539 },
15381540 }
@@ -2617,6 +2619,7 @@ impl ConversationView {
26172619 LoadError::Unsupported { .. } => "unsupported",
26182620 LoadError::FailedToInstall(_) => "failed_to_install",
26192621 LoadError::Exited { .. } => "exited",
2622+ LoadError::SessionGone => "session_gone",
26202623 LoadError::Other(_) => "other",
26212624 };
26222625
@@ -2660,6 +2663,27 @@ impl ConversationView {
26602663 .then(|| self.create_copy_button(message.clone()).into_any_element());
26612664 ("Failed to Launch", message.into(), action_slot)
26622665 }
2666+ // Nothing failed to launch. The agent started fine and does not
2667+ // have this conversation any more, which is ordinary once a thread
2668+ // outlives the agent's own session store. Say that, and give the
2669+ // reader the one action that moves them forward, instead of a
2670+ // launch failure carrying a session id they cannot use.
2671+ LoadError::SessionGone => {
2672+ return Callout::new()
2673+ .severity(Severity::Warning)
2674+ .icon(IconName::Info)
2675+ .title("Conversation No Longer Available")
2676+ .description(
2677+ "The agent no longer has this conversation. Its history is still here to \
2678+ read, and a new thread will pick up where you left off.",
2679+ )
2680+ .actions_slot(
2681+ Button::new("session-gone-new-thread", "New Thread")
2682+ .on_click(|_, window, cx| window.dispatch_action(NewThread.boxed_clone(), cx))
2683+ .into_any_element(),
2684+ )
2685+ .into_any_element();
2686+ }
26632687 LoadError::Other(msg) => (
26642688 "Failed to Launch",
26652689 msg.into(),
diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs
index a4a39d78ef..60e0ef92ce 100644
--- a/crates/component_preview/src/component_preview.rs
+++ b/crates/component_preview/src/component_preview.rs
@@ -692,9 +692,16 @@ impl Render for ComponentPreview {
692692 ),
693693 )
694694 .child(
695+ // `size_full` would set width to 100% of the whole row, not of
696+ // the space the sidebar leaves, so the pane rendered one
697+ // sidebar-width too wide and its content ran off the right
698+ // edge. `min_w_0` is what lets a flex child shrink below its
699+ // content width; without it `flex_1` still cannot pull the
700+ // pane in.
695701 v_flex()
696702 .flex_1()
697- .size_full()
703+ .min_w_0()
704+ .h_full()
698705 .child(
699706 div()
700707 .p_2()