T24e: Agent, environment

- SandboxStatus: a dot and a word (a ring while it starts or works), what it
  uses of what it has as meters, and how long it has run; the header wraps
- McpServerList over lists::ListItem: a dot per state, what it runs or why it
  failed, its tools once connected, and a switch
- ToolRegistry: tools grouped by source, found by every word of the owner's
  query, each Allow, Ask or Deny; rows wrap at 280px
- MemoryPanel: a line to keep a memory, Enter or Remember, blank words kept
  out; each memory with its source, when, and Forget
- CheckpointList over data_display::Timeline, newest first, the current point
  marked, Rewind on the rest
- gallery: every control wired to state
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 11:00:15 -04:00
1 parent 8550c086b0
commit cbdcf85cec
9 files changed
+1245 -5

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
use gpui::{
AppContext as _, Context, Entity, IntoElement, KeyBinding, ParentElement, Render, SharedString,
Styled, TestAppContext, VisualTestContext, Window, div, px,
};
use jiff::Timestamp;
use super::{press, settle};
use crate::{
agent::{
Checkpoint, CheckpointList, McpServer, McpServerList, Memory, MemoryPanel, SandboxState,
SandboxStatus, ServerState,
},
forms::{self, TextInput},
primitives::FocusNext,
theme::Theme,
};
fn setup(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
forms::bind_keys(cx);
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
});
}
/// A memory panel over a field, and the words it kept.
struct Memories {
field: Entity<TextInput>,
kept: Vec<String>,
}
impl Render for Memories {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let memory = Memory {
key: "tone".into(),
text: "Prefers plain words".into(),
source: None,
at: Timestamp::UNIX_EPOCH,
};
div().w(px(360.0)).child(
MemoryPanel::new("memory", &self.field, [memory])
.on_add(move |text, _, cx| {
view.update(cx, |memories, _| memories.kept.push(text.to_string()))
})
.on_forget(|_, _, _| {}),
)
}
}
fn memories(cx: &mut TestAppContext) -> (Entity<Memories>, &mut VisualTestContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|window, cx| Memories {
field: cx.new(|cx| TextInput::new(window, cx)),
kept: Vec::new(),
});
cx.update(|window, _| window.activate_window());
settle(cx);
(view, cx)
}
#[gpui::test]
fn enter_keeps_a_memory_and_empties_the_field(cx: &mut TestAppContext) {
let (view, cx) = memories(cx);
cx.update(|window, _| window.focus_next());
cx.simulate_input(" ");
press("enter", cx);
cx.simulate_input("Lifts stay under 20%");
press("enter", cx);
view.read_with(cx, |memories, cx| {
assert_eq!(
memories.kept,
["Lifts stay under 20%"],
"blank words are kept out"
);
assert_eq!(memories.field.read(cx).text(), "");
});
}
/// Two servers, and the switches thrown.
struct Servers {
thrown: Vec<(SharedString, bool)>,
}
impl Render for Servers {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let server = |key: &str, state| McpServer {
key: key.to_string().into(),
name: key.to_string().into(),
detail: "npx server".into(),
state,
tools: 3,
};
div().w(px(360.0)).child(
McpServerList::new(
"servers",
[
server("files", ServerState::Connected),
server("web", ServerState::Off),
],
)
.on_toggle(move |key, on, _, cx| {
view.update(cx, |servers, _| servers.thrown.push((key.clone(), on)))
}),
)
}
}
#[gpui::test]
fn a_servers_switch_turns_it_off_and_on(cx: &mut TestAppContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|_, _| Servers { thrown: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
cx.update(|window, _| window.focus_next());
press("space", cx);
cx.update(|window, _| window.focus_next());
press("space", cx);
let thrown = view.read_with(cx, |servers, _| servers.thrown.clone());
assert_eq!(thrown, [("files".into(), false), ("web".into(), true)]);
}
/// Three checkpoints standing on the middle one, and the rewinds asked for.
struct Points {
asked: Vec<usize>,
}
impl Render for Points {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let point = |key: &str| Checkpoint {
key: key.to_string().into(),
label: key.to_string().into(),
at: Timestamp::UNIX_EPOCH,
files: 2,
};
div().w(px(360.0)).child(
CheckpointList::new(
"points",
[point("first"), point("second"), point("third")],
1,
)
.on_rewind(move |ix, _, cx| view.update(cx, |points, _| points.asked.push(ix))),
)
}
}
#[gpui::test]
fn every_checkpoint_but_the_current_rewinds(cx: &mut TestAppContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|_, _| Points { asked: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
cx.update(|window, _| window.focus_next());
press("enter", cx);
cx.update(|window, _| window.focus_next());
press("enter", cx);
assert_eq!(
view.read_with(cx, |points, _| points.asked.clone()),
[2, 0],
"newest first, the current skipped"
);
}
#[test]
#[should_panic(expected = "memory 5 of 4")]
fn memory_past_its_limit_fails_loud() {
let _ = SandboxStatus::new("box", "python", SandboxState::Ready).memory(5, 4);
}
+1
View File
@@ -4,6 +4,7 @@ use gpui::{
};
mod changes;
mod environment;
mod previews;
use super::{AgentProgress, Permission, PermissionPrompt, ToolApprovalDialog};