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:
1 parent
8550c086b0
commit
cbdcf85cec
9 files changed
+1245
-5
No files matched your search
@@ -0,0 +1,281 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use ely_gpui_component::{
|
||||
agent::{
|
||||
Checkpoint, CheckpointList, McpServer, McpServerList, Memory, MemoryPanel, RegisteredTool,
|
||||
SandboxState, SandboxStatus, ServerState, ToolAccess, ToolRegistry,
|
||||
},
|
||||
forms::{SearchInput, TextInput},
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use jiff::{Timestamp, ToSpan};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, section, set},
|
||||
};
|
||||
|
||||
pub fn sandbox_section(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
section(
|
||||
"SandboxStatus",
|
||||
"Where an agent's sandbox stands: a dot and a word, what it uses of what it has, and how long it has run. A dot rings while it starts or works.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_start()
|
||||
.gap_4()
|
||||
.child(
|
||||
div().w(px(320.)).child(
|
||||
SandboxStatus::new("agent-sandbox", "python 3.12 · 2 vCPU", SandboxState::Ready)
|
||||
.cpu(0.18)
|
||||
.memory(1_240_000_000, 4_294_967_296)
|
||||
.uptime(Duration::from_secs(47 * 60)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().w(px(280.)).child(
|
||||
SandboxStatus::new("agent-sandbox-starting", "node 22 · 1 vCPU", SandboxState::Starting),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn servers() -> Vec<McpServer> {
|
||||
[
|
||||
(
|
||||
"files",
|
||||
"Filesystem",
|
||||
"npx @modelcontextprotocol/server-filesystem ~/ely",
|
||||
ServerState::Connected,
|
||||
11,
|
||||
),
|
||||
(
|
||||
"github",
|
||||
"GitHub",
|
||||
"https://api.githubcopilot.com/mcp",
|
||||
ServerState::Connected,
|
||||
38,
|
||||
),
|
||||
(
|
||||
"figma",
|
||||
"Figma",
|
||||
"Connecting to the desktop app…",
|
||||
ServerState::Connecting,
|
||||
0,
|
||||
),
|
||||
(
|
||||
"postgres",
|
||||
"Postgres",
|
||||
"Refused: password authentication failed",
|
||||
ServerState::Failed,
|
||||
0,
|
||||
),
|
||||
(
|
||||
"linear",
|
||||
"Linear",
|
||||
"https://mcp.linear.app/sse",
|
||||
ServerState::Off,
|
||||
24,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(key, name, detail, state, tools)| McpServer {
|
||||
key: key.into(),
|
||||
name: name.into(),
|
||||
detail: detail.into(),
|
||||
state,
|
||||
tools,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tools() -> Vec<RegisteredTool> {
|
||||
[
|
||||
(
|
||||
"read_file",
|
||||
"Filesystem",
|
||||
"Reads a file under the project",
|
||||
ToolAccess::Allow,
|
||||
),
|
||||
(
|
||||
"write_file",
|
||||
"Filesystem",
|
||||
"Writes or replaces a file",
|
||||
ToolAccess::Ask,
|
||||
),
|
||||
(
|
||||
"run_command",
|
||||
"Filesystem",
|
||||
"Runs a shell command in the sandbox",
|
||||
ToolAccess::Ask,
|
||||
),
|
||||
(
|
||||
"create_issue",
|
||||
"GitHub",
|
||||
"Opens an issue in a repository",
|
||||
ToolAccess::Ask,
|
||||
),
|
||||
(
|
||||
"merge_pull_request",
|
||||
"GitHub",
|
||||
"Merges a pull request",
|
||||
ToolAccess::Deny,
|
||||
),
|
||||
(
|
||||
"web_search",
|
||||
"Built in",
|
||||
"Searches the web and cites sources",
|
||||
ToolAccess::Allow,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, source, description, access)| RegisteredTool {
|
||||
name: name.into(),
|
||||
source: source.into(),
|
||||
description: description.into(),
|
||||
access,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn connectors_section(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let list = keep("agent-servers", servers, window, cx);
|
||||
let registry = keep("agent-tools", tools, window, cx);
|
||||
let query = window.use_keyed_state("agent-find-tool", cx, |window, cx| {
|
||||
TextInput::new(window, cx).placeholder("Find a tool")
|
||||
});
|
||||
let (now_list, now_tools, now_query) = (
|
||||
list.read(cx).clone(),
|
||||
registry.read(cx).clone(),
|
||||
query.read(cx).text().to_string(),
|
||||
);
|
||||
section(
|
||||
"MCPServerList / ConnectorList / ToolRegistry Panel",
|
||||
"Servers and connectors the agent can reach, each with where it stands and a switch; and every tool they bring, found by name, each allowed, asked for first, or denied.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_start()
|
||||
.gap_6()
|
||||
.child(
|
||||
div().w(px(420.)).child(McpServerList::new("agent-mcp", now_list).on_toggle(move |key, on, _, cx| {
|
||||
let mut next = list.read(cx).clone();
|
||||
let server = next.iter_mut().find(|server| &server.key == key).expect("a listed server");
|
||||
server.state = if on { ServerState::Connected } else { ServerState::Off };
|
||||
set(&list, next, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(460.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(SearchInput::new("agent-find-tool", &query))
|
||||
.child(
|
||||
ToolRegistry::new("agent-registry", now_tools)
|
||||
.query(now_query)
|
||||
.on_access(move |ix, access, _, cx| {
|
||||
let mut next = registry.read(cx).clone();
|
||||
next[ix].access = access;
|
||||
set(®istry, next, cx)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn memory_section(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let now = Timestamp::now();
|
||||
let kept = keep(
|
||||
"agent-memories",
|
||||
|| {
|
||||
vec![
|
||||
Memory {
|
||||
key: "tone".into(),
|
||||
text: "Prefers plain words and short sentences in notes.".into(),
|
||||
source: Some("Design review".into()),
|
||||
at: now - 72.hours(),
|
||||
},
|
||||
Memory {
|
||||
key: "lift".into(),
|
||||
text: "Dark themes lift accents by 18%; surfaces by 4%.".into(),
|
||||
source: Some("Lift the dark theme".into()),
|
||||
at: now - 5.hours(),
|
||||
},
|
||||
]
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let field = window.use_keyed_state("agent-memory-field", cx, |window, cx| {
|
||||
TextInput::new(window, cx).placeholder("Something to keep")
|
||||
});
|
||||
let points = keep(
|
||||
"agent-checkpoints",
|
||||
|| {
|
||||
[
|
||||
("Read the palette", 0, 40),
|
||||
("Measured contrast", 0, 32),
|
||||
("Lifted the accents", 2, 18),
|
||||
("Ran the tests", 2, 9),
|
||||
("Fixed the dark lift", 3, 2),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(label, files, minutes)| Checkpoint {
|
||||
key: SharedString::from(label),
|
||||
label: label.into(),
|
||||
at: now - (minutes as i64).minutes(),
|
||||
files,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let at = keep("agent-checkpoint", || 4_usize, window, cx);
|
||||
let (now_kept, now_points, now_at) =
|
||||
(kept.read(cx).clone(), points.read(cx).clone(), *at.read(cx));
|
||||
let (added, forgot) = (kept.clone(), kept);
|
||||
section(
|
||||
"MemoryPanel / CheckpointList / Rewind",
|
||||
"What an agent keeps between conversations, a line to add to it and a way to forget; and the points it can go back to, newest first, the one it stands on marked.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.items_start()
|
||||
.gap_6()
|
||||
.child(
|
||||
div().w(px(400.)).child(
|
||||
MemoryPanel::new("agent-memory", &field, now_kept)
|
||||
.on_add(move |text, _, cx| {
|
||||
let mut next = added.read(cx).clone();
|
||||
let key = SharedString::from(format!("kept-{}", next.len()));
|
||||
next.insert(0, Memory { key, text: text.to_string().into(), source: None, at: Timestamp::now() });
|
||||
set(&added, next, cx)
|
||||
})
|
||||
.on_forget(move |key, _, cx| {
|
||||
let mut next = forgot.read(cx).clone();
|
||||
next.retain(|memory| &memory.key != key);
|
||||
set(&forgot, next, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(probe(
|
||||
"agent-checkpoints",
|
||||
div().w(px(400.)).child(
|
||||
CheckpointList::new("agent-points", now_points, now_at)
|
||||
.on_rewind(move |ix, _, cx| set(&at, ix, cx)),
|
||||
),
|
||||
)),
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod changes;
|
||||
mod environment;
|
||||
mod previews;
|
||||
mod progress;
|
||||
mod tools;
|
||||
@@ -54,5 +55,8 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
.child(changes::changes_section(window, cx))
|
||||
.child(previews::watching(window, cx))
|
||||
.child(previews::artifacts(window, cx))
|
||||
.child(environment::sandbox_section(window, cx))
|
||||
.child(environment::connectors_section(window, cx))
|
||||
.child(environment::memory_section(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
Reference in new issue
Block a user