(opts: {
harness: HarnessName
provider: ProviderName
threadId: string
})
| 384 | |
| 385 | /** One sandbox per (harness, provider, thread): switching a picker → fresh sandbox. */ |
| 386 | export function buildSandbox(opts: { |
| 387 | harness: HarnessName |
| 388 | provider: ProviderName |
| 389 | threadId: string |
| 390 | }): SandboxDefinition { |
| 391 | const harness = HARNESSES[opts.harness] |
| 392 | // Publish/declare the preview port + any harness-specific port (opencode serve). |
| 393 | const ports = [ |
| 394 | PREVIEW_PORT, |
| 395 | ...(harness.exposePort !== undefined ? [harness.exposePort] : []), |
| 396 | ] |
| 397 | const provider = PROVIDERS[opts.provider].make(ports) |
| 398 | |
| 399 | // Inject auth secrets only for sandboxed providers — local-process inherits the |
| 400 | // host's own env, so nothing to inject. |
| 401 | const secretEnv: Record<string, string> = {} |
| 402 | if (opts.provider !== 'local') { |
| 403 | if (harness.sandboxSecrets) { |
| 404 | Object.assign(secretEnv, harness.sandboxSecrets()) |
| 405 | } else { |
| 406 | for (const key of harness.requiredEnv) { |
| 407 | const value = process.env[key] |
| 408 | if (value) secretEnv[key] = value |
| 409 | } |
| 410 | } |
| 411 | for (const key of PROVIDERS[opts.provider].requiredEnv) { |
| 412 | const value = process.env[key] |
| 413 | if (value) secretEnv[key] = value |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | return defineSandbox({ |
| 418 | id: `sandbox-web-${opts.harness}-${opts.provider}-${opts.threadId}`, |
| 419 | provider, |
| 420 | workspace: defineWorkspace({ |
| 421 | // No source to clone — the agent scaffolds a fresh app. |
| 422 | source: { type: 'none' }, |
| 423 | setup: ({ serial }) => { |
| 424 | // Install the harness CLI into the fresh sandbox for every provider EXCEPT |
| 425 | // local-process (which uses the host's CLI on PATH). Each command is |
| 426 | // self-contained (its own EACCES/sudo handling) — see HarnessSpec. |
| 427 | if (opts.provider !== 'local' && harness.installCommand) { |
| 428 | serial(harness.installCommand) |
| 429 | } |
| 430 | }, |
| 431 | secrets: createSecrets(secretEnv), |
| 432 | }), |
| 433 | lifecycle: { reuse: 'thread' }, |
| 434 | }) |
| 435 | } |
| 436 | |
| 437 | /** |
| 438 | * The `exposePreview` server tool for one run (BRIDGE strategy). Minting a preview |
no test coverage detected