mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
A sandbox is a new env type (`type: "gondolin"`): a long-lived Gondolin micro-VM on an SSH environment, with listed host folders and an HTTP(S) host allowlist. The worker reaches it over SSH to the host, where p2's launcher (plugins/envs/sandbox: native, or Docker when the host's Node/QEMU are too old) runs pi-env's daemon inside the VM, so tools, workspace, terminals, uploads and MCP work unchanged. Names outside the allowlist are refused before the host resolves them. Conversations working in a sandbox cannot leave it: location tools, subagent spawn/configure, AgentLocations.move, the terminal tool, and one-shot agents (which now inherit their caller's own location) all keep them there; only the owner's Relocate moves a session out, after a confirmation. conversationConfinement() exposes it to plugins. Owner-only management in the Envs sheet: sandboxes listed under their host, host test reports sandbox support, create/edit/start/stop/restart/test, lock icons in the top bar and sidebar. Tests in test/p2/sandboxes.test.ts (real VM cases with P2_TEST_GONDOLIN=1) and ui-sandboxes.test.ts; docs in docs/remote-envs.md.
50 lines
2.2 KiB
TypeScript
50 lines
2.2 KiB
TypeScript
import { arr, bool, int, obj, opt, type Static, str, union } from "@earendil-works/pi-schema";
|
|
import { literal, record } from "@p2/app/v1";
|
|
|
|
/** Browser-safe configuration shape. No credentials are kept in envs.json. */
|
|
export const ENV_NAME = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
|
|
/** A machine reached over SSH. */
|
|
export const SshEnvConfigSchema = obj({
|
|
type: literal("ssh"),
|
|
host: str({ minLength: 1, maxLength: 255 }),
|
|
user: opt(str({ minLength: 1, maxLength: 255 })),
|
|
port: opt(int({ minimum: 1, maximum: 65535 })),
|
|
identityFile: opt(str({ minLength: 1, maxLength: 4096 })),
|
|
shellPath: opt(str({ minLength: 1, maxLength: 4096 })),
|
|
loginShell: opt(bool()),
|
|
shellEnv: opt(record(str())),
|
|
});
|
|
|
|
/** A folder of the host shared with a sandbox at `guest`; `host` is absolute or starts with `~` (the host user's home). */
|
|
export const SandboxMountSchema = obj({
|
|
host: str({ minLength: 1, maxLength: 4096 }),
|
|
guest: str({ minLength: 2, maxLength: 4096 }),
|
|
readonly: opt(bool()),
|
|
});
|
|
|
|
/**
|
|
* A Gondolin sandbox: a VM on the SSH environment `host`. It sees only its folders and reaches only its allowed hosts
|
|
* over HTTP(S); conversations working in it cannot leave it.
|
|
*/
|
|
export const GondolinEnvConfigSchema = obj({
|
|
type: literal("gondolin"),
|
|
host: str({ minLength: 1, maxLength: 32 }),
|
|
image: opt(str({ minLength: 1, maxLength: 255 })),
|
|
mounts: opt(arr(SandboxMountSchema, { maxItems: 16 })),
|
|
allowHosts: opt(arr(str({ minLength: 1, maxLength: 255 }), { maxItems: 256 })),
|
|
cpus: opt(int({ minimum: 1, maximum: 64 })),
|
|
memoryMb: opt(int({ minimum: 256, maximum: 262144 })),
|
|
});
|
|
|
|
export const EnvConfigSchema = union([SshEnvConfigSchema, GondolinEnvConfigSchema]);
|
|
export type SshEnvConfig = Static<typeof SshEnvConfigSchema>;
|
|
export type GondolinEnvConfig = Static<typeof GondolinEnvConfigSchema>;
|
|
export type SandboxMount = Static<typeof SandboxMountSchema>;
|
|
export type EnvConfig = Static<typeof EnvConfigSchema>;
|
|
export type EnvsConfig = Record<string, EnvConfig>;
|
|
|
|
/** Whether `name` is a sandbox, decided by its configuration's type (never its name). */
|
|
export function isSandbox(envs: EnvsConfig, name: string): boolean {
|
|
return Object.hasOwn(envs, name) && envs[name]?.type === "gondolin";
|
|
}
|