diff --git a/packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts b/packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts index eb4aba4b1c..d546a956de 100644 --- a/packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts +++ b/packages/adapter-utils/src/acpx-engine/settlement-characterization.test.ts @@ -890,12 +890,12 @@ describe("ACP settlement — Layer B: restoreWorkspace order + native-sync selec // Phase order: config_sync (staging) → restore → finalize (finalize last). expect(runtimeStatuses).toEqual( expect.arrayContaining([ - "config_sync:Syncing workspace to sandbox", - "restore:Restoring workspace from sandbox", - "finalize:Finalizing sandbox workspace", + "config_sync:Syncing workspace to environment", + "restore:Restoring workspace from environment", + "finalize:Finalizing workspace", ]), ); - expect(runtimeStatuses.at(-1)).toBe("finalize:Finalizing sandbox workspace"); + expect(runtimeStatuses.at(-1)).toBe("finalize:Finalizing workspace"); }); it("test_git_backed_workspace_restore_phase_order", async () => { diff --git a/packages/adapter-utils/src/execution-target.ts b/packages/adapter-utils/src/execution-target.ts index 8c1aba3634..aa3c1e4064 100644 --- a/packages/adapter-utils/src/execution-target.ts +++ b/packages/adapter-utils/src/execution-target.ts @@ -644,7 +644,7 @@ export async function runAdapterExecutionTargetProcess( const env = sanitizeRemoteExecutionEnv(options.env); await options.onRuntimeProgress?.({ phase: "adapter_startup", - message: "Starting adapter in sandbox", + message: "Starting adapter in environment", }); const runLogTail = options.runLogTail?.create() ?? null; let execCommand = command; diff --git a/packages/adapter-utils/src/runtime-progress.test.ts b/packages/adapter-utils/src/runtime-progress.test.ts index 24048404e7..ef3f4b8c94 100644 --- a/packages/adapter-utils/src/runtime-progress.test.ts +++ b/packages/adapter-utils/src/runtime-progress.test.ts @@ -28,7 +28,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.report(12.6 * MB, 31.4 * MB); - expect(lines).toEqual(["[paperclip] Syncing workspace to sandbox: 40% (12.6/31.4 MB)\n"]); + expect(lines).toEqual(["[paperclip] Syncing workspace to environment: 40% (12.6/31.4 MB)\n"]); }); it("omits the label when none is provided (e.g. git history)", async () => { @@ -68,7 +68,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.report(5 * MB, 100 * MB); // 5% expect(lines).toHaveLength(1); - expect(lines[0]).toBe("[paperclip] Syncing workspace to sandbox: 1% (1.0/100.0 MB)\n"); + expect(lines[0]).toBe("[paperclip] Syncing workspace to environment: 1% (1.0/100.0 MB)\n"); }); it("emits when the percentage crosses a 10% step", async () => { @@ -89,7 +89,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.report(15 * MB, 100 * MB); // 15% -> crosses into step 1 -> emit expect(lines).toHaveLength(2); - expect(lines[1]).toBe("[paperclip] Syncing workspace to sandbox: 15% (15.0/100.0 MB)\n"); + expect(lines[1]).toBe("[paperclip] Syncing workspace to environment: 15% (15.0/100.0 MB)\n"); }); it("emits on the time threshold even without a step crossing", async () => { @@ -112,7 +112,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.report(3 * MB, 100 * MB); // 3% same step, but 2s elapsed -> emit expect(lines).toHaveLength(2); - expect(lines[1]).toBe("[paperclip] Syncing workspace to sandbox: 3% (3.0/100.0 MB)\n"); + expect(lines[1]).toBe("[paperclip] Syncing workspace to environment: 3% (3.0/100.0 MB)\n"); }); it("always emits the terminal 100% line via report reaching the total", async () => { @@ -133,7 +133,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.report(100 * MB, 100 * MB); // terminal -> always emit expect(lines[lines.length - 1]).toBe( - "[paperclip] Restoring workspace from sandbox: 100% (100.0/100.0 MB)\n", + "[paperclip] Restoring workspace from environment: 100% (100.0/100.0 MB)\n", ); }); @@ -156,7 +156,7 @@ describe("createRuntimeProgressReporter", () => { await reporter.complete(); expect(lines[lines.length - 1]).toBe( - "[paperclip] Syncing workspace to sandbox: 100% (100.0/100.0 MB)\n", + "[paperclip] Syncing workspace to environment: 100% (100.0/100.0 MB)\n", ); }); diff --git a/packages/adapter-utils/src/runtime-progress.ts b/packages/adapter-utils/src/runtime-progress.ts index e217927675..46c8497bae 100644 --- a/packages/adapter-utils/src/runtime-progress.ts +++ b/packages/adapter-utils/src/runtime-progress.ts @@ -91,7 +91,11 @@ export function createRuntimeProgressReporter( const minIntervalMs = options.minIntervalMs && options.minIntervalMs > 0 ? options.minIntervalMs : 2000; const now = options.now ?? Date.now; - const prefix = `[paperclip] ${options.phase}${options.label ? ` ${options.label}` : ""} ${options.direction} ${options.target}`; + // "sandbox" is the transport key, not product vocabulary: progress lines are + // user-visible run status, and the product refers to the run's machine as an + // environment ("Paperclip Computer" on managed deployments). + const targetDisplay = options.target === "sandbox" ? "environment" : options.target; + const prefix = `[paperclip] ${options.phase}${options.label ? ` ${options.label}` : ""} ${options.direction} ${targetDisplay}`; let lastEmitAt: number | null = null; let lastStep = -1; diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts index 49101dc73b..8e60c71fb0 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts @@ -427,17 +427,17 @@ describe("sandbox managed runtime", () => { await expect(readFile(path.join(localWorkspaceDir, ".claude", "settings.json"), "utf8")).resolves.toBe("{\"local\":true}\n"); await expect(readFile(path.join(localWorkspaceDir, ".paperclip-runtime", "state.json"), "utf8")).resolves.toBe("{}\n"); expect(runtimeStatuses).toEqual(expect.arrayContaining([ - "config_sync:Syncing workspace to sandbox", - "config_sync:Syncing runtime assets to sandbox", - "restore:Restoring workspace from sandbox", - "finalize:Finalizing sandbox workspace", + "config_sync:Syncing workspace to environment", + "config_sync:Syncing runtime assets to environment", + "restore:Restoring workspace from environment", + "finalize:Finalizing workspace", ])); expect(runtimeStatuses).toEqual(expect.arrayContaining([ - expect.stringMatching(/^config_sync:Syncing workspace to sandbox: 100% \(\d+\.\d\/\d+\.\d MB\)$/), - expect.stringMatching(/^config_sync:Syncing skills to sandbox: 100% \(\d+\.\d\/\d+\.\d MB\)$/), - expect.stringMatching(/^restore:Restoring workspace from sandbox: 100% \(\d+\.\d\/\d+\.\d MB\)$/), + expect.stringMatching(/^config_sync:Syncing workspace to environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/), + expect.stringMatching(/^config_sync:Syncing skills to environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/), + expect.stringMatching(/^restore:Restoring workspace from environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/), ])); - expect(runtimeStatuses.at(-1)).toBe("finalize:Finalizing sandbox workspace"); + expect(runtimeStatuses.at(-1)).toBe("finalize:Finalizing workspace"); }); it.each(["workspace", "git-workspace"])( @@ -645,11 +645,11 @@ describe("sandbox managed runtime", () => { // check above). expect(runtimeStatuses.some((status) => ( status.phase === "config_sync" && - /^Syncing workspace to sandbox: 100% \(\d+\.\d\/\d+\.\d MB\)$/.test(status.message) + /^Syncing workspace to environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/.test(status.message) ))).toBe(true); expect(runtimeStatuses.some((status) => ( status.phase === "export" && - /^Exporting git history from sandbox: 100% \(\d+\.\d\/\d+\.\d MB\)$/.test(status.message) + /^Exporting git history from environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/.test(status.message) ))).toBe(true); }); @@ -1109,8 +1109,8 @@ describe("sandbox managed runtime", () => { }, }); - const uploadWorkspaceLines = lines.filter((line) => line.includes("Syncing workspace to sandbox")); - const uploadAssetLines = lines.filter((line) => line.includes("Syncing skills to sandbox")); + const uploadWorkspaceLines = lines.filter((line) => line.includes("Syncing workspace to environment")); + const uploadAssetLines = lines.filter((line) => line.includes("Syncing skills to environment")); expect(uploadWorkspaceLines.length).toBeGreaterThan(0); expect(uploadAssetLines.length).toBeGreaterThan(0); // 100 reported increments must be throttled to at most ~one line per 10% step. @@ -1120,7 +1120,7 @@ describe("sandbox managed runtime", () => { expect(uploadWorkspaceLines.every((line) => /\(\d+\.\d\/\d+\.\d MB\)/.test(line))).toBe(true); await prepared.restoreWorkspace(); - const restoreLines = lines.filter((line) => line.includes("Restoring workspace from sandbox")); + const restoreLines = lines.filter((line) => line.includes("Restoring workspace from environment")); expect(restoreLines.length).toBeGreaterThan(0); expect(restoreLines.length).toBeLessThanOrEqual(11); expect(restoreLines.some((line) => line.includes("100%"))).toBe(true); diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.ts b/packages/adapter-utils/src/sandbox-managed-runtime.ts index 176adcfb90..26196a474f 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.ts @@ -959,7 +959,7 @@ export async function prepareSandboxManagedRuntime(input: { // wipes the target tree EXCEPT `.paperclip-runtime`, so the overlay tar, // which sits under `.paperclip-runtime`, survives to run its own extract. if (gitSnapshot) { - await emitRuntimeStatus(input.onRuntimeProgress, "git_sync", "Syncing git history to sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "git_sync", "Syncing git history to environment"); const gitTarPath = path.join(tempDir, "git-workspace.tar"); const remoteGitTar = path.posix.join(runtimeRootDir, "git-workspace-upload.tar"); await withShallowGitWorkspaceClone({ @@ -986,7 +986,7 @@ export async function prepareSandboxManagedRuntime(input: { // 2. workspace-overlay tar. A git-backed overlay merges on top of the just // extracted git tree (no wipe); a plain workspace wipes every child except // the preserved names first. The extract runs AFTER the git extract. - await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing workspace to sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing workspace to environment"); const workspaceTarPath = path.join(tempDir, "workspace.tar"); const workspaceArchiveDir = gitSnapshot ? path.join(tempDir, "workspace-overlay") : input.workspaceLocalDir; if (gitSnapshot) { @@ -1043,7 +1043,7 @@ export async function prepareSandboxManagedRuntime(input: { inboundTaskIsRequired.push(true); inboundTasks.push(() => runStepSpan(`stage.asset.${asset.key}`, async () => { - await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing runtime assets to sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing runtime assets to environment"); const remoteAssetDir = path.posix.join(runtimeRootDir, asset.key); const remoteAssetTar = path.posix.join(runtimeRootDir, `${asset.key}-upload.tar`); // Every asset — default OR custom-provisioned (e.g. an adapter credential @@ -1137,7 +1137,7 @@ export async function prepareSandboxManagedRuntime(input: { throw new Error(`additional source projectId is not a simple path segment: ${projectId}`); } const remoteProjectDir = path.posix.join(runtimeRootDir, label); - await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing referenced project to sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing referenced project to environment"); await stageConfinedSyncIn({ files: [{ sourcePath: localPath, @@ -1237,7 +1237,7 @@ export async function prepareSandboxManagedRuntime(input: { let remoteWorkspaceStatus = "dirty"; try { if (gitSnapshot) { - await emitRuntimeStatus(input.onRuntimeProgress, "export", "Exporting git changes from sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "export", "Exporting git changes from environment"); importedRef = createImportedGitRef("sandbox"); const remoteGitBundle = path.posix.join(runtimeRootDir, "git-delta.bundle"); const remoteWorkspaceStatusPath = path.posix.join(runtimeRootDir, "workspace-status.txt"); @@ -1323,7 +1323,7 @@ export async function prepareSandboxManagedRuntime(input: { } } - await emitRuntimeStatus(input.onRuntimeProgress, "restore", "Restoring workspace from sandbox"); + await emitRuntimeStatus(input.onRuntimeProgress, "restore", "Restoring workspace from environment"); const extractedDir = path.join(tempDir, "workspace"); if (nativeSyncOut) { // Native outbound: the provider materializes the sandbox workspace into @@ -1405,7 +1405,7 @@ export async function prepareSandboxManagedRuntime(input: { : undefined, }); } finally { - await emitRuntimeStatus(input.onRuntimeProgress, "finalize", "Finalizing sandbox workspace"); + await emitRuntimeStatus(input.onRuntimeProgress, "finalize", "Finalizing workspace"); if (importedRef) { await deleteLocalGitRef({ localDir: input.workspaceLocalDir, ref: importedRef }); } diff --git a/packages/adapters/claude-local/src/server/acp.ts b/packages/adapters/claude-local/src/server/acp.ts index 449e495b9d..721e2c39ab 100644 --- a/packages/adapters/claude-local/src/server/acp.ts +++ b/packages/adapters/claude-local/src/server/acp.ts @@ -520,8 +520,8 @@ function buildAcpAuthMissingChecks(input: { checks.push({ code: ADAPTER_AUTH_MISSING_CHECK_CODE, level: "warn", - message: "The sandbox has no ready authentication for this adapter.", - hint: "Provide credentials for this adapter, or start login in the sandbox.", + message: "This environment has no ready authentication for this adapter.", + hint: "Provide credentials for this adapter, or start login in the environment.", }); } return checks; @@ -544,7 +544,7 @@ function buildAcpLoginProbeUnavailableCheck( level: "warn", message, hint: targetIsSandbox - ? "Verify that the sandbox can run `claude` and retry the Test. Set engine=cli to use the Claude CLI lane." + ? "Verify that the environment can run `claude` and retry the Test. Set engine=cli to use the Claude CLI lane." : "Verify that `claude` can run in this environment and retry the Test. Set engine=cli to use the Claude CLI lane.", }; } diff --git a/packages/adapters/claude-local/src/server/claude-config.ts b/packages/adapters/claude-local/src/server/claude-config.ts index a95f7f7629..2195a00544 100644 --- a/packages/adapters/claude-local/src/server/claude-config.ts +++ b/packages/adapters/claude-local/src/server/claude-config.ts @@ -345,7 +345,7 @@ export async function prepareSandboxClaudeProbeRuntime(input: { checks.push({ code: "claude_managed_config_dir", level: "info", - message: "Sandbox probe is using Paperclip-managed Claude config materialization.", + message: "The environment probe is using Paperclip-managed Claude config materialization.", detail: remoteClaudeConfigDir, }); } catch (err) { @@ -353,14 +353,14 @@ export async function prepareSandboxClaudeProbeRuntime(input: { // only the fixed context, the allowlisted classification, and a safe // error class name. logSandboxProbeDiagnostic( - "Could not materialize Paperclip-managed Claude config for the sandbox probe", + "Could not materialize Paperclip-managed Claude config for the environment probe", "spawn_error", { errorClass: classifyThrownErrorClass(err) }, ); checks.push({ code: "claude_managed_config_dir_failed", level: "error", - message: "Could not materialize Paperclip-managed Claude config for the sandbox probe.", + message: "Could not materialize Paperclip-managed Claude config for the environment probe.", hint: "Retry the Test. If the failure repeats, check the server log for the redacted diagnostic.", }); } finally { diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index d0cbbe8140..6dcdd81045 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -715,7 +715,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { it("includes ephemeral current status fields on active run polling", async () => { mockHeartbeatService.decorateActiveRunStatus.mockImplementation((run) => ({ ...run, - currentStatusMessage: "Syncing workspace to sandbox", + currentStatusMessage: "Syncing workspace to environment", currentStatusUpdatedAt: new Date("2026-04-10T09:30:05.000Z"), currentToolName: "bash", lastAssistantSnippet: "Inspecting files", @@ -341,7 +341,7 @@ describe("agent live run routes", () => { { companyId: "company-1", issueId: "issue-1" }, ); expect(res.body).toMatchObject({ - currentStatusMessage: "Syncing workspace to sandbox", + currentStatusMessage: "Syncing workspace to environment", currentStatusUpdatedAt: "2026-04-10T09:30:05.000Z", currentToolName: "bash", lastAssistantSnippet: "Inspecting files", diff --git a/server/src/__tests__/agent-test-environment-routes.test.ts b/server/src/__tests__/agent-test-environment-routes.test.ts index e263c8d03c..699785dc11 100644 --- a/server/src/__tests__/agent-test-environment-routes.test.ts +++ b/server/src/__tests__/agent-test-environment-routes.test.ts @@ -314,7 +314,7 @@ describe("agent test-environment route", () => { expect.objectContaining({ code: "sandbox_test_identity", level: "info", - message: 'Sandbox test identity for "Sandbox QA".', + message: 'Environment test identity for "Sandbox QA".', detail: expect.stringContaining("paperclipLeaseId=lease-1"), }), expect.objectContaining({ diff --git a/server/src/__tests__/heartbeat-runtime-state.test.ts b/server/src/__tests__/heartbeat-runtime-state.test.ts index 24ae9173f1..50990f6b38 100644 --- a/server/src/__tests__/heartbeat-runtime-state.test.ts +++ b/server/src/__tests__/heartbeat-runtime-state.test.ts @@ -147,7 +147,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { const heartbeat = heartbeatService(db); const status = await heartbeat.recordRuntimeProgress(run, { phase: "config_sync", - message: "Syncing workspace to sandbox", + message: "Syncing workspace to environment", currentToolName: "bash", lastAssistantSnippet: "Inspecting the repository", lastEventAt: "2026-06-24T00:00:05.000Z", @@ -159,7 +159,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { agentId, runId, phase: "config_sync", - message: "Syncing workspace to sandbox", + message: "Syncing workspace to environment", currentToolName: "bash", lastAssistantSnippet: "Inspecting the repository", lastEventAt: new Date("2026-06-24T00:00:05.000Z"), @@ -171,7 +171,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { issueId, status: "running", })).toMatchObject({ - currentStatusMessage: "Syncing workspace to sandbox", + currentStatusMessage: "Syncing workspace to environment", currentToolName: "bash", lastAssistantSnippet: "Inspecting the repository", lastEventAt: new Date("2026-06-24T00:00:05.000Z"), @@ -184,7 +184,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { agentId, issueId, phase: "config_sync", - message: "Syncing workspace to sandbox", + message: "Syncing workspace to environment", currentToolName: "bash", lastAssistantSnippet: "Inspecting the repository", lastEventAt: "2026-06-24T00:00:05.000Z", @@ -245,7 +245,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { const heartbeat = heartbeatService(db); await heartbeat.recordRuntimeProgress(staleRunningRun, { phase: "config_sync", - message: "Syncing workspace to sandbox", + message: "Syncing workspace to environment", }, issueId); expect(getHeartbeatRunRuntimeStatus(runId)).toMatchObject({ @@ -265,7 +265,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { const lateStatus = await heartbeat.recordRuntimeProgress(staleRunningRun, { phase: "finalize", - message: "Finalizing sandbox workspace", + message: "Finalizing workspace", }, issueId); expect(lateStatus).toBeNull(); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 5b8d1b823c..2803a17a40 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -893,7 +893,7 @@ export function agentRoutes( { code: "environment_target_failed", level: "error", - message: `Could not resolve a sandbox execution target for "${environment.name}".`, + message: `Could not resolve an execution target for "${environment.name}".`, detail: err instanceof Error ? err.message : String(err), }, ], @@ -985,9 +985,9 @@ export function agentRoutes( return { code: "sandbox_test_identity", level: "info", - message: `Sandbox test identity for "${input.environmentName}".`, + message: `Environment test identity for "${input.environmentName}".`, detail: detailParts.join("; "), - hint: "Use these provider-neutral IDs when comparing model-test output with provider logs or refreshed sandbox snapshots.", + hint: "Use these provider-neutral IDs when comparing model-test output with provider logs or refreshed environment snapshots.", }; } diff --git a/server/src/services/heartbeat-run-runtime-status.test.ts b/server/src/services/heartbeat-run-runtime-status.test.ts index f603900127..80984155ee 100644 --- a/server/src/services/heartbeat-run-runtime-status.test.ts +++ b/server/src/services/heartbeat-run-runtime-status.test.ts @@ -64,7 +64,7 @@ describe("heartbeat run runtime status store", () => { agentId: "agent-1", runId: "run-1", phase: "finalize", - message: "Finalizing sandbox workspace", + message: "Finalizing workspace", }); expect(clearHeartbeatRunRuntimeStatus("run-1")).toBe(true); diff --git a/ui/src/components/AgentConfigForm.render.test.tsx b/ui/src/components/AgentConfigForm.render.test.tsx index 94a63dfdcb..d9664a98eb 100644 --- a/ui/src/components/AgentConfigForm.render.test.tsx +++ b/ui/src/components/AgentConfigForm.render.test.tsx @@ -787,6 +787,27 @@ describe("AgentConfigForm environment selector", () => { expect(selector?.textContent).toContain("Fake Sandbox · sandbox"); }); + it("labels the platform-managed instance default by name, without the driver key", async () => { + mockInstanceSettingsApi.get.mockResolvedValue({ defaultEnvironmentId: "managed-1" }); + const result = await renderForm([ + makeEnvironment({ + id: "managed-1", + name: "Paperclip Computer", + driver: "sandbox", + config: { provider: "daytona" }, + metadata: { managedByPaperclip: true }, + }), + ]); + roots.push(result.root); + + const selector = result.container.querySelector("select"); + + expect(selector?.textContent).toContain("Default: Paperclip Computer"); + expect(selector?.textContent).toContain("Paperclip Computer"); + expect(selector?.textContent).not.toContain("(sandbox)"); + expect(selector?.textContent).not.toContain("· sandbox"); + }); + it("renders non-local adapter config fields in the Adapter card", async () => { const result = await renderForm( [makeEnvironment({ id: "local-1", name: "Local", driver: "local" })], diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 50e3eadd25..496da7f9be 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -37,6 +37,7 @@ import { resolveLocalDefaultEnvironmentId, resolveManagedSandboxEnvironmentId, } from "../lib/adapter-test-environment"; +import { environmentDisplayLabel } from "../lib/managed-sandbox-environment"; import { extractModelName, extractProviderId } from "../lib/model-utils"; import { queryKeys } from "../lib/queryKeys"; import { useCompany } from "../context/CompanyContext"; @@ -679,9 +680,9 @@ export function AgentConfigForm(props: AgentConfigFormProps) { ); const managedSandboxOnly = experimentalSettings?.enableManagedSandboxOnly === true; const inheritedEnvironmentLabel = instanceDefaultEnvironment - ? `${instanceDefaultEnvironment.name} (${instanceDefaultEnvironment.driver})` + ? environmentDisplayLabel(instanceDefaultEnvironment) : managedSandboxOnly - ? "Managed sandbox" + ? "Paperclip Computer" : "Local"; // Fetch adapter models for the effective adapter type @@ -1484,7 +1485,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { {environmentOptions.map((environment) => ( ))} @@ -2052,7 +2053,7 @@ function AdapterLoginTerminalState({ return (
- Authenticated. The sandbox has credentials now. + Authenticated. The environment has credentials now.
); } @@ -2180,7 +2181,7 @@ function DisplayedCodeLoginPanel({ return (
- Sign in to the sandbox + Sign in to the environment
{isActive && (
)} diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index 9181d86ede..0557c37ee9 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -656,7 +656,12 @@ const TimelineList = memo(function TimelineList({ {run.environment ? ( Environment {run.environment.name} - · {run.environment.driver} + {/* The raw "sandbox" driver key stays off run details — the + environment's name and the Provider entry below already + identify it; other drivers (ssh, local) remain useful. */} + {run.environment.driver !== "sandbox" ? ( + · {run.environment.driver} + ) : null} ) : null} {run.environmentLease?.provider ? ( diff --git a/ui/src/components/IssueChatThread.test.tsx b/ui/src/components/IssueChatThread.test.tsx index ac548ba055..2fbba554b0 100644 --- a/ui/src/components/IssueChatThread.test.tsx +++ b/ui/src/components/IssueChatThread.test.tsx @@ -3828,7 +3828,7 @@ describe("IssueChatThread", () => { agentId: "agent-1", agentName: "Agent 1", adapterType: "codex_local", - currentStatusMessage: "Syncing git worktree to sandbox", + currentStatusMessage: "Syncing git worktree to environment", currentStatusUpdatedAt: "2026-04-06T12:00:05.000Z", currentToolName: "bash", lastEventAt: new Date(Date.now() - 2000).toISOString(), diff --git a/ui/src/components/IssueWorkspaceCard.tsx b/ui/src/components/IssueWorkspaceCard.tsx index 9d66058e93..4f2e5921ab 100644 --- a/ui/src/components/IssueWorkspaceCard.tsx +++ b/ui/src/components/IssueWorkspaceCard.tsx @@ -97,7 +97,7 @@ function workspaceModeLabel(mode: string | null | undefined) { switch (mode) { case "isolated_workspace": return "Isolated workspace"; case "operator_branch": return "Operator branch"; - case "cloud_sandbox": return "Cloud sandbox"; + case "cloud_sandbox": return "Cloud environment"; case "adapter_managed": return "Adapter managed"; default: return "Workspace"; } diff --git a/ui/src/components/ProjectProperties.tsx b/ui/src/components/ProjectProperties.tsx index 76ed0faeaa..1f8c95d24c 100644 --- a/ui/src/components/ProjectProperties.tsx +++ b/ui/src/components/ProjectProperties.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { filterManagedSandboxSelectableEnvironments } from "@/lib/managed-sandbox-environment"; +import { environmentDisplayLabel, filterManagedSandboxSelectableEnvironments } from "@/lib/managed-sandbox-environment"; import { Link } from "@/lib/router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { Project, SharedWorkspaceConcurrency } from "@paperclipai/shared"; @@ -68,7 +68,7 @@ const SHARED_WORKSPACE_CONCURRENCY_OPTIONS: { { value: "auto", label: "Auto", - help: "Concurrent runs on local/SSH runners; runs take turns in cloud sandboxes.", + help: "Concurrent runs on local/SSH runners; runs take turns in cloud environments.", }, { value: "serialize", @@ -1119,7 +1119,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa {runSelectableEnvironments.map((environment) => ( ))} diff --git a/ui/src/components/TaskChatThread.test.tsx b/ui/src/components/TaskChatThread.test.tsx index 479d35a29d..ab47408b1d 100644 --- a/ui/src/components/TaskChatThread.test.tsx +++ b/ui/src/components/TaskChatThread.test.tsx @@ -575,13 +575,13 @@ describe("TaskChatThread live transcript", () => { comments={[]} onAdd={async () => {}} issueStatus="in_progress" - activeRun={{ ...baseRun, currentStatusMessage: "Syncing workspace to sandbox" }} + activeRun={{ ...baseRun, currentStatusMessage: "Syncing workspace to environment" }} />, ); const tail = container.querySelector('[data-testid="task-chat-live-transcript"]'); expect(tail).not.toBeNull(); - expect(tail!.textContent).toContain("Syncing workspace to sandbox"); + expect(tail!.textContent).toContain("Syncing workspace to environment"); expect(tail!.textContent).not.toContain("Waiting for transcript..."); // Without a runtime status, the generic wait message still shows. diff --git a/ui/src/components/TaskChatThread.tsx b/ui/src/components/TaskChatThread.tsx index f40ff2de90..b6e27856fd 100644 --- a/ui/src/components/TaskChatThread.tsx +++ b/ui/src/components/TaskChatThread.tsx @@ -698,7 +698,7 @@ export function TaskChatThread(props: TaskChatThreadProps) { ? "Waiting to start..." : // Before the first transcript token, surface the run's // live runtime status (sandbox preparation phases like - // "Syncing workspace to sandbox" emitted via + // "Syncing workspace to environment" emitted via // onRuntimeProgress) instead of an opaque wait message. (liveRun && liveRun.id === tailRunId ? liveRun.currentStatusMessage diff --git a/ui/src/lib/issue-chat-messages.test.ts b/ui/src/lib/issue-chat-messages.test.ts index 8e0ad50189..68fa9c2c90 100644 --- a/ui/src/lib/issue-chat-messages.test.ts +++ b/ui/src/lib/issue-chat-messages.test.ts @@ -813,7 +813,7 @@ describe("buildIssueChatMessages", () => { agentId: "agent-1", agentName: "CodexCoder", adapterType: "codex_local", - currentStatusMessage: "Syncing git worktree to sandbox", + currentStatusMessage: "Syncing git worktree to environment", currentStatusUpdatedAt: "2026-04-06T12:03:05.000Z", currentToolName: "bash", lastAssistantSnippet: "Checking repository status", @@ -837,7 +837,7 @@ describe("buildIssueChatMessages", () => { custom: { kind: "live-run", runId: "run-active-1", - currentStatusMessage: "Syncing git worktree to sandbox", + currentStatusMessage: "Syncing git worktree to environment", currentStatusUpdatedAt: "2026-04-06T12:03:05.000Z", currentToolName: "bash", lastAssistantSnippet: "Checking repository status", diff --git a/ui/src/lib/managed-sandbox-environment.test.ts b/ui/src/lib/managed-sandbox-environment.test.ts index d5ea54f1d9..6de63daa96 100644 --- a/ui/src/lib/managed-sandbox-environment.test.ts +++ b/ui/src/lib/managed-sandbox-environment.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + environmentDisplayLabel, filterManagedSandboxSelectableEnvironments, isPlatformManagedEnvironment, } from "./managed-sandbox-environment"; @@ -13,6 +14,22 @@ describe("managed sandbox environment helpers", () => { expect(isPlatformManagedEnvironment(null)).toBe(false); }); + it("labels platform-managed rows by name alone and keeps the driver suffix elsewhere", () => { + expect( + environmentDisplayLabel({ + name: "Paperclip Computer", + driver: "sandbox", + metadata: { managedByPaperclip: true }, + }), + ).toBe("Paperclip Computer"); + expect( + environmentDisplayLabel({ name: "E2B", driver: "sandbox", metadata: null }), + ).toBe("E2B · sandbox"); + expect( + environmentDisplayLabel({ name: "Build box", driver: "ssh", metadata: {} }), + ).toBe("Build box · ssh"); + }); + it("filters the local environment only under managed-sandbox-only", () => { const environments = [ { driver: "local" as const }, diff --git a/ui/src/lib/managed-sandbox-environment.ts b/ui/src/lib/managed-sandbox-environment.ts index c7233c9fa2..e1681c274d 100644 --- a/ui/src/lib/managed-sandbox-environment.ts +++ b/ui/src/lib/managed-sandbox-environment.ts @@ -13,6 +13,20 @@ export function isPlatformManagedEnvironment( return environment?.metadata?.managedByPaperclip === true; } +/** + * Display label for an environment in selectors and lists. Platform-managed + * rows render their name alone: their name is the product name for the + * default Paperclip environment, and the raw driver key ("sandbox") is + * infrastructure vocabulary we don't surface next to it. User-created rows + * keep the driver suffix so mixed lists (ssh vs sandbox) stay tellable apart. + */ +export function environmentDisplayLabel( + environment: Pick, +): string { + if (isPlatformManagedEnvironment(environment)) return environment.name; + return `${environment.name} · ${environment.driver}`; +} + /** * Client-side mirror of the server's managed-sandbox-only read filter: the * local environment never renders when `enableManagedSandboxOnly` is on. diff --git a/ui/src/pages/Agents.tsx b/ui/src/pages/Agents.tsx index f22cadd3ee..8e519f538b 100644 --- a/ui/src/pages/Agents.tsx +++ b/ui/src/pages/Agents.tsx @@ -11,6 +11,7 @@ import { useDialogActions } from "../context/DialogContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useSidebar } from "../context/SidebarContext"; import { queryKeys } from "../lib/queryKeys"; +import { isPlatformManagedEnvironment } from "../lib/managed-sandbox-environment"; import { AgentStatusBadge, AgentStatusCapsule } from "../components/StatusBadge"; import { AgentActionButtons } from "../components/AgentActionButtons"; import { MembershipAction } from "../components/MembershipAction"; @@ -129,11 +130,13 @@ function describeEnvironment( environment: Environment, capabilities?: EnvironmentCapabilities | null, ): EnvironmentDescriptor { - const detail = environment.driver === "sandbox" - ? `${getSandboxProviderLabel(environment, capabilities)} sandbox provider` - : environment.driver === "local" - ? "Paperclip host" - : formatEnvironmentDriver(environment.driver); + const detail = isPlatformManagedEnvironment(environment) + ? "Managed by Paperclip" + : environment.driver === "sandbox" + ? `${getSandboxProviderLabel(environment, capabilities)} sandbox provider` + : environment.driver === "local" + ? "Paperclip host" + : formatEnvironmentDriver(environment.driver); return { label: environment.name, diff --git a/ui/src/pages/CompanyEnvironments.tsx b/ui/src/pages/CompanyEnvironments.tsx index f2899f5add..3a3667d210 100644 --- a/ui/src/pages/CompanyEnvironments.tsx +++ b/ui/src/pages/CompanyEnvironments.tsx @@ -43,7 +43,7 @@ import { import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { useCompany } from "@/context/CompanyContext"; import { useToast } from "@/context/ToastContext"; -import { isPlatformManagedEnvironment } from "@/lib/managed-sandbox-environment"; +import { environmentDisplayLabel, isPlatformManagedEnvironment } from "@/lib/managed-sandbox-environment"; import { queryKeys } from "@/lib/queryKeys"; import { Link, useNavigate, useParams } from "@/lib/router"; import { buildSameOriginWebSocketUrl } from "@/lib/websocket-url"; @@ -1790,7 +1790,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps) )} {nonLocalEnvironments.map((environment) => ( ))} @@ -1821,7 +1821,10 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
- {environment.name} · {environment.driver} + {environment.name} + {isPlatformManagedEnvironment(environment) ? null : ( + · {environment.driver} + )} {isPlatformManagedEnvironment(environment) ? ( @@ -1842,6 +1845,13 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)
{(() => { const summary = summarizeSandboxConfig(environment.config as Record); + // The managed row's badge already says "Managed by + // Paperclip"; repeating provider vocabulary like + // "sandbox provider" next to the default environment + // is noise the product avoids. + if (isPlatformManagedEnvironment(environment)) { + return summary ?? "Provisioned and maintained for you."; + } return `${sandboxProviderDisplayName} sandbox provider${summary ? ` · ${summary}` : ""}`; })()}
@@ -1926,7 +1936,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps)

- {editingEnvironment.description ?? "Your agent runs in a sandbox managed by Paperclip."} + {editingEnvironment.description ?? "Your agent runs on a computer managed by Paperclip."}

This environment is provisioned and maintained for you. You can add environment @@ -2176,7 +2186,7 @@ export function CompanyEnvironments({ mode = "list" }: CompanyEnvironmentsProps) )} setEnvironmentForm((current) => ({ diff --git a/ui/src/pages/InstanceExperimentalSettings.tsx b/ui/src/pages/InstanceExperimentalSettings.tsx index 672a516ddb..284fed071b 100644 --- a/ui/src/pages/InstanceExperimentalSettings.tsx +++ b/ui/src/pages/InstanceExperimentalSettings.tsx @@ -705,14 +705,14 @@ export function InstanceExperimentalSettings() { /> toggleMutation.mutate({ enableManagedSandboxOnly: checked })} disabled={toggleMutation.isPending} settingKey="enableManagedSandboxOnly" managed={managedKeys.enableManagedSandboxOnly} - ariaLabel="Toggle managed sandbox only experimental setting" + ariaLabel="Toggle managed environment only experimental setting" /> {inWorktree ? (