From 6dbc871db9a711ea3f8f039e7b92ea83794a8249 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 6 Mar 2026 00:14:55 -0800 Subject: [PATCH] feat: improve inspector UI for processes and fix PTY terminal - Simplify ProcessRunTab layout: compact form with collapsible Advanced section for timeout/maxOutputBytes - Rewrite ProcessesTab: collapsible create form, lightweight list items with status dots, clean detail panel with tabs - Extract error details: use problem.detail instead of generic "Stream Error" title for better error messages - Fix GhosttyTerminal binary frame parsing: handle server's binary ArrayBuffer control frames (ready/exit/error) - Enable WebSocket proxying in Vite dev server with ws: true - Set TERM=xterm-256color default for TTY processes so tools like tmux, vim, htop work out of the box - Remove orange gradient background from terminal container for cleaner look - Remove orange left border from selected process list items - Update inspector CSS with new process/terminal styles Co-Authored-By: Claude Haiku 4.5 --- Cargo.toml | 2 +- docs/openapi.json | 1172 +++++++++++++++++ frontend/CLAUDE.md | 26 +- frontend/packages/inspector/index.html | 416 ++++++ frontend/packages/inspector/package.json | 1 + .../src/components/debug/DebugPanel.tsx | 22 +- .../src/components/debug/ProcessRunTab.tsx | 165 +++ .../src/components/debug/ProcessesTab.tsx | 430 ++++++ .../components/processes/GhosttyTerminal.tsx | 263 ++++ frontend/packages/inspector/vite.config.ts | 1 + pnpm-lock.yaml | 388 +++--- sdks/acp-http-client/tests/smoke.test.ts | 4 + .../tests/integration.test.ts | 4 + .../tests/integration.test.ts | 4 + sdks/persist-sqlite/tests/integration.test.ts | 4 + sdks/typescript/package.json | 8 +- sdks/typescript/src/client.ts | 272 +++- sdks/typescript/src/generated/openapi.ts | 556 ++++++++ sdks/typescript/src/index.ts | 28 + sdks/typescript/src/types.ts | 53 + sdks/typescript/tests/integration.test.ts | 347 ++++- server/CLAUDE.md | 24 +- server/packages/error/src/lib.rs | 13 + server/packages/sandbox-agent/Cargo.toml | 1 + server/packages/sandbox-agent/src/lib.rs | 1 + .../sandbox-agent/src/process_runtime.rs | 1086 +++++++++++++++ server/packages/sandbox-agent/src/router.rs | 838 ++++++++++++ .../sandbox-agent/src/router/support.rs | 72 +- .../sandbox-agent/src/router/types.rs | 170 +++ server/packages/sandbox-agent/tests/v1_api.rs | 56 +- .../sandbox-agent/tests/v1_api/processes.rs | 661 ++++++++++ 31 files changed, 6881 insertions(+), 207 deletions(-) create mode 100644 frontend/packages/inspector/src/components/debug/ProcessRunTab.tsx create mode 100644 frontend/packages/inspector/src/components/debug/ProcessesTab.tsx create mode 100644 frontend/packages/inspector/src/components/processes/GhosttyTerminal.tsx create mode 100644 server/packages/sandbox-agent/src/process_runtime.rs create mode 100644 server/packages/sandbox-agent/tests/v1_api/processes.rs diff --git a/Cargo.toml b/Cargo.toml index ebef66d..5a0581e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ schemars = "0.8" utoipa = { version = "4.2", features = ["axum_extras"] } # Web framework -axum = "0.7" +axum = { version = "0.7", features = ["ws"] } tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.5", features = ["cors", "trace"] } diff --git a/docs/openapi.json b/docs/openapi.json index c6e35f4..d600fda 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -948,6 +948,785 @@ } } } + }, + "/v1/processes": { + "get": { + "tags": [ + "v1" + ], + "operationId": "get_v1_processes", + "responses": { + "200": { + "description": "List processes", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessListResponse" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_processes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Started process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInfo" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Process limit or state conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/config": { + "get": { + "tags": [ + "v1" + ], + "operationId": "get_v1_processes_config", + "responses": { + "200": { + "description": "Current runtime process config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessConfig" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_processes_config", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessConfig" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated runtime process config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessConfig" + } + } + } + }, + "400": { + "description": "Invalid config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/run": { + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_processes_run", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessRunRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "One-off command result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessRunResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}": { + "get": { + "tags": [ + "v1" + ], + "operationId": "get_v1_process", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Process details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInfo" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "delete": { + "tags": [ + "v1" + ], + "operationId": "delete_v1_process", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Process deleted" + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Process is still running", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/input": { + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_process_input", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInputRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Input accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInputResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Process not writable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "413": { + "description": "Input exceeds configured limit", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/kill": { + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_process_kill", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "waitMs", + "in": "query", + "description": "Wait up to N ms for process to exit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Kill signal sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInfo" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/logs": { + "get": { + "tags": [ + "v1" + ], + "operationId": "get_v1_process_logs", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "stream", + "in": "query", + "description": "stdout|stderr|combined|pty", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ProcessLogsStream" + } + ], + "nullable": true + } + }, + { + "name": "tail", + "in": "query", + "description": "Tail N entries", + "required": false, + "schema": { + "type": "integer", + "nullable": true, + "minimum": 0 + } + }, + { + "name": "follow", + "in": "query", + "description": "Follow via SSE", + "required": false, + "schema": { + "type": "boolean", + "nullable": true + } + }, + { + "name": "since", + "in": "query", + "description": "Only entries with sequence greater than this", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Process logs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessLogsResponse" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/stop": { + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_process_stop", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "waitMs", + "in": "query", + "description": "Wait up to N ms for process to exit", + "required": false, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Stop signal sent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessInfo" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/terminal/resize": { + "post": { + "tags": [ + "v1" + ], + "operationId": "post_v1_process_terminal_resize", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessTerminalResizeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Resize accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProcessTerminalResizeResponse" + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Not a terminal process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v1/processes/{id}/terminal/ws": { + "get": { + "tags": [ + "v1" + ], + "operationId": "get_v1_process_terminal_ws", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Process ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "access_token", + "in": "query", + "description": "Bearer token alternative for WS auth", + "required": false, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "101": { + "description": "WebSocket upgraded" + }, + "400": { + "description": "Invalid websocket frame or upgrade request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Unknown process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Not a terminal process", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "501": { + "description": "Process API unsupported on this platform", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } } }, "components": { @@ -1596,6 +2375,399 @@ }, "additionalProperties": {} }, + "ProcessConfig": { + "type": "object", + "required": [ + "maxConcurrentProcesses", + "defaultRunTimeoutMs", + "maxRunTimeoutMs", + "maxOutputBytes", + "maxLogBytesPerProcess", + "maxInputBytesPerRequest" + ], + "properties": { + "defaultRunTimeoutMs": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "maxConcurrentProcesses": { + "type": "integer", + "minimum": 0 + }, + "maxInputBytesPerRequest": { + "type": "integer", + "minimum": 0 + }, + "maxLogBytesPerProcess": { + "type": "integer", + "minimum": 0 + }, + "maxOutputBytes": { + "type": "integer", + "minimum": 0 + }, + "maxRunTimeoutMs": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "ProcessCreateRequest": { + "type": "object", + "required": [ + "command" + ], + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "type": "string" + }, + "cwd": { + "type": "string", + "nullable": true + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "interactive": { + "type": "boolean" + }, + "tty": { + "type": "boolean" + } + } + }, + "ProcessInfo": { + "type": "object", + "required": [ + "id", + "command", + "args", + "tty", + "interactive", + "status", + "createdAtMs" + ], + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "type": "string" + }, + "createdAtMs": { + "type": "integer", + "format": "int64" + }, + "cwd": { + "type": "string", + "nullable": true + }, + "exitCode": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "exitedAtMs": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "id": { + "type": "string" + }, + "interactive": { + "type": "boolean" + }, + "pid": { + "type": "integer", + "format": "int32", + "nullable": true, + "minimum": 0 + }, + "status": { + "$ref": "#/components/schemas/ProcessState" + }, + "tty": { + "type": "boolean" + } + } + }, + "ProcessInputRequest": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "string" + }, + "encoding": { + "type": "string", + "nullable": true + } + } + }, + "ProcessInputResponse": { + "type": "object", + "required": [ + "bytesWritten" + ], + "properties": { + "bytesWritten": { + "type": "integer", + "minimum": 0 + } + } + }, + "ProcessListResponse": { + "type": "object", + "required": [ + "processes" + ], + "properties": { + "processes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProcessInfo" + } + } + } + }, + "ProcessLogEntry": { + "type": "object", + "required": [ + "sequence", + "stream", + "timestampMs", + "data", + "encoding" + ], + "properties": { + "data": { + "type": "string" + }, + "encoding": { + "type": "string" + }, + "sequence": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "stream": { + "$ref": "#/components/schemas/ProcessLogsStream" + }, + "timestampMs": { + "type": "integer", + "format": "int64" + } + } + }, + "ProcessLogsQuery": { + "type": "object", + "properties": { + "follow": { + "type": "boolean", + "nullable": true + }, + "since": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + }, + "stream": { + "allOf": [ + { + "$ref": "#/components/schemas/ProcessLogsStream" + } + ], + "nullable": true + }, + "tail": { + "type": "integer", + "nullable": true, + "minimum": 0 + } + } + }, + "ProcessLogsResponse": { + "type": "object", + "required": [ + "processId", + "stream", + "entries" + ], + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProcessLogEntry" + } + }, + "processId": { + "type": "string" + }, + "stream": { + "$ref": "#/components/schemas/ProcessLogsStream" + } + } + }, + "ProcessLogsStream": { + "type": "string", + "enum": [ + "stdout", + "stderr", + "combined", + "pty" + ] + }, + "ProcessRunRequest": { + "type": "object", + "required": [ + "command" + ], + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "command": { + "type": "string" + }, + "cwd": { + "type": "string", + "nullable": true + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "maxOutputBytes": { + "type": "integer", + "nullable": true, + "minimum": 0 + }, + "timeoutMs": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + } + } + }, + "ProcessRunResponse": { + "type": "object", + "required": [ + "timedOut", + "stdout", + "stderr", + "stdoutTruncated", + "stderrTruncated", + "durationMs" + ], + "properties": { + "durationMs": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "exitCode": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "stderr": { + "type": "string" + }, + "stderrTruncated": { + "type": "boolean" + }, + "stdout": { + "type": "string" + }, + "stdoutTruncated": { + "type": "boolean" + }, + "timedOut": { + "type": "boolean" + } + } + }, + "ProcessSignalQuery": { + "type": "object", + "properties": { + "waitMs": { + "type": "integer", + "format": "int64", + "nullable": true, + "minimum": 0 + } + } + }, + "ProcessState": { + "type": "string", + "enum": [ + "running", + "exited" + ] + }, + "ProcessTerminalResizeRequest": { + "type": "object", + "required": [ + "cols", + "rows" + ], + "properties": { + "cols": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "rows": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "ProcessTerminalResizeResponse": { + "type": "object", + "required": [ + "cols", + "rows" + ], + "properties": { + "cols": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "rows": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, "ServerStatus": { "type": "string", "enum": [ diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 4ae817d..c4515dc 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -1,26 +1,4 @@ # Frontend Instructions -## Inspector Architecture - -- Inspector source is `frontend/packages/inspector/`. -- `/ui/` must use ACP over HTTP (`/v2/rpc`) for session/prompt traffic. -- Primary flow: - - `initialize` - - `session/new` - - `session/prompt` - - `session/update` over SSE -- Keep backend/protocol changes in client bindings; avoid unnecessary full UI rewrites. - -## Testing - -Run inspector checks after transport or chat-flow changes: -```bash -pnpm --filter @sandbox-agent/inspector test -pnpm --filter @sandbox-agent/inspector test:agent-browser -``` - -## Docs Sync - -- Update `docs/inspector.mdx` when `/ui/` behavior changes. -- Update `docs/sdks/typescript.mdx` when inspector SDK bindings or ACP transport behavior changes. - +- When the user asks for UI changes, capture screenshots of the updated UI after implementation and verification. +- At the end, offer to open those screenshots for the user and provide absolute filesystem paths to the screenshot files. diff --git a/frontend/packages/inspector/index.html b/frontend/packages/inspector/index.html index de98dc8..fe1bc2d 100644 --- a/frontend/packages/inspector/index.html +++ b/frontend/packages/inspector/index.html @@ -2648,6 +2648,402 @@ flex-shrink: 0; } + /* ── Process form buttons ── */ + .process-run-form .button.primary, + .process-create-form .button.primary { + width: auto; + } + + .process-detail > .button { + align-self: flex-start; + } + + /* ── Run Once tab ── */ + .process-run-container { + display: flex; + flex-direction: column; + gap: 16px; + } + + .process-run-form { + display: flex; + flex-direction: column; + gap: 10px; + } + + .process-run-row { + display: flex; + gap: 10px; + } + + .process-run-field { + display: flex; + flex-direction: column; + gap: 4px; + } + + .process-run-field-grow { + flex: 1; + min-width: 0; + } + + .process-run-field .setup-input { + width: 100%; + } + + .process-run-field textarea.setup-input { + resize: vertical; + min-height: 42px; + } + + .process-advanced-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + background: none; + border: none; + color: var(--muted); + font-size: 11px; + cursor: pointer; + padding: 2px 0; + align-self: flex-start; + } + + .process-advanced-toggle:hover { + color: var(--text-secondary); + } + + .process-run-result { + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + } + + .process-run-result-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + background: var(--surface); + } + + .process-run-output { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + } + + .process-run-output-section { + display: flex; + flex-direction: column; + min-width: 0; + } + + .process-run-output-section + .process-run-output-section { + border-left: 1px solid var(--border); + } + + .process-run-output-label { + padding: 6px 12px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.3px; + color: var(--muted); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + background: var(--surface-2); + } + + .process-run-output-section .process-log-block { + border: none; + border-radius: 0; + min-height: 80px; + } + + /* ── Processes tab ── */ + .processes-container { + display: flex; + flex-direction: column; + gap: 20px; + } + + .processes-section { + display: flex; + flex-direction: column; + gap: 8px; + } + + .processes-section-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + background: none; + border: none; + color: var(--text); + font-size: 12px; + font-weight: 600; + cursor: pointer; + padding: 2px 0; + align-self: flex-start; + } + + .processes-section-toggle:hover { + color: var(--accent); + } + + .processes-section-label { + font-size: 12px; + font-weight: 600; + color: var(--text); + } + + .processes-list-header { + display: flex; + align-items: center; + justify-content: space-between; + } + + .process-create-form { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + } + + .process-checkbox-row { + display: flex; + flex-wrap: wrap; + gap: 14px; + } + + .process-checkbox { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-secondary); + cursor: pointer; + } + + .process-checkbox input { + margin: 0; + } + + /* Process list items */ + .process-list { + display: flex; + flex-direction: column; + gap: 2px; + } + + .process-list-item { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px 10px; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition); + } + + .process-list-item:hover { + background: var(--surface); + } + + .process-list-item.selected { + background: var(--surface); + } + + .process-list-item-main { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + } + + .process-status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + background: var(--muted); + } + + .process-status-dot.running { + background: var(--success); + } + + .process-status-dot.exited { + background: var(--muted); + } + + .process-list-item-cmd { + font-size: 12px; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + } + + .process-list-item-meta { + display: flex; + align-items: center; + gap: 8px; + font-size: 10px; + color: var(--muted); + padding-left: 14px; + } + + .process-list-item-id { + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace; + opacity: 0.7; + } + + .process-list-item-actions { + display: flex; + gap: 4px; + padding-left: 14px; + margin-top: 2px; + } + + .process-list-item-actions .button { + padding: 4px 8px; + font-size: 11px; + } + + /* Process detail panel */ + .process-detail { + padding: 12px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + display: flex; + flex-direction: column; + gap: 10px; + } + + .process-detail-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + + .process-detail-cmd { + font-size: 12px; + color: var(--text); + word-break: break-word; + } + + .process-detail-meta { + display: flex; + flex-wrap: wrap; + gap: 6px 14px; + font-size: 11px; + color: var(--muted); + } + + .process-detail-logs { + display: flex; + flex-direction: column; + gap: 6px; + } + + .process-detail-logs-header { + display: flex; + align-items: center; + justify-content: space-between; + } + + .process-detail-logs-header .button { + padding: 4px 8px; + font-size: 11px; + } + + /* Terminal (shared) */ + .process-terminal-shell { + margin-top: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + overflow: hidden; + background: rgba(0, 0, 0, 0.3); + } + + .process-terminal-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 8px 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(0, 0, 0, 0.2); + font-size: 11px; + color: var(--text-secondary); + } + + .process-terminal-status { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--muted); + } + + .process-terminal-status.ready { + color: var(--success); + } + + .process-terminal-status.error { + color: var(--danger); + } + + .process-terminal-status.closed { + color: var(--warning); + } + + .process-terminal-host { + min-height: 320px; + max-height: 480px; + overflow: hidden; + padding: 10px; + } + + .process-terminal-host > div { + width: 100%; + height: 100%; + } + + .process-terminal-empty { + margin-top: 4px; + padding: 10px 12px; + border: 1px dashed rgba(255, 255, 255, 0.1); + border-radius: var(--radius-sm); + color: var(--muted); + font-size: 11px; + } + + /* Log block (shared) */ + .process-log-block { + margin: 0; + min-height: 80px; + max-height: 280px; + overflow: auto; + padding: 10px 12px; + border-radius: var(--radius); + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(9, 9, 11, 0.95); + color: #e4e4e7; + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace; + font-size: 11px; + line-height: 1.55; + white-space: pre-wrap; + word-break: break-word; + } + .pill { display: inline-flex; align-items: center; @@ -3026,6 +3422,26 @@ flex-shrink: 0; } + @media (max-width: 900px) { + .process-run-row { + flex-direction: column; + } + + .process-run-output { + grid-template-columns: 1fr; + } + + .process-run-output-section + .process-run-output-section { + border-left: none; + border-top: 1px solid var(--border); + } + + .process-terminal-meta { + flex-direction: column; + align-items: flex-start; + } + } + /* Scrollbar - match landing page */ * { scrollbar-width: thin; diff --git a/frontend/packages/inspector/package.json b/frontend/packages/inspector/package.json index 119b8ce..b472d63 100644 --- a/frontend/packages/inspector/package.json +++ b/frontend/packages/inspector/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@sandbox-agent/persist-indexeddb": "workspace:*", + "ghostty-web": "^0.4.0", "lucide-react": "^0.469.0", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/frontend/packages/inspector/src/components/debug/DebugPanel.tsx b/frontend/packages/inspector/src/components/debug/DebugPanel.tsx index f40c166..b15b59a 100644 --- a/frontend/packages/inspector/src/components/debug/DebugPanel.tsx +++ b/frontend/packages/inspector/src/components/debug/DebugPanel.tsx @@ -1,15 +1,17 @@ -import { ChevronLeft, ChevronRight, Cloud, PlayCircle, Server, Terminal, Wrench } from "lucide-react"; +import { ChevronLeft, ChevronRight, Cloud, Play, PlayCircle, Server, Terminal, Wrench } from "lucide-react"; import type { AgentInfo, SandboxAgent, SessionEvent } from "sandbox-agent"; type AgentModeInfo = { id: string; name: string; description: string }; import AgentsTab from "./AgentsTab"; import EventsTab from "./EventsTab"; import McpTab from "./McpTab"; +import ProcessesTab from "./ProcessesTab"; +import ProcessRunTab from "./ProcessRunTab"; import SkillsTab from "./SkillsTab"; import RequestLogTab from "./RequestLogTab"; import type { RequestLog } from "../../types/requestLog"; -export type DebugTab = "log" | "events" | "agents" | "mcp" | "skills"; +export type DebugTab = "log" | "events" | "agents" | "mcp" | "skills" | "processes" | "run-process"; const DebugPanel = ({ debugTab, @@ -81,6 +83,14 @@ const DebugPanel = ({ MCP + +