@finalai/final-agent-provider 0.12.0 → 0.13.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/sandboxes/session.ts"],
4
- "sourcesContent": ["import type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxAttachedProcessIface,\n SandboxAttachProcessOptionsIface,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk, splitChunks } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport {\n exitStatus,\n type SandboxTransportIface,\n type TransportOutputIface,\n type TransportOutputStreamIface,\n type TransportProcessIface,\n} from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env;\n // read again for every process.\n baseEnv: () => Promise<Record<string, string>>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n // What else a process ref must match beyond this session and the process's\n // boot, read when spawning and attaching (a Device's lease generation).\n processScope?: () => string;\n}\n\n// A durable process's ref: this session, the process and its boot, and the scope.\nconst PROCESS_REF_PREFIX = 'final-process.v1';\n\nconst encodeProcessRef = (parts: {\n session: string;\n process: TransportProcessIface;\n scope: string;\n}): string =>\n [PROCESS_REF_PREFIX, parts.session, parts.process.id, parts.process.bootId, parts.scope]\n .map(encodeURIComponent)\n .join(':');\n\nconst decodeProcessRef = (\n ref: string,\n): { session: string; process: TransportProcessIface; scope: string } | null => {\n const parts = ref.split(':');\n if (parts.length !== 5 || parts[0] !== PROCESS_REF_PREFIX) return null;\n try {\n const [session = '', id = '', bootId = '', scope = ''] = parts.slice(1).map(decodeURIComponent);\n return { session, process: { id, bootId }, scope };\n } catch {\n return null;\n }\n};\n\n// Where output read from `requested` starts: there, or later when the\n// backend no longer retains the bytes from there.\nconst outputStart = (chunk: TransportOutputStreamIface, requested: number): number =>\n Math.max(requested, chunk.nextOffset - chunk.data.byteLength);\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const kill =\n (process: TransportProcessIface) =>\n async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n\n // Polls a process's output from the given offsets (after `first`, output\n // already read from there) until it exits. Aborting `detach` stops polling\n // and fails the streams and the exit; the process is untouched.\n const followProcess = (args: {\n process: TransportProcessIface;\n stdoutOffset: number;\n stderrOffset: number;\n first?: TransportOutputIface;\n detach?: AbortSignal;\n }) => {\n const { process } = args;\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n void (async () => {\n let stdoutOffset = args.stdoutOffset;\n let stderrOffset = args.stderrOffset;\n let waitMs = transport.outputWaitMs;\n let pending = args.first;\n try {\n for (;;) {\n args.detach?.throwIfAborted();\n const output =\n pending ??\n (await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n signal: args.detach,\n }));\n pending = undefined;\n args.detach?.throwIfAborted();\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n }\n })();\n return { stdout: stdoutPush.stream, stderr: stderrPush.stream, exit, kill: kill(process) };\n };\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n // Durable only where processes outlive this client (`processGone`).\n const durable = options.durable === true && transport.processGone != null;\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...(await args.baseEnv()), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const followed = followProcess({\n process,\n stdoutOffset: 0,\n stderrOffset: 0,\n detach: durable ? options.abortSignal : undefined,\n });\n if (!durable) {\n const onAbort = (): void => {\n void followed.kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n void followed.exit\n .catch(() => undefined)\n .finally(() => options.abortSignal?.removeEventListener('abort', onAbort));\n }\n\n // A detached durable handle neither writes to nor closes the process's stdin.\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n if (durable) options.abortSignal?.throwIfAborted();\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n // Forward input as it arrives: a stdin stream is often interactive.\n for await (const chunk of splitChunks(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n ref: durable\n ? encodeProcessRef({ session: args.id, process, scope: args.processScope?.() ?? '' })\n : null,\n stdout: followed.stdout,\n stderr: followed.stderr,\n write,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n // Finds a durable process again: null for a ref of another session or scope,\n // or a process the backend no longer knows.\n const attachProcess = async (\n options: SandboxAttachProcessOptionsIface,\n ): Promise<SandboxAttachedProcessIface | null> => {\n options.abortSignal?.throwIfAborted();\n const decoded = decodeProcessRef(options.ref);\n if (\n transport.processGone == null ||\n decoded == null ||\n decoded.session !== args.id ||\n decoded.scope !== (args.processScope?.() ?? '')\n )\n return null;\n const { process } = decoded;\n let first: TransportOutputIface;\n try {\n first = await transport.output({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n waitMs: 0,\n signal: options.abortSignal,\n });\n } catch (cause) {\n if (transport.processGone(cause)) return null;\n throw cause;\n }\n const followed = followProcess({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n first,\n detach: options.abortSignal,\n });\n return {\n ref: options.ref,\n stdoutOffset: outputStart(first.stdout, options.stdoutOffset),\n stderrOffset: outputStart(first.stderr, options.stderrOffset),\n stdout: followed.stdout,\n stderr: followed.stderr,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess({ ...options, durable: false }, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n if (transport.processGone != null) session.attachProcess = attachProcess;\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox handle is read-only');\n};\n\n// A passive viewer's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the backend the provider asked.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n delete restricted.egress;\n delete restricted.attachProcess;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxAttachedProcessIface,\n SandboxAttachProcessOptionsIface,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk, splitChunks } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport {\n exitStatus,\n type SandboxTransportIface,\n type TransportOutputIface,\n type TransportOutputStreamIface,\n type TransportProcessIface,\n} from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env;\n // read again for every process.\n baseEnv: () => Promise<Record<string, string>>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n // What else a process ref must match beyond this session and the process's\n // boot, read when spawning and attaching (a Device's lease generation).\n processScope?: () => string;\n}\n\n// A durable process's ref: this session, the process and its boot, and the scope.\nconst PROCESS_REF_PREFIX = 'final-process.v1';\n\nconst encodeProcessRef = (parts: {\n session: string;\n process: TransportProcessIface;\n scope: string;\n}): string =>\n [PROCESS_REF_PREFIX, parts.session, parts.process.id, parts.process.bootId, parts.scope]\n .map(encodeURIComponent)\n .join(':');\n\nconst decodeProcessRef = (\n ref: string,\n): { session: string; process: TransportProcessIface; scope: string } | null => {\n const parts = ref.split(':');\n if (parts.length !== 5 || parts[0] !== PROCESS_REF_PREFIX) return null;\n try {\n const [session = '', id = '', bootId = '', scope = ''] = parts.slice(1).map(decodeURIComponent);\n return { session, process: { id, bootId }, scope };\n } catch {\n return null;\n }\n};\n\n// Where output read from `requested` starts: there, or later when the\n// backend no longer retains the bytes from there.\nconst outputStart = (chunk: TransportOutputStreamIface, requested: number): number =>\n Math.max(requested, chunk.nextOffset - chunk.data.byteLength);\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const kill =\n (process: TransportProcessIface) =>\n async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n\n // Polls a process's output from the given offsets (after `first`, output\n // already read from there) until it exits. Aborting `detach` stops polling\n // and fails the streams and the exit; the process is untouched.\n const followProcess = (args: {\n process: TransportProcessIface;\n stdoutOffset: number;\n stderrOffset: number;\n first?: TransportOutputIface;\n detach?: AbortSignal;\n }) => {\n const { process } = args;\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n void (async () => {\n let stdoutOffset = args.stdoutOffset;\n let stderrOffset = args.stderrOffset;\n let waitMs = transport.outputWaitMs;\n let pending = args.first;\n try {\n for (;;) {\n args.detach?.throwIfAborted();\n const output =\n pending ??\n (await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n signal: args.detach,\n }));\n pending = undefined;\n args.detach?.throwIfAborted();\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n }\n })();\n return { stdout: stdoutPush.stream, stderr: stderrPush.stream, exit, kill: kill(process) };\n };\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n // Durable only where processes outlive this client (`processGone`).\n const durable = options.durable === true && transport.processGone != null;\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...(await args.baseEnv()), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const followed = followProcess({\n process,\n stdoutOffset: 0,\n stderrOffset: 0,\n detach: durable ? options.abortSignal : undefined,\n });\n if (!durable) {\n const onAbort = (): void => {\n void followed.kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n void followed.exit\n .catch(() => undefined)\n .finally(() => options.abortSignal?.removeEventListener('abort', onAbort));\n }\n\n // A detached durable handle neither writes to nor closes the process's stdin.\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n if (durable) options.abortSignal?.throwIfAborted();\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n // Forward input as it arrives: a stdin stream is often interactive.\n for await (const chunk of splitChunks(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n ref: durable\n ? encodeProcessRef({ session: args.id, process, scope: args.processScope?.() ?? '' })\n : null,\n stdout: followed.stdout,\n stderr: followed.stderr,\n write,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n // Finds a durable process again: null for a ref of another session or scope,\n // or a process the backend no longer knows.\n const attachProcess = async (\n options: SandboxAttachProcessOptionsIface,\n ): Promise<SandboxAttachedProcessIface | null> => {\n options.abortSignal?.throwIfAborted();\n const decoded = decodeProcessRef(options.ref);\n if (\n transport.processGone == null ||\n decoded == null ||\n decoded.session !== args.id ||\n decoded.scope !== (args.processScope?.() ?? '')\n )\n return null;\n const { process } = decoded;\n let first: TransportOutputIface;\n try {\n first = await transport.output({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n waitMs: 0,\n signal: options.abortSignal,\n });\n } catch (cause) {\n if (transport.processGone(cause)) return null;\n throw cause;\n }\n const followed = followProcess({\n process,\n stdoutOffset: options.stdoutOffset,\n stderrOffset: options.stderrOffset,\n first,\n detach: options.abortSignal,\n });\n return {\n ref: options.ref,\n stdoutOffset: outputStart(first.stdout, options.stdoutOffset),\n stderrOffset: outputStart(first.stderr, options.stderrOffset),\n stdout: followed.stdout,\n stderr: followed.stderr,\n wait: () => followed.exit,\n kill: followed.kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess({ ...options, durable: false }, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n if (transport.processGone != null) session.attachProcess = attachProcess;\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox handle is read-only');\n};\n\n// A passive viewer's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the backend the provider asked.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n delete restricted.attachProcess;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["args"]
7
7
  }
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@finalai/final-agent-provider",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "description": "Final's harness and ACP adapter definitions and the general harness provider, computer, device and local sandbox providers, and Final's MCP gateway integration.",
6
6
  "license": "Apache-2.0",
7
7
  "files": [
8
8
  "dist",
9
9
  "README.md",
10
- "WORKSPACE-EGRESS.md",
11
10
  "LICENSE",
12
11
  "NOTICE",
13
12
  "HOSTING.md"
@@ -100,11 +99,6 @@
100
99
  "import": "./dist/modules/sandboxes/local.mjs",
101
100
  "default": "./dist/modules/sandboxes/local.mjs"
102
101
  },
103
- "./sandboxes/access": {
104
- "types": "./dist/modules/sandboxes/access.d.mts",
105
- "import": "./dist/modules/sandboxes/access.mjs",
106
- "default": "./dist/modules/sandboxes/access.mjs"
107
- },
108
102
  "./package.json": "./package.json",
109
103
  "./harnesses/config-fields": {
110
104
  "types": "./dist/modules/harnesses/config-fields.d.mts",
@@ -126,16 +120,6 @@
126
120
  "import": "./dist/modules/sandboxes/testing/quoted-shell-arguments.mjs",
127
121
  "default": "./dist/modules/sandboxes/testing/quoted-shell-arguments.mjs"
128
122
  },
129
- "./sandboxes/egress": {
130
- "types": "./dist/modules/sandboxes/egress.d.mts",
131
- "import": "./dist/modules/sandboxes/egress.mjs",
132
- "default": "./dist/modules/sandboxes/egress.mjs"
133
- },
134
- "./sandboxes/egress-bootstrap": {
135
- "types": "./dist/modules/sandboxes/egress-bootstrap.d.mts",
136
- "import": "./dist/modules/sandboxes/egress-bootstrap.mjs",
137
- "default": "./dist/modules/sandboxes/egress-bootstrap.mjs"
138
- },
139
123
  "./integrations": {
140
124
  "types": "./dist/modules/integrations.d.mts",
141
125
  "import": "./dist/modules/integrations.mjs",
@@ -148,11 +132,11 @@
148
132
  },
149
133
  "dependencies": {
150
134
  "@agentclientprotocol/sdk": "1.8.0",
151
- "@finalai/agent-protocol": "0.10.0",
152
- "@finalai/agent-provider": "0.11.0",
135
+ "@finalai/agent-protocol": "0.11.0",
136
+ "@finalai/agent-provider": "0.12.0",
153
137
  "@finalai/computers": "0.3.1",
154
138
  "zod": "4.6.6",
155
- "@finalai/agent-definition": "0.10.0"
139
+ "@finalai/agent-definition": "0.11.0"
156
140
  },
157
141
  "engines": {
158
142
  "node": ">=26"
@@ -1,184 +0,0 @@
1
- # Workspace Device egress integration
2
-
3
- `/sandboxes/egress` provides a provider-neutral TCP/UDP/DNS exit through an approved Final Device. IPv6 application egress is blocked; kernel and root-owned neighbor discovery remain available; link neighbor discovery remains available for inbound provider control. Selecting a Device intentionally permits access to its reachable LAN and services addressed through its tailnet IP, under its owner/process permission. Grants exclude other workspaces and unapproved Devices. Accepted provider control and preview TCP replies retain their physical return path; this capability routes outbound workspace connections.
4
-
5
- ## Host composition and identity
6
-
7
- Final Device egress is an opinionated implementation of the generic
8
- `SandboxNetworkingExtensionIface`, not a requirement on sandbox implementations or
9
- a public replacement-Device protocol. Compose it with a provider through the same
10
- `networking` option used by other networking implementations:
11
-
12
- ```ts
13
- const networking = createWorkspaceDeviceNetworking({ api: deviceApi, needed });
14
- const computers = createComputerSandboxProvider({ computersApi, networking });
15
- const daytona = createDaytonaSandboxProvider({ networking });
16
- ```
17
-
18
- The factories are exported from `/sandboxes/egress`, `/sandboxes/computer`, and
19
- `@finalai/daytona-agent-provider/sandboxes/daytona`, respectively. Keep the trusted
20
- Device access through the extension's configured `access`. A custom provider accepts
21
- the same generic extension: call `enabled`, validate its workspace identity, call
22
- `prepare` before provider fencing, and use `attachSandboxNetworking` after
23
- obtaining its raw handle. Supply the saved policy, restore/fence callbacks,
24
- namespace existence check and deferred setup. Physical teardown calls `suspend`
25
- even if another cleanup step fails. Providers needing earlier namespace setup can
26
- use `controller` and `withSandboxNetworking` directly, as Final Computer does.
27
-
28
- The extension's configured `access({ organizationId, projectId, actingUserId })`
29
- returns the Device API origin and a token. Device
30
- operations use the current actor; background lease reconciliation may use the stored
31
- lease owner instead of a present turn author. Hosts must authorize both forms of work.
32
- Admission, policy persistence, and cleanup also make trusted service calls with
33
- `actingUserId: null`. Enabling workspace Device networking requires access
34
- accepted by those service endpoints for that case, separate from user-bound grants.
35
- Returning `null` refuses the call before HTTP; a user-token-only configuration cannot
36
- start or clean up networked workspaces. Its host must add service authentication
37
- before enabling this extension. The SDK never reuses a user's cached grant for a
38
- service call or another user: it asks the configured access on every call.
39
-
40
- The extension owns Device authorization, enrollment, helper installation, leases
41
- and failover. Providers need no Device networking implementation. Its generic
42
- controller also authorizes external editor access and reports its expiry limit;
43
- providers must bound editor grants by that deadline. The Device executable and
44
- hosted control services remain separate first-party implementations.
45
-
46
- The public Computers API accepts an optional browser credential `expiresAt` bound.
47
- Managed editors request that bound and refuse a backend response that exceeds it;
48
- an older API rejecting the field cannot silently grant longer access.
49
-
50
- 1. Assign one stable host-owned identity per logical workspace and persist whether networking is managed. Supply that identity to start, resume, editors and teardown.
51
- 2. Save the actual direct policy before a boot fence. Prefer a provider fence; explicitly unsupported providers use the logged startup-window fallback.
52
- 3. Construct the manager with the raw session and privileged executor, then declare its policy on the provider handle with the connection's grant and current-actor reader.
53
- 4. Detach on runtime shutdown. On physical stop/deletion, attempt fencing, lease cleanup and provider cleanup independently; retain saved desired settings for recreation.
54
-
55
- Inject the Device API URL, trusted Device credential callback, stable workspace ID, current acting user, and the raw Linux sandbox session. Pass that unguarded session into `createWorkspaceEgressManager`, then use `withWorkspaceEgress` to declare its execution policy and lifecycle hooks on the returned handle. `withWorkspaceEgress` constructs a checked handle through the generic SDK constructor, so direct callers are safe too. `defineSandboxProvider` automatically adds any host admission policy on start and resume without reinstalling the Device policy. Using a guarded session for privileged controller execution recursively deadlocks. `/sandboxes/egress-bootstrap` exports the exact controller source for custom privileged executors and manual acceptance. Custom executors run `python3 -I -c source source` with JSON on stdin, or execute a root-owned file providing `__file__`; the source argument permits persistence for later controller launches.
56
-
57
- The host owns the logical workspace identity and durable networking registration. Each identity must name one physical workspace at a time; do not reuse it for unrelated resources or derive authorization from opaque provider state. Editor contexts supply the same `workspaceId` as start/resume. When `networking` is configured, legacy editor states also require that host identity so the predicate can determine management before granting access. Managed editor startup is guarded. Missing enrolled workspace nodes automatically recover using the approved desired configuration.
58
-
59
- Supply the extension's `enabled` predicate through `createWorkspaceDeviceNetworking({ needed, api })`. Configuring an API client alone does not opt in workspaces. Keep the predicate true for every workspace with saved networking policy, including an explicitly disabled allow-all route and after new opt-in is removed. Teardown can leave a provider boot fence, so management and the Device API remain required until the stable workspace is deleted. The adapters also remember management within their current process; that cannot replace durable host discovery after a restart. Hosts filter potential provider capabilities per workspace. `additionalCapabilities` offers known positive capabilities while the rest of pre-start availability stays unknown; Computer uses it for egress without claiming Screen or ComputerUse availability.
60
-
61
- Before creating or booting a fenced namespace, call `preserveWorkspaceNetworkPolicy` with its actual current provider policy. This atomically saves only a missing policy; a temporary boot fence must never replace a saved choice. Both providers call the extension's `prepare` hook for durable create-if-absent behavior, and its `suspend` hook during physical teardown. The Final Device extension implements those hooks through the Device API. The initialization endpoint is deliberately separate: an older Device API rejects boot before changing provider policy rather than misinterpreting the operation as a policy update.
62
-
63
- Custom networking hooks own resources allocated before they reject and must clean up those resources. A failed attachment must not revoke another connection’s shared lease. The Device API must expire provisioning leases whose create acknowledgement is lost; a client cannot revoke an ID it never received, and cannot activate it without the acknowledged enrollment.
64
-
65
- Adapters must supply `afterGuard` to restore restrictive saved policies or undo an outer fence. Missing restoration refuses Configure/Disable rather than acknowledging an unrestricted direct route. Provider instances retain remembered managed-workspace and prepared-policy identifiers for their lifetime; include that inventory in host capacity planning. Durable host registration remains required across process replacement.
66
-
67
- ## Boot, resume and saved policy
68
-
69
- Prefer provider networking fences before boot or rebase, then apply the transport policy only after the manager guards the namespace. Daytona defaults to a structured-warning fallback only when the account explicitly rejects custom networking; `workspaceNetworkingBootFence: true` requires the provider fence, and `false` deliberately selects command-readiness protection. Startup/resumed background traffic before controller initialization is outside the fallback guarantee. A connection whose grant does not both write files and run commands is passive for networking: it records nothing, never recovers a route, and cannot wake a managed paused/stopped Computer or stopped/archived Daytona sandbox; a connection whose grant does both must resume it through guarded initialization. A sandbox's id is its workspace identity for Device egress; provider resource names remain private. A new sandbox starts from its setup's network policy. Missing or unreadable provider policy is denied. Fallback identities escape delimiters; Device egress identities must fit the 256-character protocol limit without restricting ordinary sandbox identities; supply a bounded host identity for longer provider names. Disable before capturing an image; do not clone a live managed identity.
70
-
71
- Device configuration and Disable ask for approval by default through the public sandbox tool policy, and can be explicitly preauthorized. Consent selects the Device’s destination controls, including private access; provider destination allowlists do not constrain traffic inside the tunnel. Retain the saved direct-access policy and restore it on Disable. Unknown initial policy prevents configuration because restoration would be undefined. Disable before changing the saved direct-access policy. In-tunnel destination allowlists are unsupported. Configure failures intentionally retain a blocked route and desired settings until successful configuration or explicit disable; they never silently restore direct access.
72
-
73
- Daytona treats block-all, CIDR allowlists, and domain allowlists as mutually exclusive. Its adapter explicitly clears domain allowlists on block/unblock, including legacy servers that clear only CIDRs; eligible-account acceptance must verify actual enforcement before admission, especially whether settings accepted on a stopped or archived workspace are applied during boot. Settings read-back alone does not prove that boot-time fence. See [Daytona network limits](https://www.daytona.io/docs/en/network-limits/).
74
-
75
- Provider instance deletion does not erase saved logical-workspace networking. Recreating the same logical workspace deliberately recovers it; never reuse a workspace identifier for an unrelated workspace. A new managed attachment must preserve the initial policy through Device API even if the instance is already running.
76
-
77
- An authorized Configure that subsequently fails carries a separate persistent choice marker, including before enrollment begins; resolver/firewall restoration artifacts do not authorize returning to direct access. The marker survives loss of volatile controller state and clears only after explicit Disable or an authorized direct-policy restoration. Custom providers may supply `namespaceExists` from their own authoritative API so deletion cleanup revokes leases without entering a deleted namespace. In-guest reboots and direct provider administration are outside the host lifecycle fence; route restarts through the adapter to retain boot-time protection.
78
-
79
- ## Execution policy boundary
80
-
81
- The generic handle constructor applies the handle's `executionPolicy` automatically.
82
- The provider factory composes any host policy supplied in the start context before
83
- that policy, including when the handle was already constructed. The Final
84
- integration declares Device authorization, lease readiness and deferred setup;
85
- it does not install its own execution wrapper. The generic factory does not read
86
- the acting user, call Device API, select Devices or decide approvals.
87
-
88
- Policy checks cover the standard commands, mutations and MCP channels. Configure
89
- and Disable retain separate authorization so they can repair blocked networking.
90
- The direct-network policy setter performs its own authorization and repair without
91
- requiring a working tunnel. Host-supplied admission still applies to that setter.
92
- A workspace shares one network namespace; checks on new operations do not isolate
93
- already-running processes by user. The Linux firewall independently enforces
94
- physical-egress denial.
95
-
96
- ## Authority, grants and shutdown
97
-
98
- Device Configure/Disable require write access and lifecycle authority. Direct network-policy operations require execute or write access plus lifecycle authority; a grant without files write and shell execute cannot mutate networking. MCP projections enforce tool consent; hosts exposing handle RPC must install their own operation-consent hook. Configure and Disable default to Ask. Only exact `network.egress.configure`/`network.egress.disable` action preauthorization can waive the default; broad lifecycle/network Allow cannot. Status honors `network.egress.status` and `egress` policy. MCP hosts pass lifecycle authority and action input to the policy-key helper, then use `resolveSandboxToolPolicy` so any applicable Ask wins for Configure/Disable. Status retains ordinary first-match read-policy precedence for `network.egress.status` and `egress`. This release exposes status to grants that write files and run commands, with lifecycle authority. Foreground actor changes suspend as the currently acting user, including a trusted userless host, and block until an explicit route choice; background connections do not revoke another actor's chosen route. Userless execution cannot inherit someone else's Device access.
99
-
100
- Passive connections skip recovery and deferred setup. Pass the connection's grant to `withWorkspaceEgress`; never initialize a manager for a passive connection. They may inspect files but cannot repair a blocked route or run setup. A connection whose grant writes files and runs commands must configure or explicitly disable first. Networking changes do not run deferred repository/setup work; that runs before the next guarded execution, write, or foreground MCP endpoint/channel acquisition.
101
-
102
- The manager owns renewal; failure observers cannot interrupt networking or claim cleanup. Editor access by another actor is refused before changing the owner’s route. A successful direct-policy retry clears a previous provider-update failure, including for userless workspaces. `close()` detaches a handle; runtime shutdown must not disrupt another connection sharing the workspace. `close(true)` attempts the namespace fence and suspends every lease for an explicit workspace stop/deletion, even if its cached lease has been replaced. Failure cleanup revokes only a lease that this handle created. Provider teardown independently attempts the fence, resource cleanup and lease revocation, and reports failures after cleanup. Removing boot admission must still permit teardown; hosts distinguish it with the predicate's `teardown` flag. Desired settings survive stop and lease expiry.
103
-
104
- Teardown preserves the current provider policy before applying a fence. If eligibility or initial-policy persistence is unavailable for an unknown workspace, teardown still stops the resource, attempts suspension, and reports the error; it does not overwrite an unrelated policy. Host-managed boot waits for eligibility and saved policy recovery. Managed Computer creation and write/resume disable automatic wake. Passive read attachments do not change provider settings; migrate existing resources through a write attachment before relying on managed wake protection. Daytona’s preferred-fence fallback retains the documented startup window.
105
-
106
- Handles opened before opt-in check admission before new commands, stdin, policy changes, and MCP messages; they must reconnect when the shared workspace becomes managed. Existing processes and previously issued editor/preview credentials still share that namespace and are not revoked by opting in or changing the exit. Hosts must close existing interactive sessions before allowing a different actor to use a personal Device on a shared workspace. Editor grants report a refresh deadline at least ninety seconds before lease expiry; refresh rechecks and renews networking, while closing an editor detaches without revoking an agent’s shared lease. Both providers persist management learned after start when parking; the durable host registration remains authoritative across restarts. Unsupported provider fences intentionally leave namespace-only denial; that retains the documented boot window, and does not authorize physical egress after guard readiness.
107
-
108
- A failed route mutation from another actor must pass a fresh Device API authorization check before changing the namespace. The current actor's still-active owned lease permits local cleanup during a control-plane outage. MCP batches received before a transient guard failure are retained until a later authorized receive; business requests are never replayed.
109
-
110
- ## Controller, recovery and status
111
-
112
- The controller verifies helper hashes/architecture, maintains physical-egress and IPv6 denial, and repairs managed DNS after network-manager rewrites. Disable restores resolver/firewall ownership, including after loss of volatile state, and clears persisted identity. Only discovered namespace-local nested bridges may forward through the exit; unrelated physical hosts cannot use the workspace as a router. Cooperative root/NET_ADMIN workloads can override their own namespace, so this is not a security boundary against hostile root.
113
-
114
- The enrollment API returns `provisioning`; activation alone publishes grants and returns `active`. Configure and disable serialize through a bounded root-owned claim, refreshed every minute and expiring within five minutes after its last refresh. A superseded operation cannot activate an enrollment or undo a later disable. Each claim-acquisition or controller-stability wait is capped at three minutes; recovery can perform more than one wait. Lease deadlines use server wall time; retry throttles use monotonic time. Clocks must stay within one minute of the server.
115
-
116
- A live, nonexpired controller survives exit/control outages without repeated helper transfers. Renewal attempts are spaced at least 30 seconds apart; failed automatic re-enrollment has a 60-second cooldown. Explicit configure can retry sooner. Failed control-state recovery has a separate five-second backoff across guarded calls. Foreground operations and delivered MCP traffic with an active route, including stdin writes and MCP messages, recheck namespace ownership through a privileged provider exec with a 100 ms cache. Recovery/configuration serializes them. Direct unconfigured handles make no background namespace probes. Handle lifetime bounds host renewal.
117
-
118
- Workspace helpers enable shields-up to reject unsolicited tunnel connections independently of control-server grants. Recovery after a crashed namespace controller can take about thirty seconds and retains denial throughout. The default privileged launcher sends source and input on stdin, keeping shell arguments small. Custom argv-based executors must stay below Linux’s per-argument size limit; cached execution uses a short digest-pinned loader.
119
-
120
- Status reports error when no denial tier can be installed; blocked is reserved for confirmed namespace denial. Configuration validation after guard installation leaves blocked status, rather than stale direct/device status. Cleanup errors are retained with the original failure and reported through onFailure.
121
-
122
- ## MCP and editor transports
123
-
124
- Managed screen grants are view-only; interactive input uses the guarded computer-use MCP channel. URL MCP endpoints are converted to owned message channels so actor and policy checks also apply to cached endpoints.
125
-
126
- Managed MCP uses guarded message channels. Hosted tools named `egress`, `network.egress`, or its configure/disable/status keys receive their server-name prefix in projections, retaining the ordinary hosted-tool approval policy. Handle RPC clients must negotiate `mcpChannels` for local MCP/computer-use, or use the MCP projection; legacy URL-only clients do not receive an unguarded URL. Known unsupported provider firewalls cannot restore a restrictive direct-access policy: Disable leaves namespace traffic blocked and reports the missing provider capability rather than claiming restoration succeeded.
127
-
128
- Generic MCP channel transports poll while idle (with backoff capped at one second) to deliver server notifications and requests. Hosted discovery clients consume messages only while requests are pending; explicit catalog reads refresh the server listing. URL-backed HTTP transports may independently reconnect their notification stream, which rechecks admission and can refresh a Device lease. Cache hosts should close idle channels if they require strictly passive discovery. These private catalog clients do not service idle server requests or guarantee idle notifications; servers may end an idle session. An already-ended owned HTTP channel reconnects before sending a business request. Once a request may have been sent, failure is reported without replay; the following call reconnects. Buffer limits deliberately close a flooding channel. New HTTP streams and received messages also recheck admission. A notification-stream error alone does not cancel independent tool requests. A terminal receive failure closes the channel and evicts its cached client; the next call reconnects without replaying a tool invocation. Close cached clients when their handle closes. URL-backed MCP channels retain the SDK request timeout and per-request SSE recovery behavior; a failed POST does not fail unrelated requests. Each parsed receive queue rejects batches exceeding 4,096 messages or 16 MiB. This bounds retained queues per guard layer, not the HTTP/SSE parser’s allocation before delivery; hosts must bound transport response bodies independently. Closing attempts bounded server-session termination, then closes the local transport.
129
-
130
- Background MCP queue polling retains the last authorized owner and does not run workspace setup for empty queues. Delivered messages and stream reconnects pass through admission; active routes also recheck namespace ownership. Direct and choice-blocked handles skip background namespace probes. Business requests still require the current foreground actor; catalog/protocol replies retain the last authorized owner. Opening a hosted endpoint initially requires foreground admission; hosts may authorize those discovery operations explicitly. Automatic retries of incomplete catalogs have a one-second cooldown; explicit catalog refreshes retry immediately. Failed deferred setup is reported and retried on the next guarded use. Setup steps must tolerate partial execution, as during host recovery. Generic workspace IDs may be opaque strings; Device egress requires a stable ID of at most 256 characters with no control characters.
131
-
132
- ## Provider prerequisites and limits
133
-
134
- Custom images require root, the netfilter owner match, Python 3.8 or newer, TUN, IPv4 policy routing, `iproute2`, and IPv4/IPv6 netfilter before opting in. Direct status/disable remain usable on images without iptables; blocking and restoration of an installed guard still require netfilter tools. Blocking/disable do not require TUN. A missing executor or prerequisite cannot silently remove a guard; repair the image or use the provider's administrative API. Disabling automatic wake on a managed Computer is deliberate: boot must pass through its fence. Providers claiming boot fencing must verify its behavior on creation, stop/archive and resume; an accepted API request alone is insufficient. Preferred fencing may instead log the documented startup-window fallback. Paid-provider tests are manual and opt-in. Modal's tested VM kernel lacks policy routing and is rejected before enrollment.
135
-
136
- Install the guard before exceeding 128 existing external control connections. Connection overflow installs the closed full guard before returning an error, falling back to minimal denial if needed. Emergency denial preserves loopback except DNS stubs and ACK replies on captured inbound control sockets. If no control socket could be captured, use the provider administrative API to repair the image. Device-mode listener discovery takes a few seconds. New preview listeners opened while blocked need another configure/block refresh. Helper router-mapping cleanup is best effort on graceful shutdown; abrupt process death or router failure can leave mappings for the router to remove.
137
-
138
- Existing-connection overflow applies during capture as well as configuration and installs denial before reporting failure. Missing firewall tools require repair through the provider administrative API; absent legacy NAT/mangle modules can be restored from filter-only denial.
139
-
140
- Existing untracked provider-control reply exceptions require root-owned established sockets; non-root listeners cannot preserve a physical outbound connection. Normally tracked inbound services still use conntrack reply direction, including services running as ordinary users. A failed temporary-artifact removal is treated as failed configuration and rolls back the route; fix the provider file API and retry.
141
-
142
- The controller preserves at most 256 inbound TCP listener ports, preferring already retained ports and privileged provider listeners. Additional listeners do not crash the controller; their physical-network replies may be blocked. A root-owned `listener-limit.json` diagnostic records saturation. This limit does not affect outbound connections through the Device.
143
-
144
- The workspace helper internal DNS address (100.100.100.100) is blocked; configure a Device-reachable DNS server instead. MagicDNS and host split-DNS integration remain outside this capability.
145
-
146
- The local kernel acceptance image uses Python 3.13; Python 3.8 is the controller syntax/stdlib floor, rather than a separately exercised provider image. Run manual prerequisite acceptance for each custom image before rollout.
147
-
148
- ## Compatibility and repair
149
-
150
- Configuring workspace admission requires MCP channel negotiation (or the MCP projection) even for currently unmanaged write handles. Admission checks intentionally run before new commands and messages so handles opened before opt-in cannot bypass a later change; an unavailable admission authority refuses execution. Legacy URL-only handle-RPC clients must upgrade before enabling this host integration. Read handles remain passive. `onSandboxStop` and `onSandboxDelete` represent authorized physical lifecycle actions; viewer disconnect and runtime detach use `onRuntimeShutdown`.
151
-
152
- Controller error status prevents guarded execution and triggers authorized recovery. If the local enrollment deadline has already elapsed, renewal requests fresh enrollment rather than restarting without credentials. A lost original DNS backup requires provider-administrative resolver repair; the controller never treats managed resolver content as the original. Resolver restoration replaces an unexpected dangling symlink without following its target.
153
-
154
- Raw guarded MCP channels retain received batches until a later authorized receive. The hosted MCP client treats a receive or authorization failure as terminal and reports uncertainty to outstanding callers; reconnect on the next operation, without replaying a business call. Transport-only notification-stream errors leave independent POSTs alone; failed admission closes the whole owned channel deliberately.
155
-
156
- The namespace must remain under the adapter's networking ownership. External privileged firewall flushes/reloads and direct provider or in-guest reboots bypass this lifecycle; stop execution and initialize the adapter again after such operations. Blocked status records successful denial installation by the owned controller, not continuous attestation against a privileged process rewriting the firewall. This mechanism is not a boundary against namespace root/NET_ADMIN. Supported nested-container filter reordering remains covered by the earlier mangle guard.
157
-
158
- Workspace identity must be stable before the first managed start. Changing from a derived identity to another ID while reusing the same physical resource is an unsupported identity migration; disable and retire the old managed workspace first. The SDK does not infer authorization or a saved policy from provider-private state. Passive recovery is a read-only viewer operation for all hosts, independent of egress opt-in, matching the provider interface contract.
159
-
160
- A claim's five-minute expiry is a renewable lease, not an absolute total operation deadline. Hosts must provide bounded custom execution/upload/restoration hooks; a live owner awaiting an unbounded custom promise can keep refreshing its claim. Built-in public API calls have request deadlines. Failure cleanup attempts provider deny-all when namespace denial cannot be confirmed. With confirmed namespace denial, an unrestricted saved provider policy may be restored while the namespace stays blocked; Disable separately restores direct namespace access and the fresh saved policy.
161
-
162
- Computer service bookkeeping uses the raw setup session's public file operation, whose directory creation waits through the public process API before the file write. It runs before deferred repository or service setup, including on fenced fresh read attachments. Helper publication atomically installs executable permissions so a concurrent restart cannot observe a temporary non-executable binary.
163
-
164
- Disabling and reconfiguring is required after changing namespace bridge layouts,
165
- so stale owned routing rules are removed. Reinitialize after enabling IPv6 kernel
166
- support that was unavailable during the controller prerequisite probe. Symlinked
167
- `/etc/hosts` images require a regular hosts file before Device configuration.
168
-
169
- Supply `fetch` to `withWorkspaceEgress`/`withWorkspaceAdmission` for a custom MCP
170
- transport; Computer integrations supply `networking.fetch` through `createWorkspaceDeviceNetworking({ mcpFetch })`. Artifact download
171
- fetch and MCP fetch are independent dependencies. A first failed attempt without
172
- NET_ADMIN can be explicitly disabled when the controller proves no networking
173
- changes were possible; prior managed namespaces still require privileged repair.
174
-
175
- Namespace-local bridge subnets take precedence over exit routes. Use bridge ranges
176
- that do not overlap private destinations you need to reach through the Device.
177
-
178
- Daytona cannot verify pre-boot enforcement from control-plane policy readback for a stopped sandbox. Its default resume path logs the startup window, starts the sandbox, then installs provider and namespace denial before agent tools. Explicitly requiring a verified boot fence refuses that stopped resume.
179
-
180
- The default Computer and Daytona executors use passwordless sudo in the workload session. This is cooperative routing, not a security boundary against that workload: it can change its own firewall. For containment, supply an out-of-band privilegedRun through the composable controller and remove workload root/sudo and CAP_NET_RAW/CAP_NET_ADMIN. AF_PACKET frames can bypass IP netfilter; the raw-IP packet probes do not establish containment for link-layer injection. Killing processes and closing existing access remain available while execution admission is denied.
181
-
182
- The retained direct policy is the actual provider policy, including existing platform-required destinations. Device policy persistence supports at most 1,024 entries of 253 characters; managed policy admission rejects unsupported snapshots before changing provider rules. Hosts own complete RPC authorization through authorizeOperation, including any consent required for Egress. An audit-only hook is not an authorization policy.
183
-
184
- Custom privileged executors must launch the trusted system interpreter with a sanitized environment, including loader variables, before running supplied Python source. The controller additionally pins system command lookup and strips custom TLS trust overrides from its owned helpers.
@@ -1,34 +0,0 @@
1
- export interface FinalPlatformAccessIface {
2
- baseUrl: string;
3
- token: string;
4
- }
5
- export type ComputersAccessIface = (args: {
6
- organizationId: string;
7
- projectId: string;
8
- refresh: boolean;
9
- }) => Promise<FinalPlatformAccessIface | null>;
10
- export type DeviceAccessIface = (args: {
11
- organizationId: string;
12
- projectId: string | null;
13
- actingUserId: string | null;
14
- refresh: boolean;
15
- }) => Promise<FinalPlatformAccessIface | null>;
16
- export interface ComputersApiCredentialsIface {
17
- computersAccess(args?: {
18
- refresh?: boolean;
19
- }): Promise<FinalPlatformAccessIface | null>;
20
- }
21
- export interface DeviceApiCredentialsIface {
22
- deviceAccess(args?: {
23
- actingUserId?: string | null;
24
- refresh?: boolean;
25
- }): Promise<FinalPlatformAccessIface | null>;
26
- }
27
- export declare const computersCredentialsFor: (access: ComputersAccessIface | undefined, scope: {
28
- organizationId: string;
29
- projectId: string;
30
- }) => ComputersApiCredentialsIface;
31
- export declare const deviceCredentialsFor: (access: DeviceAccessIface | undefined, scope: {
32
- organizationId: string;
33
- projectId: string | null;
34
- }) => DeviceApiCredentialsIface;
@@ -1,15 +0,0 @@
1
- const computersCredentialsFor = (access, scope) => ({
2
- computersAccess: async (args) => access == null ? null : access({ ...scope, refresh: args?.refresh === true })
3
- });
4
- const deviceCredentialsFor = (access, scope) => ({
5
- deviceAccess: async (args) => access == null ? null : access({
6
- ...scope,
7
- actingUserId: args?.actingUserId ?? null,
8
- refresh: args?.refresh === true
9
- })
10
- });
11
- export {
12
- computersCredentialsFor,
13
- deviceCredentialsFor
14
- };
15
- //# sourceMappingURL=access.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../../src/sandboxes/access.ts"],
4
- "sourcesContent": ["// Access to Final's Computers and Device APIs. It is configuration of the\n// `computer` and `device` providers (and of workspace Device egress), supplied\n// by whoever runs them, never part of the generic provider context.\n\nexport interface FinalPlatformAccessIface {\n // The API origin; requests go to `<baseUrl>/v1/...`.\n baseUrl: string;\n token: string;\n}\n\n// Computers API access for one project's sandboxes. `refresh` asks for a new\n// token after the API rejected the last one. Null: none granted.\nexport type ComputersAccessIface = (args: {\n organizationId: string;\n projectId: string;\n refresh: boolean;\n}) => Promise<FinalPlatformAccessIface | null>;\n\n// Device API access acting for `actingUserId` (null: service access for\n// workspace networking). Null: none granted.\nexport type DeviceAccessIface = (args: {\n organizationId: string;\n projectId: string | null;\n actingUserId: string | null;\n refresh: boolean;\n}) => Promise<FinalPlatformAccessIface | null>;\n\n// The per-call form the API clients ask before each request.\nexport interface ComputersApiCredentialsIface {\n computersAccess(args?: { refresh?: boolean }): Promise<FinalPlatformAccessIface | null>;\n}\n\nexport interface DeviceApiCredentialsIface {\n deviceAccess(args?: {\n actingUserId?: string | null;\n refresh?: boolean;\n }): Promise<FinalPlatformAccessIface | null>;\n}\n\nexport const computersCredentialsFor = (\n access: ComputersAccessIface | undefined,\n scope: { organizationId: string; projectId: string },\n): ComputersApiCredentialsIface => ({\n computersAccess: async (args) =>\n access == null ? null : access({ ...scope, refresh: args?.refresh === true }),\n});\n\nexport const deviceCredentialsFor = (\n access: DeviceAccessIface | undefined,\n scope: { organizationId: string; projectId: string | null },\n): DeviceApiCredentialsIface => ({\n deviceAccess: async (args) =>\n access == null\n ? null\n : access({\n ...scope,\n actingUserId: args?.actingUserId ?? null,\n refresh: args?.refresh === true,\n }),\n});\n"],
5
- "mappings": "AAuCO,MAAM,0BAA0B,CACrC,QACA,WACkC;AAAA,EAClC,iBAAiB,OAAO,SACtB,UAAU,OAAO,OAAO,OAAO,EAAE,GAAG,OAAO,SAAS,MAAM,YAAY,KAAK,CAAC;AAChF;AAEO,MAAM,uBAAuB,CAClC,QACA,WAC+B;AAAA,EAC/B,cAAc,OAAO,SACnB,UAAU,OACN,OACA,OAAO;AAAA,IACL,GAAG;AAAA,IACH,cAAc,MAAM,gBAAgB;AAAA,IACpC,SAAS,MAAM,YAAY;AAAA,EAC7B,CAAC;AACT;",
6
- "names": []
7
- }
@@ -1,3 +0,0 @@
1
- export declare const workspaceEgressBootstrap: string;
2
- export declare const cachedWorkspaceEgressBootstrap: (sha256: string) => string;
3
- export declare const workspaceEgressLauncher: string;