diff --git a/frontend/packages/inspector/index.html b/frontend/packages/inspector/index.html index 0f09aa1..f71abe1 100644 --- a/frontend/packages/inspector/index.html +++ b/frontend/packages/inspector/index.html @@ -678,6 +678,28 @@ width: auto; } + .empty-state-menu-wrapper { + position: relative; + } + + .empty-state-menu { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + margin-top: 4px; + min-width: 160px; + background: var(--surface); + border: 1px solid var(--border-2); + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); + padding: 6px; + display: flex; + flex-direction: column; + gap: 4px; + z-index: 60; + } + .message { display: flex; gap: 10px; diff --git a/frontend/packages/inspector/src/App.tsx b/frontend/packages/inspector/src/App.tsx index 374885f..29d8b41 100644 --- a/frontend/packages/inspector/src/App.tsx +++ b/frontend/packages/inspector/src/App.tsx @@ -836,6 +836,9 @@ export default function App() { onSendMessage={sendMessage} onKeyDown={handleKeyDown} onCreateSession={createNewSession} + availableAgents={availableAgents} + agentsLoading={agentsLoading} + agentsError={agentsError} messagesEndRef={messagesEndRef} agentLabel={agentLabel} agentMode={agentMode} diff --git a/frontend/packages/inspector/src/components/chat/ChatPanel.tsx b/frontend/packages/inspector/src/components/chat/ChatPanel.tsx index 8f58b08..d5b9418 100644 --- a/frontend/packages/inspector/src/components/chat/ChatPanel.tsx +++ b/frontend/packages/inspector/src/components/chat/ChatPanel.tsx @@ -1,4 +1,5 @@ import { MessageSquare, PauseCircle, PlayCircle, Plus, Terminal } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import type { AgentModeInfo, PermissionEventData, QuestionEventData } from "sandbox-agent"; import ApprovalsTab from "../debug/ApprovalsTab"; import ChatInput from "./ChatInput"; @@ -17,6 +18,9 @@ const ChatPanel = ({ onSendMessage, onKeyDown, onCreateSession, + availableAgents, + agentsLoading, + agentsError, messagesEndRef, agentLabel, agentMode, @@ -53,7 +57,10 @@ const ChatPanel = ({ onMessageChange: (value: string) => void; onSendMessage: () => void; onKeyDown: (event: React.KeyboardEvent) => void; - onCreateSession: () => void; + onCreateSession: (agentId: string) => void; + availableAgents: string[]; + agentsLoading: boolean; + agentsError: string | null; messagesEndRef: React.RefObject; agentLabel: string; agentMode: string; @@ -81,6 +88,29 @@ const ChatPanel = ({ onRejectQuestion: (requestId: string) => void; onReplyPermission: (requestId: string, reply: "once" | "always" | "reject") => void; }) => { + const [showAgentMenu, setShowAgentMenu] = useState(false); + const menuRef = useRef(null); + + useEffect(() => { + if (!showAgentMenu) return; + const handler = (event: MouseEvent) => { + if (!menuRef.current) return; + if (!menuRef.current.contains(event.target as Node)) { + setShowAgentMenu(false); + } + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [showAgentMenu]); + + const agentLabels: Record = { + claude: "Claude Code", + codex: "Codex", + opencode: "OpenCode", + amp: "Amp", + mock: "Mock" + }; + const hasApprovals = questionRequests.length > 0 || permissionRequests.length > 0; const isTurnMode = streamMode === "turn"; const isStreaming = isTurnMode ? turnStreaming : polling; @@ -141,10 +171,37 @@ const ChatPanel = ({
No Session Selected

Create a new session to start chatting with an agent.

- +
+ + {showAgentMenu && ( +
+ {agentsLoading &&
Loading agents...
} + {agentsError &&
{agentsError}
} + {!agentsLoading && !agentsError && availableAgents.length === 0 && ( +
No agents available.
+ )} + {!agentsLoading && !agentsError && + availableAgents.map((id) => ( + + ))} +
+ )} +
) : transcriptEntries.length === 0 && !sessionError ? (
diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_claude.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_claude.snap new file mode 100644 index 0000000..1b82694 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_install_claude.snap @@ -0,0 +1,5 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: snapshot_status(status) +--- +status: 204 diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_claude.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_claude.snap new file mode 100644 index 0000000..37a7c12 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agent_modes_claude.snap @@ -0,0 +1,11 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: normalize_agent_modes(&modes) +--- +modes: + - description: true + id: build + name: Build + - description: true + id: plan + name: Plan diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agents_list_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agents_list_global.snap new file mode 100644 index 0000000..cb83e46 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@agents_list_global.snap @@ -0,0 +1,10 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: normalize_agent_list(&agents) +--- +agents: + - id: amp + - id: claude + - id: codex + - id: mock + - id: opencode diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@health_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@health_global.snap new file mode 100644 index 0000000..4488da9 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__agent_endpoints_snapshots@health_global.snap @@ -0,0 +1,5 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: normalize_health(&health) +--- +status: ok diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_health_public_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_health_public_global.snap new file mode 100644 index 0000000..9cebf1f --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_health_public_global.snap @@ -0,0 +1,7 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "json!({ \"status\": status.as_u16(), \"payload\": normalize_health(&payload), })" +--- +payload: + status: ok +status: 200 diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_invalid_token_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_invalid_token_global.snap new file mode 100644 index 0000000..15cd1ac --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_invalid_token_global.snap @@ -0,0 +1,12 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "json!({ \"status\": status.as_u16(), \"payload\": payload, })" +--- +payload: + detail: token invalid + details: + message: missing or invalid token + status: 401 + title: Token Invalid + type: "urn:sandbox-agent:error:token_invalid" +status: 401 diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_missing_token_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_missing_token_global.snap new file mode 100644 index 0000000..15cd1ac --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_missing_token_global.snap @@ -0,0 +1,12 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "json!({ \"status\": status.as_u16(), \"payload\": payload, })" +--- +payload: + detail: token invalid + details: + message: missing or invalid token + status: 401 + title: Token Invalid + type: "urn:sandbox-agent:error:token_invalid" +status: 401 diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_valid_token_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_valid_token_global.snap new file mode 100644 index 0000000..09ca205 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__auth_snapshots@auth_valid_token_global.snap @@ -0,0 +1,12 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "json!({\n \"status\": status.as_u16(), \"payload\": normalize_agent_list(&payload),\n})" +--- +payload: + agents: + - id: amp + - id: claude + - id: codex + - id: mock + - id: opencode +status: 200 diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_actual_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_actual_global.snap new file mode 100644 index 0000000..9fe6790 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_actual_global.snap @@ -0,0 +1,10 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "json!({\n \"cors\": snapshot_cors(status, &headers), \"payload\":\n normalize_health(&payload),\n})" +--- +cors: + access-control-allow-origin: "http://example.com" + status: 200 + vary: "origin, access-control-request-method, access-control-request-headers" +payload: + status: ok diff --git a/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_preflight_global.snap b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_preflight_global.snap new file mode 100644 index 0000000..a9a8c59 --- /dev/null +++ b/server/packages/sandbox-agent/tests/http/snapshots/http_endpoints__agent_endpoints__cors_snapshots@cors_preflight_global.snap @@ -0,0 +1,9 @@ +--- +source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs +expression: "snapshot_cors(status, &headers)" +--- +access-control-allow-headers: "content-type,authorization" +access-control-allow-methods: "GET,POST" +access-control-allow-origin: "http://example.com" +status: 200 +vary: "origin, access-control-request-method, access-control-request-headers" diff --git a/server/packages/sandbox-agent/tests/server-manager/mod.rs b/server/packages/sandbox-agent/tests/server-manager/mod.rs index 5dbab28..d0771c5 100644 --- a/server/packages/sandbox-agent/tests/server-manager/mod.rs +++ b/server/packages/sandbox-agent/tests/server-manager/mod.rs @@ -1 +1,2 @@ +#[cfg(feature = "test-utils")] mod agent_server_manager;