From bda9c50693596d900a593ba923a4556793f44451 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 9 Mar 2026 19:59:35 -0700 Subject: [PATCH] chore: recover edinburgh workspace state --- Cargo.toml | 82 - LICENSE | 190 - docker/release/build.sh | 76 - docker/release/linux-aarch64.Dockerfile | 84 - docker/release/linux-x86_64.Dockerfile | 118 - docker/release/macos-aarch64.Dockerfile | 116 - docker/release/macos-x86_64.Dockerfile | 116 - docker/release/windows.Dockerfile | 102 - docker/runtime/Dockerfile | 170 - frontend/AGENTS.md | 1 - frontend/CLAUDE.md | 4 - frontend/packages/inspector/Dockerfile | 52 - frontend/packages/inspector/index.html | 3561 ---- frontend/packages/inspector/package.json | 31 - .../packages/inspector/public/favicon.svg | 1 - .../packages/inspector/public/logos/amp.svg | 1 - .../inspector/public/logos/claude.svg | 7 - .../inspector/public/logos/openai.svg | 2 - .../inspector/public/logos/opencode.svg | 1 - .../packages/inspector/public/logos/pi.svg | 22 - .../inspector/public/logos/sandboxagent.svg | 23 - frontend/packages/inspector/src/App.tsx | 1715 -- .../src/components/ConnectScreen.tsx | 128 - .../src/components/SessionCreateMenu.tsx | 300 - .../src/components/SessionSidebar.tsx | 201 - .../agents/FeatureCoverageBadges.tsx | 63 - .../src/components/chat/ChatInput.tsx | 37 - .../src/components/chat/ChatMessages.tsx | 292 - .../src/components/chat/ChatPanel.tsx | 277 - .../src/components/chat/MarkdownText.tsx | 206 - .../src/components/chat/messageUtils.tsx | 19 - .../inspector/src/components/chat/types.ts | 18 - .../src/components/debug/AgentsTab.tsx | 127 - .../src/components/debug/DebugPanel.tsx | 151 - .../src/components/debug/EventsTab.tsx | 269 - .../inspector/src/components/debug/McpTab.tsx | 243 - .../src/components/debug/ProcessRunTab.tsx | 165 - .../src/components/debug/ProcessesTab.tsx | 433 - .../src/components/debug/RequestLogTab.tsx | 126 - .../src/components/debug/SkillsTab.tsx | 412 - .../packages/inspector/src/lib/permissions.ts | 48 - frontend/packages/inspector/src/main.tsx | 9 - .../packages/inspector/src/types/agents.ts | 45 - .../inspector/src/types/requestLog.ts | 12 - .../packages/inspector/src/utils/format.ts | 24 - frontend/packages/inspector/src/utils/http.ts | 15 - frontend/packages/inspector/src/vite-env.d.ts | 1 - frontend/packages/inspector/tsconfig.json | 17 - .../packages/inspector/tsconfig.node.json | 10 - frontend/packages/inspector/vite.config.ts | 17 - frontend/packages/website/.gitignore | 3 - frontend/packages/website/Caddyfile | 5 - frontend/packages/website/Dockerfile | 26 - frontend/packages/website/astro.config.mjs | 14 - frontend/packages/website/index.html | 10 - frontend/packages/website/package.json | 30 - frontend/packages/website/public/favicon.svg | 1 - .../website/public/images/inspector.png | Bin 1749186 -> 0 bytes .../packages/website/public/logos/amp.svg | 1 - .../packages/website/public/logos/claude.svg | 7 - .../packages/website/public/logos/daytona.png | Bin 63613 -> 0 bytes .../packages/website/public/logos/daytona.svg | 8 - .../packages/website/public/logos/e2b.png | Bin 10625 -> 0 bytes .../packages/website/public/logos/e2b.svg | 6 - .../packages/website/public/logos/openai.svg | 2 - .../website/public/logos/opencode.svg | 1 - frontend/packages/website/public/logos/pi.svg | 22 - .../website/public/logos/sandboxagent.svg | 23 - .../website/public/logos/sourcegraph.svg | 0 .../packages/website/public/logos/vercel.svg | 3 - frontend/packages/website/public/og.png | Bin 278343 -> 0 bytes .../packages/website/public/rivet-icon.svg | 1 - .../website/public/rivet-logo-text-white.svg | 9 - frontend/packages/website/public/robots.txt | 4 - .../packages/website/src/components/FAQ.tsx | 132 - .../website/src/components/FeatureGrid.tsx | 121 - .../website/src/components/Footer.tsx | 168 - .../website/src/components/GetStarted.tsx | 237 - .../website/src/components/GitHubStars.tsx | 78 - .../packages/website/src/components/Hero.tsx | 288 - .../website/src/components/Inspector.tsx | 46 - .../website/src/components/Integrations.tsx | 35 - .../website/src/components/Navigation.tsx | 151 - .../website/src/components/PainPoints.tsx | 92 - .../website/src/components/ProblemsSolved.tsx | 53 - .../website/src/components/ui/Badge.tsx | 11 - .../website/src/components/ui/Button.tsx | 49 - .../website/src/components/ui/CopyButton.tsx | 32 - .../website/src/components/ui/FeatureIcon.tsx | 23 - .../packages/website/src/layouts/Layout.astro | 84 - .../packages/website/src/pages/index.astro | 24 - .../packages/website/src/styles/global.css | 244 - frontend/packages/website/tailwind.config.mjs | 64 - frontend/packages/website/tsconfig.json | 7 - frontend/packages/website/vite.config.ts | 7 - pi.svg | 22 - pnpm-lock.yaml | 13473 ---------------- research/acp/00-delete-first.md | 53 - research/acp/README.md | 41 - research/acp/acp-notes.md | 66 - research/acp/acp-over-http-findings.md | 99 - research/acp/extensibility-status.md | 126 - research/acp/friction.md | 249 - research/acp/inspector-unimplemented.md | 14 - research/acp/merge-acp.md | 242 - research/acp/migration-steps.md | 174 - .../acp/missing-features-spec/01-questions.md | 205 - .../04-filesystem-api.md | 387 - .../05-health-endpoint.md | 90 - .../missing-features-spec/06-server-status.md | 144 - .../07-session-termination.md | 123 - .../08-model-variants.md | 130 - .../missing-features-spec/10-include-raw.md | 98 - .../missing-features-spec/12-agent-listing.md | 222 - .../13-models-modes-listing.md | 67 - .../14-message-attachments.md | 132 - .../15-session-creation-richness.md | 107 - .../missing-features-spec/16-session-info.md | 170 - .../17-error-termination-metadata.md | 191 - .../missing-features-spec/feature-index.md | 30 - research/acp/missing-features-spec/plan.md | 132 - research/acp/old-rest-openapi-list.md | 437 - research/acp/rfds-vs-extensions.md | 21 - research/acp/simplify-server.md | 242 - research/acp/spec.md | 366 - research/acp/todo.md | 170 - research/acp/ts-client.md | 221 - research/acp/v1-schema-to-acp-mapping.md | 157 - research/agent-json-schemas.md | 305 - research/agents/amp.md | 449 - research/agents/claude.md | 340 - research/agents/codex.md | 453 - research/agents/openclaw.md | 553 - research/agents/opencode.md | 648 - research/detect-sandbox.md | 599 - research/human-in-the-loop.md | 261 - research/opencode-compat/COMPARISON.md | 70 - research/opencode-compat/capture-native.ts | 260 - .../opencode-compat/capture-sandbox-agent.ts | 249 - .../snapshots/native/all-events.json | 1281 -- .../snapshots/native/message-1-response.json | 69 - .../snapshots/native/message-2-response.json | 52 - .../snapshots/native/messages-after-1.json | 99 - .../snapshots/native/messages-after-2.json | 281 - .../snapshots/native/metadata-agent.json | 605 - .../snapshots/native/metadata-config.json | 102 - .../snapshots/native/metadata-providers.json | 3716 ----- .../snapshots/native/session-create.json | 12 - .../snapshots/native/session-details.json | 17 - .../snapshots/native/session-events.json | 156 - .../snapshots/native/session-status.json | 1 - .../snapshots/sandbox-agent/all-events.json | 682 - .../sandbox-agent/messages-after-1.json | 106 - .../sandbox-agent/messages-after-2.json | 269 - .../sandbox-agent/metadata-agent.json | 11 - .../sandbox-agent/metadata-config.json | 1 - .../sandbox-agent/session-create.json | 12 - .../sandbox-agent/session-details.json | 12 - .../sandbox-agent/session-events.json | 655 - .../sandbox-agent/session-status.json | 5 - research/opencode-tmux-test.md | 55 - research/opencode-web-customization.md | 82 - research/process-terminal-design.md | 374 - research/specs/command-shell-exec.md | 23 - research/specs/event-stream-parity.md | 23 - research/specs/filesystem-integration.md | 25 - research/specs/formatter-lsp.md | 21 - research/specs/mcp-integration.md | 31 - .../specs/opencode-adapter-package-plan.md | 200 - research/specs/project-worktree.md | 27 - research/specs/provider-auth.md | 26 - research/specs/pty-management.md | 28 - research/specs/search-symbol-indexing.md | 23 - research/specs/session-persistence.md | 39 - research/specs/summarize-todo.md | 21 - research/specs/toolcall-file-actions.md | 23 - research/specs/tui-control-flow.md | 32 - research/specs/vcs-integration.md | 25 - research/wip-agent-support.md | 442 - 179 files changed, 44514 deletions(-) delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100755 docker/release/build.sh delete mode 100644 docker/release/linux-aarch64.Dockerfile delete mode 100644 docker/release/linux-x86_64.Dockerfile delete mode 100644 docker/release/macos-aarch64.Dockerfile delete mode 100644 docker/release/macos-x86_64.Dockerfile delete mode 100644 docker/release/windows.Dockerfile delete mode 100644 docker/runtime/Dockerfile delete mode 120000 frontend/AGENTS.md delete mode 100644 frontend/CLAUDE.md delete mode 100644 frontend/packages/inspector/Dockerfile delete mode 100644 frontend/packages/inspector/index.html delete mode 100644 frontend/packages/inspector/package.json delete mode 100644 frontend/packages/inspector/public/favicon.svg delete mode 100644 frontend/packages/inspector/public/logos/amp.svg delete mode 100644 frontend/packages/inspector/public/logos/claude.svg delete mode 100644 frontend/packages/inspector/public/logos/openai.svg delete mode 100644 frontend/packages/inspector/public/logos/opencode.svg delete mode 100644 frontend/packages/inspector/public/logos/pi.svg delete mode 100644 frontend/packages/inspector/public/logos/sandboxagent.svg delete mode 100644 frontend/packages/inspector/src/App.tsx delete mode 100644 frontend/packages/inspector/src/components/ConnectScreen.tsx delete mode 100644 frontend/packages/inspector/src/components/SessionCreateMenu.tsx delete mode 100644 frontend/packages/inspector/src/components/SessionSidebar.tsx delete mode 100644 frontend/packages/inspector/src/components/agents/FeatureCoverageBadges.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/ChatInput.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/ChatMessages.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/ChatPanel.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/MarkdownText.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/messageUtils.tsx delete mode 100644 frontend/packages/inspector/src/components/chat/types.ts delete mode 100644 frontend/packages/inspector/src/components/debug/AgentsTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/DebugPanel.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/EventsTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/McpTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/ProcessRunTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/ProcessesTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/RequestLogTab.tsx delete mode 100644 frontend/packages/inspector/src/components/debug/SkillsTab.tsx delete mode 100644 frontend/packages/inspector/src/lib/permissions.ts delete mode 100644 frontend/packages/inspector/src/main.tsx delete mode 100644 frontend/packages/inspector/src/types/agents.ts delete mode 100644 frontend/packages/inspector/src/types/requestLog.ts delete mode 100644 frontend/packages/inspector/src/utils/format.ts delete mode 100644 frontend/packages/inspector/src/utils/http.ts delete mode 100644 frontend/packages/inspector/src/vite-env.d.ts delete mode 100644 frontend/packages/inspector/tsconfig.json delete mode 100644 frontend/packages/inspector/tsconfig.node.json delete mode 100644 frontend/packages/inspector/vite.config.ts delete mode 100644 frontend/packages/website/.gitignore delete mode 100644 frontend/packages/website/Caddyfile delete mode 100644 frontend/packages/website/Dockerfile delete mode 100644 frontend/packages/website/astro.config.mjs delete mode 100644 frontend/packages/website/index.html delete mode 100644 frontend/packages/website/package.json delete mode 100644 frontend/packages/website/public/favicon.svg delete mode 100644 frontend/packages/website/public/images/inspector.png delete mode 100644 frontend/packages/website/public/logos/amp.svg delete mode 100644 frontend/packages/website/public/logos/claude.svg delete mode 100644 frontend/packages/website/public/logos/daytona.png delete mode 100644 frontend/packages/website/public/logos/daytona.svg delete mode 100644 frontend/packages/website/public/logos/e2b.png delete mode 100644 frontend/packages/website/public/logos/e2b.svg delete mode 100644 frontend/packages/website/public/logos/openai.svg delete mode 100644 frontend/packages/website/public/logos/opencode.svg delete mode 100644 frontend/packages/website/public/logos/pi.svg delete mode 100644 frontend/packages/website/public/logos/sandboxagent.svg delete mode 100644 frontend/packages/website/public/logos/sourcegraph.svg delete mode 100644 frontend/packages/website/public/logos/vercel.svg delete mode 100644 frontend/packages/website/public/og.png delete mode 100644 frontend/packages/website/public/rivet-icon.svg delete mode 100644 frontend/packages/website/public/rivet-logo-text-white.svg delete mode 100644 frontend/packages/website/public/robots.txt delete mode 100644 frontend/packages/website/src/components/FAQ.tsx delete mode 100644 frontend/packages/website/src/components/FeatureGrid.tsx delete mode 100644 frontend/packages/website/src/components/Footer.tsx delete mode 100644 frontend/packages/website/src/components/GetStarted.tsx delete mode 100644 frontend/packages/website/src/components/GitHubStars.tsx delete mode 100644 frontend/packages/website/src/components/Hero.tsx delete mode 100644 frontend/packages/website/src/components/Inspector.tsx delete mode 100644 frontend/packages/website/src/components/Integrations.tsx delete mode 100644 frontend/packages/website/src/components/Navigation.tsx delete mode 100644 frontend/packages/website/src/components/PainPoints.tsx delete mode 100644 frontend/packages/website/src/components/ProblemsSolved.tsx delete mode 100644 frontend/packages/website/src/components/ui/Badge.tsx delete mode 100644 frontend/packages/website/src/components/ui/Button.tsx delete mode 100644 frontend/packages/website/src/components/ui/CopyButton.tsx delete mode 100644 frontend/packages/website/src/components/ui/FeatureIcon.tsx delete mode 100644 frontend/packages/website/src/layouts/Layout.astro delete mode 100644 frontend/packages/website/src/pages/index.astro delete mode 100644 frontend/packages/website/src/styles/global.css delete mode 100644 frontend/packages/website/tailwind.config.mjs delete mode 100644 frontend/packages/website/tsconfig.json delete mode 100644 frontend/packages/website/vite.config.ts delete mode 100644 pi.svg delete mode 100644 pnpm-lock.yaml delete mode 100644 research/acp/00-delete-first.md delete mode 100644 research/acp/README.md delete mode 100644 research/acp/acp-notes.md delete mode 100644 research/acp/acp-over-http-findings.md delete mode 100644 research/acp/extensibility-status.md delete mode 100644 research/acp/friction.md delete mode 100644 research/acp/inspector-unimplemented.md delete mode 100644 research/acp/merge-acp.md delete mode 100644 research/acp/migration-steps.md delete mode 100644 research/acp/missing-features-spec/01-questions.md delete mode 100644 research/acp/missing-features-spec/04-filesystem-api.md delete mode 100644 research/acp/missing-features-spec/05-health-endpoint.md delete mode 100644 research/acp/missing-features-spec/06-server-status.md delete mode 100644 research/acp/missing-features-spec/07-session-termination.md delete mode 100644 research/acp/missing-features-spec/08-model-variants.md delete mode 100644 research/acp/missing-features-spec/10-include-raw.md delete mode 100644 research/acp/missing-features-spec/12-agent-listing.md delete mode 100644 research/acp/missing-features-spec/13-models-modes-listing.md delete mode 100644 research/acp/missing-features-spec/14-message-attachments.md delete mode 100644 research/acp/missing-features-spec/15-session-creation-richness.md delete mode 100644 research/acp/missing-features-spec/16-session-info.md delete mode 100644 research/acp/missing-features-spec/17-error-termination-metadata.md delete mode 100644 research/acp/missing-features-spec/feature-index.md delete mode 100644 research/acp/missing-features-spec/plan.md delete mode 100644 research/acp/old-rest-openapi-list.md delete mode 100644 research/acp/rfds-vs-extensions.md delete mode 100644 research/acp/simplify-server.md delete mode 100644 research/acp/spec.md delete mode 100644 research/acp/todo.md delete mode 100644 research/acp/ts-client.md delete mode 100644 research/acp/v1-schema-to-acp-mapping.md delete mode 100644 research/agent-json-schemas.md delete mode 100644 research/agents/amp.md delete mode 100644 research/agents/claude.md delete mode 100644 research/agents/codex.md delete mode 100644 research/agents/openclaw.md delete mode 100644 research/agents/opencode.md delete mode 100644 research/detect-sandbox.md delete mode 100644 research/human-in-the-loop.md delete mode 100644 research/opencode-compat/COMPARISON.md delete mode 100644 research/opencode-compat/capture-native.ts delete mode 100644 research/opencode-compat/capture-sandbox-agent.ts delete mode 100644 research/opencode-compat/snapshots/native/all-events.json delete mode 100644 research/opencode-compat/snapshots/native/message-1-response.json delete mode 100644 research/opencode-compat/snapshots/native/message-2-response.json delete mode 100644 research/opencode-compat/snapshots/native/messages-after-1.json delete mode 100644 research/opencode-compat/snapshots/native/messages-after-2.json delete mode 100644 research/opencode-compat/snapshots/native/metadata-agent.json delete mode 100644 research/opencode-compat/snapshots/native/metadata-config.json delete mode 100644 research/opencode-compat/snapshots/native/metadata-providers.json delete mode 100644 research/opencode-compat/snapshots/native/session-create.json delete mode 100644 research/opencode-compat/snapshots/native/session-details.json delete mode 100644 research/opencode-compat/snapshots/native/session-events.json delete mode 100644 research/opencode-compat/snapshots/native/session-status.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/all-events.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/messages-after-1.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/messages-after-2.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/metadata-agent.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/metadata-config.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/session-create.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/session-details.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/session-events.json delete mode 100644 research/opencode-compat/snapshots/sandbox-agent/session-status.json delete mode 100644 research/opencode-tmux-test.md delete mode 100644 research/opencode-web-customization.md delete mode 100644 research/process-terminal-design.md delete mode 100644 research/specs/command-shell-exec.md delete mode 100644 research/specs/event-stream-parity.md delete mode 100644 research/specs/filesystem-integration.md delete mode 100644 research/specs/formatter-lsp.md delete mode 100644 research/specs/mcp-integration.md delete mode 100644 research/specs/opencode-adapter-package-plan.md delete mode 100644 research/specs/project-worktree.md delete mode 100644 research/specs/provider-auth.md delete mode 100644 research/specs/pty-management.md delete mode 100644 research/specs/search-symbol-indexing.md delete mode 100644 research/specs/session-persistence.md delete mode 100644 research/specs/summarize-todo.md delete mode 100644 research/specs/toolcall-file-actions.md delete mode 100644 research/specs/tui-control-flow.md delete mode 100644 research/specs/vcs-integration.md delete mode 100644 research/wip-agent-support.md diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 95f13c72..00000000 --- a/Cargo.toml +++ /dev/null @@ -1,82 +0,0 @@ -[workspace] -resolver = "2" -members = ["server/packages/*", "gigacode"] - -[workspace.package] -version = "0.3.0" -edition = "2021" -authors = [ "Rivet Gaming, LLC " ] -license = "Apache-2.0" -repository = "https://github.com/rivet-dev/sandbox-agent" -description = "Universal API for automatic coding agents in sandboxes. Supports Claude Code, Codex, OpenCode, and Amp." - -[workspace.dependencies] -# Internal crates -sandbox-agent = { version = "0.3.0", path = "server/packages/sandbox-agent" } -sandbox-agent-error = { version = "0.3.0", path = "server/packages/error" } -sandbox-agent-agent-management = { version = "0.3.0", path = "server/packages/agent-management" } -sandbox-agent-agent-credentials = { version = "0.3.0", path = "server/packages/agent-credentials" } -sandbox-agent-opencode-adapter = { version = "0.3.0", path = "server/packages/opencode-adapter" } -sandbox-agent-opencode-server-manager = { version = "0.3.0", path = "server/packages/opencode-server-manager" } -acp-http-adapter = { version = "0.3.0", path = "server/packages/acp-http-adapter" } - -# Serialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -# Error handling -thiserror = "1.0" - -# Schema generation -schemars = "0.8" -utoipa = { version = "4.2", features = ["axum_extras"] } - -# Web framework -axum = { version = "0.7", features = ["ws"] } -tower = { version = "0.5", features = ["util"] } -tower-http = { version = "0.5", features = ["cors", "trace"] } - -# Async runtime -tokio = { version = "1.36", features = ["macros", "rt-multi-thread", "signal", "time"] } -tokio-stream = { version = "0.1", features = ["sync"] } -futures = "0.3" - -# HTTP client -reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls", "stream"] } - -# CLI -clap = { version = "4.5", features = ["derive"] } - -# Logging -tracing = "0.1" -tracing-logfmt = "0.3" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Time/date -time = { version = "0.3", features = ["parsing", "formatting"] } -chrono = { version = "0.4", features = ["serde"] } - -# Filesystem/paths -dirs = "5.0" -tempfile = "3.10" - -# Archive handling -flate2 = "1.0" -tar = "0.4" -zip = { version = "0.6", default-features = false, features = ["deflate"] } - -# Misc -url = "2.5" -regress = "0.10" -include_dir = "0.7" -base64 = "0.22" -toml_edit = "0.22" - -# Code generation (build deps) -typify = "0.4" -prettyplease = "0.2" -syn = "2.0" - -# Testing -http-body-util = "0.1" -insta = { version = "1.41", features = ["yaml"] } diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d448362c..00000000 --- a/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - -Copyright 2026 Sandbox Agent Contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docker/release/build.sh b/docker/release/build.sh deleted file mode 100755 index 2f5204e6..00000000 --- a/docker/release/build.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash -set -euo pipefail - -TARGET=${1:-x86_64-unknown-linux-musl} -VERSION=${2:-} - -# Build arguments for Docker -BUILD_ARGS="" -if [ -n "$VERSION" ]; then - BUILD_ARGS="--build-arg SANDBOX_AGENT_VERSION=$VERSION" - echo "Building with version: $VERSION" -fi - -case $TARGET in - x86_64-unknown-linux-musl) - echo "Building for Linux x86_64 musl" - DOCKERFILE="linux-x86_64.Dockerfile" - TARGET_STAGE="x86_64-builder" - BINARY="sandbox-agent-$TARGET" - GIGACODE="gigacode-$TARGET" - ;; - aarch64-unknown-linux-musl) - echo "Building for Linux aarch64 musl" - DOCKERFILE="linux-aarch64.Dockerfile" - TARGET_STAGE="aarch64-builder" - BINARY="sandbox-agent-$TARGET" - GIGACODE="gigacode-$TARGET" - ;; - x86_64-pc-windows-gnu) - echo "Building for Windows x86_64" - DOCKERFILE="windows.Dockerfile" - TARGET_STAGE="" - BINARY="sandbox-agent-$TARGET.exe" - GIGACODE="gigacode-$TARGET.exe" - ;; - x86_64-apple-darwin) - echo "Building for macOS x86_64" - DOCKERFILE="macos-x86_64.Dockerfile" - TARGET_STAGE="x86_64-builder" - BINARY="sandbox-agent-$TARGET" - GIGACODE="gigacode-$TARGET" - ;; - aarch64-apple-darwin) - echo "Building for macOS aarch64" - DOCKERFILE="macos-aarch64.Dockerfile" - TARGET_STAGE="aarch64-builder" - BINARY="sandbox-agent-$TARGET" - GIGACODE="gigacode-$TARGET" - ;; - *) - echo "Unsupported target: $TARGET" - exit 1 - ;; - esac - -DOCKER_BUILDKIT=1 -if [ -n "$TARGET_STAGE" ]; then - docker build --target "$TARGET_STAGE" $BUILD_ARGS -f "docker/release/$DOCKERFILE" -t "sandbox-agent-builder-$TARGET" . -else - docker build $BUILD_ARGS -f "docker/release/$DOCKERFILE" -t "sandbox-agent-builder-$TARGET" . -fi - -CONTAINER_ID=$(docker create "sandbox-agent-builder-$TARGET") -mkdir -p dist - -docker cp "$CONTAINER_ID:/artifacts/$BINARY" "dist/" -docker cp "$CONTAINER_ID:/artifacts/$GIGACODE" "dist/" -docker rm "$CONTAINER_ID" - -if [[ "$BINARY" != *.exe ]]; then - chmod +x "dist/$BINARY" - chmod +x "dist/$GIGACODE" -fi - -echo "Binary saved to: dist/$BINARY" -echo "Binary saved to: dist/$GIGACODE" diff --git a/docker/release/linux-aarch64.Dockerfile b/docker/release/linux-aarch64.Dockerfile deleted file mode 100644 index 412e6c0b..00000000 --- a/docker/release/linux-aarch64.Dockerfile +++ /dev/null @@ -1,84 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# Build inspector frontend -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -# Use Alpine with native musl for ARM64 builds (runs natively on ARM64 runner) -FROM rust:1.88-alpine AS aarch64-builder - -# Accept version as build arg -ARG SANDBOX_AGENT_VERSION -ENV SANDBOX_AGENT_VERSION=${SANDBOX_AGENT_VERSION} - -# Install dependencies -RUN apk add --no-cache \ - musl-dev \ - clang \ - llvm-dev \ - openssl-dev \ - openssl-libs-static \ - pkgconfig \ - git \ - curl \ - build-base - -# Add musl target -RUN rustup target add aarch64-unknown-linux-musl - -# Set environment variables for native musl build -ENV CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true \ - RUSTFLAGS="-C target-feature=+crt-static" - -WORKDIR /build - -# Copy the source code -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -# Build for Linux with musl (static binary) - aarch64 -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent -p gigacode --release --target aarch64-unknown-linux-musl && \ - mkdir -p /artifacts && \ - cp target/aarch64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-aarch64-unknown-linux-musl && \ - cp target/aarch64-unknown-linux-musl/release/gigacode /artifacts/gigacode-aarch64-unknown-linux-musl - -# Default command to show help -CMD ["ls", "-la", "/artifacts"] diff --git a/docker/release/linux-x86_64.Dockerfile b/docker/release/linux-x86_64.Dockerfile deleted file mode 100644 index 323e4713..00000000 --- a/docker/release/linux-x86_64.Dockerfile +++ /dev/null @@ -1,118 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# Build inspector frontend -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -FROM rust:1.88.0 AS base - -# Install dependencies -RUN apt-get update && apt-get install -y \ - musl-tools \ - musl-dev \ - llvm-14-dev \ - libclang-14-dev \ - clang-14 \ - libssl-dev \ - pkg-config \ - ca-certificates \ - g++ \ - g++-multilib \ - git \ - curl && \ - rm -rf /var/lib/apt/lists/* && \ - wget -q https://github.com/cross-tools/musl-cross/releases/latest/download/x86_64-unknown-linux-musl.tar.xz && \ - tar -xf x86_64-unknown-linux-musl.tar.xz -C /opt/ && \ - rm x86_64-unknown-linux-musl.tar.xz - -# Install musl targets -RUN rustup target add x86_64-unknown-linux-musl - -# Set environment variables -ENV PATH="/opt/x86_64-unknown-linux-musl/bin:$PATH" \ - LIBCLANG_PATH=/usr/lib/llvm-14/lib \ - CLANG_PATH=/usr/bin/clang-14 \ - CC_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-gcc \ - CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ \ - AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc \ - CARGO_INCREMENTAL=0 \ - RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-static-libgcc" \ - CARGO_NET_GIT_FETCH_WITH_CLI=true - -# Set working directory -WORKDIR /build - -# Build for x86_64 -FROM base AS x86_64-builder - -# Accept version as build arg -ARG SANDBOX_AGENT_VERSION -ENV SANDBOX_AGENT_VERSION=${SANDBOX_AGENT_VERSION} - -# Set up OpenSSL for x86_64 musl target -ENV SSL_VER=1.1.1w -RUN wget https://www.openssl.org/source/openssl-$SSL_VER.tar.gz \ - && tar -xzf openssl-$SSL_VER.tar.gz \ - && cd openssl-$SSL_VER \ - && ./Configure no-shared no-async --prefix=/musl --openssldir=/musl/ssl linux-x86_64 \ - && make -j$(nproc) \ - && make install_sw \ - && cd .. \ - && rm -rf openssl-$SSL_VER* - -# Configure OpenSSL env vars for the build -ENV OPENSSL_DIR=/musl \ - OPENSSL_INCLUDE_DIR=/musl/include \ - OPENSSL_LIB_DIR=/musl/lib \ - PKG_CONFIG_ALLOW_CROSS=1 - -# Copy the source code -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -# Build for Linux with musl (static binary) - x86_64 -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent -p gigacode --release --target x86_64-unknown-linux-musl && \ - mkdir -p /artifacts && \ - cp target/x86_64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-x86_64-unknown-linux-musl && \ - cp target/x86_64-unknown-linux-musl/release/gigacode /artifacts/gigacode-x86_64-unknown-linux-musl - -# Default command to show help -CMD ["ls", "-la", "/artifacts"] diff --git a/docker/release/macos-aarch64.Dockerfile b/docker/release/macos-aarch64.Dockerfile deleted file mode 100644 index 000157eb..00000000 --- a/docker/release/macos-aarch64.Dockerfile +++ /dev/null @@ -1,116 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# Build inspector frontend -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -FROM rust:1.88.0 AS base - -# Install dependencies -RUN apt-get update && apt-get install -y \ - clang \ - cmake \ - patch \ - libxml2-dev \ - wget \ - xz-utils \ - curl \ - git && \ - rm -rf /var/lib/apt/lists/* - -# Install osxcross -RUN git config --global --add safe.directory '*' && \ - git clone https://github.com/tpoechtrager/osxcross /root/osxcross && \ - cd /root/osxcross && \ - wget -nc https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz && \ - mv MacOSX11.3.sdk.tar.xz tarballs/ && \ - UNATTENDED=yes OSX_VERSION_MIN=10.7 ./build.sh - -# Add osxcross to PATH -ENV PATH="/root/osxcross/target/bin:$PATH" - -# Tell Clang/bindgen to use the macOS SDK, and nudge Clang to prefer osxcross binutils. -ENV OSXCROSS_SDK=MacOSX11.3.sdk \ - SDKROOT=/root/osxcross/target/SDK/MacOSX11.3.sdk \ - BINDGEN_EXTRA_CLANG_ARGS_aarch64_apple_darwin="--sysroot=/root/osxcross/target/SDK/MacOSX11.3.sdk -isystem /root/osxcross/target/SDK/MacOSX11.3.sdk/usr/include" \ - CFLAGS_aarch64_apple_darwin="-B/root/osxcross/target/bin" \ - CXXFLAGS_aarch64_apple_darwin="-B/root/osxcross/target/bin" \ - CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=aarch64-apple-darwin20.4-clang \ - CC_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang \ - CXX_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang++ \ - AR_aarch64_apple_darwin=aarch64-apple-darwin20.4-ar \ - RANLIB_aarch64_apple_darwin=aarch64-apple-darwin20.4-ranlib \ - MACOSX_DEPLOYMENT_TARGET=10.14 \ - CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true - -# Set working directory -WORKDIR /build - -# Build for ARM64 macOS -FROM base AS aarch64-builder - -# Accept version as build arg -ARG SANDBOX_AGENT_VERSION -ENV SANDBOX_AGENT_VERSION=${SANDBOX_AGENT_VERSION} - -# Install macOS ARM64 target -RUN rustup target add aarch64-apple-darwin - -# Configure Cargo for cross-compilation (ARM64) -RUN mkdir -p /root/.cargo && \ - echo '\ -[target.aarch64-apple-darwin]\n\ -linker = "aarch64-apple-darwin20.4-clang"\n\ -ar = "aarch64-apple-darwin20.4-ar"\n\ -' > /root/.cargo/config.toml - -# Copy the source code -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -# Build for ARM64 macOS -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent -p gigacode --release --target aarch64-apple-darwin && \ - mkdir -p /artifacts && \ - cp target/aarch64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-aarch64-apple-darwin && \ - cp target/aarch64-apple-darwin/release/gigacode /artifacts/gigacode-aarch64-apple-darwin - -# Default command to show help -CMD ["ls", "-la", "/artifacts"] diff --git a/docker/release/macos-x86_64.Dockerfile b/docker/release/macos-x86_64.Dockerfile deleted file mode 100644 index 9082018a..00000000 --- a/docker/release/macos-x86_64.Dockerfile +++ /dev/null @@ -1,116 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# Build inspector frontend -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -FROM rust:1.88.0 AS base - -# Install dependencies -RUN apt-get update && apt-get install -y \ - clang \ - cmake \ - patch \ - libxml2-dev \ - wget \ - xz-utils \ - curl \ - git && \ - rm -rf /var/lib/apt/lists/* - -# Install osxcross -RUN git config --global --add safe.directory '*' && \ - git clone https://github.com/tpoechtrager/osxcross /root/osxcross && \ - cd /root/osxcross && \ - wget -nc https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz && \ - mv MacOSX11.3.sdk.tar.xz tarballs/ && \ - UNATTENDED=yes OSX_VERSION_MIN=10.7 ./build.sh - -# Add osxcross to PATH -ENV PATH="/root/osxcross/target/bin:$PATH" - -# Tell Clang/bindgen to use the macOS SDK, and nudge Clang to prefer osxcross binutils. -ENV OSXCROSS_SDK=MacOSX11.3.sdk \ - SDKROOT=/root/osxcross/target/SDK/MacOSX11.3.sdk \ - BINDGEN_EXTRA_CLANG_ARGS_X86_64_apple_darwin="--sysroot=/root/osxcross/target/SDK/MacOSX11.3.sdk -isystem /root/osxcross/target/SDK/MacOSX11.3.sdk/usr/include" \ - CFLAGS_X86_64_apple_darwin="-B/root/osxcross/target/bin" \ - CXXFLAGS_X86_64_apple_darwin="-B/root/osxcross/target/bin" \ - CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=x86_64-apple-darwin20.4-clang \ - CC_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang \ - CXX_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang++ \ - AR_X86_64_apple_darwin=x86_64-apple-darwin20.4-ar \ - RANLIB_X86_64_apple_darwin=x86_64-apple-darwin20.4-ranlib \ - MACOSX_DEPLOYMENT_TARGET=10.14 \ - CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true - -# Set working directory -WORKDIR /build - -# Build for x86_64 macOS -FROM base AS x86_64-builder - -# Accept version as build arg -ARG SANDBOX_AGENT_VERSION -ENV SANDBOX_AGENT_VERSION=${SANDBOX_AGENT_VERSION} - -# Install macOS x86_64 target -RUN rustup target add x86_64-apple-darwin - -# Configure Cargo for cross-compilation (x86_64) -RUN mkdir -p /root/.cargo && \ - echo '\ -[target.x86_64-apple-darwin]\n\ -linker = "x86_64-apple-darwin20.4-clang"\n\ -ar = "x86_64-apple-darwin20.4-ar"\n\ -' > /root/.cargo/config.toml - -# Copy the source code -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -# Build for x86_64 macOS -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent -p gigacode --release --target x86_64-apple-darwin && \ - mkdir -p /artifacts && \ - cp target/x86_64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-x86_64-apple-darwin && \ - cp target/x86_64-apple-darwin/release/gigacode /artifacts/gigacode-x86_64-apple-darwin - -# Default command to show help -CMD ["ls", "-la", "/artifacts"] diff --git a/docker/release/windows.Dockerfile b/docker/release/windows.Dockerfile deleted file mode 100644 index 9c7694d0..00000000 --- a/docker/release/windows.Dockerfile +++ /dev/null @@ -1,102 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# Build inspector frontend -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -FROM rust:1.88.0 - -# Accept version as build arg -ARG SANDBOX_AGENT_VERSION -ENV SANDBOX_AGENT_VERSION=${SANDBOX_AGENT_VERSION} - -# Install dependencies -RUN apt-get update && apt-get install -y \ - llvm-14-dev \ - libclang-14-dev \ - clang-14 \ - gcc-mingw-w64-x86-64 \ - g++-mingw-w64-x86-64 \ - binutils-mingw-w64-x86-64 \ - ca-certificates \ - curl \ - git && \ - rm -rf /var/lib/apt/lists/* - -# Switch MinGW-w64 to the POSIX threading model toolchain -RUN update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix && \ - update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - -# Install target -RUN rustup target add x86_64-pc-windows-gnu - -# Configure Cargo for Windows cross-compilation -RUN mkdir -p /root/.cargo && \ - echo '\ -[target.x86_64-pc-windows-gnu]\n\ -linker = "x86_64-w64-mingw32-gcc"\n\ -' > /root/.cargo/config.toml - -# Set environment variables for cross-compilation -ENV CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc \ - CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc \ - CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++ \ - CC_x86_64-pc-windows-gnu=x86_64-w64-mingw32-gcc \ - CXX_x86_64-pc-windows-gnu=x86_64-w64-mingw32-g++ \ - LIBCLANG_PATH=/usr/lib/llvm-14/lib \ - CLANG_PATH=/usr/bin/clang-14 \ - CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true - -# Set working directory -WORKDIR /build - -# Copy the source code -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -# Build for Windows -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent -p gigacode --release --target x86_64-pc-windows-gnu && \ - mkdir -p /artifacts && \ - cp target/x86_64-pc-windows-gnu/release/sandbox-agent.exe /artifacts/sandbox-agent-x86_64-pc-windows-gnu.exe && \ - cp target/x86_64-pc-windows-gnu/release/gigacode.exe /artifacts/gigacode-x86_64-pc-windows-gnu.exe - -# Default command to show help -CMD ["ls", "-la", "/artifacts"] diff --git a/docker/runtime/Dockerfile b/docker/runtime/Dockerfile deleted file mode 100644 index 27b95607..00000000 --- a/docker/runtime/Dockerfile +++ /dev/null @@ -1,170 +0,0 @@ -# syntax=docker/dockerfile:1.10.0 - -# ============================================================================ -# Build inspector frontend -# ============================================================================ -FROM node:22-alpine AS inspector-build -WORKDIR /app -RUN npm install -g pnpm - -# Copy package files for workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ -COPY sdks/typescript/package.json ./sdks/typescript/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy SDK source (with pre-generated types from docs/openapi.json) -COPY docs/openapi.json ./docs/ -COPY sdks/cli-shared ./sdks/cli-shared -COPY sdks/acp-http-client ./sdks/acp-http-client -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -COPY sdks/react ./sdks/react -COPY sdks/typescript ./sdks/typescript - -# Build cli-shared, acp-http-client, SDK, then persist-indexeddb and react (depends on SDK) -RUN cd sdks/cli-shared && pnpm exec tsup -RUN cd sdks/acp-http-client && pnpm exec tsup -RUN cd sdks/typescript && SKIP_OPENAPI_GEN=1 pnpm exec tsup -RUN cd sdks/persist-indexeddb && pnpm exec tsup -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source and build -COPY frontend/packages/inspector ./frontend/packages/inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -# ============================================================================ -# AMD64 Builder - Uses cross-tools musl toolchain -# ============================================================================ -FROM --platform=linux/amd64 rust:1.88.0 AS builder-amd64 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y \ - musl-tools \ - musl-dev \ - llvm-14-dev \ - libclang-14-dev \ - clang-14 \ - libssl-dev \ - pkg-config \ - ca-certificates \ - g++ \ - g++-multilib \ - git \ - curl \ - wget && \ - rm -rf /var/lib/apt/lists/* - -# Download cross-tools musl toolchain -RUN wget -q https://github.com/cross-tools/musl-cross/releases/latest/download/x86_64-unknown-linux-musl.tar.xz && \ - tar -xf x86_64-unknown-linux-musl.tar.xz -C /opt/ && \ - rm x86_64-unknown-linux-musl.tar.xz && \ - rustup target add x86_64-unknown-linux-musl - -ENV PATH="/opt/x86_64-unknown-linux-musl/bin:$PATH" \ - LIBCLANG_PATH=/usr/lib/llvm-14/lib \ - CLANG_PATH=/usr/bin/clang-14 \ - CC_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-gcc \ - CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ \ - AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar \ - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc \ - CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true - -# Build OpenSSL for musl -ENV SSL_VER=1.1.1w -RUN wget https://www.openssl.org/source/openssl-$SSL_VER.tar.gz && \ - tar -xzf openssl-$SSL_VER.tar.gz && \ - cd openssl-$SSL_VER && \ - ./Configure no-shared no-async --prefix=/musl --openssldir=/musl/ssl linux-x86_64 && \ - make -j$(nproc) && \ - make install_sw && \ - cd .. && \ - rm -rf openssl-$SSL_VER* - -ENV OPENSSL_DIR=/musl \ - OPENSSL_INCLUDE_DIR=/musl/include \ - OPENSSL_LIB_DIR=/musl/lib \ - PKG_CONFIG_ALLOW_CROSS=1 \ - RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-static-libgcc" - -WORKDIR /build -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent --release --target x86_64-unknown-linux-musl && \ - cp target/x86_64-unknown-linux-musl/release/sandbox-agent /sandbox-agent - -# ============================================================================ -# ARM64 Builder - Uses Alpine with native musl -# ============================================================================ -FROM --platform=linux/arm64 rust:1.88-alpine AS builder-arm64 - -RUN apk add --no-cache \ - musl-dev \ - clang \ - llvm-dev \ - openssl-dev \ - openssl-libs-static \ - pkgconfig \ - git \ - curl \ - build-base - -RUN rustup target add aarch64-unknown-linux-musl - -ENV CARGO_INCREMENTAL=0 \ - CARGO_NET_GIT_FETCH_WITH_CLI=true \ - RUSTFLAGS="-C target-feature=+crt-static" - -WORKDIR /build -COPY . . - -# Copy pre-built inspector frontend -COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/packages/inspector/dist - -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/usr/local/cargo/git \ - --mount=type=cache,target=/build/target \ - cargo build -p sandbox-agent --release --target aarch64-unknown-linux-musl && \ - cp target/aarch64-unknown-linux-musl/release/sandbox-agent /sandbox-agent - -# ============================================================================ -# Select the appropriate builder based on target architecture -# ============================================================================ -ARG TARGETARCH -FROM builder-${TARGETARCH} AS builder - -# Runtime stage - minimal image -FROM debian:bookworm-slim - -RUN apt-get update && apt-get install -y \ - ca-certificates \ - curl \ - git && \ - rm -rf /var/lib/apt/lists/* - -# Copy the binary from builder -COPY --from=builder /sandbox-agent /usr/local/bin/sandbox-agent -RUN chmod +x /usr/local/bin/sandbox-agent - -# Create non-root user -RUN useradd -m -s /bin/bash sandbox -USER sandbox -WORKDIR /home/sandbox - -EXPOSE 2468 - -ENTRYPOINT ["sandbox-agent"] -CMD ["--host", "0.0.0.0", "--port", "2468"] diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md deleted file mode 120000 index 681311eb..00000000 --- a/frontend/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md deleted file mode 100644 index c4515dc0..00000000 --- a/frontend/CLAUDE.md +++ /dev/null @@ -1,4 +0,0 @@ -# Frontend Instructions - -- 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/Dockerfile b/frontend/packages/inspector/Dockerfile deleted file mode 100644 index dff28db0..00000000 --- a/frontend/packages/inspector/Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -FROM node:22-alpine AS build -WORKDIR /app -RUN npm install -g pnpm@9 - -# Copy package files for all workspaces -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend/packages/inspector/package.json ./frontend/packages/inspector/ -COPY sdks/typescript/package.json ./sdks/typescript/ -COPY sdks/cli-shared/package.json ./sdks/cli-shared/ -COPY sdks/acp-http-client/package.json ./sdks/acp-http-client/ -COPY sdks/persist-indexeddb/package.json ./sdks/persist-indexeddb/ -COPY sdks/react/package.json ./sdks/react/ - -# Install dependencies -RUN pnpm install --filter @sandbox-agent/inspector... - -# Copy cli-shared source and build it -COPY sdks/cli-shared ./sdks/cli-shared -RUN cd sdks/cli-shared && pnpm exec tsup - -# Copy acp-http-client source and build it -COPY sdks/acp-http-client ./sdks/acp-http-client -RUN cd sdks/acp-http-client && pnpm exec tsup - -# Copy SDK source (with pre-generated types) and build -COPY sdks/typescript ./sdks/typescript -RUN cd sdks/typescript && pnpm exec tsup - -# Copy persist-indexeddb and build (depends on SDK) -COPY sdks/persist-indexeddb ./sdks/persist-indexeddb -RUN cd sdks/persist-indexeddb && pnpm exec tsup - -# Copy react and build (depends on SDK) -COPY sdks/react ./sdks/react -RUN cd sdks/react && pnpm exec tsup - -# Copy inspector source -COPY frontend/packages/inspector ./frontend/packages/inspector - -# Build inspector -RUN cd frontend/packages/inspector && pnpm exec vite build - -FROM caddy:alpine -COPY --from=build /app/frontend/packages/inspector/dist /srv/ui -RUN cat > /etc/caddy/Caddyfile <<'EOF' -:80 { - root * /srv - file_server - try_files {path} /ui/index.html -} -EOF -EXPOSE 80 diff --git a/frontend/packages/inspector/index.html b/frontend/packages/inspector/index.html deleted file mode 100644 index 8a2080bd..00000000 --- a/frontend/packages/inspector/index.html +++ /dev/null @@ -1,3561 +0,0 @@ - - - - - - - Sandbox Agent - - - - - - - -
- - - diff --git a/frontend/packages/inspector/package.json b/frontend/packages/inspector/package.json deleted file mode 100644 index 9671ecb7..00000000 --- a/frontend/packages/inspector/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@sandbox-agent/inspector", - "private": true, - "version": "0.0.0", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "SKIP_OPENAPI_GEN=1 pnpm --filter @sandbox-agent/persist-indexeddb build && pnpm --filter @sandbox-agent/react build && vite build", - "preview": "vite preview", - "typecheck": "SKIP_OPENAPI_GEN=1 pnpm --filter @sandbox-agent/persist-indexeddb build && pnpm --filter @sandbox-agent/react build && tsc --noEmit", - "test": "SKIP_OPENAPI_GEN=1 pnpm --filter @sandbox-agent/persist-indexeddb build && pnpm --filter @sandbox-agent/react build && vitest run" - }, - "devDependencies": { - "@sandbox-agent/react": "workspace:*", - "sandbox-agent": "workspace:*", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "fake-indexeddb": "^6.2.4", - "typescript": "^5.7.3", - "vite": "^5.4.7", - "vitest": "^3.0.0" - }, - "dependencies": { - "@sandbox-agent/persist-indexeddb": "workspace:*", - "lucide-react": "^0.469.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" - } -} diff --git a/frontend/packages/inspector/public/favicon.svg b/frontend/packages/inspector/public/favicon.svg deleted file mode 100644 index 287c4257..00000000 --- a/frontend/packages/inspector/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/packages/inspector/public/logos/amp.svg b/frontend/packages/inspector/public/logos/amp.svg deleted file mode 100644 index 624c311d..00000000 --- a/frontend/packages/inspector/public/logos/amp.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/packages/inspector/public/logos/claude.svg b/frontend/packages/inspector/public/logos/claude.svg deleted file mode 100644 index 879ad812..00000000 --- a/frontend/packages/inspector/public/logos/claude.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/frontend/packages/inspector/public/logos/openai.svg b/frontend/packages/inspector/public/logos/openai.svg deleted file mode 100644 index ee3125f5..00000000 --- a/frontend/packages/inspector/public/logos/openai.svg +++ /dev/null @@ -1,2 +0,0 @@ - -OpenAI icon \ No newline at end of file diff --git a/frontend/packages/inspector/public/logos/opencode.svg b/frontend/packages/inspector/public/logos/opencode.svg deleted file mode 100644 index c2404f26..00000000 --- a/frontend/packages/inspector/public/logos/opencode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/packages/inspector/public/logos/pi.svg b/frontend/packages/inspector/public/logos/pi.svg deleted file mode 100644 index ed14b638..00000000 --- a/frontend/packages/inspector/public/logos/pi.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/frontend/packages/inspector/public/logos/sandboxagent.svg b/frontend/packages/inspector/public/logos/sandboxagent.svg deleted file mode 100644 index f4704bd5..00000000 --- a/frontend/packages/inspector/public/logos/sandboxagent.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/packages/inspector/src/App.tsx b/frontend/packages/inspector/src/App.tsx deleted file mode 100644 index 2dfe24dc..00000000 --- a/frontend/packages/inspector/src/App.tsx +++ /dev/null @@ -1,1715 +0,0 @@ -import { BookOpen } from "lucide-react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { - SandboxAgent, - SandboxAgentError, - type AgentInfo, - type SessionEvent, - type Session, - InMemorySessionPersistDriver, - type SessionPersistDriver, -} from "sandbox-agent"; - -type ConfigSelectOption = { value: string; name: string; description?: string }; -type ConfigOption = { - id: string; - name: string; - category?: string; - type?: string; - currentValue?: string; - options?: ConfigSelectOption[] | Array<{ group: string; name: string; options: ConfigSelectOption[] }>; -}; -type AgentModeInfo = { id: string; name: string; description: string }; -type AgentModelInfo = { id: string; name?: string }; -import { IndexedDbSessionPersistDriver } from "@sandbox-agent/persist-indexeddb"; -import ChatPanel from "./components/chat/ChatPanel"; -import type { TimelineEntry } from "./components/chat/types"; -import ConnectScreen from "./components/ConnectScreen"; -import DebugPanel, { type DebugTab } from "./components/debug/DebugPanel"; -import SessionSidebar from "./components/SessionSidebar"; -import type { RequestLog } from "./types/requestLog"; -import { buildCurl } from "./utils/http"; - -const flattenSelectOptions = ( - options: ConfigSelectOption[] | Array<{ group: string; name: string; options: ConfigSelectOption[] }> -): ConfigSelectOption[] => { - if (options.length === 0) return []; - if ("value" in options[0]) return options as ConfigSelectOption[]; - return (options as Array<{ options: ConfigSelectOption[] }>).flatMap((g) => g.options); -}; - -const logoUrl = `${import.meta.env.BASE_URL}logos/sandboxagent.svg`; -const defaultAgents = ["claude", "codex", "opencode", "amp", "pi", "cursor"]; - -type ErrorToast = { - id: number; - message: string; -}; - -type SessionListItem = { - sessionId: string; - agent: string; - ended: boolean; - archived: boolean; -}; - -const ERROR_TOAST_MS = 6000; -const MAX_ERROR_TOASTS = 3; -const CREATE_SESSION_SLOW_WARNING_MS = 90_000; -const HTTP_ERROR_EVENT = "inspector-http-error"; -const ARCHIVED_SESSIONS_KEY = "sandbox-agent-inspector-archived-sessions"; -const SESSION_MODELS_KEY = "sandbox-agent-inspector-session-models"; - -const DEFAULT_ENDPOINT = "http://localhost:2468"; - -const getCurrentOriginEndpoint = () => { - if (typeof window === "undefined") { - return null; - } - return window.location.origin; -}; - -const getErrorMessage = (error: unknown, fallback: string) => { - if (error instanceof SandboxAgentError) { - return error.problem?.detail ?? error.problem?.title ?? error.message; - } - if (error instanceof Error) { - // ACP RequestError may carry a data object with a hint or details field. - const data = (error as { data?: Record }).data; - if (data && typeof data === "object") { - const hint = typeof data.hint === "string" ? data.hint : null; - const details = typeof data.details === "string" ? data.details : null; - if (hint) return hint; - if (details) return details; - } - return error.message; - } - return fallback; -}; - -const getHttpErrorMessage = (status: number, statusText: string, responseBody: string) => { - const base = statusText ? `HTTP ${status} ${statusText}` : `HTTP ${status}`; - const body = responseBody.trim(); - if (!body) { - return base; - } - try { - const parsed = JSON.parse(body); - if (parsed && typeof parsed === "object") { - const detail = (parsed as { detail?: unknown }).detail; - if (typeof detail === "string" && detail.trim()) { - return detail; - } - const title = (parsed as { title?: unknown }).title; - if (typeof title === "string" && title.trim()) { - return title; - } - const message = (parsed as { message?: unknown }).message; - if (typeof message === "string" && message.trim()) { - return message; - } - } - } catch { - // Ignore parse failures and fall through to body text. - } - const clippedBody = body.length > 240 ? `${body.slice(0, 240)}...` : body; - return `${base}: ${clippedBody}`; -}; - -const shouldIgnoreGlobalError = (value: unknown): boolean => { - const name = value instanceof Error ? value.name : ""; - const message = (() => { - if (typeof value === "string") return value; - if (value instanceof Error) return value.message; - if (value && typeof value === "object" && "message" in value && typeof (value as { message?: unknown }).message === "string") { - return (value as { message: string }).message; - } - return ""; - })().toLowerCase(); - - if (name === "AbortError") return true; - if (!message) return false; - - return ( - message.includes("aborterror") || - message.includes("the operation was aborted") || - message.includes("signal is aborted") || - message.includes("acp client is closed") || - (message.includes("method not found") && message.includes("unstable/set_session_model")) || - message.includes("resizeobserver loop limit exceeded") || - message.includes("resizeobserver loop completed with undelivered notifications") - ); -}; - -const getSessionIdFromPath = (): string => { - const basePath = import.meta.env.BASE_URL; - const path = window.location.pathname; - const relative = path.startsWith(basePath) ? path.slice(basePath.length) : path; - const match = relative.match(/^sessions\/(.+)/); - return match ? match[1] : ""; -}; - -const getArchivedSessionIds = (): Set => { - if (typeof window === "undefined") return new Set(); - try { - const raw = window.localStorage.getItem(ARCHIVED_SESSIONS_KEY); - if (!raw) return new Set(); - const parsed = JSON.parse(raw); - if (!Array.isArray(parsed)) return new Set(); - return new Set(parsed.filter((value): value is string => typeof value === "string" && value.length > 0)); - } catch { - return new Set(); - } -}; - -const archiveSessionId = (id: string): void => { - if (typeof window === "undefined" || !id) return; - const archived = getArchivedSessionIds(); - archived.add(id); - window.localStorage.setItem(ARCHIVED_SESSIONS_KEY, JSON.stringify([...archived])); -}; - -const unarchiveSessionId = (id: string): void => { - if (typeof window === "undefined" || !id) return; - const archived = getArchivedSessionIds(); - if (!archived.delete(id)) return; - window.localStorage.setItem(ARCHIVED_SESSIONS_KEY, JSON.stringify([...archived])); -}; - -const getPersistedSessionModels = (): Record => { - if (typeof window === "undefined") return {}; - try { - const raw = window.localStorage.getItem(SESSION_MODELS_KEY); - if (!raw) return {}; - const parsed = JSON.parse(raw); - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {}; - return Object.fromEntries( - Object.entries(parsed).filter( - (entry): entry is [string, string] => typeof entry[0] === "string" && typeof entry[1] === "string" && entry[1].length > 0 - ) - ); - } catch { - return {}; - } -}; - -const updateSessionPath = (id: string) => { - const basePath = import.meta.env.BASE_URL; - const params = window.location.search; - const newPath = id ? `${basePath}sessions/${id}${params}` : `${basePath}${params}`; - if (window.location.pathname + window.location.search !== newPath) { - window.history.replaceState(null, "", newPath); - } -}; - -const areEventsEqualById = (a: SessionEvent[], b: SessionEvent[]): boolean => { - if (a === b) return true; - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i += 1) { - if (a[i]?.id !== b[i]?.id) return false; - } - return true; -}; - -const getInitialConnection = () => { - if (typeof window === "undefined") { - return { endpoint: "http://127.0.0.1:2468", token: "", headers: {} as Record, hasUrlParam: false }; - } - const params = new URLSearchParams(window.location.search); - const urlParam = params.get("url")?.trim(); - const tokenParam = params.get("token") ?? ""; - const headersParam = params.get("headers"); - let headers: Record = {}; - if (headersParam) { - try { - headers = JSON.parse(headersParam); - } catch { - console.warn("Invalid headers query param, ignoring"); - } - } - const hasUrlParam = urlParam != null && urlParam.length > 0; - const defaultEndpoint = import.meta.env.DEV - ? DEFAULT_ENDPOINT - : (getCurrentOriginEndpoint() ?? DEFAULT_ENDPOINT); - return { - endpoint: hasUrlParam ? urlParam : defaultEndpoint, - token: tokenParam, - headers, - hasUrlParam - }; -}; - -const agentDisplayNames: Record = { - claude: "Claude Code", - codex: "Codex", - opencode: "OpenCode", - amp: "Amp", - pi: "Pi", - cursor: "Cursor" -}; - -export default function App() { - const issueTrackerUrl = "https://github.com/rivet-dev/sandbox-agent/issues"; - const docsUrl = "https://sandboxagent.dev/docs"; - const discordUrl = "https://rivet.dev/discord"; - const initialConnectionRef = useRef(getInitialConnection()); - const [endpoint, setEndpoint] = useState(initialConnectionRef.current.endpoint); - const [token, setToken] = useState(initialConnectionRef.current.token); - const [extraHeaders] = useState(initialConnectionRef.current.headers); - const [connected, setConnected] = useState(false); - const [connecting, setConnecting] = useState(false); - const [connectError, setConnectError] = useState(null); - - const [agents, setAgents] = useState([]); - const [sessions, setSessions] = useState([]); - const [agentsLoading, setAgentsLoading] = useState(false); - const [agentsError, setAgentsError] = useState(null); - const [sessionsLoading, setSessionsLoading] = useState(false); - const [sessionsError, setSessionsError] = useState(null); - - const [agentId, setAgentId] = useState("claude"); - const [sessionId, setSessionId] = useState(getSessionIdFromPath()); - const [sessionError, setSessionError] = useState(null); - const [sessionModelById, setSessionModelById] = useState>(() => getPersistedSessionModels()); - - const [message, setMessage] = useState(""); - const [events, setEvents] = useState([]); - const [sendingSessionId, setSendingSessionId] = useState(null); - const [historyLoadingSessionId, setHistoryLoadingSessionId] = useState(null); - - const [requestLog, setRequestLog] = useState([]); - const logIdRef = useRef(1); - const [copiedLogId, setCopiedLogId] = useState(null); - const [errorToasts, setErrorToasts] = useState([]); - const toastIdRef = useRef(1); - const toastTimeoutsRef = useRef>(new Map()); - const toastExpiryRef = useRef>(new Map()); - const toastRemainingMsRef = useRef>(new Map()); - - const [debugTab, setDebugTab] = useState("events"); - const [highlightedEventId, setHighlightedEventId] = useState(null); - const [debugPanelCollapsed, setDebugPanelCollapsed] = useState(false); - - const messagesEndRef = useRef(null); - - const clientRef = useRef(null); - const activeSessionRef = useRef(null); - const eventUnsubRef = useRef<(() => void) | null>(null); - const sessionEventsCacheRef = useRef>(new Map()); - const selectedSessionIdRef = useRef(sessionId); - const resumeInFlightSessionIdRef = useRef(null); - const subscriptionGenerationRef = useRef(0); - const reconnectingAfterCreateFailureRef = useRef(false); - const creatingSessionRef = useRef(false); - const createNoiseIgnoreUntilRef = useRef(0); - - useEffect(() => { - selectedSessionIdRef.current = sessionId; - if (!sessionId) { - setHistoryLoadingSessionId(null); - } - }, [sessionId]); - - const logRequest = useCallback((entry: RequestLog) => { - setRequestLog((prev) => { - const next = [entry, ...prev]; - return next.slice(0, 200); - }); - }, []); - - const createClient = useCallback(async (overrideEndpoint?: string) => { - const targetEndpoint = overrideEndpoint ?? endpoint; - const fetchWithLog: typeof fetch = async (input, init) => { - const method = init?.method ?? "GET"; - const url = - typeof input === "string" - ? input - : input instanceof URL - ? input.toString() - : input.url; - const bodyText = typeof init?.body === "string" ? init.body : undefined; - const curl = buildCurl(method, url, bodyText, token); - const logId = logIdRef.current++; - - const headers: Record = {}; - if (init?.headers) { - const h = new Headers(init.headers as HeadersInit); - h.forEach((v, k) => { headers[k] = v; }); - } - - const entry: RequestLog = { - id: logId, - method, - url, - headers, - body: bodyText, - time: new Date().toLocaleTimeString(), - curl - }; - let logged = false; - - const fetchInit = { - ...init, - targetAddressSpace: "loopback" - }; - - try { - const response = await fetch(input, fetchInit); - const acceptsStream = headers["accept"]?.includes("text/event-stream"); - if (acceptsStream) { - const ct = response.headers.get("content-type") ?? ""; - if (!ct.includes("text/event-stream")) { - throw new Error( - `Expected text/event-stream from ${method} ${url} but got ${ct || "(no content-type)"} (HTTP ${response.status})` - ); - } - logRequest({ ...entry, status: response.status, responseBody: "(SSE stream)" }); - logged = true; - return response; - } - const clone = response.clone(); - const responseBody = await clone.text().catch(() => ""); - logRequest({ ...entry, status: response.status, responseBody }); - if (!response.ok && response.status >= 500) { - const messageText = getHttpErrorMessage(response.status, response.statusText, responseBody); - window.dispatchEvent(new CustomEvent(HTTP_ERROR_EVENT, { detail: messageText })); - } - logged = true; - return response; - } catch (error) { - const messageText = error instanceof Error ? error.message : "Request failed"; - if (!logged) { - logRequest({ ...entry, status: 0, error: messageText }); - } - throw error; - } - }; - - let persist: SessionPersistDriver; - try { - persist = new IndexedDbSessionPersistDriver({ - databaseName: "sandbox-agent-inspector", - }); - } catch { - persist = new InMemorySessionPersistDriver({ - maxSessions: 512, - maxEventsPerSession: 5_000, - }); - } - - const client = await SandboxAgent.connect({ - baseUrl: targetEndpoint, - token: token || undefined, - fetch: fetchWithLog, - headers: Object.keys(extraHeaders).length > 0 ? extraHeaders : undefined, - persist, - }); - clientRef.current = client; - return client; - }, [endpoint, token, extraHeaders, logRequest]); - - const getClient = useCallback((): SandboxAgent => { - if (!clientRef.current) { - throw new Error("Not connected"); - } - return clientRef.current; - }, []); - - const dismissErrorToast = useCallback((toastId: number) => { - const timeoutId = toastTimeoutsRef.current.get(toastId); - if (timeoutId != null) { - window.clearTimeout(timeoutId); - toastTimeoutsRef.current.delete(toastId); - } - toastExpiryRef.current.delete(toastId); - toastRemainingMsRef.current.delete(toastId); - setErrorToasts((prev) => prev.filter((toast) => toast.id !== toastId)); - }, []); - - const scheduleErrorToastDismiss = useCallback((toastId: number, delayMs: number) => { - const existingTimeoutId = toastTimeoutsRef.current.get(toastId); - if (existingTimeoutId != null) { - window.clearTimeout(existingTimeoutId); - toastTimeoutsRef.current.delete(toastId); - } - - const clampedDelayMs = Math.max(0, delayMs); - const timeoutId = window.setTimeout(() => { - dismissErrorToast(toastId); - }, clampedDelayMs); - - toastTimeoutsRef.current.set(toastId, timeoutId); - toastExpiryRef.current.set(toastId, Date.now() + clampedDelayMs); - toastRemainingMsRef.current.set(toastId, clampedDelayMs); - }, [dismissErrorToast]); - - const pauseErrorToastDismiss = useCallback((toastId: number) => { - const expiryMs = toastExpiryRef.current.get(toastId); - if (expiryMs == null) return; - const remainingMs = Math.max(0, expiryMs - Date.now()); - toastRemainingMsRef.current.set(toastId, remainingMs); - - const timeoutId = toastTimeoutsRef.current.get(toastId); - if (timeoutId != null) { - window.clearTimeout(timeoutId); - toastTimeoutsRef.current.delete(toastId); - } - toastExpiryRef.current.delete(toastId); - }, []); - - const resumeErrorToastDismiss = useCallback((toastId: number) => { - if (toastTimeoutsRef.current.has(toastId)) return; - const remainingMs = toastRemainingMsRef.current.get(toastId); - if (remainingMs == null) return; - scheduleErrorToastDismiss(toastId, remainingMs); - }, [scheduleErrorToastDismiss]); - - const pushErrorToast = useCallback((error: unknown, fallback: string) => { - const messageText = getErrorMessage(error, fallback).trim() || fallback; - const toastId = toastIdRef.current++; - setErrorToasts((prev) => { - if (prev.some((toast) => toast.message === messageText)) { - return prev; - } - return [...prev, { id: toastId, message: messageText }].slice(-MAX_ERROR_TOASTS); - }); - scheduleErrorToastDismiss(toastId, ERROR_TOAST_MS); - }, [scheduleErrorToastDismiss]); - - // Subscribe to events for the current active session - const subscribeToSession = useCallback((session: Session) => { - const generation = ++subscriptionGenerationRef.current; - const isCurrentSubscription = (): boolean => - subscriptionGenerationRef.current === generation - && activeSessionRef.current?.id === session.id - && selectedSessionIdRef.current === session.id; - - // Unsubscribe from previous - if (eventUnsubRef.current) { - eventUnsubRef.current(); - eventUnsubRef.current = null; - } - - activeSessionRef.current = session; - const cachedEvents = sessionEventsCacheRef.current.get(session.id); - if (cachedEvents && isCurrentSubscription()) { - setEvents(cachedEvents); - setHistoryLoadingSessionId((current) => (current === session.id ? null : current)); - } else if (isCurrentSubscription()) { - setHistoryLoadingSessionId(session.id); - } - - // Hydrate existing events from persistence - const hydrateEvents = async () => { - const allEvents: SessionEvent[] = []; - let cursor: string | undefined; - while (true) { - const page = await getClient().getEvents({ - sessionId: session.id, - cursor, - limit: 250, - }); - allEvents.push(...page.items); - if (!page.nextCursor) break; - cursor = page.nextCursor; - } - sessionEventsCacheRef.current.set(session.id, allEvents); - if (!isCurrentSubscription()) return; - setEvents((prev) => (areEventsEqualById(prev, allEvents) ? prev : allEvents)); - setHistoryLoadingSessionId((current) => (current === session.id ? null : current)); - }; - hydrateEvents().catch((error) => { - console.error("Failed to hydrate events:", error); - if (isCurrentSubscription()) { - setHistoryLoadingSessionId((current) => (current === session.id ? null : current)); - } - }); - - // Subscribe to new events - const unsub = session.onEvent((event) => { - if (!isCurrentSubscription()) return; - setEvents((prev) => { - if (prev.some((existing) => existing.id === event.id)) { - return prev; - } - const next = [...prev, event]; - sessionEventsCacheRef.current.set(session.id, next); - return next; - }); - }); - eventUnsubRef.current = unsub; - }, [getClient]); - - const connectToDaemon = async (reportError: boolean, overrideEndpoint?: string) => { - setConnecting(true); - if (reportError) { - setConnectError(null); - } - try { - // Ensure reconnects do not keep stale session subscriptions/clients around. - if (eventUnsubRef.current) { - eventUnsubRef.current(); - eventUnsubRef.current = null; - } - subscriptionGenerationRef.current += 1; - activeSessionRef.current = null; - if (clientRef.current) { - try { - await clientRef.current.dispose(); - } catch (disposeError) { - console.warn("Failed to dispose previous client during reconnect:", disposeError); - } finally { - clientRef.current = null; - } - } - - const client = await createClient(overrideEndpoint); - await client.getHealth(); - if (overrideEndpoint) { - setEndpoint(overrideEndpoint); - } - setConnected(true); - await refreshAgents(); - await fetchSessions(); - if (sessionId) { - try { - const resumed = await client.resumeSession(sessionId); - subscribeToSession(resumed); - } catch (resumeError) { - console.warn("Failed to resume current session after reconnect:", resumeError); - setHistoryLoadingSessionId(null); - } - } - if (reportError) { - setConnectError(null); - } - } catch (error) { - if (reportError) { - const messageText = getErrorMessage(error, "Unable to connect"); - setConnectError(messageText); - } - setConnected(false); - clientRef.current = null; - throw error; - } finally { - setConnecting(false); - } - }; - - const connect = () => connectToDaemon(true); - - const disconnect = () => { - if (eventUnsubRef.current) { - eventUnsubRef.current(); - eventUnsubRef.current = null; - } - subscriptionGenerationRef.current += 1; - activeSessionRef.current = null; - if (clientRef.current) { - void clientRef.current.dispose().catch((error) => { - console.warn("Failed to dispose client on disconnect:", error); - }); - } - setConnected(false); - clientRef.current = null; - setSessionError(null); - setEvents([]); - setHistoryLoadingSessionId(null); - setAgents([]); - setSessions([]); - sessionEventsCacheRef.current.clear(); - setAgentsLoading(false); - setSessionsLoading(false); - setAgentsError(null); - setSessionsError(null); - }; - - const refreshAgents = async () => { - setAgentsLoading(true); - setAgentsError(null); - try { - const data = await getClient().listAgents(); - setAgents(data.agents ?? []); - } catch (error) { - setAgentsError(getErrorMessage(error, "Unable to refresh agents")); - } finally { - setAgentsLoading(false); - } - }; - - const loadAgentConfig = useCallback(async (targetAgentId: string) => { - console.log("[loadAgentConfig] Loading config for agent:", targetAgentId); - try { - const info = await getClient().getAgent(targetAgentId, { config: true }); - console.log("[loadAgentConfig] Got agent info:", info); - setAgents((prev) => - prev.map((a) => (a.id === targetAgentId ? { ...a, configOptions: info.configOptions, configError: info.configError } : a)) - ); - } catch (error) { - console.error("[loadAgentConfig] Failed to load config:", error); - // Config loading is best-effort; the menu still works without it. - } - }, [getClient]); - - const fetchSessions = async () => { - setSessionsLoading(true); - setSessionsError(null); - try { - const archivedSessionIds = getArchivedSessionIds(); - // TODO: This eagerly paginates all sessions so we can reverse-sort to - // show newest first. Replace with a server-side descending sort or a - // dedicated "recent sessions" query once the API supports it. - const all: SessionListItem[] = []; - let cursor: string | undefined; - do { - const page = await getClient().listSessions({ cursor, limit: 200 }); - for (const s of page.items) { - all.push({ - sessionId: s.id, - agent: s.agent, - ended: s.destroyedAt != null, - archived: archivedSessionIds.has(s.id), - }); - } - cursor = page.nextCursor; - } while (cursor); - all.reverse(); - setSessions(all); - } catch { - setSessionsError("Unable to load sessions."); - } finally { - setSessionsLoading(false); - } - }; - - const archiveSession = async (targetSessionId: string) => { - archiveSessionId(targetSessionId); - try { - try { - await getClient().destroySession(targetSessionId); - } catch (error) { - // If the server already considers the session gone, still archive in local UI. - console.warn("Destroy session returned an error while archiving:", error); - } - setSessions((prev) => - prev.map((session) => - session.sessionId === targetSessionId - ? { ...session, archived: true, ended: true } - : session - ) - ); - setSessionModelById((prev) => { - if (!(targetSessionId in prev)) return prev; - const next = { ...prev }; - delete next[targetSessionId]; - return next; - }); - await fetchSessions(); - } catch (error) { - console.error("Failed to archive session:", error); - } - }; - - const unarchiveSession = async (targetSessionId: string) => { - unarchiveSessionId(targetSessionId); - setSessions((prev) => - prev.map((session) => - session.sessionId === targetSessionId ? { ...session, archived: false } : session - ) - ); - await fetchSessions(); - }; - - const installAgent = async (targetId: string, reinstall: boolean) => { - try { - await getClient().installAgent(targetId, { reinstall }); - await refreshAgents(); - } catch (error) { - setConnectError(getErrorMessage(error, "Install failed")); - } - }; - - const sendMessage = async () => { - const prompt = message.trim(); - if (!prompt || !sessionId || sendingSessionId) return; - const targetSessionId = sessionId; - setSessionError(null); - setMessage(""); - setSendingSessionId(targetSessionId); - - try { - let session = activeSessionRef.current; - if (!session || session.id !== targetSessionId) { - session = await getClient().resumeSession(targetSessionId); - subscribeToSession(session); - } - await session.prompt([{ type: "text", text: prompt }]); - } catch (error) { - setSessionError(getErrorMessage(error, "Unable to send message")); - } finally { - setSendingSessionId(null); - } - }; - - const selectSession = (session: SessionListItem) => { - setSessionId(session.sessionId); - selectedSessionIdRef.current = session.sessionId; - updateSessionPath(session.sessionId); - setAgentId(session.agent); - setSessionModelById((prev) => { - if (prev[session.sessionId]) return prev; - const fallbackModel = defaultModelByAgent[session.agent]; - if (!fallbackModel) return prev; - return { ...prev, [session.sessionId]: fallbackModel }; - }); - const cachedEvents = sessionEventsCacheRef.current.get(session.sessionId); - if (cachedEvents) { - setEvents(cachedEvents); - setHistoryLoadingSessionId(null); - } else { - setEvents([]); - setHistoryLoadingSessionId(session.sessionId); - } - setSessionError(null); - }; - - const createNewSession = async (nextAgentId: string, config: { agentMode: string; model: string }) => { - console.log("[createNewSession] Creating session for agent:", nextAgentId, "config:", config); - setSessionError(null); - creatingSessionRef.current = true; - createNoiseIgnoreUntilRef.current = Date.now() + 10_000; - - try { - console.log("[createNewSession] Calling createSession..."); - const createSessionPromise = getClient().createSession({ - agent: nextAgentId, - sessionInit: { - cwd: "/", - mcpServers: [], - }, - }); - - let slowWarningShown = false; - const slowWarningTimerId = window.setTimeout(() => { - slowWarningShown = true; - setSessionError("Session creation is taking longer than expected. Waiting for agent startup..."); - }, CREATE_SESSION_SLOW_WARNING_MS); - let session: Awaited>; - try { - session = await createSessionPromise; - } finally { - window.clearTimeout(slowWarningTimerId); - } - console.log("[createNewSession] Session created:", session.id); - if (slowWarningShown) { - setSessionError(null); - } - - setAgentId(nextAgentId); - setEvents([]); - setHistoryLoadingSessionId(null); - setSessionId(session.id); - selectedSessionIdRef.current = session.id; - updateSessionPath(session.id); - sessionEventsCacheRef.current.set(session.id, []); - subscribeToSession(session); - const skipPostCreateConfig = nextAgentId === "opencode"; - - // Apply mode if selected - if (!skipPostCreateConfig && config.agentMode) { - try { - await session.send("session/set_mode", { modeId: config.agentMode }); - } catch { - // Mode application is best-effort - } - } - - // Apply model if selected - if (config.model) { - setSessionModelById((prev) => ({ ...prev, [session.id]: config.model })); - if (!skipPostCreateConfig) { - try { - const agentInfo = agents.find((agent) => agent.id === nextAgentId); - const modelOption = ((agentInfo?.configOptions ?? []) as ConfigOption[]).find( - (opt) => opt.category === "model" && opt.type === "select" && typeof opt.id === "string" - ); - if (modelOption && config.model !== modelOption.currentValue) { - await session.send("session/set_config_option", { - optionId: modelOption.id, - value: config.model, - }); - } - } catch { - // Model application is best-effort - } - } - } - - // Refresh session list in background; UI should not stay blocked on list pagination. - void fetchSessions(); - } catch (error) { - console.error("[createNewSession] Failed to create session:", error); - const messageText = getErrorMessage(error, "Unable to create session"); - console.error("[createNewSession] Error message:", messageText); - setSessionError(messageText); - pushErrorToast(error, messageText); - if (!reconnectingAfterCreateFailureRef.current) { - reconnectingAfterCreateFailureRef.current = true; - // Run recovery in background so failed creates do not block UI. - void connectToDaemon(false) - .catch((reconnectError) => { - console.error("[createNewSession] Soft reconnect failed:", reconnectError); - }) - .finally(() => { - reconnectingAfterCreateFailureRef.current = false; - }); - } - throw error; - } finally { - creatingSessionRef.current = false; - // Keep a short post-create window for delayed transport rejections. - createNoiseIgnoreUntilRef.current = Date.now() + 2_000; - } - }; - - const endSession = async () => { - if (!sessionId) return; - try { - await getClient().destroySession(sessionId); - if (eventUnsubRef.current) { - eventUnsubRef.current(); - eventUnsubRef.current = null; - } - activeSessionRef.current = null; - await fetchSessions(); - } catch (error) { - setSessionError(getErrorMessage(error, "Unable to end session")); - } - }; - - const handleCopy = (entry: RequestLog) => { - const text = entry.curl; - const onSuccess = () => { - setCopiedLogId(entry.id); - window.setTimeout(() => setCopiedLogId(null), 1500); - }; - - if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(text).then(onSuccess).catch(() => { - fallbackCopy(text, onSuccess); - }); - } else { - fallbackCopy(text, onSuccess); - } - }; - - const fallbackCopy = (text: string, onSuccess?: () => void) => { - const textarea = document.createElement("textarea"); - textarea.value = text; - textarea.style.position = "fixed"; - textarea.style.opacity = "0"; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand("copy"); - onSuccess?.(); - } catch (err) { - console.error("Failed to copy:", err); - } - document.body.removeChild(textarea); - }; - - // Build transcript entries from raw SessionEvents - const transcriptEntries = useMemo(() => { - const entries: TimelineEntry[] = []; - - // Accumulators for streaming chunks - let assistantAccumId: string | null = null; - let assistantAccumText = ""; - let thoughtAccumId: string | null = null; - let thoughtAccumText = ""; - - const flushAssistant = (time: string) => { - if (assistantAccumId) { - const existing = entries.find((e) => e.id === assistantAccumId); - if (existing) { - existing.text = assistantAccumText; - existing.time = time; - } - } - assistantAccumId = null; - assistantAccumText = ""; - }; - - const flushThought = (time: string) => { - if (thoughtAccumId) { - const existing = entries.find((e) => e.id === thoughtAccumId); - if (existing && existing.reasoning) { - existing.reasoning.text = thoughtAccumText; - existing.time = time; - } - } - thoughtAccumId = null; - thoughtAccumText = ""; - }; - - // Track tool calls by ID for updates - const toolEntryMap = new Map(); - - for (const event of events) { - const payload = event.payload as Record; - const method = typeof payload.method === "string" ? payload.method : null; - const time = new Date(event.createdAt).toISOString(); - - if (event.sender === "client" && method === "session/prompt") { - // User message - flushAssistant(time); - flushThought(time); - const params = payload.params as Record | undefined; - const promptArray = params?.prompt as Array<{ type: string; text?: string }> | undefined; - const replayPrefix = "Previous session history is replayed below"; - const text = (promptArray ?? []) - .filter((part) => part?.type === "text" && typeof part.text === "string") - .map((part) => part.text!.trim()) - // SDK replay prompt is prepended to the real user prompt; drop only replay blocks. - .filter((partText) => partText.length > 0 && !partText.startsWith(replayPrefix)) - .join("\n\n") - .trim(); - if (!text) { - continue; - } - entries.push({ - id: event.id, - eventId: event.id, - kind: "message", - time, - role: "user", - text, - }); - continue; - } - - if (event.sender === "agent" && method === "session/update") { - const params = payload.params as Record | undefined; - const update = params?.update as Record | undefined; - if (!update || typeof update.sessionUpdate !== "string") continue; - - switch (update.sessionUpdate) { - case "agent_message_chunk": { - const content = update.content as { type?: string; text?: string } | undefined; - if (content?.type === "text" && content.text) { - if (!assistantAccumId) { - assistantAccumId = `assistant-${event.id}`; - assistantAccumText = ""; - entries.push({ - id: assistantAccumId, - eventId: event.id, - kind: "message", - time, - role: "assistant", - text: "", - }); - } - assistantAccumText += content.text; - const entry = entries.find((e) => e.id === assistantAccumId); - if (entry) { - entry.text = assistantAccumText; - entry.time = time; - } - } - break; - } - case "agent_thought_chunk": { - const content = update.content as { type?: string; text?: string } | undefined; - if (content?.type === "text" && content.text) { - if (!thoughtAccumId) { - thoughtAccumId = `thought-${event.id}`; - thoughtAccumText = ""; - entries.push({ - id: thoughtAccumId, - eventId: event.id, - kind: "reasoning", - time, - reasoning: { text: "", visibility: "public" }, - }); - } - thoughtAccumText += content.text; - const entry = entries.find((e) => e.id === thoughtAccumId); - if (entry && entry.reasoning) { - entry.reasoning.text = thoughtAccumText; - entry.time = time; - } - } - break; - } - case "user_message_chunk": { - const content = update.content as { type?: string; text?: string } | undefined; - const text = content?.type === "text" ? (content.text ?? "") : JSON.stringify(content); - entries.push({ - id: event.id, - eventId: event.id, - kind: "message", - time, - role: "user", - text, - }); - break; - } - case "tool_call": { - flushAssistant(time); - flushThought(time); - const toolCallId = (update.toolCallId as string) ?? event.id; - const existing = toolEntryMap.get(toolCallId); - if (existing) { - // Update existing entry instead of creating a duplicate - if (update.status) existing.toolStatus = update.status as string; - if (update.rawInput != null) existing.toolInput = JSON.stringify(update.rawInput, null, 2); - if (update.rawOutput != null) existing.toolOutput = JSON.stringify(update.rawOutput, null, 2); - if (update.title) existing.toolName = update.title as string; - existing.time = time; - } else { - const entry: TimelineEntry = { - id: `tool-${toolCallId}`, - eventId: event.id, - kind: "tool", - time, - toolName: (update.title as string) ?? "tool", - toolInput: update.rawInput != null ? JSON.stringify(update.rawInput, null, 2) : undefined, - toolOutput: update.rawOutput != null ? JSON.stringify(update.rawOutput, null, 2) : undefined, - toolStatus: (update.status as string) ?? "in_progress", - }; - toolEntryMap.set(toolCallId, entry); - entries.push(entry); - } - break; - } - case "tool_call_update": { - const toolCallId = update.toolCallId as string; - const existing = toolEntryMap.get(toolCallId); - if (existing) { - if (update.status) existing.toolStatus = update.status as string; - if (update.rawOutput != null) existing.toolOutput = JSON.stringify(update.rawOutput, null, 2); - if (update.title) existing.toolName = (existing.toolName ?? "") + (update.title as string); - existing.time = time; - } - break; - } - case "plan": { - const planEntries = (update.entries as Array<{ content: string; status: string }>) ?? []; - const detail = planEntries.map((e) => `[${e.status}] ${e.content}`).join("\n"); - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: "Plan", detail, severity: "info" }, - }); - break; - } - case "session_info_update": { - const title = update.title as string | undefined; - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: "Session info update", detail: title ? `Title: ${title}` : undefined, severity: "info" }, - }); - break; - } - case "usage_update": { - // Token usage is displayed in the config bar, not in the transcript - break; - } - case "current_mode_update": { - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: "Mode changed", detail: update.currentModeId as string, severity: "info" }, - }); - break; - } - case "config_option_update": { - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: "Config option update", severity: "info" }, - }); - break; - } - case "available_commands_update": { - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: "Available commands update", severity: "info" }, - }); - break; - } - default: { - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: `session/update: ${update.sessionUpdate}`, severity: "info" }, - }); - break; - } - } - continue; - } - - if (event.sender === "agent" && method === "_sandboxagent/agent/unparsed") { - const params = payload.params as { error?: string; location?: string } | undefined; - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { - title: "Agent parse failure", - detail: `${params?.location ?? "unknown"}: ${params?.error ?? "unknown error"}`, - severity: "error", - }, - }); - continue; - } - - // For any other ACP envelope, show as generic meta - if (method) { - entries.push({ - id: event.id, - eventId: event.id, - kind: "meta", - time, - meta: { title: method, detail: event.sender, severity: "info" }, - }); - } - } - - return entries; - }, [events]); - - useEffect(() => { - return () => { - if (eventUnsubRef.current) { - eventUnsubRef.current(); - eventUnsubRef.current = null; - } - }; - }, []); - - useEffect(() => { - const shouldIgnoreCreateNoise = (value: unknown): boolean => { - if (Date.now() > createNoiseIgnoreUntilRef.current) return false; - const message = getErrorMessage(value, "").trim().toLowerCase(); - return ( - message.length === 0 || - message === "request failed" || - message.includes("request failed") || - message.includes("unhandled promise rejection") - ); - }; - - const handleWindowError = (event: ErrorEvent) => { - const errorLike = event.error ?? event.message; - if (shouldIgnoreCreateNoise(errorLike)) return; - if (shouldIgnoreGlobalError(errorLike)) return; - pushErrorToast(errorLike, "Unexpected error"); - }; - const handleUnhandledRejection = (event: PromiseRejectionEvent) => { - if (shouldIgnoreCreateNoise(event.reason)) { - event.preventDefault(); - return; - } - if (shouldIgnoreGlobalError(event.reason)) { - event.preventDefault(); - return; - } - pushErrorToast(event.reason, "Unhandled promise rejection"); - }; - const handleHttpError = (event: Event) => { - const detail = (event as CustomEvent).detail; - if (typeof detail === "string" && detail.trim()) { - pushErrorToast(new Error(detail), detail); - } - }; - window.addEventListener("error", handleWindowError); - window.addEventListener("unhandledrejection", handleUnhandledRejection); - window.addEventListener(HTTP_ERROR_EVENT, handleHttpError); - return () => { - window.removeEventListener("error", handleWindowError); - window.removeEventListener("unhandledrejection", handleUnhandledRejection); - window.removeEventListener(HTTP_ERROR_EVENT, handleHttpError); - }; - }, [pushErrorToast]); - - useEffect(() => { - return () => { - for (const timeoutId of toastTimeoutsRef.current.values()) { - window.clearTimeout(timeoutId); - } - toastTimeoutsRef.current.clear(); - toastExpiryRef.current.clear(); - toastRemainingMsRef.current.clear(); - }; - }, []); - - useEffect(() => { - let active = true; - const attempt = async () => { - const { hasUrlParam } = initialConnectionRef.current; - - if (hasUrlParam) { - try { - await connectToDaemon(false); - } catch { - // Keep the URL param endpoint in the form even if connection failed - } - return; - } - - const originEndpoint = getCurrentOriginEndpoint(); - if (originEndpoint) { - try { - await connectToDaemon(false, originEndpoint); - return; - } catch { - // Origin failed, continue to fallback - } - } - - if (!active) return; - try { - await connectToDaemon(false, DEFAULT_ENDPOINT); - } catch { - setEndpoint(DEFAULT_ENDPOINT); - } - }; - attempt().catch(() => { - if (!active) return; - setConnecting(false); - }); - return () => { - active = false; - }; - }, []); - - useEffect(() => { - if (!connected) return; - refreshAgents(); - }, [connected]); - - // Auto-load session when sessionId changes - useEffect(() => { - if (!connected || !sessionId) return; - if (creatingSessionRef.current) return; - if (resumeInFlightSessionIdRef.current === sessionId) return; - const sessionInfo = sessions.find((s) => s.sessionId === sessionId); - if (!sessionInfo) return; - if (activeSessionRef.current?.id === sessionId) return; - - // Set the correct agent from the session - setAgentId(sessionInfo.agent); - const cachedEvents = sessionEventsCacheRef.current.get(sessionId); - if (cachedEvents) { - setEvents(cachedEvents); - setHistoryLoadingSessionId(null); - } else { - setEvents([]); - setHistoryLoadingSessionId(sessionId); - } - setSessionError(null); - - const requestedSessionId = sessionId; - resumeInFlightSessionIdRef.current = requestedSessionId; - - getClient().resumeSession(requestedSessionId).then((session) => { - if (selectedSessionIdRef.current !== requestedSessionId) return; - subscribeToSession(session); - }).catch((error) => { - if (selectedSessionIdRef.current !== requestedSessionId) return; - setSessionError(getErrorMessage(error, "Unable to resume session")); - setHistoryLoadingSessionId((current) => (current === requestedSessionId ? null : current)); - }).finally(() => { - if (resumeInFlightSessionIdRef.current === requestedSessionId) { - resumeInFlightSessionIdRef.current = null; - } - }); - }, [connected, sessionId, sessions, getClient, subscribeToSession]); - - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [transcriptEntries]); - - const currentAgent = agents.find((agent) => agent.id === agentId); - const agentLabel = agentDisplayNames[agentId] ?? agentId; - const selectedSession = sessions.find((s) => s.sessionId === sessionId); - const sessionArchived = selectedSession?.archived ?? false; - // Archived sessions are treated as ended in UI so they can never be "ended again". - const sessionEnded = (selectedSession?.ended ?? false) || sessionArchived; - - // Determine if agent is thinking (has in-progress tools or waiting for response) - const isThinking = useMemo(() => { - if (!sessionId || sessionEnded) return false; - // If actively sending a prompt, show thinking - if (sendingSessionId === sessionId) return true; - // Check for in-progress tool calls - const hasInProgressTool = transcriptEntries.some( - (e) => e.kind === "tool" && e.toolStatus === "in_progress" - ); - if (hasInProgressTool) return true; - // Check if last message was from user with no subsequent agent activity - const lastUserMessageIndex = [...transcriptEntries].reverse().findIndex((e) => e.kind === "message" && e.role === "user"); - if (lastUserMessageIndex === -1) return false; - // If user message is the very last entry, we're waiting for response - if (lastUserMessageIndex === 0) return true; - // Check if there's any agent response after the user message - const entriesAfterUser = transcriptEntries.slice(-(lastUserMessageIndex)); - const hasAgentResponse = entriesAfterUser.some( - (e) => e.kind === "message" && e.role === "assistant" - ); - // If no assistant message after user, but there are completed tools, not thinking - const hasCompletedTools = entriesAfterUser.some( - (e) => e.kind === "tool" && (e.toolStatus === "completed" || e.toolStatus === "failed") - ); - if (!hasAgentResponse && !hasCompletedTools) return true; - return false; - }, [sessionId, sessionEnded, transcriptEntries, sendingSessionId]); - - // Extract latest token usage from events - const tokenUsage = useMemo(() => { - let latest: { used: number; size: number; cost?: number } | null = null; - for (const event of events) { - const payload = event.payload as Record; - const method = typeof payload.method === "string" ? payload.method : null; - if (event.sender === "agent" && method === "session/update") { - const params = payload.params as Record | undefined; - const update = params?.update as Record | undefined; - if (update?.sessionUpdate === "usage_update") { - latest = { - used: (update.used as number) ?? 0, - size: (update.size as number) ?? 0, - cost: (update.cost as { total?: number })?.total, - }; - } - } - } - return latest; - }, [events]); - - // Extract modes and models from configOptions - const modesByAgent = useMemo(() => { - const result: Record = {}; - for (const agent of agents) { - const options = (agent.configOptions ?? []) as ConfigOption[]; - for (const opt of options) { - if (opt.category === "mode" && opt.type === "select" && opt.options) { - result[agent.id] = flattenSelectOptions(opt.options).map((o) => ({ - id: o.value, - name: o.name, - description: o.description ?? "", - })); - } - } - } - return result; - }, [agents]); - - const modelsByAgent = useMemo(() => { - const result: Record = {}; - for (const agent of agents) { - const options = (agent.configOptions ?? []) as ConfigOption[]; - for (const opt of options) { - if (opt.category === "model" && opt.type === "select" && opt.options) { - result[agent.id] = flattenSelectOptions(opt.options).map((o) => ({ - id: o.value, - name: o.name, - })); - } - } - } - return result; - }, [agents]); - - const defaultModelByAgent = useMemo(() => { - const result: Record = {}; - for (const agent of agents) { - const options = (agent.configOptions ?? []) as ConfigOption[]; - for (const opt of options) { - if (opt.category === "model" && opt.type === "select" && opt.currentValue) { - result[agent.id] = opt.currentValue; - } - } - } - return result; - }, [agents]); - - const currentSessionModelId = useMemo(() => { - let latestModelId: string | null = null; - - for (const event of events) { - const payload = event.payload as Record; - const method = typeof payload.method === "string" ? payload.method : null; - const params = payload.params as Record | undefined; - - if (event.sender === "agent" && method === "session/update") { - const update = params?.update as Record | undefined; - if (update?.sessionUpdate !== "config_option_update") continue; - - const category = (update.category as string | undefined) - ?? ((update.option as Record | undefined)?.category as string | undefined); - if (category && category !== "model") continue; - - const optionId = (update.optionId as string | undefined) - ?? (update.configOptionId as string | undefined) - ?? ((update.option as Record | undefined)?.id as string | undefined); - const seemsModelOption = !optionId || optionId.toLowerCase().includes("model"); - if (!seemsModelOption) continue; - - const candidate = (update.value as string | undefined) - ?? (update.currentValue as string | undefined) - ?? (update.selectedValue as string | undefined) - ?? (update.modelId as string | undefined); - if (candidate) { - latestModelId = candidate; - } - continue; - } - - // Capture explicit client-side model changes; these are persisted and survive refresh. - if (event.sender === "client" && method === "unstable/set_session_model") { - const candidate = params?.modelId as string | undefined; - if (candidate) { - latestModelId = candidate; - } - continue; - } - - if (event.sender === "client" && method === "session/set_config_option") { - const category = params?.category as string | undefined; - const optionId = params?.optionId as string | undefined; - const seemsModelOption = category === "model" || (typeof optionId === "string" && optionId.toLowerCase().includes("model")); - if (!seemsModelOption) continue; - const candidate = (params?.value as string | undefined) - ?? (params?.currentValue as string | undefined) - ?? (params?.modelId as string | undefined); - if (candidate) { - latestModelId = candidate; - } - } - } - - return latestModelId; - }, [events]); - - const modelPillLabel = useMemo(() => { - const sessionModelId = - currentSessionModelId - ?? (sessionId ? sessionModelById[sessionId] : undefined) - ?? (sessionId ? defaultModelByAgent[agentId] : undefined); - if (!sessionModelId) return null; - return sessionModelId; - }, [agentId, currentSessionModelId, defaultModelByAgent, sessionId, sessionModelById]); - - useEffect(() => { - if (!sessionId || !currentSessionModelId) return; - setSessionModelById((prev) => - prev[sessionId] === currentSessionModelId ? prev : { ...prev, [sessionId]: currentSessionModelId } - ); - }, [currentSessionModelId, sessionId]); - - useEffect(() => { - try { - window.localStorage.setItem(SESSION_MODELS_KEY, JSON.stringify(sessionModelById)); - } catch { - // Ignore storage write failures. - } - }, [sessionModelById]); - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); - sendMessage(); - } - }; - - const toastStack = ( -
- {errorToasts.map((toast) => ( -
pauseErrorToastDismiss(toast.id)} - onMouseLeave={() => resumeErrorToastDismiss(toast.id)} - onFocus={() => pauseErrorToastDismiss(toast.id)} - onBlur={() => resumeErrorToastDismiss(toast.id)} - > - -
-
Request failed
-
{toast.message}
-
-
- ))} -
- ); - - if (!connected) { - return ( - <> - - {toastStack} - - ); - } - - return ( -
-
-
- Sandbox Agent - {endpoint} -
- -
- -
- ({ - id, - installed: false, - credentialsAvailable: true, - capabilities: {} as AgentInfo["capabilities"], - }))} - agentsLoading={agentsLoading} - agentsError={agentsError} - sessionsLoading={sessionsLoading} - sessionsError={sessionsError} - modesByAgent={modesByAgent} - modelsByAgent={modelsByAgent} - defaultModelByAgent={defaultModelByAgent} - /> - - ({ - id, - installed: false, - credentialsAvailable: true, - capabilities: {} as AgentInfo["capabilities"], - }))} - agentsLoading={agentsLoading} - agentsError={agentsError} - messagesEndRef={messagesEndRef} - agentLabel={agentLabel} - modelLabel={modelPillLabel} - currentAgentVersion={currentAgent?.version ?? null} - sessionEnded={sessionEnded} - sessionArchived={sessionArchived} - onEndSession={endSession} - onArchiveSession={() => { - if (sessionId) { - void archiveSession(sessionId); - } - }} - onUnarchiveSession={() => { - if (sessionId) { - void unarchiveSession(sessionId); - } - }} - modesByAgent={modesByAgent} - modelsByAgent={modelsByAgent} - defaultModelByAgent={defaultModelByAgent} - onEventClick={(eventId) => { - setDebugTab("events"); - setHighlightedEventId(eventId); - }} - isThinking={isThinking} - agentId={agentId} - tokenUsage={tokenUsage} - /> - - setEvents([])} - highlightedEventId={highlightedEventId} - onClearHighlight={() => setHighlightedEventId(null)} - requestLog={requestLog} - copiedLogId={copiedLogId} - onClearRequestLog={() => setRequestLog([])} - onCopyRequestLog={handleCopy} - agents={agents} - defaultAgents={defaultAgents} - modesByAgent={modesByAgent} - onRefreshAgents={refreshAgents} - onInstallAgent={installAgent} - agentsLoading={agentsLoading} - agentsError={agentsError} - getClient={getClient} - collapsed={debugPanelCollapsed} - onToggleCollapse={() => setDebugPanelCollapsed(!debugPanelCollapsed)} - /> -
- {toastStack} -
- ); -} diff --git a/frontend/packages/inspector/src/components/ConnectScreen.tsx b/frontend/packages/inspector/src/components/ConnectScreen.tsx deleted file mode 100644 index 1337b4b2..00000000 --- a/frontend/packages/inspector/src/components/ConnectScreen.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { AlertTriangle, BookOpen, Zap } from "lucide-react"; -import { isHttpsToHttpConnection, isLocalNetworkTarget } from "../lib/permissions"; - -const logoUrl = `${import.meta.env.BASE_URL}logos/sandboxagent.svg`; - -const ConnectScreen = ({ - endpoint, - token, - connectError, - connecting, - onEndpointChange, - onTokenChange, - onConnect, - reportUrl, - docsUrl, - discordUrl, -}: { - endpoint: string; - token: string; - connectError: string | null; - connecting: boolean; - onEndpointChange: (value: string) => void; - onTokenChange: (value: string) => void; - onConnect: () => void; - reportUrl?: string; - docsUrl?: string; - discordUrl?: string; -}) => { - return ( -
-
-
- Sandbox Agent -
- {(docsUrl || discordUrl || reportUrl) && ( -
- {docsUrl && ( - - - Docs - - )} - {discordUrl && ( - - - Discord - - )} - {reportUrl && ( - - - Issues - - )} -
- )} -
- -
-
-
- Sandbox Agent -
- -
-
Connect to Server
- - {connectError &&
{connectError}
} - - {isHttpsToHttpConnection(window.location.href, endpoint) && - isLocalNetworkTarget(endpoint) && ( -
- - - Connecting from HTTPS to a local HTTP server requires{" "} - local network access permission. Your browser may prompt you to - allow this connection. - -
- )} - - - - - - - -

- Having trouble connecting? See the CORS documentation. -

-
-
-
-
- ); -}; - -export default ConnectScreen; diff --git a/frontend/packages/inspector/src/components/SessionCreateMenu.tsx b/frontend/packages/inspector/src/components/SessionCreateMenu.tsx deleted file mode 100644 index e952890f..00000000 --- a/frontend/packages/inspector/src/components/SessionCreateMenu.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { ArrowLeft, ArrowRight } from "lucide-react"; -import { useEffect, useState } from "react"; -import type { AgentInfo } from "sandbox-agent"; - -type AgentModeInfo = { id: string; name: string; description: string }; -type AgentModelInfo = { id: string; name?: string }; - -export type SessionConfig = { - agentMode: string; - model: string; -}; - -const CUSTOM_MODEL_VALUE = "__custom__"; - -const agentLabels: Record = { - claude: "Claude Code", - codex: "Codex", - opencode: "OpenCode", - amp: "Amp", - pi: "Pi", - cursor: "Cursor" -}; - -const agentLogos: Record = { - claude: `${import.meta.env.BASE_URL}logos/claude.svg`, - codex: `${import.meta.env.BASE_URL}logos/openai.svg`, - opencode: `${import.meta.env.BASE_URL}logos/opencode.svg`, - amp: `${import.meta.env.BASE_URL}logos/amp.svg`, - pi: `${import.meta.env.BASE_URL}logos/pi.svg`, -}; - -const SessionCreateMenu = ({ - agents, - agentsLoading, - agentsError, - modesByAgent, - modelsByAgent, - defaultModelByAgent, - onCreateSession, - onSelectAgent, - open, - onClose -}: { - agents: AgentInfo[]; - agentsLoading: boolean; - agentsError: string | null; - modesByAgent: Record; - modelsByAgent: Record; - defaultModelByAgent: Record; - onCreateSession: (agentId: string, config: SessionConfig) => Promise; - onSelectAgent: (agentId: string) => Promise; - open: boolean; - onClose: () => void; -}) => { - const [phase, setPhase] = useState<"agent" | "config" | "loading-config">("agent"); - const [selectedAgent, setSelectedAgent] = useState(""); - const [agentMode, setAgentMode] = useState(""); - const [selectedModel, setSelectedModel] = useState(""); - const [customModel, setCustomModel] = useState(""); - const [isCustomModel, setIsCustomModel] = useState(false); - const [creating, setCreating] = useState(false); - - // Reset state when menu closes - useEffect(() => { - if (!open) { - setPhase("agent"); - setSelectedAgent(""); - setAgentMode(""); - setSelectedModel(""); - setCustomModel(""); - setIsCustomModel(false); - setCreating(false); - } - }, [open]); - - // Auto-select first mode when modes load for selected agent - useEffect(() => { - if (!selectedAgent) return; - const modes = modesByAgent[selectedAgent]; - if (modes && modes.length > 0 && !agentMode) { - setAgentMode(modes[0].id); - } - }, [modesByAgent, selectedAgent, agentMode]); - - // Agent-specific config should not leak between agent selections. - useEffect(() => { - setAgentMode(""); - setSelectedModel(""); - setCustomModel(""); - setIsCustomModel(false); - }, [selectedAgent]); - - // Auto-select default model when agent is selected - useEffect(() => { - if (!selectedAgent) return; - if (selectedModel) return; - const defaultModel = defaultModelByAgent[selectedAgent]; - if (defaultModel) { - setSelectedModel(defaultModel); - } else { - const models = modelsByAgent[selectedAgent]; - if (models && models.length > 0) { - setSelectedModel(models[0].id); - } - } - }, [modelsByAgent, defaultModelByAgent, selectedAgent, selectedModel]); - - if (!open) return null; - - const handleAgentClick = (agentId: string) => { - setSelectedAgent(agentId); - setPhase("config"); - // Load agent config in background; creation should not block on this call. - void onSelectAgent(agentId).catch((error) => { - console.error("[SessionCreateMenu] Failed to load agent config:", error); - }); - }; - - const handleBack = () => { - if (creating) return; - setPhase("agent"); - setSelectedAgent(""); - setAgentMode(""); - setSelectedModel(""); - setCustomModel(""); - setIsCustomModel(false); - }; - - const handleModelSelectChange = (value: string) => { - if (value === CUSTOM_MODEL_VALUE) { - setIsCustomModel(true); - setSelectedModel(""); - } else { - setIsCustomModel(false); - setCustomModel(""); - setSelectedModel(value); - } - }; - - const resolvedModel = isCustomModel ? customModel : selectedModel; - - const handleCreate = async () => { - if (!selectedAgent) return; - setCreating(true); - try { - await onCreateSession(selectedAgent, { agentMode, model: resolvedModel }); - onClose(); - } catch (error) { - console.error("[SessionCreateMenu] Failed to create session:", error); - } finally { - setCreating(false); - } - }; - - if (phase === "agent") { - return ( -
- {agentsLoading &&
Loading agents...
} - {agentsError &&
{agentsError}
} - {!agentsLoading && !agentsError && agents.length === 0 && ( -
No agents available.
- )} - {!agentsLoading && !agentsError && (() => { - const codingAgents = agents.filter((a) => a.id !== "mock"); - const mockAgent = agents.find((a) => a.id === "mock"); - return ( - <> - {codingAgents.map((agent) => ( - - ))} - {mockAgent && ( - <> -
- - - )} - - ); - })()} -
- ); - } - - const agentLabel = agentLabels[selectedAgent] ?? selectedAgent; - - // Phase 2: config form - const activeModes = modesByAgent[selectedAgent] ?? []; - const activeModels = modelsByAgent[selectedAgent] ?? []; - - return ( -
-
- - {agentLabel} -
- -
-
- Model - {isCustomModel ? ( - setCustomModel(e.target.value)} - placeholder="Enter model name..." - autoFocus - /> - ) : ( - - )} - {isCustomModel && ( - - )} -
- {activeModes.length > 0 && ( -
- Mode - -
- )} -
- -
- -
-
- ); -}; - -export default SessionCreateMenu; diff --git a/frontend/packages/inspector/src/components/SessionSidebar.tsx b/frontend/packages/inspector/src/components/SessionSidebar.tsx deleted file mode 100644 index 2771bdd4..00000000 --- a/frontend/packages/inspector/src/components/SessionSidebar.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import { Archive, ArrowLeft, ArrowUpRight, Plus, RefreshCw } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; -import type { AgentInfo } from "sandbox-agent"; -import { formatShortId } from "../utils/format"; - -type AgentModeInfo = { id: string; name: string; description: string }; -type AgentModelInfo = { id: string; name?: string }; -import SessionCreateMenu, { type SessionConfig } from "./SessionCreateMenu"; - -type SessionListItem = { - sessionId: string; - agent: string; - ended: boolean; - archived: boolean; -}; - -const agentLabels: Record = { - claude: "Claude Code", - codex: "Codex", - opencode: "OpenCode", - amp: "Amp", - pi: "Pi", - cursor: "Cursor" -}; -const persistenceDocsUrl = "https://sandboxagent.dev/docs/session-persistence"; -const MIN_REFRESH_SPIN_MS = 350; - -const SessionSidebar = ({ - sessions, - selectedSessionId, - onSelectSession, - onRefresh, - onCreateSession, - onSelectAgent, - agents, - agentsLoading, - agentsError, - sessionsLoading, - sessionsError, - modesByAgent, - modelsByAgent, - defaultModelByAgent, -}: { - sessions: SessionListItem[]; - selectedSessionId: string; - onSelectSession: (session: SessionListItem) => void; - onRefresh: () => void; - onCreateSession: (agentId: string, config: SessionConfig) => Promise; - onSelectAgent: (agentId: string) => Promise; - agents: AgentInfo[]; - agentsLoading: boolean; - agentsError: string | null; - sessionsLoading: boolean; - sessionsError: string | null; - modesByAgent: Record; - modelsByAgent: Record; - defaultModelByAgent: Record; -}) => { - const [showMenu, setShowMenu] = useState(false); - const [showArchived, setShowArchived] = useState(false); - const [refreshing, setRefreshing] = useState(false); - const menuRef = useRef(null); - const archivedCount = sessions.filter((session) => session.archived).length; - const activeSessions = sessions.filter((session) => !session.archived); - const archivedSessions = sessions.filter((session) => session.archived); - const visibleSessions = showArchived ? archivedSessions : activeSessions; - const orderedVisibleSessions = showArchived - ? [...visibleSessions].sort((a, b) => Number(a.ended) - Number(b.ended)) - : visibleSessions; - - useEffect(() => { - if (!showMenu) return; - const handler = (event: MouseEvent) => { - if (!menuRef.current) return; - if (!menuRef.current.contains(event.target as Node)) { - setShowMenu(false); - } - }; - document.addEventListener("mousedown", handler); - return () => document.removeEventListener("mousedown", handler); - }, [showMenu]); - - useEffect(() => { - // Prevent getting stuck in archived view when there are no archived sessions. - if (!showArchived) return; - if (archivedSessions.length === 0) { - setShowArchived(false); - } - }, [showArchived, archivedSessions.length]); - - const handleRefresh = async () => { - if (refreshing) return; - const startedAt = Date.now(); - setRefreshing(true); - try { - await Promise.resolve(onRefresh()); - } finally { - const elapsedMs = Date.now() - startedAt; - if (elapsedMs < MIN_REFRESH_SPIN_MS) { - await new Promise((resolve) => window.setTimeout(resolve, MIN_REFRESH_SPIN_MS - elapsedMs)); - } - setRefreshing(false); - } - }; - - return ( -
-
- Sessions -
- {archivedCount > 0 && ( - - )} - -
- - setShowMenu(false)} - /> -
-
-
- -
- {sessionsLoading ? ( -
Loading sessions...
- ) : sessionsError ? ( -
{sessionsError}
- ) : visibleSessions.length === 0 ? ( -
{showArchived ? "No archived sessions." : "No sessions yet."}
- ) : ( - <> - {showArchived &&
Archived Sessions
} - {orderedVisibleSessions.map((session) => ( -
- -
- ))} - - )} -
-
- Sessions are persisted in your browser using IndexedDB. These sessions are only from your browser; your SDK sessions are separate. Adding inspector support for SDK soon.{" "} - - Configure persistence - - -
-
- ); -}; - -export default SessionSidebar; diff --git a/frontend/packages/inspector/src/components/agents/FeatureCoverageBadges.tsx b/frontend/packages/inspector/src/components/agents/FeatureCoverageBadges.tsx deleted file mode 100644 index 3d4e42d4..00000000 --- a/frontend/packages/inspector/src/components/agents/FeatureCoverageBadges.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import type { ComponentType } from "react"; -import { - Activity, - AlertTriangle, - Brain, - CircleDot, - Download, - FileDiff, - Gauge, - GitBranch, - HelpCircle, - Image, - Layers, - MessageSquare, - Paperclip, - PlayCircle, - Plug, - Shield, - Terminal, - Wrench -} from "lucide-react"; -import type { FeatureCoverageView } from "../../types/agents"; - -const badges = [ - { key: "planMode", label: "Plan", icon: GitBranch }, - { key: "permissions", label: "Perms", icon: Shield }, - { key: "questions", label: "Q&A", icon: HelpCircle }, - { key: "toolCalls", label: "Tool Calls", icon: Wrench }, - { key: "toolResults", label: "Tool Results", icon: Download }, - { key: "textMessages", label: "Text", icon: MessageSquare }, - { key: "images", label: "Images", icon: Image }, - { key: "fileAttachments", label: "Files", icon: Paperclip }, - { key: "sessionLifecycle", label: "Lifecycle", icon: PlayCircle }, - { key: "errorEvents", label: "Errors", icon: AlertTriangle }, - { key: "reasoning", label: "Reasoning", icon: Brain }, - { key: "status", label: "Status", icon: Gauge }, - { key: "commandExecution", label: "Commands", icon: Terminal }, - { key: "fileChanges", label: "File Changes", icon: FileDiff }, - { key: "mcpTools", label: "MCP", icon: Plug }, - { key: "streamingDeltas", label: "Deltas", icon: Activity }, - { key: "itemStarted", label: "Item Start", icon: CircleDot }, - { key: "variants", label: "Variants", icon: Layers } -] as const; - -type BadgeItem = (typeof badges)[number]; - -const getEnabled = (featureCoverage: FeatureCoverageView, key: BadgeItem["key"]) => - Boolean((featureCoverage as unknown as Record)[key]); - -const FeatureCoverageBadges = ({ featureCoverage }: { featureCoverage: FeatureCoverageView }) => { - return ( -
- {badges.map(({ key, label, icon: Icon }) => ( - - - {label} - - ))} -
- ); -}; - -export default FeatureCoverageBadges; diff --git a/frontend/packages/inspector/src/components/chat/ChatInput.tsx b/frontend/packages/inspector/src/components/chat/ChatInput.tsx deleted file mode 100644 index 6f8b2a71..00000000 --- a/frontend/packages/inspector/src/components/chat/ChatInput.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Send } from "lucide-react"; - -const ChatInput = ({ - message, - onMessageChange, - onSendMessage, - onKeyDown, - placeholder, - disabled -}: { - message: string; - onMessageChange: (value: string) => void; - onSendMessage: () => void; - onKeyDown: (event: React.KeyboardEvent) => void; - placeholder: string; - disabled: boolean; -}) => { - return ( -
-
-