@tangle-network/agent-app 0.35.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DTS5TZRN.js +293 -0
- package/dist/chunk-DTS5TZRN.js.map +1 -0
- package/dist/{chunk-GZGF3JWQ.js → chunk-FFBZJEBE.js} +226 -171
- package/dist/chunk-FFBZJEBE.js.map +1 -0
- package/dist/{chunk-MH6AVXQ7.js → chunk-VT5DI6GL.js} +40 -34
- package/dist/chunk-VT5DI6GL.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/sandbox/index.d.ts +22 -26
- package/dist/sandbox/index.js +2 -2
- package/dist/studio/index.d.ts +132 -0
- package/dist/studio/index.js +59 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/studio-react/index.d.ts +193 -0
- package/dist/studio-react/index.js +1217 -0
- package/dist/studio-react/index.js.map +1 -0
- package/dist/studio-react/studio.css +73 -0
- package/dist/tools/index.js +1 -1
- package/dist/web-react/index.js +31 -13
- package/dist/web-react/index.js.map +1 -1
- package/package.json +38 -6
- package/dist/chunk-GZGF3JWQ.js.map +0 -1
- package/dist/chunk-MH6AVXQ7.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandbox/index.ts","../src/sandbox/outcome.ts","../src/sandbox/terminal-proxy-token.ts","../src/sandbox/workspace-terminal.ts"],"sourcesContent":["import {\n Sandbox,\n type AgentProfile,\n type AgentProfileFileMount,\n type AgentProfileMcpServer,\n type SandboxConnection,\n type SandboxInstance,\n type ScopedTokenScope,\n type StorageConfig,\n type PromptResult,\n} from '@tangle-network/sandbox'\nimport {\n buildAppToolMcpServer,\n type AppToolName,\n type AppToolContext,\n type ToolHeaderNames,\n} from '../tools/index'\nimport { assertHarnessModelCompatible, type Harness } from '../harness/index'\nimport { ok, fail, type Outcome } from './outcome'\n\nexport type { Outcome } from './outcome'\n\nexport interface SandboxClientCredentials {\n apiKey: string\n baseUrl: string\n}\n\nexport interface SandboxResourceConfig {\n image: string\n cpuCores: number\n memoryMB: number\n diskGB: number\n maxLifetimeSeconds: number\n idleTimeoutSeconds: number\n}\n\nexport interface ProviderResolutionConfig {\n routerBaseUrl?: string\n apiKey?: string\n providerName?: string\n modelName?: string\n defaultModel?: string\n openaiApiKey?: string\n}\n\nexport interface SandboxBuildContext {\n workspaceId: string\n connectedIntegrationIds: string[]\n userId?: string\n}\n\n// SDK-typed snapshot storage config (re-exported for product seam closures).\nexport type { StorageConfig }\n\n// Scope handed to per-identity seams. workspaceId is always present; userId is\n// present when the lifecycle op carries one. Workspace-keyed products ignore userId.\nexport interface SandboxScope {\n workspaceId: string\n userId?: string\n}\n\n// Snapshot RESTORE-on-create. Returned alongside storage; undefined => fresh box.\nexport interface SandboxRestoreSpec {\n fromSnapshot: string\n fromSandboxId: string\n}\n\n// Reuse health gate + sidecar liveness. The exec+timeout-race is generic; the\n// sidecarProcessPattern is harness-specific (which process is the live sidecar),\n// so it is a closure. Absent => no liveness probe (reuse on metadata.harness match).\nexport interface LivenessProbeConfig {\n sidecarProcessPattern: (harness: Harness) => string\n execTimeoutMs?: number\n psTimeoutMs?: number\n}\n\nexport interface ProfileComposeOptions {\n systemPrompt?: string\n extraFiles?: AgentProfileFileMount[]\n extraMcp?: Record<string, AgentProfileMcpServer>\n name?: string\n}\n\nexport interface SandboxRuntimeConfig {\n // Widened to accept an optional scope and be async so a per-user key can be\n // minted. The sync, no-arg form still satisfies the type (back-compat).\n credentials: (\n scope?: SandboxScope,\n ) => SandboxClientCredentials | null | Promise<SandboxClientCredentials | null>\n name: (workspaceId: string) => string\n metadata: (harness: Harness) => Record<string, unknown>\n connectedIntegrationIds: (workspaceId: string) => Promise<string[]>\n env: (ctx: SandboxBuildContext) => Promise<Record<string, string>>\n files: (ctx: SandboxBuildContext) => Promise<AgentProfileFileMount[]>\n secrets: (workspaceId: string) => Promise<string[]>\n profile: (options: ProfileComposeOptions) => AgentProfile\n permissionRole?: (workspaceRole: string) => SandboxPermissionLevel\n resources?: SandboxResourceConfig\n provider?: ProviderResolutionConfig\n\n // BYOS3/R2 snapshot storage. Returns undefined => key omitted entirely\n // (fail-closed when creds absent). Product owns bucket/endpoint/credentials/prefix.\n storage?: (ctx: SandboxBuildContext) => StorageConfig | undefined\n // Snapshot RESTORE-on-create. undefined => fresh box.\n restore?: (ctx: SandboxBuildContext) => SandboxRestoreSpec | undefined\n // Per-identity box NAME. Defaults to name(scope.workspaceId) when absent.\n boxKey?: (scope: SandboxScope) => string\n // Per-workspace child-key mint: overrides the resolved model apiKey before create.\n // Applied only when a model is resolved and its provider is openai-compat.\n childKeyMint?: (scope: SandboxScope) => Promise<Outcome<string>>\n // One-shot post-running bootstrap, on BOTH create and reuse paths (idempotency\n // is the closure's job — it owns the marker check).\n bootstrap?: (box: SandboxInstance, scope: SandboxScope) => Promise<Outcome<void>>\n // Reuse health gate + sidecar liveness probe.\n livenessProbe?: LivenessProbeConfig\n // Enable browser terminal endpoints on newly-created sandboxes.\n webTerminalEnabled?: boolean\n // default true: try stopped-resume before create.\n resumeStopped?: boolean\n // default false: bake resolveModel() into backend.model at create.\n backendModelAtCreate?: boolean\n // default false: write the profile's `resources.files` INTO the box after it\n // reaches running (via `box.exec`), instead of inlining them in the create\n // payload. The orchestrator caps the provision body at 256 KiB; a large\n // file corpus (skills, tool scripts) blows that cap. Deferring it keeps the\n // provision body small and uncapped in corpus size, and lands real files on\n // disk — which is also the only path that works for harnesses whose backend\n // does not materialize the provider-neutral `resources.files` channel.\n //\n // Only `kind: 'inline'` files are deferred; non-inline refs (e.g. github)\n // stay in the create payload so the orchestrator resolves them. Runs on the\n // create AND resume/reuse paths (idempotent overwrite). Inline files are\n // STRIPPED from `resources.files` before create when this is set.\n deferProfileFiles?: boolean\n}\n\nexport const DEFAULT_SANDBOX_RESOURCES: SandboxResourceConfig = {\n image: 'universal',\n cpuCores: 2,\n memoryMB: 4096,\n diskGB: 10,\n maxLifetimeSeconds: 86400,\n idleTimeoutSeconds: 3600,\n}\n\ninterface ClientCacheEntry {\n client: Sandbox\n fingerprint: string\n}\n\nlet _cached: ClientCacheEntry | null = null\n\nfunction getClientFromCreds(creds: SandboxClientCredentials): Sandbox {\n const fingerprint = `${creds.apiKey} ${creds.baseUrl}`\n if (_cached && _cached.fingerprint === fingerprint) return _cached.client\n\n const client = new Sandbox({ apiKey: creds.apiKey, baseUrl: creds.baseUrl })\n _cached = { client, fingerprint }\n return client\n}\n\n// Sync client for non-scoped callers (secretStoreFromClient etc.). Resolves\n// credentials with no scope; throws if the seam returns a Promise — scoped\n// products must use the async ensureWorkspaceSandbox path.\nexport function getClient(shell: SandboxRuntimeConfig): Sandbox {\n const creds = shell.credentials()\n if (creds && typeof (creds as Promise<unknown>).then === 'function') {\n throw new Error('getClient: scoped (async) credentials require the async sandbox path')\n }\n if (!creds) throw new Error('sandbox credentials are required (apiKey/baseUrl)')\n return getClientFromCreds(creds as SandboxClientCredentials)\n}\n\nexport function resetClientCache(): void {\n _cached = null\n}\n\nexport interface AppToolDescriptor {\n tool: AppToolName\n key: string\n description: string\n}\n\nexport interface BuildAppToolMcpServersOptions {\n tools: AppToolDescriptor[]\n baseUrl: string\n token: string\n ctx: AppToolContext\n headerNames?: ToolHeaderNames\n}\n\nexport function buildAppToolMcpServers(\n options: BuildAppToolMcpServersOptions,\n): Record<string, AgentProfileMcpServer> {\n const entries: Record<string, AgentProfileMcpServer> = {}\n for (const { tool, key, description } of options.tools) {\n entries[key] = buildAppToolMcpServer({\n tool,\n baseUrl: options.baseUrl,\n token: options.token,\n ctx: options.ctx,\n description,\n headerNames: options.headerNames,\n }) as AgentProfileMcpServer\n }\n return entries\n}\n\nexport interface EnsureWorkspaceSandboxOptions {\n workspaceId: string\n userId?: string\n harness: Harness\n // When set, both the running-reuse and stopped-resume short-circuits are\n // skipped and any name-matched box is deleted before create.\n forceNew?: boolean\n}\n\n// Single-quote a string for safe interpolation into a shell command.\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n// Build a shell-safe path token that preserves tilde-home semantics. A path\n// beginning `~/` (or a bare `~`) must resolve to the box user's real `$HOME`,\n// but single-quoting the whole path suppresses shell `~` expansion and lands\n// the file in a literal directory named `~`. Expand the leading `~` to an\n// UNQUOTED `\"$HOME\"` (so the shell expands it) and single-quote only the\n// remainder. Absolute and relative paths are single-quoted unchanged.\nfunction shellPath(path: string): string {\n if (path === '~') return '\"$HOME\"'\n if (path.startsWith('~/')) {\n const rest = path.slice(2)\n return rest ? `\"$HOME\"/${shellSingleQuote(rest)}` : '\"$HOME\"'\n }\n return shellSingleQuote(path)\n}\n\n// Split a profile's `resources.files` into the inline mounts that can be\n// written into a running box and the rest (non-inline refs that the\n// orchestrator must resolve, so they stay in the create payload). Returns the\n// inline set to defer and a profile copy with those inline files removed.\nexport function splitDeferredProfileFiles(\n profile: AgentProfile,\n): { leanProfile: AgentProfile; deferredFiles: AgentProfileFileMount[] } {\n const files = profile.resources?.files ?? []\n const deferredFiles: AgentProfileFileMount[] = []\n const keptFiles: AgentProfileFileMount[] = []\n for (const mount of files) {\n if (mount.resource.kind === 'inline') deferredFiles.push(mount)\n else keptFiles.push(mount)\n }\n if (deferredFiles.length === 0) return { leanProfile: profile, deferredFiles }\n const leanProfile: AgentProfile = {\n ...profile,\n resources: { ...(profile.resources ?? {}), files: keptFiles },\n }\n return { leanProfile, deferredFiles }\n}\n\n// The runtime exec proxy (`box.exec` → /terminals/commands) hangs (30s\n// timeout) on any request whose body crosses ~4096 bytes, and one oversized\n// exec wedges the channel so every later exec on the box hangs too. We slice\n// each file's base64 into appends whose full command string stays well under\n// that cap. 3000 chars of base64 leaves ~1000 bytes of headroom for the\n// surrounding `printf '%s' '<slice>' >> <path>.b64` command plus the proxy's\n// JSON request envelope — comfortably below 4096.\nconst PROFILE_WRITE_B64_CHUNK_CHARS = 3000\n\n// gtm's corpus is ~45 small execs; on a cold box the runtime exec proxy can\n// return HTTP 429 (rate limit) mid-batch. A 429 is transient, so retry it with\n// exponential backoff before failing loud. Non-429 errors are NOT retried.\nconst PROFILE_WRITE_MAX_429_RETRIES = 4\nconst PROFILE_WRITE_RETRY_BASE_MS = 250\nconst PROFILE_WRITE_RETRY_MAX_MS = 2000\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\n// Detect a rate-limit (HTTP 429) surfaced by the exec proxy, regardless of\n// whether it arrives as a SandboxError-shaped object (status/code/retryAfterMs)\n// or as a generic thrown error whose message carries the 429.\nfunction rateLimit(err: unknown): { is429: boolean; retryAfterMs?: number } {\n if (err && typeof err === 'object') {\n const e = err as { status?: unknown; code?: unknown; message?: unknown; retryAfterMs?: unknown }\n const retryAfterMs = typeof e.retryAfterMs === 'number' ? e.retryAfterMs : undefined\n if (e.status === 429) return { is429: true, retryAfterMs }\n if (typeof e.code === 'string' && /rate.?limit|too.?many.?requests|429/i.test(e.code)) {\n return { is429: true, retryAfterMs }\n }\n if (typeof e.message === 'string' && /\\b429\\b|rate.?limit|too many requests/i.test(e.message)) {\n return { is429: true, retryAfterMs }\n }\n }\n return { is429: false }\n}\n\n// Materialize inline profile files into a running box via `box.exec`. Uses a\n// base64 pipe so arbitrary content (scripts, unicode, special chars) lands\n// byte-exact, and writes to ANY absolute path (e.g. /usr/local/bin) or a\n// `~`-relative path — the exec runs as the sidecar, which is not bound by the\n// safe-prefix allow-list the /files/write API enforces. Sets the executable\n// bit when the mount declares it OR the target is a bin directory.\n//\n// Each file is written in several small execs (mkdir, one append per base64\n// chunk, then a decode+cleanup) so no single exec request body trips the\n// ~4 KiB proxy cap. Writes are sequential; a single bad mount stops the batch\n// and the first failure is returned (fail-loud), the rest are not attempted.\nexport async function writeProfileFilesToBox(\n box: SandboxInstance,\n files: AgentProfileFileMount[],\n): Promise<Outcome<void>> {\n for (const mount of files) {\n if (mount.resource.kind !== 'inline') continue\n const content = mount.resource.content ?? ''\n const b64 = Buffer.from(content, 'utf8').toString('base64')\n const path = mount.path\n const dir = path.replace(/\\/[^/]*$/, '')\n const isBin = /(^|\\/)(s?bin)\\//.test(path)\n const executable = mount.executable ?? isBin\n const q = shellPath(path)\n const qb64 = shellPath(`${path}.b64`)\n\n // Run one exec step, surfacing a non-zero exit or transport error as a\n // fail-loud Outcome with the underlying NetworkError/TimeoutError as cause.\n // A 429 (rate limit) from the proxy is transient: retry with exponential\n // backoff up to PROFILE_WRITE_MAX_429_RETRIES. Any non-429 error fails loud\n // immediately. A non-zero exit is a real command failure, not retried.\n const step = async (cmd: string): Promise<Outcome<void>> => {\n for (let attempt = 0; ; attempt++) {\n try {\n const res = await box.exec(cmd)\n if (res.exitCode !== 0) {\n return fail(\n new Error(\n `writeProfileFilesToBox: failed to write ${path} (exit ${res.exitCode}): ${res.stderr.slice(0, 500)}`,\n ),\n )\n }\n return ok(undefined)\n } catch (err) {\n const { is429, retryAfterMs } = rateLimit(err)\n if (is429 && attempt < PROFILE_WRITE_MAX_429_RETRIES) {\n const backoff = Math.min(\n PROFILE_WRITE_RETRY_BASE_MS * 2 ** attempt,\n PROFILE_WRITE_RETRY_MAX_MS,\n )\n await sleep(retryAfterMs ?? backoff)\n continue\n }\n return fail(new Error(`writeProfileFilesToBox: exec failed for ${path}`, { cause: err }))\n }\n }\n }\n\n // Start the staging file empty so re-runs (redeploy with new skills)\n // overwrite rather than append. mkdir -p creates the target directory;\n // shellPath keeps `~/...` resolving to the box user's real $HOME.\n const mkdir = dir && dir !== path ? `mkdir -p ${shellPath(dir)} && ` : ''\n let res = await step(`${mkdir}: > ${qb64}`)\n if (!res.succeeded) return res\n\n // Append the base64 in capped slices; the base64 alphabet has no single\n // quotes, so single-quoting each slice is safe. printf '%s' adds no newline.\n for (let i = 0; i < b64.length; i += PROFILE_WRITE_B64_CHUNK_CHARS) {\n const slice = b64.slice(i, i + PROFILE_WRITE_B64_CHUNK_CHARS)\n res = await step(`printf '%s' '${slice}' >> ${qb64}`)\n if (!res.succeeded) return res\n }\n\n // Decode the staged base64 to the real path, drop the staging file, and set\n // the executable bit when required. base64 -d reconstructs the exact bytes.\n const chmod = executable ? ` && chmod +x ${q}` : ''\n res = await step(`base64 -d ${qb64} > ${q} && rm -f ${qb64}${chmod}`)\n if (!res.succeeded) return res\n }\n return ok(undefined)\n}\n\n// Resolve the shell's deferred (inline) profile files and write them into a\n// box that already exists (reuse/resume paths). No-op unless the shell opts\n// into deferProfileFiles. Idempotent overwrite — a redeploy with new skills\n// refreshes the corpus on the next ensure call.\nasync function materializeDeferredFilesForExistingBox(\n shell: SandboxRuntimeConfig,\n box: SandboxInstance,\n workspaceId: string,\n userId: string | undefined,\n): Promise<Outcome<void>> {\n if (!shell.deferProfileFiles) return ok(undefined)\n const connectedIntegrationIds = await shell.connectedIntegrationIds(workspaceId)\n const buildCtx: SandboxBuildContext = {\n workspaceId,\n connectedIntegrationIds,\n ...(userId ? { userId } : {}),\n }\n const files = await shell.files(buildCtx)\n const fullProfile = shell.profile({ extraFiles: files })\n const { deferredFiles } = splitDeferredProfileFiles(fullProfile)\n if (deferredFiles.length === 0) return ok(undefined)\n return writeProfileFilesToBox(box, deferredFiles)\n}\n\nasync function listRunning(\n client: Sandbox,\n name: string,\n): Promise<Outcome<SandboxInstance | null>> {\n try {\n const running = await client.list({ status: 'running' })\n return ok(running.find((s) => s.name === name) ?? null)\n } catch (err) {\n return fail(err)\n }\n}\n\nasync function deleteBox(box: SandboxInstance): Promise<Outcome<void>> {\n try {\n await box.delete()\n return ok(undefined)\n } catch (err) {\n return fail(err)\n }\n}\n\n// The SDK narrows `backend.type` to its own BackendType union and\n// `initialUsers[].role` to PermissionLevel — neither symbol is exported. The\n// create payload is assembled with the product's Harness/role strings, which\n// are a superset surface; the localized cast at the boundary is the only place\n// this widening is allowed, and the runtime contract (the sidecar boots the\n// named harness) is what enforces correctness.\ntype CreatePayload = Parameters<Sandbox['create']>[0]\n\n// Generic exec+sidecar liveness probe. Absent probe => always alive (the prior\n// reuse-on-metadata-match behavior). With a probe: the container must answer an\n// `echo alive` exec within execTimeoutMs, and the sidecar process must be found\n// by pgrep within psTimeoutMs (an inconclusive pgrep is treated as reusable).\nasync function isBoxAlive(\n box: SandboxInstance,\n harness: Harness,\n probe: LivenessProbeConfig | undefined,\n): Promise<boolean> {\n if (!probe) return true\n const execTimeout = probe.execTimeoutMs ?? 5000\n const psTimeout = probe.psTimeoutMs ?? 3000\n const race = <T>(p: Promise<T>, ms: number, label: string): Promise<T> =>\n Promise.race([\n p,\n new Promise<T>((_, reject) => setTimeout(() => reject(new Error(label)), ms)),\n ])\n try {\n const alive = await race(box.exec('echo alive'), execTimeout, 'alive check timeout')\n if (!alive.stdout.includes('alive')) return false\n const pattern = probe.sidecarProcessPattern(harness)\n try {\n const ps = await race(\n box.exec(`pgrep -f ${shellSingleQuote(pattern)} || echo no-sidecar`),\n psTimeout,\n 'ps check timeout',\n )\n if (ps.stdout.includes('no-sidecar')) return false\n } catch {\n // sidecar probe inconclusive — container is alive, treat as reusable\n }\n return true\n } catch {\n return false\n }\n}\n\nconst RUNTIME_CONNECTION_WAIT_MS = 30_000\nconst RUNTIME_CONNECTION_POLL_MS = 1_000\n\n// `sidecarUrl` is a product/forward-compat field the orchestrator may set on\n// the connection ahead of the SDK declaring it — the v0.6 `SandboxConnection`\n// exposes only `runtimeUrl`. Read it through a typed augmentation rather than an\n// ad-hoc cast (a plain `SandboxConnection` satisfies the optional field), and\n// fall back to the SDK runtime URL. The product-facing `WorkspaceSandboxInstanceLike`\n// carries the same field for consumers driving their own box types.\ntype RuntimeConnectionFields = SandboxConnection & { sidecarUrl?: string }\n\nfunction sandboxRuntimeUrl(box: SandboxInstance): string | undefined {\n const connection: RuntimeConnectionFields | undefined = box.connection\n return connection?.sidecarUrl ?? connection?.runtimeUrl\n}\n\nfunction sandboxEdgeFailed(box: SandboxInstance): boolean {\n // `edgeStatus`/`edgeError` are declared on the SDK's `SandboxConnection`, so no cast.\n const connection = box.connection\n return connection?.edgeStatus === 'failed' || Boolean(connection?.edgeError)\n}\n\nasync function refreshRuntimeConnection(\n client: Sandbox,\n box: SandboxInstance,\n): Promise<SandboxInstance> {\n let current = box\n if (sandboxRuntimeUrl(current)) return current\n\n const deadline = Date.now() + RUNTIME_CONNECTION_WAIT_MS\n while (Date.now() < deadline) {\n // Tolerate transient refresh/get failures (5xx, network blips) while the\n // orchestrator is still attaching the connection: swallow and retry. A box\n // that never surfaces a runtime URL is returned as-is so the caller's\n // readiness gate (isReusableBox) drops and recreates it — rather than this\n // poll throwing a hard provisioning failure on a recoverable hiccup.\n try {\n await current.refresh()\n if (sandboxRuntimeUrl(current)) return current\n\n const latest = await client.get(current.id)\n if (latest) current = latest\n if (sandboxRuntimeUrl(current)) return current\n } catch {\n // transient — fall through to the poll delay and retry\n }\n\n await new Promise((resolve) => setTimeout(resolve, RUNTIME_CONNECTION_POLL_MS))\n }\n\n return current\n}\n\n// Decide whether an existing (reused or resumed) box is safe to hand back.\n// `refreshRuntimeConnection` has already polled for the runtime URL, so a box\n// that still has none never became connectable and must be recreated rather\n// than silently reused — downstream exec/terminal traffic would fail against\n// it. A failed edge is likewise unusable. Only when the connection is present\n// do we spend an exec round-trip on the liveness probe.\nasync function isReusableBox(\n box: SandboxInstance,\n harness: Harness,\n probe: LivenessProbeConfig | undefined,\n): Promise<boolean> {\n if (sandboxEdgeFailed(box)) return false\n if (!sandboxRuntimeUrl(box)) return false\n return isBoxAlive(box, harness, probe)\n}\n\n// Resume a name-matched stopped box and wait for it to reach running. Returns\n// ok(null) when no stopped box matches the name.\nasync function resumeStoppedBox(\n client: Sandbox,\n name: string,\n timeoutMs: number,\n): Promise<Outcome<SandboxInstance | null>> {\n try {\n const stopped = await client.list({ status: 'stopped' })\n const match = stopped.find((s) => s.name === name) ?? null\n if (!match) return ok(null)\n await match.resume()\n await match.waitFor('running', { timeoutMs })\n return ok(match)\n } catch (err) {\n return fail(err)\n }\n}\n\nexport async function ensureWorkspaceSandbox(\n shell: SandboxRuntimeConfig,\n options: EnsureWorkspaceSandboxOptions,\n): Promise<SandboxInstance> {\n const { workspaceId, userId, harness, forceNew } = options\n const scope: SandboxScope = { workspaceId, ...(userId ? { userId } : {}) }\n const creds = await shell.credentials(scope)\n if (!creds) throw new Error('sandbox credentials are required (apiKey/baseUrl)')\n const client = getClientFromCreds(creds)\n const name = shell.boxKey ? shell.boxKey(scope) : shell.name(workspaceId)\n const resources = shell.resources ?? DEFAULT_SANDBOX_RESOURCES\n const resumeTimeout = 120_000\n\n // Stage 1 — running-box reuse (skipped on forceNew).\n const existing = await listRunning(client, name)\n if (existing.succeeded && existing.value) {\n const found = existing.value\n if (forceNew) {\n const dropped = await deleteBox(found)\n if (!dropped.succeeded) {\n throw new Error(`forceNew: sandbox ${name} could not be deleted`, { cause: dropped.error })\n }\n } else if (found.metadata?.harness === harness) {\n const ready = await refreshRuntimeConnection(client, found)\n if (await isReusableBox(ready, harness, shell.livenessProbe)) {\n const written = await materializeDeferredFilesForExistingBox(shell, ready, workspaceId, userId)\n if (!written.succeeded) {\n throw new Error(`deferred file write failed on reused box ${name}`, { cause: written.error })\n }\n if (shell.bootstrap) {\n const boot = await shell.bootstrap(ready, scope)\n if (!boot.succeeded) {\n throw new Error(`bootstrap failed on reused box ${name}`, { cause: boot.error })\n }\n }\n return ready\n }\n const dropped = await deleteBox(ready)\n if (!dropped.succeeded) {\n throw new Error(\n `sandbox ${name} ` +\n `(was ${String(found.metadata?.harness ?? 'unknown')}, want ${harness}, or unresponsive) ` +\n `could not be deleted`,\n { cause: dropped.error },\n )\n }\n } else {\n const dropped = await deleteBox(found)\n if (!dropped.succeeded) {\n throw new Error(\n `sandbox ${name} ` +\n `(was ${String(found.metadata?.harness ?? 'unknown')}, want ${harness}, or unresponsive) ` +\n `could not be deleted`,\n { cause: dropped.error },\n )\n }\n }\n }\n\n // Stage 2 — stopped-box resume (skipped on forceNew or resumeStopped===false).\n if (!forceNew && shell.resumeStopped !== false) {\n const resumed = await resumeStoppedBox(client, name, resumeTimeout)\n if (resumed.succeeded && resumed.value) {\n const box = await refreshRuntimeConnection(client, resumed.value)\n if (await isReusableBox(box, harness, shell.livenessProbe)) {\n const written = await materializeDeferredFilesForExistingBox(shell, box, workspaceId, userId)\n if (!written.succeeded) {\n throw new Error(`deferred file write failed on resumed box ${name}`, { cause: written.error })\n }\n if (shell.bootstrap) {\n const boot = await shell.bootstrap(box, scope)\n if (!boot.succeeded) {\n throw new Error(`bootstrap failed on resumed box ${name}`, { cause: boot.error })\n }\n }\n return box\n }\n const dropped = await deleteBox(box)\n if (!dropped.succeeded) {\n throw new Error(\n `resumed sandbox ${name} ` +\n `(was ${String(box.metadata?.harness ?? 'unknown')}, want ${harness}, or unresponsive) ` +\n `could not be deleted`,\n { cause: dropped.error },\n )\n }\n }\n }\n\n // Stage 3 — create fresh.\n const connectedIntegrationIds = await shell.connectedIntegrationIds(workspaceId)\n const buildCtx: SandboxBuildContext = {\n workspaceId,\n connectedIntegrationIds,\n ...(userId ? { userId } : {}),\n }\n const [secrets, env, files] = await Promise.all([\n shell.secrets(workspaceId),\n shell.env(buildCtx),\n shell.files(buildCtx),\n ])\n const fullProfile = shell.profile({ extraFiles: files })\n // When deferring, strip inline files from the create payload and write them\n // into the box after it reaches running. Keeps the provision body under the\n // orchestrator's 256 KiB cap and lands real files on disk.\n const { leanProfile, deferredFiles } = shell.deferProfileFiles\n ? splitDeferredProfileFiles(fullProfile)\n : { leanProfile: fullProfile, deferredFiles: [] as AgentProfileFileMount[] }\n const profile = leanProfile\n\n const role = userId && shell.permissionRole ? shell.permissionRole('developer') : undefined\n\n // Bake the model at create when opted in. childKeyMint overrides the apiKey\n // per-workspace; a typed mint failure falls through to the parent key (logged).\n let model = shell.backendModelAtCreate ? resolveModel(shell.provider) : undefined\n if (model && shell.childKeyMint && model.provider === 'openai-compat') {\n const minted = await shell.childKeyMint(scope)\n if (minted.succeeded) model = { ...model, apiKey: minted.value }\n else {\n console.error(\n `[sandbox] childKeyMint failed for ${workspaceId}; using parent key:`,\n minted.error.message,\n )\n }\n }\n\n const storage = shell.storage?.(buildCtx)\n const restore = shell.restore?.(buildCtx)\n\n const payload = {\n name,\n image: resources.image,\n metadata: shell.metadata(harness),\n ...(userId ? { permissions: { initialUsers: [{ userId, role }] } } : {}),\n env,\n secrets,\n backend: { type: harness, profile, ...(model ? { model } : {}) },\n ...(storage ? { storage } : {}),\n ...(restore ? restore : {}),\n ...(shell.webTerminalEnabled ? { webTerminalEnabled: true } : {}),\n maxLifetimeSeconds: resources.maxLifetimeSeconds,\n idleTimeoutSeconds: resources.idleTimeoutSeconds,\n resources: {\n cpuCores: resources.cpuCores,\n memoryMB: resources.memoryMB,\n diskGB: resources.diskGB,\n },\n } as CreatePayload\n\n let box = await client.create(payload)\n\n await box.waitFor('running', { timeoutMs: 120_000 })\n box = await refreshRuntimeConnection(client, box)\n\n if (deferredFiles.length > 0) {\n const written = await writeProfileFilesToBox(box, deferredFiles)\n if (!written.succeeded) {\n throw new Error(`deferred file write failed on new box ${name}`, { cause: written.error })\n }\n }\n\n if (shell.bootstrap) {\n const boot = await shell.bootstrap(box, scope)\n if (!boot.succeeded) {\n throw new Error(`bootstrap failed on new box ${name}`, { cause: boot.error })\n }\n }\n return box\n}\n\nexport interface ResolvedModel {\n model: string\n provider: string\n apiKey: string\n baseUrl?: string\n}\n\nexport function resolveModel(\n config: ProviderResolutionConfig | undefined,\n override?: { model?: string; modelApiKey?: string },\n): ResolvedModel | undefined {\n const c = config ?? {}\n const explicitBaseUrl = c.routerBaseUrl\n const explicitApiKey = override?.modelApiKey ?? c.apiKey\n const provider =\n c.providerName ?? (explicitApiKey ? 'openai-compat' : c.openaiApiKey ? 'openai' : undefined)\n const modelName =\n override?.model ??\n c.modelName ??\n (provider === 'openai' || provider === 'openai-compat' ? c.defaultModel : undefined)\n const apiKey = explicitApiKey ?? (provider === 'openai' ? c.openaiApiKey : undefined)\n if (!provider || !modelName || !apiKey) return undefined\n return {\n model: modelName,\n provider,\n apiKey,\n ...(explicitBaseUrl ? { baseUrl: explicitBaseUrl } : {}),\n }\n}\n\nexport function flattenHistory(\n message: string,\n history?: Array<{ role: 'user' | 'assistant'; content: string }>,\n): string {\n if (!history?.length) return message\n const transcript = history\n .map((entry) => `${entry.role === 'assistant' ? 'Assistant' : 'User'}: ${entry.content}`)\n .join('\\n\\n')\n return `${transcript}\\n\\nUser: ${message}`\n}\n\nexport function mergeExtraMcp(\n appToolMcp: Record<string, AgentProfileMcpServer>,\n baseProfileMcp: Record<string, AgentProfileMcpServer>,\n extra: Record<string, AgentProfileMcpServer> | undefined,\n): Record<string, AgentProfileMcpServer> {\n for (const key of Object.keys(extra ?? {})) {\n if (key in appToolMcp || key in baseProfileMcp) {\n throw new Error(`extraMcp key '${key}' collides with an existing profile MCP server`)\n }\n }\n return { ...appToolMcp, ...(extra ?? {}) }\n}\n\nexport function attachReasoningEffort(\n profile: AgentProfile,\n harness: Harness,\n effort: 'auto' | 'low' | 'medium' | 'high' | undefined,\n): AgentProfile {\n if (!effort || effort === 'auto') return profile\n return {\n ...profile,\n extensions: {\n ...(profile.extensions ?? {}),\n [harness]: {\n ...(profile.extensions?.[harness] ?? {}),\n reasoningEffort: effort,\n },\n },\n }\n}\n\nexport interface StreamSandboxPromptOptions {\n sessionId?: string\n executionId?: string\n lastEventId?: string\n systemPrompt?: string\n model?: string\n modelApiKey?: string\n history?: Array<{ role: 'user' | 'assistant'; content: string }>\n harness?: Harness\n effort?: 'auto' | 'low' | 'medium' | 'high'\n appToolMcp?: Record<string, AgentProfileMcpServer>\n baseProfileMcp?: Record<string, AgentProfileMcpServer>\n extraMcp?: Record<string, AgentProfileMcpServer>\n signal?: AbortSignal\n timeoutMs?: number\n // When true, an interactive question event throws instead of yielding —\n // detached (cron/mission-step) runs have no consumer to answer it.\n disallowQuestions?: boolean\n}\n\ntype StreamPromptOptions = Parameters<SandboxInstance['streamPrompt']>[1]\n\nexport async function* streamSandboxPrompt(\n shell: SandboxRuntimeConfig,\n box: SandboxInstance,\n message: string,\n options?: StreamSandboxPromptOptions,\n): AsyncGenerator<unknown> {\n const harness = options?.harness ?? 'opencode'\n const model = resolveModel(shell.provider, {\n model: options?.model,\n modelApiKey: options?.modelApiKey,\n })\n\n // Server-side enforcement of the harness↔model policy: a vendor-locked harness\n // (claude-code/codex/kimi-code) must not be sent a foreign-provider model, even\n // if the UI snap was bypassed. Provider-less ids pass (session's own config).\n if (model?.model) assertHarnessModelCompatible(harness, model.model)\n\n const prompt = flattenHistory(message, options?.history)\n\n const appToolMcp = options?.appToolMcp ?? {}\n const extraMcp = mergeExtraMcp(appToolMcp, options?.baseProfileMcp ?? {}, options?.extraMcp)\n\n const profile = shell.profile({ systemPrompt: options?.systemPrompt, extraMcp })\n const profileWithEffort = attachReasoningEffort(profile, harness, options?.effort)\n\n const stream = box.streamPrompt(prompt, {\n sessionId: options?.sessionId,\n executionId: options?.executionId,\n lastEventId: options?.lastEventId,\n ...(options?.signal ? { signal: options.signal } : {}),\n ...(options?.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n backend: {\n type: harness,\n profile: profileWithEffort,\n ...(model ? { model } : {}),\n },\n } as StreamPromptOptions)\n\n let severedFinishReason: string | null = null\n for await (const event of stream) {\n const step = classifySeveredStream(event)\n if (step) severedFinishReason = step.kind === 'step-finish' && step.severed ? step.reason : null\n if (severedFinishReason && isTerminalPromptEvent(event)) {\n throw new Error(`sandbox model stream severed mid-turn (reason=\"${severedFinishReason}\")`)\n }\n if (options?.disallowQuestions) {\n const q = detectInteractiveQuestion(event)\n if (q) {\n throw new Error(`sandbox agent asked an interactive question during an autonomous run: ${q}`)\n }\n }\n yield event\n }\n // Reconnect-exhausted path: the stream ended on a severed step without a\n // terminal event. A truncated turn must fail loud, not return silently.\n if (severedFinishReason) {\n throw new Error(`sandbox model stream severed mid-turn (reason=\"${severedFinishReason}\")`)\n }\n}\n\nexport async function runSandboxPrompt(\n shell: SandboxRuntimeConfig,\n box: SandboxInstance,\n message: string,\n options?: StreamSandboxPromptOptions,\n): Promise<string> {\n let fullText = ''\n let firstTextSeen = false\n\n for await (const rawEvent of streamSandboxPrompt(shell, box, message, options)) {\n const event = rawEvent as { type?: string; data?: Record<string, unknown> }\n if (!event.type) continue\n\n if (event.type === 'message.part.updated') {\n const part = event.data?.part as Record<string, unknown> | undefined\n const delta = typeof event.data?.delta === 'string' ? event.data.delta : null\n if (String(part?.type ?? '') === 'text') {\n if (!firstTextSeen) {\n firstTextSeen = true\n continue\n }\n if (delta) fullText += delta\n else if (typeof part?.text === 'string') fullText = part.text\n }\n } else if (event.type === 'result') {\n const finalText = typeof event.data?.finalText === 'string' ? event.data.finalText : null\n if (finalText) fullText = finalText\n }\n }\n\n return fullText\n}\n\n// Mirrors the SDK's PermissionLevel union (not re-exported by\n// @tangle-network/sandbox). The product's role-mapping seam must produce one of\n// these; binding the seam's return type to the union makes a wrong mapping a\n// compile error rather than a runtime 400 from the orchestrator.\nexport type SandboxPermissionLevel = 'owner' | 'admin' | 'developer' | 'viewer'\n\nexport interface MemberSyncSeam {\n roleToSandboxRole: (workspaceRole: string) => SandboxPermissionLevel\n}\n\nexport async function syncSandboxMemberAdd(\n box: SandboxInstance,\n seam: MemberSyncSeam,\n userId: string,\n role: string,\n): Promise<Outcome<void>> {\n try {\n await box.permissions.add({ userId, role: seam.roleToSandboxRole(role) })\n return ok(undefined)\n } catch (err) {\n return fail(err)\n }\n}\n\nexport async function syncSandboxMemberRemove(\n box: SandboxInstance,\n userId: string,\n): Promise<Outcome<void>> {\n try {\n await box.permissions.remove(userId, { preserveHomeDir: true })\n return ok(undefined)\n } catch (err) {\n return fail(err)\n }\n}\n\nexport async function syncSandboxMemberRole(\n box: SandboxInstance,\n seam: MemberSyncSeam,\n userId: string,\n role: string,\n): Promise<Outcome<void>> {\n try {\n await box.permissions.update(userId, { role: seam.roleToSandboxRole(role) })\n return ok(undefined)\n } catch (err) {\n return fail(err)\n }\n}\n\nexport interface SecretStore {\n create: (name: string, value: string) => Promise<void>\n update: (name: string, value: string) => Promise<void>\n get: (name: string) => Promise<string>\n delete: (name: string) => Promise<void>\n}\n\nexport function secretStoreFromClient(shell: SandboxRuntimeConfig): SecretStore {\n const client = getClient(shell)\n return {\n create: async (name, value) => {\n await client.secrets.create(name, value)\n },\n update: async (name, value) => {\n await client.secrets.update(name, value)\n },\n get: (name) => client.secrets.get(name),\n delete: async (name) => {\n await client.secrets.delete(name)\n },\n }\n}\n\nexport async function storeSecret(\n store: SecretStore,\n name: string,\n value: string,\n): Promise<Outcome<void>> {\n try {\n await store.create(name, value)\n return ok(undefined)\n } catch {\n try {\n await store.update(name, value)\n return ok(undefined)\n } catch (err) {\n return fail(new Error(`Failed to store sandbox secret ${name}`, { cause: err }))\n }\n }\n}\n\nexport async function readSecret(store: SecretStore, name: string): Promise<Outcome<string>> {\n try {\n return ok(await store.get(name))\n } catch (err) {\n return fail(err)\n }\n}\n\nexport async function deleteSecret(store: SecretStore, name: string): Promise<Outcome<void>> {\n try {\n await store.delete(name)\n return ok(undefined)\n } catch (err) {\n return fail(err)\n }\n}\n\nexport interface ScopedTokenResult {\n token: string\n expiresAt: Date\n scope: ScopedTokenScope\n}\n\n/**\n * Mint a scoped token for an already-provisioned box (e.g. to hand a terminal\n * proxy a narrowed credential). Uses the SDK's native `box.mintScopedToken`,\n * which normalizes `expiresAt` to a Date — no hand-rolled wire call.\n */\nexport async function mintSandboxScopedToken(\n box: SandboxInstance,\n options: { scope: ScopedTokenScope; sessionId?: string; ttlMinutes?: number },\n): Promise<Outcome<ScopedTokenResult>> {\n try {\n const token = await box.mintScopedToken({\n scope: options.scope,\n ...(options.sessionId ? { sessionId: options.sessionId } : {}),\n ...(options.ttlMinutes ? { ttlMinutes: options.ttlMinutes } : {}),\n })\n return ok({ token: token.token, expiresAt: token.expiresAt, scope: token.scope })\n } catch (err) {\n return fail(err)\n }\n}\n\n// Detached single-turn advance. The SDK SandboxInstance has no `driveTurn`; the\n// non-streaming sibling of streamPrompt is box.prompt(message, opts) -> PromptResult.\n// Returns a typed Outcome so a failed turn is inspected, not swallowed.\nexport async function driveSandboxTurn(\n shell: SandboxRuntimeConfig,\n box: SandboxInstance,\n message: string,\n options: StreamSandboxPromptOptions & { sessionId: string },\n): Promise<Outcome<PromptResult>> {\n const harness = options.harness ?? 'opencode'\n const model = resolveModel(shell.provider, {\n model: options.model,\n modelApiKey: options.modelApiKey,\n })\n if (model?.model) assertHarnessModelCompatible(harness, model.model)\n const prompt = flattenHistory(message, options.history)\n const appToolMcp = options.appToolMcp ?? {}\n const extraMcp = mergeExtraMcp(appToolMcp, options.baseProfileMcp ?? {}, options.extraMcp)\n const profile = attachReasoningEffort(\n shell.profile({ systemPrompt: options.systemPrompt, extraMcp }),\n harness,\n options.effort,\n )\n try {\n const result = await box.prompt(prompt, {\n sessionId: options.sessionId,\n ...(options.executionId ? { executionId: options.executionId } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n backend: { type: harness, profile, ...(model ? { model } : {}) },\n } as Parameters<SandboxInstance['prompt']>[1])\n if (!result.success) return fail(new Error(result.error ?? 'sandbox turn failed'))\n return ok(result)\n } catch (err) {\n return fail(err)\n }\n}\n\n// Severed-stream classifier. Generic to any router-backed harness: a final step\n// that finished with error/other/unknown is a truncated turn, not a completed one.\nconst SEVERED_FINISH_REASONS = new Set(['error', 'other', 'unknown'])\n\nexport type SandboxStepTransition =\n | { kind: 'step-start' }\n | { kind: 'step-finish'; reason: string; severed: boolean }\n\nfunction asPlainRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null\n}\n\nexport function classifySeveredStream(event: unknown): SandboxStepTransition | null {\n const root = asPlainRecord(event)\n if (!root || root.type !== 'message.part.updated') return null\n const body = asPlainRecord(root.properties) ?? asPlainRecord(root.data) ?? root\n const part = asPlainRecord(body.part)\n if (!part) return null\n if (part.type === 'step-start') return { kind: 'step-start' }\n if (part.type !== 'step-finish') return null\n const reason = typeof part.reason === 'string' && part.reason ? part.reason : 'unknown'\n return { kind: 'step-finish', reason, severed: SEVERED_FINISH_REASONS.has(reason) }\n}\n\nexport function isTerminalPromptEvent(event: unknown): boolean {\n const t = asPlainRecord(event)?.type\n return t === 'result' || t === 'done'\n}\n\n// Interactive-question detector. Returns the question text or null. Used by\n// streamSandboxPrompt when disallowQuestions is set.\nexport function detectInteractiveQuestion(event: unknown): string | null {\n const root = asPlainRecord(event)\n if (!root) return null\n const type = typeof root.type === 'string' ? root.type : undefined\n const data = asPlainRecord(root.data)\n const props = asPlainRecord(root.properties)\n const body = props ?? data ?? root\n if (type === 'question.asked' || type === 'question') return firstQuestionText(body)\n const part = asPlainRecord(data?.part) ?? asPlainRecord(body.part)\n const tool =\n (typeof part?.tool === 'string' && part.tool) ||\n (typeof part?.name === 'string' && part.name) ||\n (typeof body.tool === 'string' && body.tool) ||\n undefined\n const isQ =\n type === 'message.part.updated' &&\n (tool === 'question' || asPlainRecord(part)?.type === 'question')\n if (!isQ) return null\n const state = asPlainRecord(asPlainRecord(part)?.state)\n return firstQuestionText(asPlainRecord(state?.input) ?? state ?? part ?? body)\n}\n\nfunction firstQuestionText(value: Record<string, unknown> | null): string {\n const arr = Array.isArray(value?.questions)\n ? value!.questions\n : Array.isArray(asPlainRecord(value?.input)?.questions)\n ? (asPlainRecord(value!.input)!.questions as unknown[])\n : []\n const first = asPlainRecord(arr[0])\n const q =\n (typeof first?.question === 'string' && first.question) ||\n (typeof first?.prompt === 'string' && first.prompt) ||\n undefined\n return q ?? 'interactive question'\n}\n// Workspace sandbox terminal handlers: WebSocket upgrade proxy, connection\n// + runtime-proxy handlers, and scoped terminal-token mint/verify.\nexport * from './terminal-proxy-token'\nexport * from './workspace-terminal'\n","// Shared Outcome triple for the sandbox modules. Lives in a dependency-free\n// leaf so both index.ts and terminal-proxy-token.ts import it instead of\n// re-declaring the type (index.ts re-exports * from terminal-proxy-token, so\n// the token module cannot import from index without a cycle).\nexport type Outcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; error: Error }\n\nexport const ok = <T>(value: T): Outcome<T> => ({ succeeded: true, value })\nexport const fail = (error: unknown): Outcome<never> => ({\n succeeded: false,\n error: error instanceof Error ? error : new Error(String(error)),\n})\n","import {\n base64UrlDecodeText,\n base64UrlEncodeText,\n constantTimeEqual,\n hmacSha256Base64Url,\n} from '../crypto/web-token'\nimport { ok, fail, type Outcome } from './outcome'\n\n// Terminal-proxy HMAC token. Identity tuple is generic; the secret comes from a\n// closure (fail-loud if absent).\nexport interface TerminalProxyIdentity {\n userId: string\n workspaceId: string\n sandboxId: string\n}\n\nconst TERMINAL_PROXY_TOKEN_TTL_MS = 15 * 60 * 1000\n\nexport async function mintTerminalProxyToken(\n secret: string,\n identity: TerminalProxyIdentity,\n ttlMs = TERMINAL_PROXY_TOKEN_TTL_MS,\n now: () => number = Date.now,\n): Promise<Outcome<{ token: string; expiresAt: Date }>> {\n if (!secret) return fail(new Error('mintTerminalProxyToken: secret is required'))\n if (!identity.userId || !identity.workspaceId || !identity.sandboxId) {\n return fail(new Error('mintTerminalProxyToken: userId/workspaceId/sandboxId are required'))\n }\n const expiresAt = new Date(now() + ttlMs)\n const payload = { ...identity, exp: Math.floor(expiresAt.getTime() / 1000) }\n const encoded = base64UrlEncodeText(JSON.stringify(payload))\n const sig = await hmacSha256Base64Url(encoded, secret)\n return ok({ token: `${encoded}.${sig}`, expiresAt })\n}\n\nexport async function verifyTerminalProxyToken(\n secret: string,\n token: string,\n expected: TerminalProxyIdentity,\n now: () => number = Date.now,\n): Promise<boolean> {\n if (!secret) return false\n const [encoded, sig, extra] = token.split('.')\n if (!encoded || !sig || extra !== undefined) return false\n const expectedSig = await hmacSha256Base64Url(encoded, secret)\n if (!constantTimeEqual(sig, expectedSig)) return false\n let payload: TerminalProxyIdentity & { exp: number }\n try {\n payload = JSON.parse(base64UrlDecodeText(encoded))\n } catch {\n return false\n }\n return (\n payload.userId === expected.userId &&\n payload.workspaceId === expected.workspaceId &&\n payload.sandboxId === expected.sandboxId &&\n Number.isFinite(payload.exp) &&\n payload.exp > Math.floor(now() / 1000)\n )\n}\n","import { base64UrlDecodeText } from '../crypto/web-token'\nimport {\n mintTerminalProxyToken,\n verifyTerminalProxyToken,\n type TerminalProxyIdentity,\n} from './terminal-proxy-token'\n\nexport interface WorkspaceSandboxInstanceLike {\n id: string\n name?: string\n status?: string\n connection?: {\n runtimeUrl?: string\n sidecarUrl?: string\n authToken?: string\n sidecarToken?: string\n authTokenExpiresAt?: string\n } | null\n}\n\n// Generic name-keyed sandbox lifecycle manager. A structural, substrate-free\n// helper for products that drive their own SDK/box types (distinct from the\n// concrete `ensureWorkspaceSandbox` in ./index, which is bound to the\n// @tangle-network/sandbox client). Kept as a public export — external\n// consumers compose it; removing it would be a breaking change.\nexport interface WorkspaceSandboxEnsureContext {\n workspaceId: string\n userId: string\n}\n\nexport interface WorkspaceSandboxManagerOptions<TClient, TBox extends WorkspaceSandboxInstanceLike, TEnsureOptions = void> {\n getClient: (ctx: WorkspaceSandboxEnsureContext) => Promise<TClient> | TClient\n nameForWorkspace: (workspaceId: string, ctx: WorkspaceSandboxEnsureContext) => string\n listSandboxes: (client: TClient, ctx: WorkspaceSandboxEnsureContext) => Promise<TBox[]>\n createSandbox: (args: {\n client: TClient\n ctx: WorkspaceSandboxEnsureContext\n name: string\n options: TEnsureOptions\n listError?: unknown\n }) => Promise<TBox>\n waitForRunning?: (box: TBox, ctx: WorkspaceSandboxEnsureContext) => Promise<void>\n prepareExisting?: (box: TBox, ctx: WorkspaceSandboxEnsureContext, options: TEnsureOptions) => Promise<TBox | void>\n prepareCreated?: (box: TBox, ctx: WorkspaceSandboxEnsureContext, options: TEnsureOptions) => Promise<TBox | void>\n onListError?: (error: unknown, ctx: WorkspaceSandboxEnsureContext) => void\n}\n\nexport interface WorkspaceSandboxManager<TBox extends WorkspaceSandboxInstanceLike, TEnsureOptions = void> {\n ensureWorkspaceSandbox: (\n workspaceId: string,\n userId: string,\n options?: TEnsureOptions,\n ) => Promise<TBox>\n}\n\nexport function createWorkspaceSandboxManager<TClient, TBox extends WorkspaceSandboxInstanceLike, TEnsureOptions = void>(\n opts: WorkspaceSandboxManagerOptions<TClient, TBox, TEnsureOptions>,\n): WorkspaceSandboxManager<TBox, TEnsureOptions> {\n return {\n async ensureWorkspaceSandbox(workspaceId, userId, options) {\n if (!workspaceId) throw new Error('workspaceId is required')\n if (!userId) throw new Error('userId is required')\n const ctx = { workspaceId, userId }\n const client = await opts.getClient(ctx)\n const name = opts.nameForWorkspace(workspaceId, ctx)\n let listError: unknown\n let existing: TBox[] = []\n\n try {\n existing = await opts.listSandboxes(client, ctx)\n } catch (err) {\n listError = err\n opts.onListError?.(err, ctx)\n }\n\n const found = existing.find((box) => box.name === name)\n if (found) {\n return (await opts.prepareExisting?.(found, ctx, options as TEnsureOptions)) ?? found\n }\n\n const created = await opts.createSandbox({\n client,\n ctx,\n name,\n options: options as TEnsureOptions,\n listError,\n })\n await opts.waitForRunning?.(created, ctx)\n return (await opts.prepareCreated?.(created, ctx, options as TEnsureOptions)) ?? created\n },\n }\n}\n\nexport interface SandboxTerminalTokenOptions {\n secret?: string\n expiresInMs?: number\n now?: () => number\n}\n\nexport type SandboxTerminalTokenSubject = TerminalProxyIdentity\n\nexport interface SandboxTerminalTokenResult {\n token: string\n expiresAt: Date\n}\n\nconst DEFAULT_TERMINAL_TOKEN_TTL_MS = 15 * 60 * 1000\nconst BEARER_SUBPROTOCOL_PREFIX = 'bearer.'\n// Legacy `createSandboxTerminalToken` (pre proxy-token extraction) prefixed\n// minted tokens with `sbxt_` and signed the unprefixed payload. New tokens\n// carry no prefix. Strip it on verify so tokens minted by a prior deploy still\n// validate within their (default 15-min) TTL window — avoids a wave of 403s on\n// in-flight browser terminal sessions right after rollout.\nconst LEGACY_TERMINAL_TOKEN_PREFIX = 'sbxt_'\n\nexport async function createSandboxTerminalToken(\n subject: SandboxTerminalTokenSubject,\n opts: SandboxTerminalTokenOptions,\n): Promise<SandboxTerminalTokenResult> {\n validateTerminalSubject(subject)\n const secret = opts.secret?.trim()\n if (!secret) throw new Error('terminal token secret is required')\n const now = opts.now ?? Date.now\n const expiresInMs = opts.expiresInMs ?? DEFAULT_TERMINAL_TOKEN_TTL_MS\n if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) throw new Error('expiresInMs must be a positive number')\n const minted = await mintTerminalProxyToken(secret, subject, expiresInMs, now)\n if (!minted.succeeded) throw minted.error\n return minted.value\n}\n\nexport async function verifySandboxTerminalToken(\n token: string,\n expected: SandboxTerminalTokenSubject,\n opts: SandboxTerminalTokenOptions,\n): Promise<boolean> {\n validateTerminalSubject(expected)\n const secret = opts.secret?.trim()\n const now = opts.now ?? Date.now\n const normalized = token.startsWith(LEGACY_TERMINAL_TOKEN_PREFIX)\n ? token.slice(LEGACY_TERMINAL_TOKEN_PREFIX.length)\n : token\n return verifyTerminalProxyToken(secret ?? '', normalized, expected, now)\n}\n\nexport interface AuthenticatedSandboxUser {\n id: string\n}\n\nexport interface WorkspaceSandboxConnectionHandlerOptions<TBox extends WorkspaceSandboxInstanceLike> {\n requireUser: (request: Request) => Promise<AuthenticatedSandboxUser>\n requireWorkspaceAccess: (args: { request: Request; userId: string; workspaceId: string }) => Promise<void>\n ensureWorkspaceSandbox: (workspaceId: string, userId: string) => Promise<TBox>\n tokenSecret: string | (() => string | undefined)\n tokenExpiresInMs?: number\n proxyRuntimeUrl?: (args: { request: Request; workspaceId: string; sandboxId: string; box: TBox }) => string\n exposeDirectSidecar?: boolean\n}\n\nexport interface WorkspaceSandboxConnectionArgs {\n request: Request\n params: {\n workspaceId?: string\n }\n}\n\nexport function createWorkspaceSandboxConnectionHandler<TBox extends WorkspaceSandboxInstanceLike>(\n opts: WorkspaceSandboxConnectionHandlerOptions<TBox>,\n) {\n return async function handleWorkspaceSandboxConnection({ request, params }: WorkspaceSandboxConnectionArgs): Promise<Response> {\n const user = await opts.requireUser(request)\n const workspaceId = params.workspaceId\n if (!workspaceId) return Response.json({ error: 'workspaceId is required' }, { status: 400 })\n await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId })\n\n let box: TBox\n try {\n box = await opts.ensureWorkspaceSandbox(workspaceId, user.id)\n } catch (err) {\n return Response.json(\n { error: err instanceof Error ? err.message : 'Failed to provision workspace sandbox' },\n { status: 500 },\n )\n }\n\n const directSidecarUrl = box.connection?.sidecarUrl ?? box.connection?.runtimeUrl\n const directSidecarToken = box.connection?.authToken ?? box.connection?.sidecarToken\n const directSidecarExpiresAt = box.connection?.authTokenExpiresAt\n if (opts.exposeDirectSidecar && directSidecarUrl && directSidecarToken && directSidecarExpiresAt) {\n return Response.json({\n runtimeUrl: directSidecarUrl,\n sidecarUrl: directSidecarUrl,\n token: directSidecarToken,\n expiresAt: directSidecarExpiresAt,\n status: box.status,\n sandboxId: box.id,\n })\n }\n\n if (!directSidecarUrl) {\n return Response.json(\n {\n error: 'Workspace sandbox runtime not ready. The sandbox is still initializing -- retry in a few seconds.',\n status: box.status,\n },\n { status: 503 },\n )\n }\n\n const secret = typeof opts.tokenSecret === 'function' ? opts.tokenSecret() : opts.tokenSecret\n let scoped: SandboxTerminalTokenResult\n try {\n scoped = await createSandboxTerminalToken(\n { userId: user.id, workspaceId, sandboxId: box.id },\n { secret, expiresInMs: opts.tokenExpiresInMs },\n )\n } catch (err) {\n return Response.json(\n { error: err instanceof Error ? err.message : 'Failed to mint sandbox token' },\n { status: 503 },\n )\n }\n\n const runtimeUrl = opts.proxyRuntimeUrl\n ? opts.proxyRuntimeUrl({ request, workspaceId, sandboxId: box.id, box })\n : `/api/workspaces/${encodeURIComponent(workspaceId)}/sandbox/runtime/${encodeURIComponent(box.id)}`\n\n return Response.json({\n runtimeUrl,\n sidecarUrl: runtimeUrl,\n token: scoped.token,\n expiresAt: scoped.expiresAt.toISOString(),\n status: box.status,\n sandboxId: box.id,\n })\n }\n}\n\nexport interface SandboxApiCredentials {\n baseUrl: string\n apiKey: string\n}\n\nexport interface SandboxRuntimeConnection {\n runtimeUrl: string\n /** Server-side sidecar bearer. Must authorize terminal routes; never expose it to browser code. */\n authToken?: string\n}\n\nexport interface WorkspaceSandboxRuntimeProxyHandlerOptions {\n requireUser: (request: Request) => Promise<AuthenticatedSandboxUser>\n requireWorkspaceAccess: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<void>\n getSandboxApiCredentials: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<SandboxApiCredentials>\n getSandboxRuntimeConnection?: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<SandboxRuntimeConnection | null | undefined>\n tokenSecret: string | (() => string | undefined)\n fetch?: typeof fetch\n forwardHeaders?: string[]\n}\n\nexport interface WorkspaceSandboxRuntimeProxyArgs {\n request: Request\n params: {\n workspaceId?: string\n sandboxId?: string\n '*'?: string\n }\n}\n\nexport function createWorkspaceSandboxRuntimeProxyHandler(opts: WorkspaceSandboxRuntimeProxyHandlerOptions) {\n return async function handleWorkspaceSandboxRuntimeProxy({ request, params }: WorkspaceSandboxRuntimeProxyArgs): Promise<Response> {\n const user = await opts.requireUser(request)\n const workspaceId = params.workspaceId\n const sandboxId = params.sandboxId\n const runtimePath = params['*']\n if (!workspaceId || !sandboxId || !runtimePath) {\n return Response.json({ error: 'workspaceId, sandboxId, and runtime path are required' }, { status: 400 })\n }\n const encodedRuntimePath = encodeSandboxRuntimePath(runtimePath)\n if (!encodedRuntimePath) return Response.json({ error: 'Invalid sandbox runtime path' }, { status: 400 })\n\n await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId })\n\n const token = terminalTokenFromRequest(request.headers)\n const secret = typeof opts.tokenSecret === 'function' ? opts.tokenSecret() : opts.tokenSecret\n if (!token || !(await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret }))) {\n return Response.json({ error: 'Invalid terminal token' }, { status: 403 })\n }\n\n const requestUrl = new URL(request.url)\n const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId })\n const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null\n const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId })\n const upstreamUrl = directRuntimeConnection\n ? new URL(encodedRuntimePath, `${directRuntimeConnection.runtimeUrl.replace(/\\/+$/, '')}/`)\n : new URL(\n `/v1/sandboxes/${encodeURIComponent(sandboxId)}/runtime/${encodedRuntimePath}`,\n credentials!.baseUrl,\n )\n upstreamUrl.search = requestUrl.search\n\n const headers = buildSandboxRuntimeProxyHeaders(\n request.headers,\n directRuntimeConnection?.authToken ?? credentials!.apiKey,\n opts.forwardHeaders,\n )\n const init: RequestInit & { duplex?: 'half' } = {\n method: request.method,\n headers,\n redirect: 'manual',\n }\n if (request.method !== 'GET' && request.method !== 'HEAD' && request.body) {\n init.body = request.body\n init.duplex = 'half'\n }\n\n const fetchImpl = opts.fetch ?? fetch\n const response = await fetchImpl(upstreamUrl, init)\n const responseHeaders = new Headers(response.headers)\n responseHeaders.delete('set-cookie')\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n })\n }\n}\n\n// ---------------------------------------------------------------------------\n// Terminal WebSocket upgrade\n//\n// The interactive terminal is WebSocket-only on the current sidecar (the REST\n// `POST /terminals` create route was removed in the websocket-first migration).\n// `createWorkspaceSandboxRuntimeProxyHandler` runs inside a React Router\n// loader/action, which can only return a normal Response — never a 101 — so it\n// cannot perform the upgrade. The upgrade must be intercepted at the Worker\n// fetch entry (server.ts) BEFORE React Router, mirroring the session-stream WS\n// interceptor. This handler does exactly that: it auth-gates the upgrade (the\n// scoped terminal token rides in the `bearer.` subprotocol because browsers\n// can't set Authorization on a WS handshake) and forwards it to the sandbox API\n// runtime proxy with the server-to-server credential. Returning the upstream\n// 101 passes the live socket straight through to the browser — the same idiom\n// the sandbox API uses to reach the orchestrator.\n//\n// NOTE: this only runs under a WebSocket-capable runtime (Cloudflare Workers /\n// `wrangler`). `react-router dev` (Vite) never invokes the Worker fetch entry,\n// so the terminal WS is exercised under `wrangler dev` / production.\n// ---------------------------------------------------------------------------\n\nconst SANDBOX_TERMINAL_WS_PATHNAME =\n /^\\/api\\/workspaces\\/([^/]+)\\/sandbox\\/runtime\\/([^/]+)\\/(terminals\\/[^/]+\\/ws)$/\n\nexport interface SandboxTerminalWsMatch {\n workspaceId: string\n sandboxId: string\n subPath: string\n}\n\n/**\n * Parse a same-origin terminal-WS pathname into its parts, or `null` when the\n * path is not a sandbox terminal WebSocket. Matches the default `runtimeUrl`\n * convention emitted by {@link createWorkspaceSandboxConnectionHandler}\n * (`/api/workspaces/:workspaceId/sandbox/runtime/:sandboxId`) with a canonical\n * `terminals/:id/ws` sub-path. `subPath` is left URL-encoded for re-use in the\n * upstream URL; the ids are decoded for auth checks.\n */\nexport function matchSandboxTerminalWsPath(pathname: string): SandboxTerminalWsMatch | null {\n const m = SANDBOX_TERMINAL_WS_PATHNAME.exec(pathname)\n if (!m) return null\n const [, workspaceId, sandboxId, subPath] = m\n if (!workspaceId || !sandboxId || !subPath) return null\n const decodedWorkspaceId = safeDecodeURIComponent(workspaceId)\n const decodedSandboxId = safeDecodeURIComponent(sandboxId)\n if (!decodedWorkspaceId || !decodedSandboxId) return null\n return { workspaceId: decodedWorkspaceId, sandboxId: decodedSandboxId, subPath }\n}\n\n/** True when `request` is a WebSocket upgrade for a sandbox terminal path. */\nexport function isSandboxTerminalWsUpgrade(request: Request): boolean {\n if (request.headers.get('Upgrade')?.toLowerCase() !== 'websocket') return false\n try {\n return matchSandboxTerminalWsPath(new URL(request.url).pathname) !== null\n } catch {\n return false\n }\n}\n\nexport interface WorkspaceSandboxTerminalUpgradeHandlerOptions {\n requireUser: (request: Request) => Promise<AuthenticatedSandboxUser>\n requireWorkspaceAccess: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<void>\n getSandboxApiCredentials: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<SandboxApiCredentials>\n getSandboxRuntimeConnection?: (args: { request: Request; userId: string; workspaceId: string; sandboxId: string }) => Promise<SandboxRuntimeConnection | null | undefined>\n tokenSecret: string | (() => string | undefined)\n fetch?: typeof fetch\n}\n\n/**\n * Build a Worker-entry handler that proxies a sandbox terminal WebSocket\n * upgrade to the sandbox API runtime proxy. Returns `null` when the request is\n * not a terminal WS upgrade, so the caller can fall through to its normal\n * request handler:\n *\n * ```ts\n * const handled = await handleSandboxTerminalUpgrade(request)\n * if (handled) return handled\n * ```\n */\nexport function createWorkspaceSandboxTerminalUpgradeHandler(opts: WorkspaceSandboxTerminalUpgradeHandlerOptions) {\n return async function handleWorkspaceSandboxTerminalUpgrade(request: Request): Promise<Response | null> {\n if (request.headers.get('Upgrade')?.toLowerCase() !== 'websocket') return null\n let url: URL\n try {\n url = new URL(request.url)\n } catch {\n return null\n }\n const match = matchSandboxTerminalWsPath(url.pathname)\n if (!match) return null\n const { workspaceId, sandboxId, subPath } = match\n\n let user: AuthenticatedSandboxUser\n try {\n user = await opts.requireUser(request)\n } catch {\n return new Response('Unauthorized', { status: 401 })\n }\n try {\n await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId })\n } catch {\n return new Response('Forbidden', { status: 403 })\n }\n\n const token = terminalTokenFromRequest(request.headers)\n const secret = typeof opts.tokenSecret === 'function' ? opts.tokenSecret() : opts.tokenSecret\n if (!token || !(await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret }))) {\n return new Response('Invalid terminal token', { status: 403 })\n }\n\n const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId })\n const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null\n const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId })\n const upstreamUrl = directRuntimeConnection\n ? new URL(subPath, `${directRuntimeConnection.runtimeUrl.replace(/\\/+$/, '')}/`)\n : new URL(`/v1/sandboxes/${encodeURIComponent(sandboxId)}/runtime/${subPath}`, credentials!.baseUrl)\n upstreamUrl.search = url.search\n\n // Forward the upgrade verbatim — keep the Upgrade/Connection + Sec-WebSocket-*\n // headers the handshake needs, but strip the browser-only bearer subprotocol\n // and send the server-to-server sandbox credential only as Authorization.\n // Returning the upstream 101 passes the live socket straight through to the browser.\n const upstreamHeaders = new Headers(request.headers)\n const upstreamBearer = directRuntimeConnection?.authToken ?? credentials!.apiKey\n upstreamHeaders.set('Authorization', `Bearer ${upstreamBearer}`)\n upstreamHeaders.delete('host')\n stripBearerSubprotocol(upstreamHeaders)\n const fetchImpl = opts.fetch ?? fetch\n return fetchImpl(upstreamUrl.toString(), { method: request.method, headers: upstreamHeaders })\n }\n}\n\nconst DEFAULT_RUNTIME_PROXY_HEADERS = ['accept', 'content-type', 'last-event-id', 'x-session-id']\n\nexport function buildSandboxRuntimeProxyHeaders(source: Headers, sandboxApiKey: string, forwardHeaders = DEFAULT_RUNTIME_PROXY_HEADERS): Headers {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${sandboxApiKey}`)\n for (const name of forwardHeaders) {\n const value = source.get(name)\n if (value) headers.set(name, value)\n }\n return headers\n}\n\nexport function encodeSandboxRuntimePath(runtimePath: string): string | null {\n const segments = runtimePath.split('/')\n if (segments.some((segment) => !segment || segment === '.' || segment === '..')) return null\n return segments.map((segment) => encodeURIComponent(segment)).join('/')\n}\n\nexport function bearerToken(value: string | null): string | null {\n if (!value) return null\n const trimmed = value.trim()\n if (!trimmed) return null\n if (trimmed.toLowerCase() === 'bearer') return null\n if (trimmed.toLowerCase().startsWith('bearer ')) {\n const token = trimmed.slice('bearer '.length).trim()\n return token || null\n }\n return trimmed\n}\n\nexport function bearerSubprotocolToken(value: string | null): string | null {\n if (!value) return null\n for (const part of value.split(',')) {\n const protocol = part.trim()\n if (!protocol.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX)) continue\n const encoded = protocol.slice(BEARER_SUBPROTOCOL_PREFIX.length)\n if (!encoded) return null\n try {\n const token = base64UrlDecodeText(encoded).trim()\n return token || null\n } catch {\n return null\n }\n }\n return null\n}\n\nexport function terminalTokenFromRequest(headers: Headers): string | null {\n return bearerToken(headers.get('Authorization')) ?? bearerSubprotocolToken(headers.get('Sec-WebSocket-Protocol'))\n}\n\nfunction safeDecodeURIComponent(value: string): string | null {\n try {\n return decodeURIComponent(value)\n } catch {\n return null\n }\n}\n\nfunction stripBearerSubprotocol(headers: Headers): void {\n const value = headers.get('Sec-WebSocket-Protocol')\n if (!value) return\n const protocols = value\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part && !part.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX))\n if (protocols.length) {\n headers.set('Sec-WebSocket-Protocol', protocols.join(', '))\n } else {\n headers.delete('Sec-WebSocket-Protocol')\n }\n}\n\nfunction validateTerminalSubject(subject: SandboxTerminalTokenSubject): void {\n if (!subject.userId) throw new Error('userId is required')\n if (!subject.workspaceId) throw new Error('workspaceId is required')\n if (!subject.sandboxId) throw new Error('sandboxId is required')\n}\n"],"mappings":"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","names":["res","box"]}
|
|
@@ -6,6 +6,35 @@ import {
|
|
|
6
6
|
outcomeStatus
|
|
7
7
|
} from "./chunk-JZZ6AWF4.js";
|
|
8
8
|
|
|
9
|
+
// src/crypto/web-token.ts
|
|
10
|
+
function base64UrlEncode(bytes) {
|
|
11
|
+
let bin = "";
|
|
12
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
13
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
14
|
+
}
|
|
15
|
+
function base64UrlEncodeText(text) {
|
|
16
|
+
return base64UrlEncode(new TextEncoder().encode(text));
|
|
17
|
+
}
|
|
18
|
+
function base64UrlDecodeText(value) {
|
|
19
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
20
|
+
const bin = atob(padded);
|
|
21
|
+
const bytes = new Uint8Array(bin.length);
|
|
22
|
+
for (let i = 0; i < bin.length; i += 1) bytes[i] = bin.charCodeAt(i);
|
|
23
|
+
return new TextDecoder().decode(bytes);
|
|
24
|
+
}
|
|
25
|
+
async function hmacSha256Base64Url(message, secret) {
|
|
26
|
+
const enc = new TextEncoder();
|
|
27
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
28
|
+
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
|
|
29
|
+
return base64UrlEncode(new Uint8Array(sig));
|
|
30
|
+
}
|
|
31
|
+
function constantTimeEqual(a, b) {
|
|
32
|
+
if (a.length !== b.length) return false;
|
|
33
|
+
let diff = 0;
|
|
34
|
+
for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
35
|
+
return diff === 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
9
38
|
// src/tools/capability.ts
|
|
10
39
|
async function createCapabilityToken(userId, opts) {
|
|
11
40
|
const secret = opts.secret?.trim();
|
|
@@ -18,7 +47,7 @@ async function verifyCapabilityToken(userId, token, opts) {
|
|
|
18
47
|
const prefix = opts.prefix ?? "cap_";
|
|
19
48
|
if (!secret || !token.startsWith(prefix)) return false;
|
|
20
49
|
const expected = `${prefix}${await sign(userId, secret)}`;
|
|
21
|
-
return
|
|
50
|
+
return constantTimeEqual(token, expected);
|
|
22
51
|
}
|
|
23
52
|
async function createExpiringCapabilityToken(subject, opts) {
|
|
24
53
|
const secret = opts.secret?.trim();
|
|
@@ -26,8 +55,8 @@ async function createExpiringCapabilityToken(subject, opts) {
|
|
|
26
55
|
if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
|
|
27
56
|
const prefix = opts.prefix ?? "cap_";
|
|
28
57
|
const now = opts.now ?? Date.now;
|
|
29
|
-
const payload =
|
|
30
|
-
return `${prefix}${payload}.${await
|
|
58
|
+
const payload = base64UrlEncodeText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }));
|
|
59
|
+
return `${prefix}${payload}.${await hmacSha256Base64Url(payload, secret)}`;
|
|
31
60
|
}
|
|
32
61
|
async function verifyExpiringCapabilityToken(subject, token, opts) {
|
|
33
62
|
const secret = opts.secret?.trim();
|
|
@@ -38,10 +67,10 @@ async function verifyExpiringCapabilityToken(subject, token, opts) {
|
|
|
38
67
|
if (dot <= 0 || dot === body.length - 1) return false;
|
|
39
68
|
const payload = body.slice(0, dot);
|
|
40
69
|
const sig = body.slice(dot + 1);
|
|
41
|
-
if (!
|
|
70
|
+
if (!constantTimeEqual(sig, await hmacSha256Base64Url(payload, secret))) return false;
|
|
42
71
|
let parsed;
|
|
43
72
|
try {
|
|
44
|
-
parsed = JSON.parse(
|
|
73
|
+
parsed = JSON.parse(base64UrlDecodeText(payload));
|
|
45
74
|
} catch {
|
|
46
75
|
return false;
|
|
47
76
|
}
|
|
@@ -51,34 +80,7 @@ async function verifyExpiringCapabilityToken(subject, token, opts) {
|
|
|
51
80
|
return parsed.exp > now();
|
|
52
81
|
}
|
|
53
82
|
async function sign(userId, secret) {
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
async function signText(message, secret) {
|
|
57
|
-
const enc = new TextEncoder();
|
|
58
|
-
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
59
|
-
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
|
|
60
|
-
return base64url(new Uint8Array(sig));
|
|
61
|
-
}
|
|
62
|
-
function base64urlText(text) {
|
|
63
|
-
return base64url(new TextEncoder().encode(text));
|
|
64
|
-
}
|
|
65
|
-
function textFromBase64url(value) {
|
|
66
|
-
const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
67
|
-
const bin = atob(b64);
|
|
68
|
-
const bytes = new Uint8Array(bin.length);
|
|
69
|
-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
70
|
-
return new TextDecoder().decode(bytes);
|
|
71
|
-
}
|
|
72
|
-
function base64url(bytes) {
|
|
73
|
-
let s = "";
|
|
74
|
-
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
75
|
-
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
76
|
-
}
|
|
77
|
-
function timingSafeEqual(a, b) {
|
|
78
|
-
if (a.length !== b.length) return false;
|
|
79
|
-
let diff = 0;
|
|
80
|
-
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
81
|
-
return diff === 0;
|
|
83
|
+
return hmacSha256Base64Url(`user:${userId}`, secret);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
// src/tools/gating.ts
|
|
@@ -137,6 +139,10 @@ async function handleAppToolRequest(request, opts) {
|
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
export {
|
|
142
|
+
base64UrlEncodeText,
|
|
143
|
+
base64UrlDecodeText,
|
|
144
|
+
hmacSha256Base64Url,
|
|
145
|
+
constantTimeEqual,
|
|
140
146
|
createCapabilityToken,
|
|
141
147
|
verifyCapabilityToken,
|
|
142
148
|
createExpiringCapabilityToken,
|
|
@@ -145,4 +151,4 @@ export {
|
|
|
145
151
|
restrictTaxonomy,
|
|
146
152
|
handleAppToolRequest
|
|
147
153
|
};
|
|
148
|
-
//# sourceMappingURL=chunk-
|
|
154
|
+
//# sourceMappingURL=chunk-VT5DI6GL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/crypto/web-token.ts","../src/tools/capability.ts","../src/tools/gating.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Dependency-free WebCrypto primitives for HMAC-signed, base64url-encoded\n * tokens — base64url encode/decode, HMAC-SHA256, and a constant-time compare.\n * Runs on Cloudflare Workers, Node, and the browser with no Node `crypto`\n * dependency. Shared by the sandbox terminal-proxy token, the WS-upgrade token\n * parser, and the app-tool capability token so the logic lives in one place\n * rather than three near-identical private copies.\n *\n * Internal leaf: not exported from the `/crypto` barrel (that subpath is the\n * AES-GCM field-crypto surface); imported directly by the modules that need it.\n */\n\n/** base64url-encode raw bytes (RFC 4648 §5, no padding). */\nexport function base64UrlEncode(bytes: Uint8Array): string {\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/** base64url-encode a UTF-8 string. */\nexport function base64UrlEncodeText(text: string): string {\n return base64UrlEncode(new TextEncoder().encode(text))\n}\n\n/** Decode a base64url string back to its UTF-8 text. Re-pads before `atob` so\n * unpadded input decodes correctly regardless of the runtime's leniency. */\nexport function base64UrlDecodeText(value: string): string {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(value.length / 4) * 4, '=')\n const bin = atob(padded)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i += 1) bytes[i] = bin.charCodeAt(i)\n return new TextDecoder().decode(bytes)\n}\n\n/** HMAC-SHA256 `message` under `secret`, returned base64url-encoded. */\nexport async function hmacSha256Base64Url(message: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message))\n return base64UrlEncode(new Uint8Array(sig))\n}\n\n/** Length-independent-leak-free compare of two same-charset strings. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nimport {\n base64UrlDecodeText,\n base64UrlEncodeText,\n constantTimeEqual,\n hmacSha256Base64Url,\n} from '../crypto/web-token'\n\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return constantTimeEqual(token, expected)\n}\n\nexport interface ExpiringCapabilityTokenOptions extends CapabilityTokenOptions {\n /** Token lifetime. Expired tokens verify false regardless of signature. */\n expiresInMs: number\n /** Clock injection for tests; defaults to Date.now. */\n now?: () => number\n}\n\n/**\n * Mint an EXPIRING capability token: `<prefix><base64url(payload)>.<sig>` where\n * the payload carries `{ sub, exp, n }` (subject, epoch-ms expiry, random\n * nonce) and the signature is HMAC-SHA256 over the encoded payload. Use this\n * for user-initiated scoped channels (e.g. a per-sequence MCP endpoint) where\n * a captured token must not stay valid past its window; the bare\n * {@link createCapabilityToken} remains for turn-scoped tool bridges whose\n * mint+verify happen inside one request cycle. Fail-closed like the bare\n * variant: no secret → no token.\n */\nexport async function createExpiringCapabilityToken(subject: string, opts: ExpiringCapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error('expiresInMs must be a positive number')\n const prefix = opts.prefix ?? 'cap_'\n const now = opts.now ?? Date.now\n const payload = base64UrlEncodeText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }))\n return `${prefix}${payload}.${await hmacSha256Base64Url(payload, secret)}`\n}\n\n/** Verify an expiring token against `subject`: prefix, payload integrity,\n * subject match, and expiry all checked; returns false (never throws) on any\n * failure including a malformed payload. */\nexport async function verifyExpiringCapabilityToken(subject: string, token: string, opts: CapabilityTokenOptions & { now?: () => number }): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const body = token.slice(prefix.length)\n const dot = body.lastIndexOf('.')\n if (dot <= 0 || dot === body.length - 1) return false\n const payload = body.slice(0, dot)\n const sig = body.slice(dot + 1)\n if (!constantTimeEqual(sig, await hmacSha256Base64Url(payload, secret))) return false\n let parsed: { sub?: unknown; exp?: unknown }\n try {\n parsed = JSON.parse(base64UrlDecodeText(payload)) as { sub?: unknown; exp?: unknown }\n } catch {\n return false\n }\n if (parsed.sub !== subject) return false\n if (typeof parsed.exp !== 'number') return false\n const now = opts.now ?? Date.now\n return parsed.exp > now()\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n return hmacSha256Base64Url(`user:${userId}`, secret)\n}\n","/**\n * Capability gating — compose an agent session's tool surface from a\n * product-defined capability registry.\n *\n * Products that let users pick what an agent can do (a \"Studio\" agent with the\n * full build toolset vs a clean \"Assistant\" with none) all need the same\n * mechanics: a registry of named capabilities, each unlocking proposal types\n * and/or named tool groups, resolved against the product's\n * {@link AppToolTaxonomy} into the concrete tools to expose. The mechanics are\n * generic and live here; the capability VOCABULARY (ids, labels, which\n * proposal types, which tool groups) is the product's.\n */\n\nimport type { AppToolTaxonomy } from './types'\n\n/** One toggleable tool group in a product's capability registry. */\nexport interface ToolCapability {\n id: string\n label: string\n description: string\n /** Proposal types this capability unlocks (intersected with the taxonomy,\n * so a capability can never widen the product's proposal surface). */\n proposalTypes?: readonly string[]\n /** Unlocks every taxonomy proposal type beyond the base set — the domain's\n * specialized long tail (risk_assessment, vuln_report, …). */\n domainActions?: boolean\n /** Named product tool groups (e.g. 'sandbox', 'integrations') this\n * capability unlocks. The vocabulary is the product's; the resolver only\n * unions them. */\n toolGroups?: readonly string[]\n}\n\nexport interface ResolveToolCapabilitiesOptions {\n taxonomy: AppToolTaxonomy\n /** The product's full capability registry. */\n capabilities: readonly ToolCapability[]\n /** Enabled capability ids. `undefined` means full access (legacy callers\n * that don't send a capability set); an explicit `[]` means a pure chat\n * agent with no tools. Unknown ids are ignored. */\n enabled: readonly string[] | undefined\n /** The shared base proposal types `domainActions` excludes. Defaults to\n * every type some capability names explicitly via `proposalTypes` — i.e.\n * \"domain actions\" are the taxonomy types no capability claims. */\n baseProposalTypes?: readonly string[]\n}\n\nexport interface ResolvedToolCapabilities {\n /** Proposal types to keep — feed to {@link restrictTaxonomy}. */\n proposalTypes: string[]\n /** Product tool groups to expose (deduped union across enabled caps). */\n toolGroups: string[]\n}\n\n/**\n * Resolve an enabled capability-id set against a taxonomy into the concrete\n * tool surface. Fail-closed: only types present in the taxonomy survive, and\n * an empty `enabled` set yields no tools at all.\n */\nexport function resolveToolCapabilities(\n opts: ResolveToolCapabilitiesOptions,\n): ResolvedToolCapabilities {\n const { taxonomy, capabilities, enabled } = opts\n if (enabled === undefined) {\n return {\n proposalTypes: [...taxonomy.proposalTypes],\n toolGroups: [...new Set(capabilities.flatMap((c) => c.toolGroups ?? []))],\n }\n }\n const base = new Set(\n opts.baseProposalTypes ?? capabilities.flatMap((c) => c.proposalTypes ?? []),\n )\n const domainTypes = taxonomy.proposalTypes.filter((t) => !base.has(t))\n const byId = new Map(capabilities.map((c) => [c.id, c]))\n\n const proposalTypes = new Set<string>()\n const toolGroups = new Set<string>()\n for (const id of enabled) {\n const cap = byId.get(id)\n if (!cap) continue\n for (const t of cap.proposalTypes ?? []) {\n if (taxonomy.proposalTypes.includes(t)) proposalTypes.add(t)\n }\n if (cap.domainActions) for (const t of domainTypes) proposalTypes.add(t)\n for (const g of cap.toolGroups ?? []) toolGroups.add(g)\n }\n return { proposalTypes: [...proposalTypes], toolGroups: [...toolGroups] }\n}\n\n/**\n * Restrict a taxonomy to a subset of proposal types, intersecting the\n * regulated subset too — the regulated label survives restriction, so a\n * narrowed agent can never launder a regulated type into an unregulated one.\n */\nexport function restrictTaxonomy(\n taxonomy: AppToolTaxonomy,\n allowed: readonly string[],\n): AppToolTaxonomy {\n const allow = new Set(allowed)\n return {\n proposalTypes: taxonomy.proposalTypes.filter((t) => allow.has(t)),\n regulatedTypes: taxonomy.regulatedTypes.filter((t) => allow.has(t)),\n }\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport type { AppToolName } from './openai'\n\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves. */\n tool: AppToolName\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: { args?: Record<string, unknown>; arguments?: Record<string, unknown> } & Record<string, unknown>\n try {\n body = (await request.json()) as typeof body\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n const outcome = await dispatchAppTool(opts.tool, args, auth.ctx, opts)\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -283,7 +283,7 @@ import {
|
|
|
283
283
|
verifySandboxTerminalToken,
|
|
284
284
|
verifyTerminalProxyToken,
|
|
285
285
|
writeProfileFilesToBox
|
|
286
|
-
} from "./chunk-
|
|
286
|
+
} from "./chunk-FFBZJEBE.js";
|
|
287
287
|
import {
|
|
288
288
|
DEFAULT_HARNESS,
|
|
289
289
|
HARNESS_MODEL_POLICIES,
|
|
@@ -306,7 +306,7 @@ import {
|
|
|
306
306
|
restrictTaxonomy,
|
|
307
307
|
verifyCapabilityToken,
|
|
308
308
|
verifyExpiringCapabilityToken
|
|
309
|
-
} from "./chunk-
|
|
309
|
+
} from "./chunk-VT5DI6GL.js";
|
|
310
310
|
import {
|
|
311
311
|
DEFAULT_APP_TOOL_PATHS,
|
|
312
312
|
DEFAULT_HEADER_NAMES,
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -4,6 +4,25 @@ import { a as AppToolName, c as ToolHeaderNames } from '../auth-BlS9GWfL.js';
|
|
|
4
4
|
import { b as AppToolContext } from '../types-2rOJo8Hc.js';
|
|
5
5
|
import { Harness } from '../harness/index.js';
|
|
6
6
|
|
|
7
|
+
type Outcome<T> = {
|
|
8
|
+
succeeded: true;
|
|
9
|
+
value: T;
|
|
10
|
+
} | {
|
|
11
|
+
succeeded: false;
|
|
12
|
+
error: Error;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface TerminalProxyIdentity {
|
|
16
|
+
userId: string;
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
sandboxId: string;
|
|
19
|
+
}
|
|
20
|
+
declare function mintTerminalProxyToken(secret: string, identity: TerminalProxyIdentity, ttlMs?: number, now?: () => number): Promise<Outcome<{
|
|
21
|
+
token: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
}>>;
|
|
24
|
+
declare function verifyTerminalProxyToken(secret: string, token: string, expected: TerminalProxyIdentity, now?: () => number): Promise<boolean>;
|
|
25
|
+
|
|
7
26
|
interface WorkspaceSandboxInstanceLike {
|
|
8
27
|
id: string;
|
|
9
28
|
name?: string;
|
|
@@ -42,15 +61,10 @@ interface WorkspaceSandboxManager<TBox extends WorkspaceSandboxInstanceLike, TEn
|
|
|
42
61
|
declare function createWorkspaceSandboxManager<TClient, TBox extends WorkspaceSandboxInstanceLike, TEnsureOptions = void>(opts: WorkspaceSandboxManagerOptions<TClient, TBox, TEnsureOptions>): WorkspaceSandboxManager<TBox, TEnsureOptions>;
|
|
43
62
|
interface SandboxTerminalTokenOptions {
|
|
44
63
|
secret?: string;
|
|
45
|
-
prefix?: string;
|
|
46
64
|
expiresInMs?: number;
|
|
47
65
|
now?: () => number;
|
|
48
66
|
}
|
|
49
|
-
|
|
50
|
-
userId: string;
|
|
51
|
-
workspaceId: string;
|
|
52
|
-
sandboxId: string;
|
|
53
|
-
}
|
|
67
|
+
type SandboxTerminalTokenSubject = TerminalProxyIdentity;
|
|
54
68
|
interface SandboxTerminalTokenResult {
|
|
55
69
|
token: string;
|
|
56
70
|
expiresAt: Date;
|
|
@@ -70,7 +84,6 @@ interface WorkspaceSandboxConnectionHandlerOptions<TBox extends WorkspaceSandbox
|
|
|
70
84
|
ensureWorkspaceSandbox: (workspaceId: string, userId: string) => Promise<TBox>;
|
|
71
85
|
tokenSecret: string | (() => string | undefined);
|
|
72
86
|
tokenExpiresInMs?: number;
|
|
73
|
-
tokenPrefix?: string;
|
|
74
87
|
proxyRuntimeUrl?: (args: {
|
|
75
88
|
request: Request;
|
|
76
89
|
workspaceId: string;
|
|
@@ -92,6 +105,7 @@ interface SandboxApiCredentials {
|
|
|
92
105
|
}
|
|
93
106
|
interface SandboxRuntimeConnection {
|
|
94
107
|
runtimeUrl: string;
|
|
108
|
+
/** Server-side sidecar bearer. Must authorize terminal routes; never expose it to browser code. */
|
|
95
109
|
authToken?: string;
|
|
96
110
|
}
|
|
97
111
|
interface WorkspaceSandboxRuntimeProxyHandlerOptions {
|
|
@@ -115,7 +129,6 @@ interface WorkspaceSandboxRuntimeProxyHandlerOptions {
|
|
|
115
129
|
sandboxId: string;
|
|
116
130
|
}) => Promise<SandboxRuntimeConnection | null | undefined>;
|
|
117
131
|
tokenSecret: string | (() => string | undefined);
|
|
118
|
-
tokenPrefix?: string;
|
|
119
132
|
fetch?: typeof fetch;
|
|
120
133
|
forwardHeaders?: string[];
|
|
121
134
|
}
|
|
@@ -165,7 +178,6 @@ interface WorkspaceSandboxTerminalUpgradeHandlerOptions {
|
|
|
165
178
|
sandboxId: string;
|
|
166
179
|
}) => Promise<SandboxRuntimeConnection | null | undefined>;
|
|
167
180
|
tokenSecret: string | (() => string | undefined);
|
|
168
|
-
tokenPrefix?: string;
|
|
169
181
|
fetch?: typeof fetch;
|
|
170
182
|
}
|
|
171
183
|
/**
|
|
@@ -186,13 +198,6 @@ declare function bearerToken(value: string | null): string | null;
|
|
|
186
198
|
declare function bearerSubprotocolToken(value: string | null): string | null;
|
|
187
199
|
declare function terminalTokenFromRequest(headers: Headers): string | null;
|
|
188
200
|
|
|
189
|
-
type Outcome<T> = {
|
|
190
|
-
succeeded: true;
|
|
191
|
-
value: T;
|
|
192
|
-
} | {
|
|
193
|
-
succeeded: false;
|
|
194
|
-
error: Error;
|
|
195
|
-
};
|
|
196
201
|
interface SandboxClientCredentials {
|
|
197
202
|
apiKey: string;
|
|
198
203
|
baseUrl: string;
|
|
@@ -256,6 +261,7 @@ interface SandboxRuntimeConfig {
|
|
|
256
261
|
childKeyMint?: (scope: SandboxScope) => Promise<Outcome<string>>;
|
|
257
262
|
bootstrap?: (box: SandboxInstance, scope: SandboxScope) => Promise<Outcome<void>>;
|
|
258
263
|
livenessProbe?: LivenessProbeConfig;
|
|
264
|
+
webTerminalEnabled?: boolean;
|
|
259
265
|
resumeStopped?: boolean;
|
|
260
266
|
backendModelAtCreate?: boolean;
|
|
261
267
|
deferProfileFiles?: boolean;
|
|
@@ -361,16 +367,6 @@ declare function mintSandboxScopedToken(box: SandboxInstance, options: {
|
|
|
361
367
|
declare function driveSandboxTurn(shell: SandboxRuntimeConfig, box: SandboxInstance, message: string, options: StreamSandboxPromptOptions & {
|
|
362
368
|
sessionId: string;
|
|
363
369
|
}): Promise<Outcome<PromptResult>>;
|
|
364
|
-
interface TerminalProxyIdentity {
|
|
365
|
-
userId: string;
|
|
366
|
-
workspaceId: string;
|
|
367
|
-
sandboxId: string;
|
|
368
|
-
}
|
|
369
|
-
declare function mintTerminalProxyToken(secret: string, identity: TerminalProxyIdentity, ttlMs?: number): Promise<Outcome<{
|
|
370
|
-
token: string;
|
|
371
|
-
expiresAt: Date;
|
|
372
|
-
}>>;
|
|
373
|
-
declare function verifyTerminalProxyToken(secret: string, token: string, expected: TerminalProxyIdentity): Promise<boolean>;
|
|
374
370
|
type SandboxStepTransition = {
|
|
375
371
|
kind: 'step-start';
|
|
376
372
|
} | {
|
package/dist/sandbox/index.js
CHANGED
|
@@ -39,9 +39,9 @@ import {
|
|
|
39
39
|
verifySandboxTerminalToken,
|
|
40
40
|
verifyTerminalProxyToken,
|
|
41
41
|
writeProfileFilesToBox
|
|
42
|
-
} from "../chunk-
|
|
42
|
+
} from "../chunk-FFBZJEBE.js";
|
|
43
43
|
import "../chunk-5VXPDXZJ.js";
|
|
44
|
-
import "../chunk-
|
|
44
|
+
import "../chunk-VT5DI6GL.js";
|
|
45
45
|
import "../chunk-A76ZHWNF.js";
|
|
46
46
|
import "../chunk-JZZ6AWF4.js";
|
|
47
47
|
export {
|