From f2c060903fe20a1df38bcec284b52f8ec15a7326 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 27 Jan 2026 20:31:38 -0800 Subject: [PATCH] feat: add copy button to events tab to copy all events as JSON --- .../src/components/debug/EventsTab.tsx | 22 ++++- server/packages/sandbox-agent/src/router.rs | 82 ++++++++++++++++--- ...ssion_snapshot@permission_events_mock.snap | 12 +-- ..._snapshot@question_reject_events_mock.snap | 12 +-- ...n_snapshot@question_reply_events_mock.snap | 12 +-- ...sion_snapshot@concurrency_events_mock.snap | 50 +++++------ ...http_events_snapshot@http_events_mock.snap | 34 ++++---- ...n_sse_events_snapshot@sse_events_mock.snap | 34 ++++---- 8 files changed, 156 insertions(+), 102 deletions(-) diff --git a/frontend/packages/inspector/src/components/debug/EventsTab.tsx b/frontend/packages/inspector/src/components/debug/EventsTab.tsx index 0f68fdc..9c8b0c8 100644 --- a/frontend/packages/inspector/src/components/debug/EventsTab.tsx +++ b/frontend/packages/inspector/src/components/debug/EventsTab.tsx @@ -1,4 +1,4 @@ -import { ChevronDown, ChevronRight } from "lucide-react"; +import { ChevronDown, ChevronRight, Copy, Check } from "lucide-react"; import { useEffect, useState } from "react"; import type { UniversalEvent } from "sandbox-agent"; import { formatJson, formatTime } from "../../utils/format"; @@ -20,6 +20,17 @@ const EventsTab = ({ error: string | null; }) => { const [collapsedEvents, setCollapsedEvents] = useState>({}); + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(JSON.stringify(events, null, 2)); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy events:", err); + } + }; useEffect(() => { if (events.length === 0) { @@ -35,6 +46,15 @@ const EventsTab = ({ + diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index 53c9f72..12e4ced 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; use std::path::PathBuf; use std::process::Stdio; -use std::sync::atomic::{AtomicI64, Ordering}; +use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; @@ -46,6 +46,7 @@ use sandbox_agent_agent_management::credentials::{ use crate::agent_server_logs::AgentServerLogs; const MOCK_EVENT_DELAY_MS: u64 = 200; +static USER_MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1); #[derive(Debug)] pub struct AppState { @@ -263,6 +264,7 @@ struct SessionState { broadcaster: broadcast::Sender, opencode_stream_started: bool, codex_sender: Option>, + session_started_emitted: bool, } #[derive(Debug, Clone)] @@ -315,6 +317,7 @@ impl SessionState { broadcaster, opencode_stream_started: false, codex_sender: None, + session_started_emitted: false, }) } @@ -394,6 +397,18 @@ impl SessionState { } fn push_event(&mut self, conversion: EventConversion) -> Option { + if conversion.event_type == UniversalEventType::SessionStarted { + if self.session_started_emitted { + return None; + } + self.session_started_emitted = true; + } + if conversion.event_type == UniversalEventType::SessionEnded + && agent_supports_resume(self.agent) + && !conversion.synthetic + { + return None; + } if conversion.event_type == UniversalEventType::ItemStarted { if let UniversalEventData::Item(ref data) = conversion.data { if self.item_started.contains(&data.item.item_id) { @@ -1463,6 +1478,11 @@ impl SessionManager { self.send_mock_message(session_id, message).await?; return Ok(()); } + if matches!(session_snapshot.agent, AgentId::Claude | AgentId::Amp) { + let _ = self + .record_conversions(&session_id, user_message_conversions(&message)) + .await; + } if session_snapshot.agent == AgentId::Opencode { self.ensure_opencode_stream(session_id.clone()).await?; self.send_opencode_prompt(&session_snapshot, &message) @@ -2067,15 +2087,17 @@ impl SessionManager { let status = tokio::task::spawn_blocking(move || child.wait()).await; match status { Ok(Ok(status)) if status.success() => { - let message = format!("agent exited with status {:?}", status); - self.mark_session_ended( - &session_id, - status.code(), - &message, - SessionEndReason::Completed, - TerminatedBy::Agent, - ) - .await; + if !agent_supports_resume(agent) { + let message = format!("agent exited with status {:?}", status); + self.mark_session_ended( + &session_id, + status.code(), + &message, + SessionEndReason::Completed, + TerminatedBy::Agent, + ) + .await; + } } Ok(Ok(status)) => { let message = format!("agent exited with status {:?}", status); @@ -5035,6 +5057,46 @@ fn mock_user_message(prefix: &str, text: &str) -> Vec { ] } +fn user_message_conversions(text: &str) -> Vec { + let id = USER_MESSAGE_COUNTER.fetch_add(1, Ordering::Relaxed); + let native_item_id = format!("user_{id}"); + let content = vec![ContentPart::Text { + text: text.to_string(), + }]; + vec![ + EventConversion::new( + UniversalEventType::ItemStarted, + UniversalEventData::Item(ItemEventData { + item: UniversalItem { + item_id: String::new(), + native_item_id: Some(native_item_id.clone()), + parent_id: None, + kind: ItemKind::Message, + role: Some(ItemRole::User), + content: content.clone(), + status: ItemStatus::InProgress, + }, + }), + ) + .synthetic(), + EventConversion::new( + UniversalEventType::ItemCompleted, + UniversalEventData::Item(ItemEventData { + item: UniversalItem { + item_id: String::new(), + native_item_id: Some(native_item_id), + parent_id: None, + kind: ItemKind::Message, + role: Some(ItemRole::User), + content, + status: ItemStatus::Completed, + }, + }), + ) + .synthetic(), + ] +} + fn mock_assistant_message(native_item_id: String, text: String) -> Vec { let content = vec![ContentPart::Text { text }]; vec![ diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap index 5edf579..3edf1f8 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__permissions__assert_session_snapshot@permission_events_mock.snap @@ -6,23 +6,19 @@ expression: value seq: 1 session: started type: session.started -- metadata: true - seq: 2 - session: started - type: session.started - item: content_types: - text kind: message role: user status: in_progress - seq: 3 + seq: 2 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 4 + seq: 3 type: item.delta - item: content_types: @@ -30,11 +26,11 @@ expression: value kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - permission: action: command_execution id: "" status: requested - seq: 6 + seq: 5 type: permission.requested diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_events_mock.snap index 1c7a919..4559351 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reject_events_mock.snap @@ -6,23 +6,19 @@ expression: value seq: 1 session: started type: session.started -- metadata: true - seq: 2 - session: started - type: session.started - item: content_types: - text kind: message role: user status: in_progress - seq: 3 + seq: 2 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 4 + seq: 3 type: item.delta - item: content_types: @@ -30,11 +26,11 @@ expression: value kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - question: id: "" options: 2 status: requested - seq: 6 + seq: 5 type: question.requested diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap index 1c7a919..4559351 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__questions__assert_session_snapshot@question_reply_events_mock.snap @@ -6,23 +6,19 @@ expression: value seq: 1 session: started type: session.started -- metadata: true - seq: 2 - session: started - type: session.started - item: content_types: - text kind: message role: user status: in_progress - seq: 3 + seq: 2 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 4 + seq: 3 type: item.delta - item: content_types: @@ -30,11 +26,11 @@ expression: value kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - question: id: "" options: 2 status: requested - seq: 6 + seq: 5 type: question.requested diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap index e4de541..b08f8ac 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__assert_session_snapshot@concurrency_events_mock.snap @@ -7,23 +7,19 @@ session_a: seq: 1 session: started type: session.started - - metadata: true - seq: 2 - session: started - type: session.started - item: content_types: - text kind: message role: user status: in_progress - seq: 3 + seq: 2 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 4 + seq: 3 type: item.delta - item: content_types: @@ -31,7 +27,7 @@ session_a: kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - item: content_types: @@ -39,13 +35,13 @@ session_a: kind: message role: assistant status: in_progress - seq: 6 + seq: 5 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 7 + seq: 6 type: item.delta - item: content_types: @@ -53,30 +49,26 @@ session_a: kind: message role: assistant status: completed - seq: 8 + seq: 7 type: item.completed session_b: - metadata: true seq: 1 session: started type: session.started - - metadata: true + - item: + content_types: + - text + kind: message + role: user + status: in_progress seq: 2 - session: started - type: session.started - - item: - content_types: - - text - kind: message - role: user - status: in_progress + type: item.started + - delta: + delta: "" + item_id: "" + native_item_id: "" seq: 3 - type: item.started - - delta: - delta: "" - item_id: "" - native_item_id: "" - seq: 4 type: item.delta - item: content_types: @@ -84,7 +76,7 @@ session_b: kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - item: content_types: @@ -92,13 +84,13 @@ session_b: kind: message role: assistant status: in_progress - seq: 6 + seq: 5 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 7 + seq: 6 type: item.delta - item: content_types: @@ -106,5 +98,5 @@ session_b: kind: message role: assistant status: completed - seq: 8 + seq: 7 type: item.completed diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap index 57a0cfb..d7a4317 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_http_events_snapshot@http_events_mock.snap @@ -6,23 +6,19 @@ expression: normalized seq: 1 session: started type: session.started -- metadata: true +- item: + content_types: + - text + kind: message + role: user + status: in_progress seq: 2 - session: started - type: session.started -- item: - content_types: - - text - kind: message - role: user - status: in_progress + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" seq: 3 - type: item.started -- delta: - delta: "" - item_id: "" - native_item_id: "" - seq: 4 type: item.delta - item: content_types: @@ -30,7 +26,7 @@ expression: normalized kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - item: content_types: @@ -38,13 +34,13 @@ expression: normalized kind: message role: assistant status: in_progress - seq: 6 + seq: 5 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 7 + seq: 6 type: item.delta - item: content_types: @@ -52,5 +48,5 @@ expression: normalized kind: message role: assistant status: completed - seq: 8 + seq: 7 type: item.completed diff --git a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap index 57a0cfb..d7a4317 100644 --- a/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap +++ b/server/packages/sandbox-agent/tests/sessions/snapshots/sessions__sessions__session_lifecycle__run_sse_events_snapshot@sse_events_mock.snap @@ -6,23 +6,19 @@ expression: normalized seq: 1 session: started type: session.started -- metadata: true +- item: + content_types: + - text + kind: message + role: user + status: in_progress seq: 2 - session: started - type: session.started -- item: - content_types: - - text - kind: message - role: user - status: in_progress + type: item.started +- delta: + delta: "" + item_id: "" + native_item_id: "" seq: 3 - type: item.started -- delta: - delta: "" - item_id: "" - native_item_id: "" - seq: 4 type: item.delta - item: content_types: @@ -30,7 +26,7 @@ expression: normalized kind: message role: user status: completed - seq: 5 + seq: 4 type: item.completed - item: content_types: @@ -38,13 +34,13 @@ expression: normalized kind: message role: assistant status: in_progress - seq: 6 + seq: 5 type: item.started - delta: delta: "" item_id: "" native_item_id: "" - seq: 7 + seq: 6 type: item.delta - item: content_types: @@ -52,5 +48,5 @@ expression: normalized kind: message role: assistant status: completed - seq: 8 + seq: 7 type: item.completed