evOo-local/packages/adapters/src/host-aware-sandbox.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

195 lines
5.5 KiB
TypeScript

import { homedir } from "node:os";
import type {
AdapterContext,
CommandRequest,
ComputerActionRequest,
ComputerInput,
ComputerRef,
ControlLeaseRef,
PortableFile,
ProcessEvent,
SandboxProvider,
ScreenRequest,
} from "@rakazo/adapter-kit";
import type { PrismaClient } from "@rakazo/db";
import { DesktopSandboxProvider } from "./desktop-sandbox.js";
import { createSandboxProvider, type SandboxProviderOptions } from "./sandbox-factory.js";
export function sandboxKindForBot(envKind: string, computerHost: string | null | undefined) {
if (envKind === "docker" && computerHost === "this-mac") return "desktop";
return envKind;
}
export function createRunSandbox(
kind: string,
opts: SandboxProviderOptions & { prisma?: PrismaClient },
): SandboxProvider {
if (kind === "desktop") {
return new DesktopSandboxProvider({
root: opts.dataDir,
hostRoots: [homedir()],
});
}
const primary = createSandboxProvider(kind, opts);
if (kind !== "docker" || !opts.prisma) return primary;
return new HostAwareSandbox(
primary,
new DesktopSandboxProvider({
root: opts.dataDir,
hostRoots: [homedir()],
}),
async () => {
const settings = await opts.prisma!.deploymentSettings.findUnique({
where: { id: "default" },
});
return settings?.computerHost === "this-mac";
},
);
}
export class HostAwareSandbox implements SandboxProvider {
readonly pageBrowser?: SandboxProvider["pageBrowser"];
constructor(
private readonly isolated: SandboxProvider,
private readonly host: SandboxProvider,
private readonly hostEnabled: () => Promise<boolean>,
) {
if (isolated.pageBrowser || host.pageBrowser) {
this.pageBrowser = (computer, request, context) => {
const provider = this.route(computer);
return provider.pageBrowser
? provider.pageBrowser(computer, request, context)
: Promise.resolve({
ok: false,
uncertain: false,
fallback: "computer_act",
error: "Page browser is unavailable on this computer.",
});
};
}
}
describe() {
return this.isolated.describe();
}
private route(computer: ComputerRef) {
return computer.kind === "desktop" ? this.host : this.isolated;
}
async provision(
request: {
botId: string;
homePath: string;
providerRef?: string;
providerKind?: ComputerRef["kind"];
},
context: AdapterContext,
) {
const provider = (await this.hostEnabled()) ? this.host : this.isolated;
const providerKind = provider.describe().id;
return provider.provision(
{
...request,
providerRef: request.providerKind === providerKind ? request.providerRef : undefined,
},
context,
);
}
prepare(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).prepare(computer, context);
}
async *execute(
computer: ComputerRef,
request: CommandRequest,
context: AdapterContext,
): AsyncIterable<ProcessEvent> {
yield* this.route(computer).execute(computer, request, context);
}
connectScreen(computer: ComputerRef, request: ScreenRequest, context: AdapterContext) {
return this.route(computer).connectScreen(computer, request, context);
}
sendInput(
computer: ComputerRef,
input: ComputerInput,
lease: ControlLeaseRef,
context: AdapterContext,
) {
return this.route(computer).sendInput(computer, input, lease, context);
}
observe(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).observe(computer, context);
}
act(computer: ComputerRef, request: ComputerActionRequest, context: AdapterContext) {
return this.route(computer).act(computer, request, context);
}
listFiles(computer: ComputerRef, path: string, context: AdapterContext) {
return this.route(computer).listFiles(computer, path, context);
}
readFile(
computer: ComputerRef,
path: string,
context: AdapterContext,
options?: { maxBytes?: number },
) {
return this.route(computer).readFile(computer, path, context, options);
}
writeFile(computer: ComputerRef, file: PortableFile, context: AdapterContext) {
return this.route(computer).writeFile(computer, file, context);
}
exportWorkspace(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).exportWorkspace(computer, context);
}
importWorkspace(
computer: ComputerRef,
files: AsyncIterable<PortableFile>,
context: AdapterContext,
) {
return this.route(computer).importWorkspace(computer, files, context);
}
snapshot(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).snapshot(computer, context);
}
keepAlive(computer: ComputerRef) {
return this.route(computer).keepAlive?.(computer) ?? Promise.resolve();
}
releaseScreen(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).releaseScreen?.(computer, context) ?? Promise.resolve();
}
setScreenControl(
computer: ComputerRef,
interactive: boolean,
context: AdapterContext,
controlToken?: string,
) {
return (
this.route(computer).setScreenControl?.(computer, interactive, context, controlToken) ??
Promise.resolve()
);
}
stop(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).stop(computer, context);
}
destroy(computer: ComputerRef, context: AdapterContext) {
return this.route(computer).destroy(computer, context);
}
}