mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 08:03:46 +00:00
fix: make agent badges subtle, position next to name, widen dropdown
This commit is contained in:
parent
c498aeba28
commit
6f6a5ba04d
6 changed files with 78 additions and 34 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "sandbox-agent",
|
"title": "sandbox-agent",
|
||||||
"description": "",
|
"description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.",
|
||||||
"contact": {
|
"contact": {
|
||||||
"name": "Rivet Gaming, LLC",
|
"name": "Rivet Gaming, LLC",
|
||||||
"email": "developer@rivet.gg"
|
"email": "developer@rivet.gg"
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
right: 0;
|
right: 0;
|
||||||
min-width: 140px;
|
min-width: 200px;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border-2);
|
border: 1px solid var(--border-2);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
@ -484,7 +484,6 @@
|
||||||
transition: all var(--transition);
|
transition: all var(--transition);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,25 +493,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-add-option:hover .agent-badge {
|
.sidebar-add-option:hover .agent-badge {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-option-name {
|
.agent-option-name {
|
||||||
flex: 1;
|
white-space: nowrap;
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-option-badges {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge {
|
.agent-badge {
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -520,12 +508,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge.installed {
|
.agent-badge.installed {
|
||||||
background: rgba(48, 209, 88, 0.15);
|
color: var(--muted);
|
||||||
color: var(--success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge.version {
|
.agent-badge.version {
|
||||||
background: var(--border-2);
|
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -734,7 +720,7 @@
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
min-width: 160px;
|
min-width: 200px;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border-2);
|
border: 1px solid var(--border-2);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,8 @@ const SessionSidebar = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="agent-option-name">{agentLabels[agent.id] ?? agent.id}</span>
|
<span className="agent-option-name">{agentLabels[agent.id] ?? agent.id}</span>
|
||||||
{agent.installed && (
|
{agent.installed && <span className="agent-badge installed">Installed</span>}
|
||||||
<span className="agent-option-badges">
|
{agent.version && <span className="agent-badge version">v{agent.version}</span>}
|
||||||
<span className="agent-badge installed">Installed</span>
|
|
||||||
{agent.version && <span className="agent-badge version">v{agent.version}</span>}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -202,12 +202,8 @@ const ChatPanel = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="agent-option-name">{agentLabels[agent.id] ?? agent.id}</span>
|
<span className="agent-option-name">{agentLabels[agent.id] ?? agent.id}</span>
|
||||||
{agent.installed && (
|
{agent.installed && <span className="agent-badge installed">Installed</span>}
|
||||||
<span className="agent-option-badges">
|
{agent.version && <span className="agent-badge version">v{agent.version}</span>}
|
||||||
<span className="agent-badge installed">Installed</span>
|
|
||||||
{agent.version && <span className="agent-badge version">v{agent.version}</span>}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,7 @@ struct SessionState {
|
||||||
opencode_stream_started: bool,
|
opencode_stream_started: bool,
|
||||||
codex_sender: Option<mpsc::UnboundedSender<String>>,
|
codex_sender: Option<mpsc::UnboundedSender<String>>,
|
||||||
session_started_emitted: bool,
|
session_started_emitted: bool,
|
||||||
|
last_claude_message_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -318,6 +319,7 @@ impl SessionState {
|
||||||
opencode_stream_started: false,
|
opencode_stream_started: false,
|
||||||
codex_sender: None,
|
codex_sender: None,
|
||||||
session_started_emitted: false,
|
session_started_emitted: false,
|
||||||
|
last_claude_message_id: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2066,6 +2068,11 @@ impl SessionManager {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if agent == AgentId::Claude {
|
||||||
|
let conversions = self.parse_claude_line(&line, &session_id).await;
|
||||||
|
if !conversions.is_empty() {
|
||||||
|
let _ = self.record_conversions(&session_id, conversions).await;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let conversions = parse_agent_line(agent, &line, &session_id);
|
let conversions = parse_agent_line(agent, &line, &session_id);
|
||||||
if !conversions.is_empty() {
|
if !conversions.is_empty() {
|
||||||
|
|
@ -2176,6 +2183,53 @@ impl SessionManager {
|
||||||
Ok(session.record_conversions(conversions))
|
Ok(session.record_conversions(conversions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn parse_claude_line(&self, line: &str, session_id: &str) -> Vec<EventConversion> {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let mut value: Value = match serde_json::from_str(trimmed) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => {
|
||||||
|
return vec![agent_unparsed(
|
||||||
|
"claude",
|
||||||
|
&err.to_string(),
|
||||||
|
Value::String(trimmed.to_string()),
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let event_type = value.get("type").and_then(Value::as_str).unwrap_or("");
|
||||||
|
if event_type == "assistant" {
|
||||||
|
if let Some(id) = value
|
||||||
|
.get("message")
|
||||||
|
.and_then(|message| message.get("id"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
{
|
||||||
|
let mut sessions = self.sessions.lock().await;
|
||||||
|
if let Some(session) = Self::session_mut(&mut sessions, session_id) {
|
||||||
|
session.last_claude_message_id = Some(id.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if event_type == "result"
|
||||||
|
&& value.get("message_id").is_none()
|
||||||
|
&& value.get("messageId").is_none()
|
||||||
|
{
|
||||||
|
let last_id = {
|
||||||
|
let sessions = self.sessions.lock().await;
|
||||||
|
Self::session_ref(&sessions, session_id)
|
||||||
|
.and_then(|session| session.last_claude_message_id.clone())
|
||||||
|
};
|
||||||
|
if let Some(id) = last_id {
|
||||||
|
if let Some(map) = value.as_object_mut() {
|
||||||
|
map.insert("message_id".to_string(), Value::String(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_claude::event_to_universal_with_session(&value, session_id.to_string())
|
||||||
|
.unwrap_or_else(|err| vec![agent_unparsed("claude", &err, value)])
|
||||||
|
}
|
||||||
|
|
||||||
async fn record_error(
|
async fn record_error(
|
||||||
&self,
|
&self,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ pub fn event_to_universal_with_session(
|
||||||
let event_type = event.get("type").and_then(Value::as_str).unwrap_or("");
|
let event_type = event.get("type").and_then(Value::as_str).unwrap_or("");
|
||||||
let mut conversions = match event_type {
|
let mut conversions = match event_type {
|
||||||
"system" => vec![system_event_to_universal(event)],
|
"system" => vec![system_event_to_universal(event)],
|
||||||
|
"user" => Vec::new(),
|
||||||
"assistant" => assistant_event_to_universal(event, &session_id),
|
"assistant" => assistant_event_to_universal(event, &session_id),
|
||||||
"tool_use" => tool_use_event_to_universal(event, &session_id),
|
"tool_use" => tool_use_event_to_universal(event, &session_id),
|
||||||
"tool_result" => tool_result_event_to_universal(event),
|
"tool_result" => tool_result_event_to_universal(event),
|
||||||
|
|
@ -63,7 +64,7 @@ fn assistant_event_to_universal(event: &Value, session_id: &str) -> Vec<EventCon
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Use session-based native_item_id so `result` event can reference the same item
|
// Use session-based native_item_id so `result` event can reference the same item
|
||||||
let native_message_id = format!("{session_id}_message");
|
let native_message_id = claude_message_id(event, session_id);
|
||||||
let mut message_parts = Vec::new();
|
let mut message_parts = Vec::new();
|
||||||
|
|
||||||
for block in content {
|
for block in content {
|
||||||
|
|
@ -228,7 +229,7 @@ fn tool_result_event_to_universal(event: &Value) -> Vec<EventConversion> {
|
||||||
fn result_event_to_universal(event: &Value, session_id: &str) -> Vec<EventConversion> {
|
fn result_event_to_universal(event: &Value, session_id: &str) -> Vec<EventConversion> {
|
||||||
// The `result` event completes the message started by `assistant`.
|
// The `result` event completes the message started by `assistant`.
|
||||||
// Use the same native_item_id so they link to the same universal item.
|
// Use the same native_item_id so they link to the same universal item.
|
||||||
let native_message_id = format!("{session_id}_message");
|
let native_message_id = claude_message_id(event, session_id);
|
||||||
let result_text = event
|
let result_text = event
|
||||||
.get("result")
|
.get("result")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
|
|
@ -251,6 +252,17 @@ fn result_event_to_universal(event: &Value, session_id: &str) -> Vec<EventConver
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn claude_message_id(event: &Value, session_id: &str) -> String {
|
||||||
|
event
|
||||||
|
.get("message")
|
||||||
|
.and_then(|message| message.get("id"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.or_else(|| event.get("message_id").and_then(Value::as_str))
|
||||||
|
.or_else(|| event.get("messageId").and_then(Value::as_str))
|
||||||
|
.map(|id| id.to_string())
|
||||||
|
.unwrap_or_else(|| format!("{session_id}_message"))
|
||||||
|
}
|
||||||
|
|
||||||
fn item_events(item: UniversalItem, synthetic_start: bool) -> Vec<EventConversion> {
|
fn item_events(item: UniversalItem, synthetic_start: bool) -> Vec<EventConversion> {
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
if synthetic_start {
|
if synthetic_start {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue