Documentation
¶
Overview ¶
Package sandbox: session-scoped capability interfaces.
The Sandbox / Manager pair intentionally hides provider identity (Cube, E2B, Docker) from the application layer. Higher layers should never branch on Manager.GetType() to decide whether a feature is supported — that couples them to a specific backend.
Instead, session-scoped features (shell execution, per-session file inspection, attachment staging) are advertised via the capability interfaces below. A manager may satisfy the underlying methods yet still return nil from the accessors on SessionCapabilityProvider when the current runtime configuration cannot honour that capability.
Package sandbox: what makes an existing sandbox operable.
Two groups of configuration fields decide whether the sandboxes a config has already created can still be acted on. This file names them in one place because the answer depends on which SDK carries which value:
- Control plane (API URL, API key): every lifecycle call - list, delete, pause, resume, refresh timeout - is issued against it. Lose it and the sandboxes can no longer be reclaimed at all.
- Data plane (sandbox domain, and for Cube the proxy endpoint): envd traffic is routed through it. Lose it and the sandboxes are still deletable but no longer usable, so every live session on the config fails at once.
The split is a property of the clients in use, not a universal truth: github.com/matiasinsaurralde/go-e2b resolves the API base URL and the sandbox domain independently, whereas E2B's own JS/Python SDKs derive the API base from the domain (https://api.${domain}). Swapping that dependency would move the domain into the control-plane group.
Package sandbox: what a named backend config must carry on its own.
A named config is self-contained. Every value the provider client cannot synthesize has to be present in the workspace config itself, so this file names those values once and both save and resolve paths check against it.
Two classes are deliberately NOT required:
- Credentials a self-hosted deployment does not use. The common single-node Cube setup runs unauthenticated, so CubeAPIKey stays optional.
- Values the SDK resolves by itself. go-e2b defaults both the API base URL and the sandbox domain when they are left empty, so demanding them would force operators to spell out constants they cannot verify.
Everything else is required precisely because its absence fails late and obscurely: a missing Cube proxy URL or sandbox domain still creates a sandbox and only breaks when envd traffic is routed, which reads as a provider outage rather than a typo in the form.
Package sandbox: per-config sandbox inventory.
Identity changes (provider / endpoint / API key) and config deletion both need one question answered authoritatively: does this config still own live sandboxes on the provider?
The binding store cannot answer it. Bindings go missing exactly when leaks happen - a CAS rebind overwrites the old sandbox ID, Redis evicts, a process restarts - so trusting them under-reports in the one direction that costs money. The provider's own listing is the source of truth.
Package sandbox: Cube adapter for the provider-neutral RemoteSandboxClient.
CubeRemoteClient implements RemoteSandboxClient on top of the Cube SDK and envd transport. Callers speak RemoteSandboxClient; Cube-specific types, HTTP status codes, and workarounds never leak past this file — every return value is either a neutral DTO or a RemoteError with a stable Kind.
Package sandbox: interactive terminal capability for the Cube adapter.
The Cube SDK wraps envd's PTY service (sandbox.Pty) and hands back a PtyHandle with an Output channel, so the adapter's job is mostly translating between the neutral RemoteTerminal* types and the SDK's.
Package sandbox provides graphical desktop (VNC over WebSocket) capability.
The desktop is an optional, provider-neutral capability alongside RemoteTerminalManager. It differs from the terminal in one structural way: the terminal rides envd's PTY service, while the desktop is a raw WebSocket to a non-envd port (websockify on 6080) routed through the same gateway. That is why it needs its own capability bit and its own dialer.
Package sandbox provides the shell fed to the desktop sandbox over Exec.
These live as real .sh files rather than Go string literals so they keep shell tooling (shellcheck, `sh -n`, an editor that knows the language) and so a diff reads as shell instead of as an escaped blob. They are sent as the command body rather than invoked by path on purpose: unlike DesktopStartScript they are not baked into the image, so they also reach sandboxes booted from an older desktop template.
Package sandbox: E2B adapter for the provider-neutral RemoteSandboxClient.
E2BRemoteClient wraps go-e2b and keeps provider-specific configuration, lifecycle, access-token, and filesystem details behind RemoteSandboxClient.
Package sandbox: interactive terminal capability for the E2B adapter.
go-e2b wraps envd's PTY service (sandbox.Pty). Output is delivered through a Wait-driven callback, so the adapter pumps it into a channel from a dedicated goroutine; input and resize are plain unary RPCs on the same service.
Package sandbox: envd protocol compatibility for the E2B data plane.
The sandbox-side daemon (envd) authenticates every data-plane call with HTTP Basic auth carrying the sandbox account name, and accepts file uploads only as multipart/form-data. github.com/matiasinsaurralde/go-e2b predates both: it sends the account in an X-User-ID header and POSTs file contents as a raw octet-stream body.
E2B Cloud's own gateway is lenient enough to hide the difference, so the gap only surfaces against other implementations of the protocol — the very backends WeKnora wants to support without carrying one adapter per vendor. Rather than fork the SDK, this transport rewrites the two data-plane details on the way out:
- it adds "Authorization: Basic base64(user:)" when the request carries no credentials of its own;
- it re-wraps a non-multipart /files upload as multipart/form-data.
Requests are recognised by path, not by host, so the shim works whether the data plane is reached directly or through a gateway (gateway_transport.go). Control-plane calls never use these paths and pass through untouched.
Package sandbox: connection pooling and data-plane routing for per-request remote clients.
Named configs build a fresh client on every Resolve, so without an externally owned transport every request would open new TCP connections to both the control plane and the envd data plane.
Remote backends speak two planes with different dialling rules:
- control plane (Create/Connect/List) talks to the API URL directly;
- data plane (exec, filesystem) addresses sandboxes as "49983-{id}.{domain}", which E2B Cloud resolves through public DNS and TLS. Self-hosted E2B-compatible control planes (CubeSandbox's CubeProxy, Agent-Sandbox's gateway, e2b-dev/infra's client proxy) instead front every sandbox with one gateway address and route on the Host header.
Handing an SDK one http.Client for both planes drops that distinction, which only appears to work when DNS happens to resolve the sandbox domain to the gateway on the same port. This file keeps the two planes apart by routing per request: control traffic rides the process-wide transport shared by every backend, data traffic rides a transport cached per gateway endpoint so configs pointing at the same gateway share one pool.
The gateway may also be plain HTTP. Both SDKs pin the data-plane scheme to https, so a http:// gateway URL additionally rewrites the request scheme rather than forcing operators to terminate TLS in front of a local cluster.
Package sandbox: orphan sandbox reconciliation.
Session sandboxes are created with onTimeout=pause + autoResume (see buildSessionCreateRequest), so the provider TTL PAUSES them rather than destroying them. When a binding is replaced — the CAS that happens when a workspace switches provider — or lost (Redis TTL, process restart), the old sandbox ID disappears from our records while the sandbox itself lingers in a paused state, consuming snapshot storage and money.
Neither existing cleanup path covers that: session deletion works from a live binding, and the lifecycle's orphan cleanup is lazy, triggering only when someone touches the binding that has already been overwritten.
This reconciliation loop closes the gap by enumerating the provider's sandboxes via the tenant metadata every sandbox is tagged with, comparing against the authoritative bindings, and deleting what is unbound. Paused sandboxes MUST be included: they are precisely the leaked ones.
Package sandbox: provider-neutral remote sandbox contract.
This file introduces the RemoteSandboxClient interface and the neutral data-transfer types that SessionBoundManager depends on. Concrete backends (Cube, E2B, Docker) each provide an implementation in a separate adapter file.
The interface is deliberately minimal: it covers only the operations SessionBoundManager and RemoteSandbox use in production today. Optional provider capabilities (pause/resume, timeout refresh, metadata recovery, etc.) are exposed as RemoteSandboxCapabilities so higher layers can degrade gracefully instead of relying on backend-specific type assertions.
Package sandbox: provider-neutral error contract for RemoteSandboxClient.
Adapters normalize provider-native errors (HTTP status codes, SDK error types, gRPC codes) into RemoteError with a stable Kind so SessionBoundManager can make lifecycle decisions without knowing which backend is in use.
Rule of thumb for adapters:
- NotFound / Terminal → binding may be replaced
- Timeout / Unavailable / Capacity / Conflict / Authentication → PRESERVE binding
- InvalidRequest / Unsupported → surface to caller; do not touch binding
- Internal → last resort; preserve binding (default-preserve on ambiguity)
The original cause is always retained via errors.Unwrap so callers can still inspect provider-specific detail when logging.
Package sandbox: provider-neutral remote Sandbox implementation.
RemoteSandbox is the concrete Sandbox that speaks any RemoteSandboxClient backend. It contains no Cube- or E2B-specific logic; every operation is expressed against the RemoteSandboxClient contract and a RemoteSandboxHandle.
SessionBoundManager uses it in two shapes:
- Ephemeral: one call to Execute allocates a sandbox, uploads the script, runs it, then deletes the sandbox regardless of success. This gives stateless-per-call semantics for callers without a SessionID.
- Persistent (session-bound): SessionBoundManager resolves the session's RemoteSandboxHandle through the lifecycle coordinator and hands it to RemoteSandbox.ExecuteOnHandle. The handle stays owned by the manager; RemoteSandbox never creates or deletes anything in this mode.
Package sandbox provides isolated execution environments for running untrusted scripts. It supports Docker containers and remote MicroVM backends (CubeSandbox, E2B).
Package sandbox: session-bound Manager.
SessionBoundManager keeps one persistent remote sandbox per tenant session. It delegates all provider-specific work to a RemoteSandboxClient adapter and treats the authoritative session→sandbox binding as external state that lives in SessionSandboxBindingStore (Redis in production, memory in tests and single-process deployments). This makes the manager provider-neutral (Cube , E2B) and multi-instance safe: two WeKnora processes concurrently servicing the same session never allocate duplicate sandboxes, and a restart never loses the session's remote resource.
Semantics:
- An Execute call with a non-empty ExecuteConfig.SessionID resolves the session's remote sandbox (creating it lazily) and runs the script on the resolved handle. All resolution goes through the lifecycle coordinator so create/recover/replace/delete are serialised by the distributed lifecycle lock.
- An Execute call with an empty SessionID falls through to a stateless RemoteSandbox, which allocates a fresh sandbox, runs the script, and tears the sandbox down after Execute returns.
- Cube and E2B reap idle sandboxes themselves. Docker has no provider TTL, so that backend runs its own idle sweep against activity-marker mtimes.
Package sandbox reads and writes the shell-job directory protocol.
Package sandbox: tenant sandbox configuration resolution.
ResolveEffectiveConfig turns one stored config into the *Config a manager is built from. A named config is self-contained: provider fields are never inherited from process configuration; they come from the workspace config or they are missing and the config is refused (see config_required.go).
Field-level inheritance was tried first and removed. It made the stored row an incomplete picture of where a sandbox actually lives, which broke three things at once: identity comparison had to resolve against the baseline to decide whether an edit stranded anything, editing .env silently re-pointed configs that had left fields blank without cordoning their live sandboxes, and a config whose provider differed from the deployment mode inherited built-in constants instead — quietly dialling 127.0.0.1.
What still comes from the baseline is deliberately narrow: the deployment's script execution timeout, which is an operational guardrail rather than part of a backend's identity. A nil tenant config is used only by low-level callers that explicitly request the supplied baseline.
Package sandbox: per-config sandbox manager resolution.
A manager is built for one (tenant, sandbox config) pair — a workspace holds several named configs and each agent selects one. Managers are built per request and deliberately NOT cached, mirroring modelService, which rebuilds model clients on every call (see internal/application/service/model.go).
Caching was considered and rejected after checking what SessionBoundManager actually holds:
- remoteSessionLifecycle has no mutex; create/recover serialization lives entirely in the shared binding store (Redis SET NX, or the in-memory store's own lock), and that store is injected, so it stays a singleton either way.
- SessionBoundManager.mu guards only the Cleanup idempotency flag.
- No handles are held across requests: Execute re-Connects every time.
That left the construction-time Health probe as the only real cost, which SkipHealthProbe removes. Connection reuse is preserved by sharing one http.Transport across tenants (Cube additionally routes its data plane through SandboxGatewayTransportPool; see gateway_transport.go). The upshot: no cache, no eviction, no invalidation plumbing, and a config change takes effect on the next request.
Package sandbox: interactive terminal (PTY) capability.
The terminal is an optional, provider-neutral capability alongside RemoteSnapshotManager. SessionBoundManager surfaces it only when the active backend implements PTY streaming; the WebSocket handler in the application layer bridges browser terminals to it.
Package sandbox: outbound URL guard for tenant-supplied endpoints.
Tenants configure their own sandbox control-plane URLs and the server dials them. Without a guard that is a Server-Side Request Forgery primitive: the most damaging target is the cloud metadata service (169.254.169.254), which hands out instance credentials, followed by any internal service reachable from the application host.
Defence is two-layered on purpose:
- Validate runs when a config is saved or probed, giving the admin an immediate, readable error.
- DialControl runs at connect time on every request. This is the layer that actually holds, because a hostname can resolve to a public address during validation and to 169.254.169.254 at dial time (DNS rebinding).
Self-hosted deployments complicate this, so private endpoints are an explicit field on each workspace config. Even when enabled, link-local ranges — including cloud metadata — stay blocked, and that holds for the IPv6 encodings that carry an IPv4 link-local address in their payload.
Package sandbox provides WebSocket dialling for sandbox data-plane ports.
This is the ws:// sibling of gateway_transport.go's RoundTripperFor, and it exists for the same reason: sandbox data-plane traffic is addressed as "{port}-{sandboxID}.{domain}" but must be dialled at the configured gateway, with the per-sandbox inbound token attached.
It is a POOL METHOD, never a package-level function taking cfg. The process runs two SandboxGatewayTransportPools with two InboundTokenRegistries (tenant_resolver.go), selected by cfg.AllowPrivateEndpoints. A free function holding only cfg cannot know which registry to read; reading the wrong one returns an empty token and the gateway answers 401 — a symptom identical to the replica-affinity failure, and nearly impossible to tell apart in production.
Index ¶
- Constants
- Variables
- func CanReplaceRemoteBinding(err error) bool
- func ClearDockerBackendEnabledOverride()
- func DesktopEnsureCmd() string
- func DesktopResetListenersCmd() string
- func DetectLocalDockerHost() string
- func DockerBackendEnabled() bool
- func DockerSkillImageOverride(tenantCfg *types.TenantSandboxConfig) string
- func EffectiveTemplateID(cfg *Config) string
- func EffectiveTerminalIdleDisconnect(d time.Duration) time.Duration
- func EnsureDockerBackendAllowed(t SandboxType) error
- func GuardedDialContext(policy OutboundURLPolicy) func(context.Context, string, string) (net.Conn, error)
- func InboundTokenOf(handle RemoteSandboxHandle) string
- func InterpreterSkillDir(remotePath, skillDir string) (string, bool)
- func IsDesktopEnsureUnsupported(result *ExecuteResult) bool
- func IsHostSkillTarget(configID string) bool
- func IsNamedSandboxBackendType(raw string) bool
- func IsRemoteConflict(err error) bool
- func IsRemoteDirAlreadyExists(err error) bool
- func IsRemoteInvalidRequest(err error) bool
- func IsRemoteNotFound(err error) bool
- func IsRemoteTimeout(err error) bool
- func IsTemplateBuildFailed(status string) bool
- func IsTemplateReady(status string) bool
- func IsValidSkillName(name string) bool
- func MetadataAllocationKey() string
- func MetadataSessionIDKey() string
- func MissingRequiredFields(cfg *Config) []string
- func NewEnvdCompatTransport(next http.RoundTripper, user string) http.RoundTripper
- func NewGuardedTransport() *http.Transport
- func NewGuardedTransportWithPolicy(policy OutboundURLPolicy) *http.Transport
- func NewShellJobID() (string, error)
- func NormalizeConfigID(configID string) string
- func NormalizeCubeDNSServers(raw []string) ([]string, error)
- func ParseDesktopEnsureSecret(stdout string) (string, bool)
- func ParseShellJobEpilogue(epilogue string) (ShellJobStatus, ShellJobOffsets, bool)
- func PromptSafePath(p string) string
- func ReapOrphanSandboxes(ctx context.Context, deps OrphanReaperDeps) (int, error)
- func ReleaseConfigSandboxes(ctx context.Context, client ConfigSandboxClient, tenantID uint64, ...) (int, error)
- func RemoteErrorDiagnostics(err error) string
- func RequireCompleteConfig(cfg *Config) error
- func ResolveWorkspacePathIn(l WorkspaceLayout, value string) string
- func RunnableWorkspaceScript(scriptPath string) (string, bool)
- func RunnableWorkspaceScriptIn(l WorkspaceLayout, scriptPath string) (string, bool)
- func SafeDialControl(network string, address string, conn syscall.RawConn) error
- func SafeDialControlForPolicy(policy OutboundURLPolicy) func(string, string, syscall.RawConn) error
- func SetDockerBackendEnabled(enabled bool)
- func ShellJobBatchReadScript(sandboxID string, ids []string) string
- func ShellJobBatchReadScriptFor(sandboxID string, ids []string, from map[string]ShellJobOffsets) string
- func ShellJobBatchReadScriptWithBudget(sandboxID string, ids []string, from map[string]ShellJobOffsets, budget int) string
- func ShellJobCancelScript(meta ShellJobMeta, force bool) string
- func ShellJobKillOrphansScript(pgid, touchPID int) string
- func ShellJobSubmitScript(job ShellJobMeta, execCommand string, yieldMS int) (string, string)
- func ShellJobSubmitScriptWithBackend(job ShellJobMeta, execCommand string, yieldMS int, docker bool) (string, string)
- func ShellQuote(s string) string
- func SkillCommandPath(dir string) string
- func SkillDirFor(skillName string) (string, error)
- func SkillDirForImageScript(scriptPath string) (string, bool)
- func SkillDirUnder(root, skillName string) (string, error)
- func SkillImageActive(tenantCfg *types.TenantSandboxConfig) bool
- func SkillImageFingerprint(provider, apiKey, apiURL string) string
- func SkillInterpreterCommand(skillDir, scriptPath string) (string, []string)
- func SkillNameFromImagePath(p string) (name string, inImage bool)
- func SkillOwnerFingerprint(tenantCfg *types.TenantSandboxConfig) string
- func SkillRequirementsPath(skillName string) string
- func SkillRequirementsPathIn(skillDir string) string
- func SkillVenvPython(skillDir string) string
- func StateMatches(candidate RemoteSandboxState, allowed []RemoteSandboxState) bool
- func ValidShellJobID(id string) bool
- func ValidateConfig(config *Config) error
- func ValidateDockerHost(host string, allowPrivate bool) error
- func ValidateDockerNetworkMode(mode string) error
- func ValidateDockerRemoteTLS(host, tlsCertPath string) error
- func ValidateOutboundURL(raw string) error
- func ValidateOutboundURLWithPolicy(raw string, policy OutboundURLPolicy) error
- func ValidatedImageSkillDir(skillDir string) (string, bool)
- func ValidatedOutputDirIn(l WorkspaceLayout, dir string) (string, bool)
- func ValidatedSessionOutputDir(dir string) (string, bool)
- func ValidatedSkillDirUnder(root, skillDir string) (string, bool)
- func WithCommandApproval(ctx context.Context, approval CommandApproval) context.Context
- func WithCommandOutput(ctx context.Context, callback func(string, []byte)) context.Context
- func WithSessionFileOperation(ctx context.Context) context.Context
- func WriteShellJobJSON(path string, value any) error
- type AllocationDirectory
- type CommandApproval
- type CommandApprovalDecision
- type CommandApprovalRequest
- type CommandApprover
- type Config
- type ConfigSandboxClient
- type ConfigSandboxLister
- type CubeRemoteClient
- func (c *CubeRemoteClient) Capabilities() RemoteSandboxCapabilities
- func (c *CubeRemoteClient) Connect(ctx context.Context, request RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *CubeRemoteClient) ConnectSession(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *CubeRemoteClient) Create(ctx context.Context, request RemoteCreateRequest) (RemoteSandboxHandle, error)
- func (c *CubeRemoteClient) CreateSnapshot(ctx context.Context, sandboxID string, name string) (RemoteSnapshotRef, error)
- func (c *CubeRemoteClient) Delete(ctx context.Context, sandboxID string) error
- func (c *CubeRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
- func (c *CubeRemoteClient) DeleteSupersededDesktopTemplates(ctx context.Context, keepID string) error
- func (c *CubeRemoteClient) DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
- func (c *CubeRemoteClient) DialDesktop(ctx context.Context, handle RemoteSandboxHandle, opts RemoteDesktopOptions) (*websocket.Conn, error)
- func (c *CubeRemoteClient) EnsureDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *CubeRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *CubeRemoteClient) Exec(ctx context.Context, handle RemoteSandboxHandle, request RemoteExecRequest) (*RemoteExecResult, error)
- func (c *CubeRemoteClient) Get(ctx context.Context, sandboxID string) (*RemoteSandboxSummary, error)
- func (c *CubeRemoteClient) Health(ctx context.Context) error
- func (c *CubeRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, ttl time.Duration) error
- func (c *CubeRemoteClient) List(ctx context.Context, filter RemoteListFilter) ([]RemoteSandboxSummary, error)
- func (c *CubeRemoteClient) ListDir(ctx context.Context, handle RemoteSandboxHandle, path string) ([]RemoteDirEntry, error)
- func (c *CubeRemoteClient) ListSnapshots(ctx context.Context, sandboxID string) ([]RemoteSnapshotRef, error)
- func (c *CubeRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
- func (c *CubeRemoteClient) MakeDir(ctx context.Context, handle RemoteSandboxHandle, dir string) error
- func (c *CubeRemoteClient) OpenTerminal(ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
- func (c *CubeRemoteClient) Provider() RemoteProvider
- func (c *CubeRemoteClient) ReadFile(ctx context.Context, handle RemoteSandboxHandle, path string) ([]byte, error)
- func (c *CubeRemoteClient) Remove(ctx context.Context, handle RemoteSandboxHandle, path string) error
- func (c *CubeRemoteClient) ReplaceDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *CubeRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *CubeRemoteClient) StartDesktopTTLRefresh(ctx context.Context, handle RemoteSandboxHandle)
- func (c *CubeRemoteClient) Stat(ctx context.Context, handle RemoteSandboxHandle, path string) (*RemoteStatEntry, error)
- func (c *CubeRemoteClient) WriteFile(ctx context.Context, handle RemoteSandboxHandle, path string, content []byte) error
- type DefaultManager
- type DockerRemoteClient
- func (c *DockerRemoteClient) Capabilities() RemoteSandboxCapabilities
- func (c *DockerRemoteClient) Connect(ctx context.Context, request RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *DockerRemoteClient) ConnectSession(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *DockerRemoteClient) Create(ctx context.Context, req RemoteCreateRequest) (RemoteSandboxHandle, error)
- func (c *DockerRemoteClient) CreateForkSnapshot(ctx context.Context, sandboxID string, name string) (RemoteSnapshotRef, error)
- func (c *DockerRemoteClient) CreateSnapshot(ctx context.Context, sandboxID string, name string) (RemoteSnapshotRef, error)
- func (c *DockerRemoteClient) Delete(ctx context.Context, sandboxID string) error
- func (c *DockerRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
- func (c *DockerRemoteClient) DeleteSupersededStandardTemplates(context.Context, string) error
- func (c *DockerRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *DockerRemoteClient) Exec(ctx context.Context, handle RemoteSandboxHandle, req RemoteExecRequest) (*RemoteExecResult, error)
- func (c *DockerRemoteClient) Get(ctx context.Context, sandboxID string) (*RemoteSandboxSummary, error)
- func (c *DockerRemoteClient) Health(ctx context.Context) error
- func (c *DockerRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, _ time.Duration) error
- func (c *DockerRemoteClient) List(ctx context.Context, filter RemoteListFilter) ([]RemoteSandboxSummary, error)
- func (c *DockerRemoteClient) ListDir(ctx context.Context, handle RemoteSandboxHandle, dirPath string) ([]RemoteDirEntry, error)
- func (c *DockerRemoteClient) ListSnapshots(ctx context.Context, sandboxID string) ([]RemoteSnapshotRef, error)
- func (c *DockerRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
- func (c *DockerRemoteClient) MakeDir(ctx context.Context, handle RemoteSandboxHandle, dirPath string) error
- func (c *DockerRemoteClient) OpenTerminal(ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
- func (c *DockerRemoteClient) Provider() RemoteProvider
- func (c *DockerRemoteClient) ReadFile(ctx context.Context, handle RemoteSandboxHandle, filePath string) ([]byte, error)
- func (c *DockerRemoteClient) Remove(ctx context.Context, handle RemoteSandboxHandle, targetPath string) error
- func (c *DockerRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *DockerRemoteClient) Stat(ctx context.Context, handle RemoteSandboxHandle, filePath string) (*RemoteStatEntry, error)
- func (c *DockerRemoteClient) WriteFile(ctx context.Context, handle RemoteSandboxHandle, filePath string, ...) error
- type E2BRemoteClient
- func (c *E2BRemoteClient) Capabilities() RemoteSandboxCapabilities
- func (c *E2BRemoteClient) Connect(ctx context.Context, request RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *E2BRemoteClient) ConnectSession(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
- func (c *E2BRemoteClient) Create(ctx context.Context, request RemoteCreateRequest) (RemoteSandboxHandle, error)
- func (c *E2BRemoteClient) CreateSnapshot(ctx context.Context, sandboxID string, name string) (RemoteSnapshotRef, error)
- func (c *E2BRemoteClient) Delete(ctx context.Context, sandboxID string) error
- func (c *E2BRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
- func (c *E2BRemoteClient) DeleteSupersededDesktopTemplates(ctx context.Context, keepID string) error
- func (c *E2BRemoteClient) DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
- func (c *E2BRemoteClient) DialDesktop(ctx context.Context, handle RemoteSandboxHandle, opts RemoteDesktopOptions) (*websocket.Conn, error)
- func (c *E2BRemoteClient) EnsureDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *E2BRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *E2BRemoteClient) Exec(ctx context.Context, handle RemoteSandboxHandle, request RemoteExecRequest) (*RemoteExecResult, error)
- func (c *E2BRemoteClient) Get(ctx context.Context, sandboxID string) (*RemoteSandboxSummary, error)
- func (c *E2BRemoteClient) Health(ctx context.Context) error
- func (c *E2BRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, ttl time.Duration) error
- func (c *E2BRemoteClient) List(ctx context.Context, filter RemoteListFilter) ([]RemoteSandboxSummary, error)
- func (c *E2BRemoteClient) ListDir(ctx context.Context, handle RemoteSandboxHandle, path string) ([]RemoteDirEntry, error)
- func (c *E2BRemoteClient) ListSnapshots(ctx context.Context, sandboxID string) ([]RemoteSnapshotRef, error)
- func (c *E2BRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
- func (c *E2BRemoteClient) MakeDir(ctx context.Context, handle RemoteSandboxHandle, dir string) error
- func (c *E2BRemoteClient) OpenTerminal(ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
- func (c *E2BRemoteClient) Provider() RemoteProvider
- func (c *E2BRemoteClient) ReadFile(ctx context.Context, handle RemoteSandboxHandle, path string) ([]byte, error)
- func (c *E2BRemoteClient) Remove(ctx context.Context, handle RemoteSandboxHandle, path string) error
- func (c *E2BRemoteClient) ReplaceDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *E2BRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
- func (c *E2BRemoteClient) StartDesktopTTLRefresh(ctx context.Context, handle RemoteSandboxHandle)
- func (c *E2BRemoteClient) Stat(ctx context.Context, handle RemoteSandboxHandle, path string) (*RemoteStatEntry, error)
- func (c *E2BRemoteClient) WriteFile(ctx context.Context, handle RemoteSandboxHandle, path string, content []byte) error
- type ExecuteConfig
- type ExecuteResult
- type InboundTokenRegistry
- type Manager
- type MemoryActivityRegistry
- func (r *MemoryActivityRegistry) Add(ctx context.Context, s interfaces.SessionRef, a interfaces.BackgroundActivity, ...) error
- func (r *MemoryActivityRegistry) List(ctx context.Context, s interfaces.SessionRef) ([]interfaces.BackgroundActivity, error)
- func (r *MemoryActivityRegistry) Remove(ctx context.Context, s interfaces.SessionRef, id string) error
- func (r *MemoryActivityRegistry) Renew(ctx context.Context, s interfaces.SessionRef, id string, lease time.Duration) (bool, error)
- type MemorySessionSandboxBindingStore
- func (s *MemorySessionSandboxBindingStore) BeginTurn(ctx context.Context, key SessionSandboxKey) error
- func (s *MemorySessionSandboxBindingStore) ConsumeTurnRebuild(ctx context.Context, key SessionSandboxKey) error
- func (s *MemorySessionSandboxBindingStore) Create(ctx context.Context, key SessionSandboxKey, binding SessionSandboxBinding) (bool, error)
- func (s *MemorySessionSandboxBindingStore) DeleteIfMatch(ctx context.Context, key SessionSandboxKey, provider RemoteProvider, ...) (bool, error)
- func (s *MemorySessionSandboxBindingStore) EndTurn(_ context.Context, key SessionSandboxKey) error
- func (s *MemorySessionSandboxBindingStore) Get(ctx context.Context, key SessionSandboxKey) (*SessionSandboxBinding, error)
- func (s *MemorySessionSandboxBindingStore) HasRewindLock(ctx context.Context, key SessionSandboxKey) (bool, error)
- func (s *MemorySessionSandboxBindingStore) InvalidateByConfig(ctx context.Context, tenantID uint64, configID string) (int, error)
- func (s *MemorySessionSandboxBindingStore) ReplaceTrafficTokenIfMatch(ctx context.Context, key SessionSandboxKey, expected SessionSandboxBinding, ...) (bool, error)
- func (s *MemorySessionSandboxBindingStore) TryLockRewind(ctx context.Context, key SessionSandboxKey) (func(), error)
- func (s *MemorySessionSandboxBindingStore) TurnState(ctx context.Context, key SessionSandboxKey) (bool, bool, error)
- func (s *MemorySessionSandboxBindingStore) WithLifecycleLock(ctx context.Context, key SessionSandboxKey, fn func(context.Context) error) error
- type OrphanReaperDeps
- type OutboundURLPolicy
- type OutputFileStamp
- type PermissiveSessionExistenceChecker
- type RedisActivityRegistry
- func (r *RedisActivityRegistry) Add(ctx context.Context, s interfaces.SessionRef, a interfaces.BackgroundActivity, ...) error
- func (r *RedisActivityRegistry) List(ctx context.Context, s interfaces.SessionRef) ([]interfaces.BackgroundActivity, error)
- func (r *RedisActivityRegistry) Remove(ctx context.Context, s interfaces.SessionRef, id string) error
- func (r *RedisActivityRegistry) Renew(ctx context.Context, s interfaces.SessionRef, id string, lease time.Duration) (bool, error)
- func (r *RedisActivityRegistry) WithAllocations(allocations AllocationDirectory) *RedisActivityRegistry
- type RedisSessionSandboxBindingStore
- func (s *RedisSessionSandboxBindingStore) BeginTurn(ctx context.Context, key SessionSandboxKey) error
- func (s *RedisSessionSandboxBindingStore) ConsumeTurnRebuild(ctx context.Context, key SessionSandboxKey) error
- func (s *RedisSessionSandboxBindingStore) Create(ctx context.Context, key SessionSandboxKey, binding SessionSandboxBinding) (bool, error)
- func (s *RedisSessionSandboxBindingStore) DeleteIfMatch(ctx context.Context, key SessionSandboxKey, provider RemoteProvider, ...) (bool, error)
- func (s *RedisSessionSandboxBindingStore) EndTurn(ctx context.Context, key SessionSandboxKey) error
- func (s *RedisSessionSandboxBindingStore) Get(ctx context.Context, key SessionSandboxKey) (*SessionSandboxBinding, error)
- func (s *RedisSessionSandboxBindingStore) HasRewindLock(ctx context.Context, key SessionSandboxKey) (bool, error)
- func (s *RedisSessionSandboxBindingStore) InvalidateByConfig(ctx context.Context, tenantID uint64, configID string) (int, error)
- func (s *RedisSessionSandboxBindingStore) ReplaceTrafficTokenIfMatch(ctx context.Context, key SessionSandboxKey, expected SessionSandboxBinding, ...) (bool, error)
- func (s *RedisSessionSandboxBindingStore) TryLockRewind(ctx context.Context, key SessionSandboxKey) (func(), error)
- func (s *RedisSessionSandboxBindingStore) TurnState(ctx context.Context, key SessionSandboxKey) (bool, bool, error)
- func (s *RedisSessionSandboxBindingStore) WithLifecycleLock(ctx context.Context, key SessionSandboxKey, fn func(context.Context) error) error
- type RemoteConnectRequest
- type RemoteCreateRequest
- type RemoteCubeEgressRule
- type RemoteDesktopManager
- type RemoteDesktopOptions
- type RemoteDesktopTTLRefresher
- type RemoteDesktopTemplateCatalog
- type RemoteDirEntry
- type RemoteDirEntryType
- type RemoteE2BHostRule
- type RemoteError
- type RemoteErrorKind
- type RemoteExecRequest
- type RemoteExecResult
- type RemoteHeaderInject
- type RemoteInboundTokenCarrier
- type RemoteListFilter
- type RemoteNetworkPolicy
- type RemoteProvider
- type RemoteSandbox
- func (s *RemoteSandbox) Cleanup(context.Context) error
- func (s *RemoteSandbox) Execute(ctx context.Context, cfg *ExecuteConfig) (*ExecuteResult, error)
- func (s *RemoteSandbox) ExecuteOnHandle(ctx context.Context, handle RemoteSandboxHandle, cfg *ExecuteConfig) (*ExecuteResult, error)
- func (s *RemoteSandbox) ExecuteOnHandleIn(ctx context.Context, handle RemoteSandboxHandle, cfg *ExecuteConfig, ...) (*ExecuteResult, error)
- func (s *RemoteSandbox) IsAvailable(ctx context.Context) bool
- func (s *RemoteSandbox) Type() SandboxType
- type RemoteSandboxCapabilities
- type RemoteSandboxClient
- type RemoteSandboxHandle
- type RemoteSandboxKeepAliver
- type RemoteSandboxState
- type RemoteSandboxSummary
- type RemoteSessionConnector
- type RemoteSnapshotManager
- type RemoteSnapshotRef
- type RemoteStatEntry
- type RemoteTemplate
- type RemoteTemplateCatalog
- type RemoteTerminalEvent
- type RemoteTerminalManager
- type RemoteTerminalOptions
- type RemoteTerminalSession
- type RemoteTimeoutAction
- type RemoteTimeoutMode
- type RemoteTimeoutPolicy
- type RemoteVolumeMount
- type ResolvedTenantSandboxConfig
- type Sandbox
- type SandboxGatewayTransportPool
- type SandboxIdentity
- type SandboxType
- type ScriptValidator
- func (v *ScriptValidator) ValidateAll(scriptContent string, args []string, stdin string) *ValidationResult
- func (v *ScriptValidator) ValidateArgs(args []string) *ValidationResult
- func (v *ScriptValidator) ValidateScript(content string) *ValidationResult
- func (v *ScriptValidator) ValidateStdin(stdin string) *ValidationResult
- type SessionBootstrapper
- type SessionBootstrapperWithClient
- type SessionBoundManager
- func (m *SessionBoundManager) AcquireSessionExclusive(ctx context.Context, sessionID string) (context.Context, func(), error)
- func (m *SessionBoundManager) BeginSessionTurn(ctx context.Context, sessionID string) (context.Context, func(), error)
- func (m *SessionBoundManager) BeginWorkspaceFork(ctx context.Context, sourceSessionID string) (WorkspaceFork, error)
- func (m *SessionBoundManager) BoundSandboxID(ctx context.Context, sessionID string) (string, bool)
- func (m *SessionBoundManager) Cleanup(_ context.Context) error
- func (m *SessionBoundManager) CreateForkSnapshot(ctx context.Context, sessionID, name string) (string, error)
- func (m *SessionBoundManager) CreateSnapshot(ctx context.Context, sessionID string, name string) (RemoteSnapshotRef, error)
- func (m *SessionBoundManager) DeleteForkSnapshot(ctx context.Context, _, snapshotID string) error
- func (m *SessionBoundManager) DeleteSessionWorkspaces(ctx context.Context, machine allocation.UserMachine, sessionIDs []string) ([]string, error)
- func (m *SessionBoundManager) DeleteSnapshot(ctx context.Context, snapshotID string) error
- func (m *SessionBoundManager) DesktopMachine(ctx context.Context, sessionID string) (string, string, error)
- func (m *SessionBoundManager) DestroySession(ctx context.Context, sessionID string) error
- func (m *SessionBoundManager) EnsureSessionDir(ctx context.Context, sessionID, dir string) error
- func (m *SessionBoundManager) ExecShellCommand(ctx context.Context, sessionID string, command string, workDir string, ...) (*ExecuteResult, error)
- func (m *SessionBoundManager) ExecShellCommandWithOptions(ctx context.Context, sessionID string, command string, opts ShellExecOptions) (*ExecuteResult, error)
- func (m *SessionBoundManager) ExecShellCommandWithOutputSnapshot(ctx context.Context, sessionID, command string, opts ShellExecOptions, ...) (*ExecuteResult, *ShellOutputSnapshot, error)
- func (m *SessionBoundManager) Execute(ctx context.Context, cfg *ExecuteConfig) (*ExecuteResult, error)
- func (m *SessionBoundManager) GetSandbox() Sandbox
- func (m *SessionBoundManager) GetType() SandboxType
- func (m *SessionBoundManager) HasActiveTurn(ctx context.Context, sessionID string) (bool, error)
- func (m *SessionBoundManager) HasRewindLock(ctx context.Context, sessionID string) (bool, error)
- func (m *SessionBoundManager) HoldSessionOperation(ctx context.Context, sessionID, kind string) (context.Context, func(), error)
- func (m *SessionBoundManager) InvalidateConfigSandboxes(ctx context.Context, tenantID uint64, configID string) (int, error)
- func (m *SessionBoundManager) InvalidateConfigSandboxesForMachines(ctx context.Context, configID string, machines []allocation.UserMachine) (int, error)
- func (m *SessionBoundManager) KeepAliveSession(ctx context.Context, sessionID string) (time.Duration, error)
- func (m *SessionBoundManager) KeepAliveSessionExpected(ctx context.Context, sessionID, expectedSandboxID string) (time.Duration, error)
- func (m *SessionBoundManager) ListSessionFiles(ctx context.Context, sessionID, dir string) ([]RemoteDirEntry, error)
- func (m *SessionBoundManager) ListSnapshots(ctx context.Context, sandboxID string) ([]RemoteSnapshotRef, error)
- func (m *SessionBoundManager) LockRewindSession(ctx context.Context, sessionID string) (context.Context, func(), error)
- func (m *SessionBoundManager) Namespace() string
- func (m *SessionBoundManager) OpenSessionDesktop(ctx context.Context, sessionID string, opts RemoteDesktopOptions) (*SessionDesktopConn, error)
- func (m *SessionBoundManager) OpenSessionTerminal(ctx context.Context, sessionID string, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
- func (m *SessionBoundManager) ReadSessionFile(ctx context.Context, sessionID, filePath string) ([]byte, error)
- func (m *SessionBoundManager) ReclaimUserMachine(ctx context.Context, machine allocation.UserMachine, ...) (bool, error)
- func (m *SessionBoundManager) RemoveSessionInputPath(ctx context.Context, sessionID, targetPath string) error
- func (m *SessionBoundManager) RequireRunningSessionSandbox(ctx context.Context, sessionID string) error
- func (m *SessionBoundManager) SessionDesktopManager() SessionDesktopManager
- func (m *SessionBoundManager) SessionFileStore() SessionFileStore
- func (m *SessionBoundManager) SessionInstallShellExecutor() SessionInstallShellExecutor
- func (m *SessionBoundManager) SessionShellExecutor() SessionShellExecutor
- func (m *SessionBoundManager) SessionTerminalManager() SessionTerminalManager
- func (m *SessionBoundManager) SessionWorkspaceLayout(ctx context.Context, sessionID string) (WorkspaceLayout, error)
- func (m *SessionBoundManager) StatSessionFile(ctx context.Context, sessionID, filePath string) (*RemoteStatEntry, error)
- func (m *SessionBoundManager) TerminalIdleDisconnect() time.Duration
- func (m *SessionBoundManager) TryLockRewind(ctx context.Context, sessionID string) (func(), error)
- func (m *SessionBoundManager) UserMachineKey(machine allocation.UserMachine) SessionSandboxKey
- func (m *SessionBoundManager) WriteSessionFile(ctx context.Context, sessionID, filePath string, content []byte) error
- func (m *SessionBoundManager) WriteSessionInputFile(ctx context.Context, sessionID, filePath string, content []byte) error
- func (m *SessionBoundManager) WriteSessionWorkspaceFile(ctx context.Context, sessionID, filePath string, content []byte) error
- func (m *SessionBoundManager) WriteSessionWorkspaceFiles(ctx context.Context, sessionID string, files []SessionWorkspaceFile) error
- type SessionBoundManagerConfig
- type SessionCapabilityProvider
- type SessionCreateFailureHandler
- type SessionDesktopConn
- type SessionDesktopManager
- type SessionDesktopProvider
- type SessionDestroyer
- type SessionExistenceChecker
- type SessionFileReader
- type SessionFileStore
- type SessionInstallCapabilityProvider
- type SessionInstallShellExecutor
- type SessionSandboxBinding
- type SessionSandboxBindingStore
- type SessionSandboxKey
- type SessionShellExecutor
- type SessionStateReleaser
- type SessionTerminalManager
- type SessionTerminalProvider
- type SessionTurnHolder
- type SessionWorkspaceFile
- type SessionWorkspaceLayoutProvider
- type ShellExecOptions
- type ShellJobMarkerStream
- type ShellJobMeta
- type ShellJobOffsets
- type ShellJobRead
- type ShellJobSnapshot
- type ShellJobStatus
- type ShellJobStream
- type ShellOutputSnapshot
- type TenantSandboxConfigLoader
- type TenantSandboxResolver
- type TenantSandboxResolverDeps
- type ValidationError
- type ValidationResult
- type WebsocketDialer
- type WorkspaceFork
- type WorkspaceLayout
- type WorkspaceOrigin
Constants ¶
const ( DefaultDockerMemoryLimit int64 = 2 * 1024 * 1024 * 1024 DefaultDockerCPULimit = 2.0 DefaultDockerPidsLimit int64 = 512 )
DefaultDockerMemoryLimit / DefaultDockerCPULimit / DefaultDockerPidsLimit are the per-sandbox resource ceilings applied when a config names none. They are deliberately larger than the stateless backend's old 256MB/1CPU: a session container hosts a whole turn's work (package installs, data processing), not one short script.
const ( DefaultTimeout = 60 * time.Second DefaultMemoryLimit = 256 * 1024 * 1024 // 256MB DefaultCPULimit = 1.0 // 1 CPU core // DefaultDockerImage tracks main rather than latest. The latest tag only // moves when a version is released, so it still carries the image from // before /workspace and its input/output directories were handed to the // sandbox account — a sandbox built from it cannot write its own artifact // directory. Point this back at latest once a release ships that fix. DefaultDockerImage = "wechatopenai/weknora-sandbox:main" // DefaultCubeTemplateImage is the same environment with Cube's envd daemon // baked in (target "cube" of docker/Dockerfile.sandbox). // // Cube turns an OCI image into a template directly and gates the build on // GET :49983/health, which only envd answers. Building a Cube template from // DefaultDockerImage therefore always fails the probe with "connection // refused" — E2B gets away with that image because its own builder injects // envd, and the Docker backend never needs one. DefaultCubeTemplateImage = "wechatopenai/weknora-sandbox:main-cube" // DefaultDesktopDockerImage is the XFCE/x11vnc/websockify variant of // DefaultDockerImage (target "desktop" of docker/Dockerfile.sandbox). // E2B desktop templates are built from it. The Docker backend does not // consume this tag yet. DefaultDesktopDockerImage = "wechatopenai/weknora-sandbox:main-desktop" // DefaultCubeDesktopTemplateImage is DefaultDesktopDockerImage plus Cube // envd (target "desktop-cube"). DefaultCubeDesktopTemplateImage = "wechatopenai/weknora-sandbox:main-desktop-cube" // DesktopWebsockifyPort is websockify inside the sandbox. WeKnora dials // it through the provider gateway (Host "{port}-{id}.{domain}"), not by // publishing the port on the host NIC. x11vnc stays on 127.0.0.1:5900 // with no RFB password; the Basic-auth check lives on websockify. DesktopWebsockifyPort = 6080 // DesktopWebsockifyPath is websockify's WebSocket endpoint. The image // hosts no HTML UI, so GET / answers 405 by design. DesktopWebsockifyPath = "/websockify" // DesktopStartScript is the idempotent lazy-start entry point baked into // the desktop images. It is re-run on every desktop connect: that is what // heals a sandbox after pause/resume. DesktopStartScript = "/usr/local/bin/start-desktop.sh" // DesktopSecretPath holds the per-sandbox websockify Basic-auth password. // It is read by the backend and never sent to the browser. DesktopSecretPath = "/run/desktop/secret" // DesktopBasicAuthUser is the fixed username half of that credential. DesktopBasicAuthUser = "weknora" // CubeEnvdPort is the port envd listens on inside a Cube sandbox. It carries // the readiness probe as well as every exec and filesystem call, and the // data plane addresses sandboxes as "49983-{id}.{domain}". CubeEnvdPort = 49983 // CubeEnvdHealthPath is the envd endpoint Cube probes to decide whether a // template build succeeded. CubeEnvdHealthPath = "/health" // DefaultCubeAPIURL is retained for SDK tests and explicit local helpers; // workspace configs must still provide their endpoint. DefaultCubeAPIURL = "http://127.0.0.1:33000" // DefaultCubeProxyURL is the default CubeProxy endpoint (HTTP, port 80) used // to reach the in-sandbox envd via host-header routing. DefaultCubeProxyURL = "http://127.0.0.1:80" // DefaultCubeSandboxDomain is the sandbox routing domain configured on // CubeProxy (matches CUBE_API_SANDBOX_DOMAIN in the Cube deployment). DefaultCubeSandboxDomain = "cube.app" // DefaultCubeSandboxTTL is the Cube-side sandbox lifetime hint (in seconds) // requested at creation; the sandbox is torn down by CubeMaster if the // client goes silent for longer than this value. DefaultCubeSandboxTTL = 30 * time.Minute // DefaultCubeHTTPTimeout bounds a single HTTP call to the CubeAPI // (excluding user script execution which has its own per-call timeout). DefaultCubeHTTPTimeout = 30 * time.Second // DefaultE2BSandboxTTL matches the E2B SDK's built-in default so an // unset E2BSandboxTTL still yields a valid sandbox lifetime. DefaultE2BSandboxTTL = 5 * time.Minute // DefaultE2BHTTPTimeout bounds a single HTTP call to the E2B API. DefaultE2BHTTPTimeout = 30 * time.Second // DefaultE2BSandboxDomain is go-e2b's built-in sandbox routing domain. // Named E2B configs may omit sandbox_domain (config_required.go); envd // then uses this value, and DialDesktop must too. DefaultE2BSandboxDomain = "e2b.app" )
Default configuration values
const ( // SkillsImageRoot is where installed skills live inside the sandbox image. // It is outside /workspace on purpose: /workspace is per-session scratch // and is wiped before every snapshot. SkillsImageRoot = workspace.SkillsImageRoot // SkillsManifestPath lists what the image claims to contain. It is a // troubleshooting aid, never the source of truth for execution. SkillsManifestPath = SkillsImageRoot + "/.manifest.json" )
const ( // StandardTemplateName is the provider-side name of the CLI template. StandardTemplateName = "weknora" // DesktopTemplateName is the provider-side name of the desktop template. // It is a sibling of StandardTemplateName, not a replacement: a config // uses one or the other, decided by Config.DesktopEnabled. DesktopTemplateName = "weknora-desktop" )
const ( WorkspaceOriginUnspecified = workspace.OriginUnspecified WorkspaceOriginRemote = workspace.OriginRemote WorkspaceOriginHost = workspace.OriginHost )
Workspace origins re-exported for callers that do not import the subpackage.
const DefaultDockerHTTPTimeout = 30 * time.Second
DefaultDockerHTTPTimeout bounds a single short Engine API call (ping, create, inspect, list, exec create/inspect). Streaming and long storage calls — image pull, image remove with prune, commit, exec hijack, archive copy — use the caller's context instead, because a 30s deadline would kill them mid-body.
const DefaultDockerHost = "unix:///var/run/docker.sock"
DefaultDockerHost is the last-resort daemon endpoint when the config, the DOCKER_HOST environment variable, and the current docker CLI context are all empty. Linux installs typically expose this socket; macOS tools such as Colima do not.
const DefaultDockerIdleTTL = 30 * time.Minute
DefaultDockerIdleTTL is how long a session container may go without an exec before the idle sweep reclaims it. The daemon has no TTL of its own, so this is the only thing standing between an abandoned session and a container that lives until the host runs out of memory.
const DefaultE2BTemplateTag = "default"
DefaultE2BTemplateTag is the tag E2B resolves when a sandbox is created from a bare template name or ID. Builds must carry it to be spawnable at all.
const DefaultSandboxExecUser = "root"
DefaultSandboxExecUser is the account WeKnora runs sandboxed scripts as. It is root: every chat session gets its own sandbox, so the in-container account is not a tenant boundary and root is the least surprising default for an agent that installs packages and writes wherever it needs. The adapters resolve an empty request user to this constant, independent of the image USER. The image retains a "user" compatibility account for E2B/Cube tooling that explicitly selects it.
const DefaultTerminalIdleDisconnect = 15 * time.Minute
DefaultTerminalIdleDisconnect is how long an interactive terminal may sit without keystrokes or PTY output before WeKnora closes the WebSocket. Closing the socket stops TTL refresh so the sandbox can pause on its own provider timeout. 0 in a stored workspace config means this default.
const DesktopEnsureUnsupportedExit = 2
DesktopEnsureUnsupportedExit is ensure.sh when DesktopStartScript is missing. POSIX sh also exits 2 on syntax errors; pair with the stderr marker.
const DesktopEnsureUnsupportedMarker = "WEKNORA_DESKTOP_UNSUPPORTED"
DesktopEnsureUnsupportedMarker is written to stderr immediately before that exit 2. Go must see both or the failure is a broken wrapper, not a BYO image without a desktop.
const DockerBackendEnabledEnv = "WEKNORA_SANDBOX_DOCKER_ENABLED"
DockerBackendEnabledEnv is the process-level fallback for the Docker sandbox backend. System Settings (sandbox.docker_enabled) override it when a row has been pushed by SystemSettingService.
A workspace admin who can save a Docker config can create containers on whatever Engine API this process can reach — typically the host's docker.sock, which is host root. It is therefore off until a SystemAdmin or deployer opts in.
const DockerBackendEnabledSettingKey = "sandbox.docker_enabled"
DockerBackendEnabledSettingKey is the system_settings registry key.
const ( // DockerForkSnapshotRepo is the local image namespace for session-fork // snapshots. It is deliberately separate from dockerSkillSnapshotRepo: the // skill reaper prunes by the skill namespace and labels, and letting fork // snapshots share them would have each reaper collecting the other's // images. DockerForkSnapshotRepo = "weknora-fork" )
const HostSkillTargetID = "host"
HostSkillTargetID is the install target of Lite's local skills. It is a reserved literal rather than a sandbox_configs row: the host backend has no config to store, and a UUID can never collide with it.
const InboundTokenHeader = "e2b-traffic-access-token"
InboundTokenHeader is the header both providers accept. Cube additionally accepts "cube-traffic-access-token"; sending the E2B spelling covers both.
const MetadataAllocationUser = "user"
MetadataAllocationUser marks a shared user machine in provider metadata.
const SessionGitDir = workspace.GitDir
SessionGitDir is the git metadata directory for per-turn workspace checkpoints. It lives on the sandbox root filesystem so a fork snapshot still copies the object store, but outside SessionWorkspaceRoot so `rm -rf /workspace` (or an agent cleaning the work tree) cannot drop checkpoint history that rewind and fork later reset to.
Checkpoints used to live in SessionWorkspaceRoot/.git, and sandboxes provisioned before this constant existed still hold theirs there. The shared git preamble adopts that repository on first use so SHAs recorded before the move keep resolving; see gitWorkspaceAdoptLegacyRepo.
const SessionInputRoot = workspace.InputDir
SessionInputRoot is reserved for durable user attachments restored from file storage. Generated artifacts must remain under SessionOutputRoot.
const SessionOutputRoot = workspace.OutputDir
SessionOutputRoot is where skill scripts write artifacts for collection. The skills manager injects this path via skillOutputEnvVar; Execute materialises the directory via envd before the script runs so scripts do not depend on the template user being able to mkdir under /workspace.
const SessionSandboxBindingVersion = 1
SessionSandboxBindingVersion is the schema of a per-session machine binding.
const SessionWorkspaceRoot = workspace.Root
SessionWorkspaceRoot is the writable workspace root inside remote sandboxes. shell_exec work_dir must stay underneath this path.
const ShellJobDirExistsExit = 17
ShellJobDirExistsExit is the submit script's exit when the job directory is already there. The caller draws another id and tries again.
const ShellJobsRoot = "/var/lib/weknora/jobs"
ShellJobsRoot is where job directories live inside a remote sandbox, outside the /workspace git worktree so checkpoints never see them (shell 稿 §9.1).
const TemplateStatusUntagged = "untagged"
TemplateStatusUntagged marks a template whose builds finished but which has no build under the tag sandbox creation resolves. It looks identical to "still building" in the provider's template list, yet waiting will never help: the template needs a new build carrying the default tag.
const UserSandboxBindingVersion = 2
UserSandboxBindingVersion is the schema of a shared user machine binding.
Variables ¶
var ( // machine, so destroying its sandbox would destroy every sibling session's // workspace. Delete the session workspace instead. ErrSharedSandbox = errors.New("sandbox: session shares its owner's machine") // ErrSessionWorkspaceBusy is returned when the session's workspace is held // exclusively, or has operations that an exclusive request must not cut off. ErrSessionWorkspaceBusy = errors.New("sandbox: session workspace is busy") // ErrUserAllocationRejected is returned when a stored user-mode assignment's // owner identity is not, or is no longer, eligible to share a machine. ErrUserAllocationRejected = errors.New("sandbox: user-mode allocation rejected for this owner") )
var ( ErrSandboxDisabled = errors.New("sandbox is disabled") ErrTimeout = errors.New("execution timed out") ErrScriptNotFound = errors.New("script not found") ErrInvalidScript = errors.New("invalid script") ErrExecutionFailed = errors.New("script execution failed") ErrSecurityViolation = errors.New("security validation failed") ErrDangerousCommand = errors.New("script contains dangerous command") ErrArgInjection = errors.New("argument injection detected") ErrStdinInjection = errors.New("stdin injection detected") // ErrNoLiveSessionSandbox is returned by lookup-only entry points (the // interactive terminal) when the session has no currently bound sandbox. // Unlike Execute, these entry points never provision: creating a sandbox // needs the agent's config-pin context, which they do not carry. ErrNoLiveSessionSandbox = errors.New("session has no live sandbox") // ErrSandboxPaused is returned by lookup-only terminal and desktop // opens when the session has a bound sandbox that is not confirmed // running (paused, transitioning, or missing from the provider list). // Connect / Exec would resume a paused instance and start billing // again; the UI must get an explicit click first. Distinct from // ErrNoLiveSessionSandbox, which means there is no binding to resume. ErrSandboxPaused = errors.New("session sandbox is paused") // ErrSandboxStopped is ErrSandboxPaused for a backend that never pauses // (Docker): a bound sandbox that is not running is either a stopped // container, which a resume restarts with its files intact, or one the // idle sweep already deleted, which a resume replaces with a fresh one. // The UI cannot promise "wake it up" there. It wraps ErrSandboxPaused, so // every caller that only distinguishes "needs a confirmed click" keeps // working unchanged. ErrSandboxStopped = fmt.Errorf("%w: sandbox is stopped or was reclaimed", ErrSandboxPaused) // ErrSessionRewindLocked means another rewind already holds the session. ErrSessionRewindLocked = errors.New("sandbox: session rewind already in progress") // ErrSessionTurnActive means a chat turn already holds the session lease, // so rewind must not take the exclusive lock or reset the workspace. ErrSessionTurnActive = errors.New("sandbox: session turn is active") // ErrSessionWorkActive means background work is still live in the session, // so rewind must not take the exclusive lock or reset the workspace. ErrSessionWorkActive = errors.New("sandbox: session has live background work") // ErrTerminalUnsupported is returned when the active backend cannot // stream PTYs (Docker, a disabled manager). Distinct from // ErrNoLiveSessionSandbox: the session may well have a live sandbox, // it just cannot host an interactive terminal. ErrTerminalUnsupported = errors.New("sandbox backend does not support interactive terminals") )
Common errors
var ErrBoundSandboxChanged = errors.New("sandbox: bound sandbox changed before maintenance command")
ErrBoundSandboxChanged confirms that a maintenance command's original sandbox is gone. Transport failures must not be treated as this condition.
var ErrDesktopUnsupported = errors.New("sandbox: desktop is not supported by this backend")
ErrDesktopUnsupported is returned when the session's backend cannot relay a desktop, or the config's template is not a desktop image.
var ErrDockerBackendDisabled = errors.New(
"sandbox: docker backend is disabled; enable it in System Settings or set WEKNORA_SANDBOX_DOCKER_ENABLED=true",
)
ErrDockerBackendDisabled is returned when a Docker sandbox config is saved, probed, or resolved and the process has not opted in.
var ErrInvalidSkillName = errors.New("sandbox: invalid skill name")
ErrInvalidSkillName is returned when a skill name would escape SkillsImageRoot or is not a single path segment.
var ErrMachineAbsenceUnprovable = errors.New("sandbox: cannot prove the user machine is gone")
ErrMachineAbsenceUnprovable is returned when a user machine has no binding and the provider cannot list by metadata, so it may still exist. Directory cleanups keep their task instead of treating the machine as gone.
var ErrSandboxConfigCordoned = errors.New("sandbox: config is being updated")
ErrSandboxConfigCordoned means the config's identity fields are mid-change. It is transient by construction (the cordon is a short lease), so callers should surface it as retriable.
var ErrSandboxConfigIncomplete = errors.New("sandbox: config is missing required fields")
ErrSandboxConfigIncomplete marks a named config that cannot build a working client because required fields are empty. Callers map it onto 400.
var ErrSandboxConfigNotFound = errors.New("sandbox: config not found")
ErrSandboxConfigNotFound means the referenced config is gone (deleted, or never existed). Callers must surface it rather than degrade to the default backend: running an agent's scripts on a backend it was not pointed at is a silent, security-relevant substitution.
var ErrSandboxSessionDeleted = errors.New("sandbox session no longer exists")
ErrSandboxSessionDeleted reports that the owning WeKnora session no longer exists. Callers must not execute work with the returned nil handle.
var ErrUnsafeOutboundURL = errors.New("sandbox: unsafe outbound URL")
ErrUnsafeOutboundURL is returned for any endpoint that uses an unsupported scheme or resolves to an address the policy forbids.
var ErrUnsupportedSandboxType = errors.New("sandbox: unsupported sandbox type")
ErrUnsupportedSandboxType marks a sandbox type string we cannot honour. It is a sentinel so callers can classify it as bad input without matching on the message text.
Functions ¶
func CanReplaceRemoteBinding ¶
CanReplaceRemoteBinding reports whether the error proves that the bound remote sandbox is permanently gone. This is intentionally allow-list based: unknown and newly introduced errors preserve bindings by default.
func ClearDockerBackendEnabledOverride ¶
func ClearDockerBackendEnabledOverride()
ClearDockerBackendEnabledOverride restores env-only resolution. Tests that construct SystemSettingService can otherwise leak a preload push into later Setenv-based cases in the same package.
func DesktopEnsureCmd ¶
func DesktopEnsureCmd() string
DesktopEnsureCmd starts the desktop listeners (or takes the image script's fast path) and prints READY <secret> on stdout. Invoking DesktopStartScript by path hangs the Exec: envd waits for stdout/stderr to close, and the daemons keep those pipes. This wrapper points them at a log file.
func DesktopResetListenersCmd ¶
func DesktopResetListenersCmd() string
DesktopResetListenersCmd drops a half-started desktop so the next DesktopEnsureCmd run rebuilds it instead of taking its fast path.
func DetectLocalDockerHost ¶
func DetectLocalDockerHost() string
DetectLocalDockerHost returns the daemon endpoint the Docker CLI would use on this machine when the workspace config leaves the host blank: DOCKER_HOST first, then the current docker context, then DefaultDockerHost.
/var/run/docker.sock is the Linux default and is often absent on macOS (Colima, Docker Desktop, OrbStack each expose a socket under $HOME). An operator who can run `docker ps` should not have to copy that path into the settings form.
func DockerBackendEnabled ¶
func DockerBackendEnabled() bool
DockerBackendEnabled reports whether this process may run the Docker sandbox backend. Empty or unparsable env values are false.
func DockerSkillImageOverride ¶
func DockerSkillImageOverride(tenantCfg *types.TenantSandboxConfig) string
DockerSkillImageOverride returns the committed skill image the docker backend should boot instead of the config's base image, or "" to keep the base.
func EffectiveTemplateID ¶
EffectiveTemplateID returns the template the given provider will use.
func EffectiveTerminalIdleDisconnect ¶
EffectiveTerminalIdleDisconnect clamps a stored workspace value onto the range the terminal bridge will honour. 0 (unset) uses the built-in default so existing configs get an idle disconnect without a migration.
func EnsureDockerBackendAllowed ¶
func EnsureDockerBackendAllowed(t SandboxType) error
EnsureDockerBackendAllowed is the choke point for every path that would talk to a Docker daemon on behalf of a workspace config.
func GuardedDialContext ¶
func GuardedDialContext(policy OutboundURLPolicy) func(context.Context, string, string) (net.Conn, error)
GuardedDialContext is the DialContext every guarded transport uses: in whitelist-only mode a non-whitelisted hostname is refused before the dialer resolves it (#3378), and the policy's Control hook then judges the address the kernel is about to connect to.
func InboundTokenOf ¶
func InboundTokenOf(handle RemoteSandboxHandle) string
InboundTokenOf returns the inbound credential a handle carries, or "" when the provider has none. Callers persist it so a later reconnect can restore it.
func InterpreterSkillDir ¶
InterpreterSkillDir chooses the skill directory whose venv/node_modules should run remote. Image-skill scripts always win from the path so a mismatched SkillDir cannot redirect them. Workspace scripts require an explicit, validated SkillDir.
func IsDesktopEnsureUnsupported ¶
func IsDesktopEnsureUnsupported(result *ExecuteResult) bool
IsDesktopEnsureUnsupported is the BYO-image miss: exit 2 plus the marker. A bare 2 is a shell/wrapper failure and must not become ErrDesktopUnsupported.
func IsHostSkillTarget ¶
IsHostSkillTarget reports whether configID names Lite's local skill target.
func IsNamedSandboxBackendType ¶
IsNamedSandboxBackendType reports whether raw is a user-facing named sandbox backend. Host is absent because it has no session-scoped instance to pin: each command is a fresh local process. The same predicate is used by resolveSandboxForExecution.
func IsRemoteConflict ¶
IsRemoteConflict reports whether the provider refused the call because the resource is busy (concurrent mutation, snapshot still referenced, …).
func IsRemoteDirAlreadyExists ¶
IsRemoteDirAlreadyExists reports whether MakeDir failed because the directory is already present. Cube's envd MakeDir is not mkdir -p: a directory created by a previous call (or by a shell `mkdir -p` in resetSkillDir) comes back as an internal error instead of success, and seeding SKILL.md would otherwise abort on a directory it is supposed to write into.
func IsRemoteInvalidRequest ¶
IsRemoteInvalidRequest reports whether err classifies as a caller-side error (bad template, oversized payload, malformed path, etc.).
func IsRemoteNotFound ¶
IsRemoteNotFound reports whether err classifies as sandbox / path not found. Both NotFound and Terminal are treated as "gone" for coordinator decisions.
func IsRemoteTimeout ¶ added in v0.9.0
IsRemoteTimeout reports whether err classifies as a deadline the op exceeded (in-container kill wrapper, transport deadline). The caller did nothing wrong and the binding is still valid: such failures may be retried, and a transient provider stall usually clears on the next attempt.
func IsTemplateBuildFailed ¶
IsTemplateBuildFailed reports whether a template's build ended in a state no amount of waiting will improve. Such a template must be rebuilt rather than treated as an existing standard template.
func IsTemplateReady ¶
IsTemplateReady reports whether a template can spawn a sandbox. Rebuild replacements stay listed alongside the previous READY template until this is true, so sessions never lose a spawnable ID mid-build.
func IsValidSkillName ¶
IsValidSkillName reports whether name is a single directory segment under SkillsImageRoot. Empty names, "." / "..", and anything containing a path separator are rejected so install/exec cannot walk out of the skills root.
func MetadataAllocationKey ¶ added in v0.9.0
func MetadataAllocationKey() string
MetadataAllocationKey returns the provider metadata key for the allocation mode.
func MetadataSessionIDKey ¶
func MetadataSessionIDKey() string
MetadataSessionIDKey returns the provider metadata key used for session IDs.
func MissingRequiredFields ¶
MissingRequiredFields lists the fields cfg fails to supply for its own provider, named after the JSON keys the API and the settings form use so the message can be surfaced without translation. Disabled holds no backend-specific values; Docker must explicitly name its image.
func NewEnvdCompatTransport ¶
func NewEnvdCompatTransport(next http.RoundTripper, user string) http.RoundTripper
NewEnvdCompatTransport wraps next so E2B data-plane requests satisfy the current envd contract. A blank user leaves authentication untouched.
func NewGuardedTransport ¶
NewGuardedTransport returns an http.Transport whose dialer refuses addresses the outbound policy forbids. This closes the DNS-rebinding window that save-time URL validation cannot cover.
func NewGuardedTransportWithPolicy ¶
func NewGuardedTransportWithPolicy(policy OutboundURLPolicy) *http.Transport
func NewShellJobID ¶ added in v0.9.0
NewShellJobID draws sj_ plus 4 lowercase hex digits (2 random bytes).
func NormalizeConfigID ¶
NormalizeConfigID maps the empty config ID onto the sentinel used in metadata, so "the deployment default config" is addressable like any other.
func NormalizeCubeDNSServers ¶
NormalizeCubeDNSServers trims, drops empties, rejects non-IP values, and de-duplicates. Cube's template `dns` field is a list of nameserver IPs — hostnames are not accepted. An empty result means "leave Cubelet's default".
func ParseDesktopEnsureSecret ¶
ParseDesktopEnsureSecret requires stdout to be exactly one READY line. ensure.sh promises that; a last-non-empty-line parser would accept "garbage\nREADY <secret>" and dial, hiding the leak behind a 401/502. A single trailing newline (printf) is allowed; nothing else is.
func ParseShellJobEpilogue ¶ added in v0.9.0
func ParseShellJobEpilogue(epilogue string) (ShellJobStatus, ShellJobOffsets, bool)
ParseShellJobEpilogue reads the status JSON and the offset line that follow a shell-job marker. ok is false when the epilogue is not that pair.
func PromptSafePath ¶
PromptSafePath returns p when it can be embedded verbatim in prompt text.
func ReapOrphanSandboxes ¶
func ReapOrphanSandboxes(ctx context.Context, deps OrphanReaperDeps) (int, error)
ReapOrphanSandboxes deletes unbound sandboxes for one tenant and reports how many were removed.
func ReleaseConfigSandboxes ¶
func ReleaseConfigSandboxes( ctx context.Context, client ConfigSandboxClient, tenantID uint64, configID string, ) (int, error)
ReleaseConfigSandboxes deletes every sandbox the config owns and reports how many went away.
This is deliberately NOT exposed as an admin action. Its one caller is the sweep that runs immediately after a config's credentials are overwritten, using the outgoing credentials still held in memory — the only remaining moment those sandboxes can be reached at all. An admin blocked from editing a config is instead told to end the owning sessions or create a second config.
One undeletable sandbox does not abort the sweep - partial progress is strictly better than none - but the failures ARE returned. Swallowing them would leave a paused instance billing on credentials that no longer exist anywhere, with no record of which sandbox it was.
func RemoteErrorDiagnostics ¶
RemoteErrorDiagnostics formats err for logs. When err wraps a RemoteError it includes kind, op, HTTP status, and message without re-parsing text.
func RequireCompleteConfig ¶
RequireCompleteConfig is the error form of MissingRequiredFields, shared by the save path and the resolve path so both reject the same configs with the same wording.
func ResolveWorkspacePathIn ¶
func ResolveWorkspacePathIn(l WorkspaceLayout, value string) string
ResolveWorkspacePathIn anchors a relative path at the layout root.
func RunnableWorkspaceScript ¶
RunnableWorkspaceScript reports whether scriptPath is a session-writable file under /workspace that may be executed with an installed skill's interpreter. See RunnableWorkspaceScriptIn for shared machines.
func RunnableWorkspaceScriptIn ¶ added in v0.9.0
func RunnableWorkspaceScriptIn(l WorkspaceLayout, scriptPath string) (string, bool)
RunnableWorkspaceScriptIn is RunnableWorkspaceScript for an explicit layout.
func SafeDialControl ¶
SafeDialControl is a net.Dialer.Control hook using the environment's policy.
func SetDockerBackendEnabled ¶
func SetDockerBackendEnabled(enabled bool)
SetDockerBackendEnabled records the resolved 3-tier value (DB > env > false). Called at system_settings preload, Update, Reset, and pubsub reload.
func ShellJobBatchReadScript ¶ added in v0.9.0
ShellJobBatchReadScript is the sweeper / detail-read script. For each valid id it cats meta.json and status.json, sends a bounded head and tail (or the bytes after a cursor), checks /proc/<watcher_pid>/cmdline for the job id, and prints one JSON object. Ids with no status.json produce a Missing record. sandboxID is the caller's exec target; the script runs inside that sandbox and does not embed it. from is nil for a first read.
func ShellJobBatchReadScriptFor ¶ added in v0.9.0
func ShellJobBatchReadScriptFor(sandboxID string, ids []string, from map[string]ShellJobOffsets) string
ShellJobBatchReadScriptFor is ShellJobBatchReadScript with a per-id cursor. A missing id in from is a first read. A present offset, including 0, is incremental.
func ShellJobBatchReadScriptWithBudget ¶ added in v0.9.0
func ShellJobBatchReadScriptWithBudget( sandboxID string, ids []string, from map[string]ShellJobOffsets, budget int, ) string
ShellJobBatchReadScriptWithBudget bounds the combined stream bytes per job.
func ShellJobCancelScript ¶ added in v0.9.0
func ShellJobCancelScript(meta ShellJobMeta, force bool) string
ShellJobCancelScript rechecks ownership in the exec that signals the process. The directory is global on shared machines; a valid handle alone is insufficient.
func ShellJobKillOrphansScript ¶ added in v0.9.0
ShellJobKillOrphansScript stops a command whose watcher has died, and the docker activity loop that would otherwise keep the sandbox awake. Pid 1 is never a target. A missing process is not a failed exec.
func ShellJobSubmitScript ¶ added in v0.9.0
func ShellJobSubmitScript(job ShellJobMeta, execCommand string, yieldMS int) (string, string)
ShellJobSubmitScript builds the one Exec that (1) creates the job dir and meta.json, (2) launches the setsid wrapper without waiting, (3) forwards new output bytes to its own stdout/stderr until the job ends or yieldMS elapses, then emits the marker line, the final status.json and the absolute offsets. A short command therefore costs exactly one exec round-trip. meta.json is the masked job. execCommand is only the wrapper argument.
func ShellJobSubmitScriptWithBackend ¶ added in v0.9.0
func ShellJobSubmitScriptWithBackend(job ShellJobMeta, execCommand string, yieldMS int, docker bool) (string, string)
ShellJobSubmitScriptWithBackend is ShellJobSubmitScript with the backend chosen by the caller. Docker is the only backend whose wrapper must touch the activity marker; the sandbox id is not a backend name.
func ShellQuote ¶
ShellQuote renders s as one literal word for /bin/sh.
Single quotes are the only construct that keeps every metacharacter inert: inside double quotes `$`, backtick and `\` still expand, so a path chosen by an uploaded archive would execute as a command. The embedded-quote escape is the usual '\” dance. Non-ASCII bytes are passed through unchanged, so a CJK file name still names the same file inside the sandbox.
func SkillCommandPath ¶
SkillCommandPath is shared by normal skill execution and installation verification.
func SkillDirFor ¶
SkillDirFor returns the image directory of a skill. The key is the skill name from SKILL.md: that is what the installer writes, and what the agent is told to execute. The database id stays a row key and is not part of the path.
func SkillDirForImageScript ¶
SkillDirForImageScript returns the owning skill directory for an image script. It anchors on SkillsImageRoot so nested script layouts still use the venv that was installed beside the skill, not a shallower scripts directory.
func SkillDirUnder ¶
SkillDirUnder joins one validated skill name under an absolute skills root.
func SkillImageActive ¶
func SkillImageActive(tenantCfg *types.TenantSandboxConfig) bool
SkillImageActive reports whether a config's stored snapshot is the image its sessions actually boot.
It exists so the agent side can ask the same question the session side already answers, from the same inputs: a skill announced to the model while sessions keep booting the base template is a skill that cannot be invoked, and one hidden while the image carries it is an install nobody can use. It is false for backends that cannot snapshot at all (disabled), which is what "the base template is kept" means for them.
func SkillImageFingerprint ¶
SkillImageFingerprint identifies the provider account a skill snapshot lives in. Snapshots are not visible across accounts, so when credentials change the stored snapshot silently stops existing for us - this fingerprint is how we notice and fall back instead of booting sessions against a dead image ID.
func SkillInterpreterCommand ¶
SkillInterpreterCommand picks how to run one script of a skill.
The interpreter is derived per script rather than stored per skill: one skill may ship both .py and .js entry points, so a single stored "interpreter" column could never be right for all of them.
For Python we prefer the skill's own venv. The choice is made inside the sandbox with a shell conditional instead of an extra round trip to stat the path, because the extra Exec would double the latency of every skill call.
func SkillNameFromImagePath ¶
SkillNameFromImagePath reports whether p sits at or under SkillsImageRoot. The skills root itself returns ("", true). A skill directory or a file inside one returns (skillName, true). Paths outside the image return ("", false).
func SkillOwnerFingerprint ¶
func SkillOwnerFingerprint(tenantCfg *types.TenantSandboxConfig) string
SkillOwnerFingerprint is the provider-account identity stamped onto a skill snapshot. The install path refuses to record a snapshot when this is empty: a pointer with no owner is discarded at session start.
func SkillRequirementsPath ¶
SkillRequirementsPath is where the installer agent writes what the skill needs at run time. It lives inside the skill's own directory so it travels with the skill into the snapshot, and it is a file rather than a tool call on purpose: the agent reads a third-party SKILL.md, so nothing it produces may reach the database except as bytes the server parses and validates.
An invalid skill name yields an empty path, which every caller treats as "no declaration": the name is already validated before an install starts, and a best-effort read is not a place to fail an install.
func SkillRequirementsPathIn ¶
SkillRequirementsPathIn is the declaration file inside one skill directory.
func SkillVenvPython ¶
SkillVenvPython is where a skill's own Python interpreter lives when the install created one. It is exported because the model needs to be told: the system python3 deliberately carries no skill dependencies, so anything that inspects or debugs a Python skill has to name this path.
func StateMatches ¶
func StateMatches(candidate RemoteSandboxState, allowed []RemoteSandboxState) bool
func ValidShellJobID ¶ added in v0.9.0
ValidShellJobID reports whether id is a well-formed shell job handle.
func ValidateConfig ¶
ValidateConfig validates sandbox configuration
func ValidateDockerHost ¶
ValidateDockerHost checks a daemon endpoint before it is stored or dialled.
A TCP endpoint gets the same outbound treatment as any other workspace- supplied URL: a daemon socket accepts container creation, so an admin who can point it anywhere can make WeKnora talk to an arbitrary internal service. Unix sockets are local by definition and only have to be absolute.
func ValidateDockerNetworkMode ¶
ValidateDockerNetworkMode allows only bridge (egress) and none (no egress).
host and container: modes share another namespace outright, which would put sandbox code on the WeKnora host's or a sibling container's network. A user-defined network name is refused for the weaker but equally real version of the same problem: the usual deployment reaches its daemon through the mounted docker.sock, so naming the deployment's own compose network would place a sandbox on the same L3 network as Postgres and Redis. Only the operator can judge what a given named network exposes, and this value is set per workspace config, so it is not theirs to choose.
func ValidateDockerRemoteTLS ¶
ValidateDockerRemoteTLS requires client certificates for a TCP daemon. A remote Engine API that accepts container creation is a root shell on that host; plaintext tcp://2375 is not an acceptable way to reach it. Unix sockets are local to the WeKnora process and do not use TLS.
func ValidateOutboundURL ¶
ValidateOutboundURL checks raw against the environment's policy.
func ValidateOutboundURLWithPolicy ¶
func ValidateOutboundURLWithPolicy(raw string, policy OutboundURLPolicy) error
func ValidatedImageSkillDir ¶
ValidatedImageSkillDir reports whether skillDir is exactly one installed skill directory under SkillsImageRoot (for example /opt/weknora/tenant/skills/pdf).
func ValidatedOutputDirIn ¶ added in v0.9.0
func ValidatedOutputDirIn(l WorkspaceLayout, dir string) (string, bool)
ValidatedOutputDirIn is ValidatedSessionOutputDir for an explicit layout.
func ValidatedSessionOutputDir ¶
ValidatedSessionOutputDir normalises a configured artifact directory and reports whether it may be used.
It is the single gate for every WEKNORA_SKILL_OUTPUT_DIR override, wherever it comes from: the host environment the app reads at startup, or a tenant's sandbox config. Without it the two disagreed — execution validated the path and fell back to SessionOutputRoot, while the tools and the artifact collector took the host value as-is. Keep this separate from shell working directories: access to a sandbox path does not make it a delivery directory.
SessionWorkspaceRoot itself is refused. An artifact directory equal to the workspace root is not a delivery tree — it is the whole workspace, drafts included — and callers that compare the two (artifact collection) read that as "this backend collects nothing", silently dropping every artifact.
func ValidatedSkillDirUnder ¶
ValidatedSkillDirUnder reports whether skillDir is exactly one skill directory directly under root.
func WithCommandApproval ¶ added in v0.9.0
func WithCommandApproval(ctx context.Context, approval CommandApproval) context.Context
WithCommandApproval attaches the approval port for one host command.
func WithCommandOutput ¶
WithCommandOutput observes the command executed with this context. It does not grant installer privileges or change the final command result.
func WithSessionFileOperation ¶
WithSessionFileOperation reuses live handles only within one read/collection operation. Callers must create a fresh scope for every tool call or collect, never store it on a manager or reuse it across turns. A manager and tenant- scoped session key identify each handle, so scopes cannot cross backends or tenants. Lookup remains non-provisioning and failures are never cached.
func WriteShellJobJSON ¶ added in v0.9.0
WriteShellJobJSON writes v atomically (tmp + rename). The remote wrapper does the same dance in sh; Lite uses this helper.
Types ¶
type AllocationDirectory ¶ added in v0.9.0
type AllocationDirectory interface {
// SessionAllocation is the session's address. The manager caches it for
// one request; it is never trusted for validity.
SessionAllocation(ctx context.Context, sessionID string) (allocation.Assignment, bool, error)
// VerifySessionAllocation reads the database on every call and returns
// nil only if expected is still the session's live assignment and the
// session exists, is not retired, and its current owner is expected's
// owner and still eligible to share. It returns ErrSandboxSessionDeleted
// or ErrUserAllocationRejected for those cases, other errors for
// failures to verify.
VerifySessionAllocation(
ctx context.Context, sessionTenantID uint64, sessionID string, expected allocation.Assignment,
) error
UserMachinesByConfig(ctx context.Context, configTenantID uint64, configID string) ([]allocation.UserMachine, error)
}
AllocationDirectory resolves a session's recorded placement. Implemented by internal/application/service/sandboxalloc; nil keeps every session on the historical per-session path.
type CommandApproval ¶ added in v0.9.0
type CommandApproval struct {
// Approver is nil when nobody can be asked; the host sandbox then treats
// every question as refused.
Approver CommandApprover
// Command is what the model wrote, before stdin or skill wrapping.
Command string
}
CommandApproval travels with one shell_exec call on a host workspace.
func CommandApprovalFrom ¶ added in v0.9.0
func CommandApprovalFrom(ctx context.Context) (CommandApproval, bool)
CommandApprovalFrom returns the approval port attached to ctx, if any.
type CommandApprovalDecision ¶ added in v0.9.0
CommandApprovalDecision is the user's answer. Access may only narrow the proposed grant from "write" to "read".
type CommandApprovalRequest ¶ added in v0.9.0
type CommandApprovalRequest struct {
SessionID string
Command string
Cwd string
Reason string
// GrantPath and GrantAccess describe the widening asked for: after a
// "sandbox_denied" block, or with a delete outside the workspace.
GrantPath string
GrantAccess string
DenialSnippet string
FirstAttemptRan bool
AllowSession bool
// SessionRules is what a session approval of a delete remembers, such as
// "rm -f -r"; later deletes these rules cover are not asked again.
SessionRules []string
}
CommandApprovalRequest is one question for the user about a Lite host command. Reason is "delete", "dangerous" or "sandbox_denied".
type CommandApprover ¶ added in v0.9.0
type CommandApprover interface {
ApproveCommand(ctx context.Context, req CommandApprovalRequest) (CommandApprovalDecision, error)
}
CommandApprover asks the user about a host command and blocks for the answer.
type Config ¶
type Config struct {
// Type is the preferred sandbox type
Type SandboxType
// DefaultTimeout is the default execution timeout
DefaultTimeout time.Duration
// TerminalIdleDisconnect is how long an open interactive terminal may
// go without input or PTY output before the WebSocket is closed. Zero
// is treated as DefaultTerminalIdleDisconnect at use time.
TerminalIdleDisconnect time.Duration
// DesktopEnabled mirrors TenantSandboxConfig.DesktopEnabled: this
// config's template is a desktop image. The desktop endpoint refuses
// with DESKTOP_UNSUPPORTED when it is false, before touching a sandbox.
DesktopEnabled bool
// AllowPrivateEndpoints is the per-workspace outbound policy for this
// connection. Link-local addresses are blocked regardless.
AllowPrivateEndpoints bool
// DockerImage is the image every sandbox container is created from. It
// plays the same role as a Cube/E2B template ID.
DockerImage string
// DockerHost is the daemon endpoint, in DOCKER_HOST form
// ("unix:///var/run/docker.sock", "tcp://10.0.0.5:2376"). Empty uses
// DefaultDockerHost.
DockerHost string
// DockerTLSCertPath is a directory on the WeKnora host holding
// ca.pem / cert.pem / key.pem. Required for a TCP daemon; unix sockets
// do not use TLS.
DockerTLSCertPath string
// DockerCPULimit / DockerMemoryBytes / DockerPidsLimit cap one sandbox
// container. Zero uses the built-in defaults.
DockerCPULimit float64
DockerMemoryBytes int64
DockerPidsLimit int64
// DockerNetworkMode is the Docker network every sandbox joins: "bridge" or
// "none". host, container: and named networks are rejected (see
// ValidateDockerNetworkMode). Empty means "bridge"; skills that install
// packages need egress, so a sandbox is not isolated from the network by
// default.
DockerNetworkMode string
// DockerRuntime selects an alternative OCI runtime, e.g. "runsc" for
// gVisor. Empty uses the daemon's default runtime.
DockerRuntime string
// DockerIdleTTL is how long a container may go without executing anything
// before the idle sweep reclaims it. Zero uses DefaultDockerIdleTTL.
DockerIdleTTL time.Duration
// DockerHTTPTimeout bounds each Engine API call. Zero uses the default.
DockerHTTPTimeout time.Duration
// MaxMemory is the maximum memory limit in bytes
MaxMemory int64
// MaxCPU is the maximum CPU cores
MaxCPU float64
// EnvVars are additional environment variables to set for the sandbox.
EnvVars map[string]string
// Network is the outbound/inbound policy every sandbox built from this
// config is created with. DefaultConfig and ResolveEffectiveConfig fully
// specify it: leaving it nil would let adapters use provider defaults,
// which expose inbound traffic publicly.
Network RemoteNetworkPolicy
// CubeAPIURL is the base URL of the CubeAPI (E2B-compatible) endpoint.
// Only used when Type == SandboxTypeCube. Example: "http://127.0.0.1:33000".
CubeAPIURL string
// CubeProxyURL is the base URL of the CubeProxy HTTP endpoint through which
// in-sandbox envd traffic is routed via host-header rewriting. Example:
// "http://127.0.0.1:80".
CubeProxyURL string
// CubeSandboxDomain matches CubeAPI's CUBE_API_SANDBOX_DOMAIN. It is used to
// build the Host header "<port>-<sandboxID>.<domain>" that CubeProxy relies
// on to route requests into the correct MicroVM.
CubeSandboxDomain string
// CubeAPIKey is the API key sent via X-API-Key. Leave empty when the Cube
// deployment does not enforce authentication.
CubeAPIKey string
// CubeTemplate is the default template ID used when creating sandboxes.
CubeTemplate string
// CubeSandboxTTL is the Cube-side lifetime hint (passed as `timeout` when
// creating a sandbox). CubeMaster will reap the MicroVM if the client stops
// touching it for longer than this duration.
CubeSandboxTTL time.Duration
// CubeHTTPTimeout bounds each HTTP call to CubeAPI. Zero uses the default.
CubeHTTPTimeout time.Duration
// CubeDNSServers are nameserver IPs included when WeKnora builds the
// standard Cube template. Empty omits the field so Cubelet uses its
// cluster default.
CubeDNSServers []string
// E2BAPIKey is the E2B API key sent via X-API-Key. Only used when
// Type == SandboxTypeE2B.
E2BAPIKey string
// E2BAPIURL is the E2B control-plane endpoint. Empty defaults to
E2BAPIURL string
// E2BSandboxDomain is the domain envd traffic is routed through, e.g.
// "e2b.app". Empty defaults to the SDK's built-in.
E2BSandboxDomain string
// E2BProxyURL is the data-plane gateway that fronts envd for self-hosted
// E2B-compatible control planes. Empty keeps the SDK's behaviour of
// resolving the sandbox authority through DNS over TLS, which is what E2B
// Cloud expects. See types.E2BSandboxConfig.ProxyURL.
E2BProxyURL string
// E2BTemplate is the E2B template ID used at sandbox creation.
E2BTemplate string
// TemplateFromSnapshot reports that the boot target (E2BTemplate or
// CubeTemplate) was replaced by a skill snapshot. The env baked into that
// snapshot must not be sent again: Aliyun Agent Sandbox returns 400
// "creating from a snapshot does not allow envVars or function config
// overrides".
TemplateFromSnapshot bool
// E2BSandboxTTL is the E2B-side idle timeout hint.
E2BSandboxTTL time.Duration
// E2BHTTPTimeout bounds ordinary E2B HTTP calls, including response bodies.
// Command streams use their execution timeout instead.
E2BHTTPTimeout time.Duration
}
Config holds sandbox manager configuration
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns a default sandbox configuration.
It deliberately carries no Cube or E2B endpoint, credential or template: those belong to a named workspace config. Presetting them here once meant an incomplete workspace config could silently dial localhost.
func ResolveEffectiveConfig ¶
func ResolveEffectiveConfig( tenantCfg *types.TenantSandboxConfig, global *Config, ) (*Config, error)
ResolveEffectiveConfig returns the Config to build a tenant's sandbox manager from, or an error when the stored config is unsafe (ErrUnsafeOutboundURL) or incomplete (ErrSandboxConfigIncomplete).
The overrideX helpers still read as "override" below even though the provider fields were just cleared: they assign only non-empty values, which is exactly what is needed to let an omitted TTL fall through to its built-in default.
type ConfigSandboxClient ¶
type ConfigSandboxClient interface {
ConfigSandboxLister
Delete(ctx context.Context, sandboxID string) error
}
ConfigSandboxClient adds deletion, needed to release an inventory.
type ConfigSandboxLister ¶
type ConfigSandboxLister interface {
List(ctx context.Context, filter RemoteListFilter) ([]RemoteSandboxSummary, error)
}
ConfigSandboxLister is the read half of the provider client.
type CubeRemoteClient ¶
type CubeRemoteClient struct {
// contains filtered or unexported fields
}
CubeRemoteClient implements RemoteSandboxClient on top of the Cube SDK and envd transport. It is the only Cube backend the manager and lifecycle coordinator see.
func NewCubeRemoteClient ¶
func NewCubeRemoteClient(config *Config) (*CubeRemoteClient, error)
NewCubeRemoteClient constructs a Cube-backed RemoteSandboxClient using the SDK default HTTP clients (separate control/data pools). Suitable for the process-wide default manager and for throwaway connectivity probes, neither of which benefits from an externally owned pool.
func NewCubeRemoteClientWithPool ¶
func NewCubeRemoteClientWithPool( config *Config, pool *SandboxGatewayTransportPool, ) (*CubeRemoteClient, error)
NewCubeRemoteClientWithPool builds a client whose connections come from a caller-owned pool. Named configs construct a client per request, so the pool is what keeps connections alive across requests; it routes control-plane traffic onto the transport shared with E2B while preserving the SDK's proxy dial rewrite for the data plane. A nil pool keeps the SDK defaults.
func (*CubeRemoteClient) Capabilities ¶
func (c *CubeRemoteClient) Capabilities() RemoteSandboxCapabilities
func (*CubeRemoteClient) Connect ¶
func (c *CubeRemoteClient) Connect( ctx context.Context, request RemoteConnectRequest, ) (RemoteSandboxHandle, error)
func (*CubeRemoteClient) ConnectSession ¶
func (c *CubeRemoteClient) ConnectSession(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
ConnectSession reuses the connected SDK handle for the lifecycle probe.
func (*CubeRemoteClient) Create ¶
func (c *CubeRemoteClient) Create( ctx context.Context, request RemoteCreateRequest, ) (RemoteSandboxHandle, error)
func (*CubeRemoteClient) CreateSnapshot ¶
func (c *CubeRemoteClient) CreateSnapshot( ctx context.Context, sandboxID string, name string, ) (RemoteSnapshotRef, error)
CreateSnapshot snapshots a running sandbox. Cube exposes this on *Sandbox, so we connect by ID first (same shape as Delete).
func (*CubeRemoteClient) Delete ¶
func (c *CubeRemoteClient) Delete(ctx context.Context, sandboxID string) error
func (*CubeRemoteClient) DeleteSnapshot ¶
func (c *CubeRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
DeleteSnapshot removes a snapshot. Cube returns an API error for a missing template; we map not-found to success so cleanup is idempotent.
func (*CubeRemoteClient) DeleteSupersededDesktopTemplates ¶
func (c *CubeRemoteClient) DeleteSupersededDesktopTemplates(ctx context.Context, keepID string) error
DeleteSupersededDesktopTemplates drops desktop templates other than keepID.
func (*CubeRemoteClient) DeleteSupersededStandardTemplates ¶
func (c *CubeRemoteClient) DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
DeleteSupersededStandardTemplates drops WeKnora templates other than keepID.
func (*CubeRemoteClient) DialDesktop ¶
func (c *CubeRemoteClient) DialDesktop( ctx context.Context, handle RemoteSandboxHandle, opts RemoteDesktopOptions, ) (*websocket.Conn, error)
DialDesktop opens a WebSocket to websockify inside the sandbox.
It goes through the pool-owned dialer rather than building a URL here, so the inbound-token registry, the gateway target, and the SSRF guard all come from the one place that knows which pool this client belongs to.
func (*CubeRemoteClient) EnsureDesktopTemplate ¶
func (c *CubeRemoteClient) EnsureDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
EnsureDesktopTemplate returns the cluster's WeKnora desktop template, rebuilding a failed one or building it when absent. A cluster may hold both the CLI and desktop templates; the admin picks which ID a config boots.
func (*CubeRemoteClient) EnsureStandardTemplate ¶
func (c *CubeRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
EnsureStandardTemplate makes the cluster hold exactly one WeKnora template. A healthy or still-building one is returned as is; a failed one is rebuilt in place. If CubeMaster refuses the redo, the failed card is returned as-is; ReplaceStandardTemplate (the settings-page "delete and rebuild" action) is what actually replaces it.
func (*CubeRemoteClient) Exec ¶
func (c *CubeRemoteClient) Exec( ctx context.Context, handle RemoteSandboxHandle, request RemoteExecRequest, ) (*RemoteExecResult, error)
func (*CubeRemoteClient) Get ¶
func (c *CubeRemoteClient) Get( ctx context.Context, sandboxID string, ) (*RemoteSandboxSummary, error)
func (*CubeRemoteClient) KeepAlive ¶ added in v0.9.0
func (c *CubeRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, ttl time.Duration) error
KeepAlive moves the sandbox's idle timeout to ttl from now. Cube already counts commands as activity; the explicit call keeps background work from depending on which requests the control plane happens to count.
func (*CubeRemoteClient) List ¶
func (c *CubeRemoteClient) List( ctx context.Context, filter RemoteListFilter, ) ([]RemoteSandboxSummary, error)
func (*CubeRemoteClient) ListDir ¶
func (c *CubeRemoteClient) ListDir( ctx context.Context, handle RemoteSandboxHandle, path string, ) ([]RemoteDirEntry, error)
func (*CubeRemoteClient) ListSnapshots ¶
func (c *CubeRemoteClient) ListSnapshots( ctx context.Context, sandboxID string, ) ([]RemoteSnapshotRef, error)
ListSnapshots pages through every snapshot, optionally filtered by source sandbox. Used only by the orphan-reconciliation task.
func (*CubeRemoteClient) ListTemplates ¶
func (c *CubeRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
func (*CubeRemoteClient) MakeDir ¶
func (c *CubeRemoteClient) MakeDir( ctx context.Context, handle RemoteSandboxHandle, dir string, ) error
func (*CubeRemoteClient) OpenTerminal ¶
func (c *CubeRemoteClient) OpenTerminal( ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions, ) (RemoteTerminalSession, error)
OpenTerminal opens an interactive shell PTY inside the sandbox behind handle. The returned session keeps streaming until the shell exits, the context passed to OpenTerminal is cancelled (the session derives its own lifetime from it), or Close is called.
func (*CubeRemoteClient) Provider ¶
func (c *CubeRemoteClient) Provider() RemoteProvider
func (*CubeRemoteClient) ReadFile ¶
func (c *CubeRemoteClient) ReadFile( ctx context.Context, handle RemoteSandboxHandle, path string, ) ([]byte, error)
func (*CubeRemoteClient) Remove ¶
func (c *CubeRemoteClient) Remove( ctx context.Context, handle RemoteSandboxHandle, path string, ) error
func (*CubeRemoteClient) ReplaceDesktopTemplate ¶
func (c *CubeRemoteClient) ReplaceDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
ReplaceDesktopTemplate applies the current spec to the cluster's desktop template. Same persist-then-delete contract as ReplaceStandardTemplate.
func (*CubeRemoteClient) ReplaceStandardTemplate ¶
func (c *CubeRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
ReplaceStandardTemplate applies the current spec to the cluster's WeKnora template. Cube bakes DNS into the template, so a READY template only picks up a settings change via rebuild (preferred: same ID) or a replacement.
The previous template is left in the catalog. Deleting it here would race persistSpawnTemplateID: sessions would target a missing ID for the rest of the build, and a persist failure would leave every config pointing at a template that no longer exists. Callers persist a READY replacement first, then DeleteSupersededStandardTemplates.
func (*CubeRemoteClient) StartDesktopTTLRefresh ¶
func (c *CubeRemoteClient) StartDesktopTTLRefresh(ctx context.Context, handle RemoteSandboxHandle)
StartDesktopTTLRefresh extends the Cube sandbox idle timeout while the desktop relay is open. ctx must be the relay lifetime (WithoutCancel), not DialDesktop's request context.
func (*CubeRemoteClient) Stat ¶
func (c *CubeRemoteClient) Stat( ctx context.Context, handle RemoteSandboxHandle, path string, ) (*RemoteStatEntry, error)
func (*CubeRemoteClient) WriteFile ¶
func (c *CubeRemoteClient) WriteFile( ctx context.Context, handle RemoteSandboxHandle, path string, content []byte, ) error
type DefaultManager ¶
type DefaultManager struct {
// contains filtered or unexported fields
}
DefaultManager implements the Manager interface It handles sandbox selection and fallback logic
func (*DefaultManager) Cleanup ¶
func (m *DefaultManager) Cleanup(ctx context.Context) error
Cleanup releases all sandbox resources
func (*DefaultManager) Execute ¶
func (m *DefaultManager) Execute(ctx context.Context, config *ExecuteConfig) (*ExecuteResult, error)
Execute runs a script using the configured sandbox It performs security validation before execution to prevent prompt injection attacks
func (*DefaultManager) GetSandbox ¶
func (m *DefaultManager) GetSandbox() Sandbox
GetSandbox returns the active sandbox
func (*DefaultManager) GetType ¶
func (m *DefaultManager) GetType() SandboxType
GetType returns the current sandbox type
type DockerRemoteClient ¶
type DockerRemoteClient struct {
// contains filtered or unexported fields
}
DockerRemoteClient implements RemoteSandboxClient on top of one Docker daemon. It is safe for concurrent use: the moby client is, and this type holds no mutable state.
func NewDockerRemoteClient ¶
func NewDockerRemoteClient(cfg *Config) (*DockerRemoteClient, error)
NewDockerRemoteClient builds the adapter for one workspace config, reusing the shared connection to that daemon.
func NewDockerRemoteClientForCheck ¶
func NewDockerRemoteClientForCheck(cfg *Config) (*DockerRemoteClient, error)
NewDockerRemoteClientForCheck builds a client for the connectivity check.
It differs from the resolved client in one way: no idle sweeping. A probe runs against a config an admin is still editing, and a half-finished config must never delete containers a working config owns.
func (*DockerRemoteClient) Capabilities ¶
func (c *DockerRemoteClient) Capabilities() RemoteSandboxCapabilities
Capabilities reports what this backend can do.
SupportsTimeoutRefresh is false because the daemon has no timeout to refresh: idle reclamation is WeKnora's own sweep, not a provider feature. SupportsVolumes is false until the volume-mount surface is mapped onto Docker named volumes; advertising it early would let a workspace configure a mount that silently never appears. SupportsDesktop stays false: the desktop needs a way to reach the container's websockify port, which this backend has no gateway for yet.
func (*DockerRemoteClient) Connect ¶
func (c *DockerRemoteClient) Connect( ctx context.Context, request RemoteConnectRequest, ) (RemoteSandboxHandle, error)
Connect re-attaches to an existing container, resuming it when the daemon or the host stopped it. This is the docker equivalent of E2B's auto-resume: a stopped container keeps its filesystem, so the session continues where it left off instead of losing everything it installed.
func (*DockerRemoteClient) ConnectSession ¶
func (c *DockerRemoteClient) ConnectSession( ctx context.Context, req RemoteConnectRequest, ) (RemoteSandboxHandle, error)
ConnectSession already gets the lifecycle state from Connect's container inspection, including terminal detection and resuming a stopped container.
func (*DockerRemoteClient) Create ¶
func (c *DockerRemoteClient) Create( ctx context.Context, req RemoteCreateRequest, ) (RemoteSandboxHandle, error)
Create starts a new container for one sandbox.
func (*DockerRemoteClient) CreateForkSnapshot ¶
func (c *DockerRemoteClient) CreateForkSnapshot( ctx context.Context, sandboxID string, name string, ) (RemoteSnapshotRef, error)
CreateForkSnapshot commits the container into the fork image namespace. Same shape as CreateSnapshot; a different repo and labels keep the skill reaper from collecting (or being collected by) session-fork images.
func (*DockerRemoteClient) CreateSnapshot ¶
func (c *DockerRemoteClient) CreateSnapshot( ctx context.Context, sandboxID string, name string, ) (RemoteSnapshotRef, error)
CreateSnapshot commits the container's filesystem into a tagged local image.
The Engine pauses the container for the duration of the commit unless we opt out (we do not): that is the same "provider pauses while snapshotting" contract Cube and E2B honour. The resulting tag is a template ID — Create accepts it as RemoteCreateRequest.TemplateID — so the skill install path needs no Docker-specific branch past this adapter.
func (*DockerRemoteClient) Delete ¶
func (c *DockerRemoteClient) Delete(ctx context.Context, sandboxID string) error
Delete removes a container and its anonymous volumes.
func (*DockerRemoteClient) DeleteSnapshot ¶
func (c *DockerRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
DeleteSnapshot removes a committed skill image. A missing image is success: the reaper and the install-compensation path both retry deletes.
PruneChildren is what makes the delete reclaim anything. Each generation is committed from a container started off the previous one, so generation N+1 holds generation N's layers as ancestors. Untagging N alone therefore frees nothing while N+1 exists — which is correct and unavoidable — but the layers of a chain whose every tag has been retired would stay on disk forever without this, because the Go client defaults to noprune. Layers a live image still references are refcounted by the daemon, so cascading here cannot take the current image's storage out from under it.
func (*DockerRemoteClient) DeleteSupersededStandardTemplates ¶
func (c *DockerRemoteClient) DeleteSupersededStandardTemplates(context.Context, string) error
DeleteSupersededStandardTemplates is a no-op: Docker has no cluster-side template object, and skill snapshots live in a separate image namespace.
func (*DockerRemoteClient) EnsureStandardTemplate ¶
func (c *DockerRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
EnsureStandardTemplate makes the configured image available on the daemon, pulling it in the background when it is missing.
func (*DockerRemoteClient) Exec ¶
func (c *DockerRemoteClient) Exec( ctx context.Context, handle RemoteSandboxHandle, req RemoteExecRequest, ) (*RemoteExecResult, error)
Exec runs one command inside the sandbox.
The command is wrapped so that the container, not the client, enforces the timeout: cancelling the HTTP request leaves the process running (verified in docs/poc/docker-sandbox), which would let a runaway script keep burning the host's CPU long after WeKnora reported a timeout to the user.
func (*DockerRemoteClient) Get ¶
func (c *DockerRemoteClient) Get( ctx context.Context, sandboxID string, ) (*RemoteSandboxSummary, error)
Get returns one sandbox summary.
func (*DockerRemoteClient) Health ¶
func (c *DockerRemoteClient) Health(ctx context.Context) error
Health pings the daemon.
func (*DockerRemoteClient) KeepAlive ¶ added in v0.9.0
func (c *DockerRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, _ time.Duration) error
KeepAlive runs a no-op exec. dockerExecCommand touches the activity marker on every exec, and the marker's mtime is all the idle sweeper reads, so ttl is not needed here.
func (*DockerRemoteClient) List ¶
func (c *DockerRemoteClient) List( ctx context.Context, filter RemoteListFilter, ) ([]RemoteSandboxSummary, error)
List enumerates the containers this backend owns, filtered server-side by the metadata labels the caller asked for.
func (*DockerRemoteClient) ListDir ¶
func (c *DockerRemoteClient) ListDir( ctx context.Context, handle RemoteSandboxHandle, dirPath string, ) ([]RemoteDirEntry, error)
ListDir lists one directory level.
find(1) is used rather than ls because a single -printf format yields the type, size, mtime and path the caller needs, with a tab separator that cannot appear in find's own output fields.
func (*DockerRemoteClient) ListSnapshots ¶
func (c *DockerRemoteClient) ListSnapshots( ctx context.Context, sandboxID string, ) ([]RemoteSnapshotRef, error)
ListSnapshots returns skill-snapshot images on this daemon. An empty sandboxID lists every skill snapshot; a non-empty one keeps those committed from that container.
func (*DockerRemoteClient) ListTemplates ¶
func (c *DockerRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
ListTemplates reports the sandbox images available on this daemon.
func (*DockerRemoteClient) MakeDir ¶
func (c *DockerRemoteClient) MakeDir( ctx context.Context, handle RemoteSandboxHandle, dirPath string, ) error
MakeDir creates a directory (and its parents) inside the sandbox.
func (*DockerRemoteClient) OpenTerminal ¶
func (c *DockerRemoteClient) OpenTerminal( ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions, ) (RemoteTerminalSession, error)
OpenTerminal opens an interactive shell PTY inside the container behind handle. The returned session streams until the shell exits, the context passed to OpenTerminal is cancelled, or Close is called — the last two end it identically (through Close, which also terminates the shell).
func (*DockerRemoteClient) Provider ¶
func (c *DockerRemoteClient) Provider() RemoteProvider
Provider identifies this backend.
func (*DockerRemoteClient) ReadFile ¶
func (c *DockerRemoteClient) ReadFile( ctx context.Context, handle RemoteSandboxHandle, filePath string, ) ([]byte, error)
ReadFile reads one file as the sandbox account. See WriteFile for why this does not use the archive endpoint.
func (*DockerRemoteClient) Remove ¶
func (c *DockerRemoteClient) Remove( ctx context.Context, handle RemoteSandboxHandle, targetPath string, ) error
Remove deletes a path recursively.
func (*DockerRemoteClient) ReplaceStandardTemplate ¶
func (c *DockerRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
ReplaceStandardTemplate is a pull of the configured image. Docker has no cluster-side template object to delete; a missing image is fetched, a present one is left as-is.
func (*DockerRemoteClient) Stat ¶
func (c *DockerRemoteClient) Stat( ctx context.Context, handle RemoteSandboxHandle, filePath string, ) (*RemoteStatEntry, error)
Stat returns metadata for one path.
find reports the FINAL component without following it, so a path that names a link reports RemoteEntryOther rather than whatever it points at, and callers that only accept regular files refuse it before any read is attempted.
That guarantee stops at the final component. Intermediate components are resolved by the kernel during path lookup, exactly as they are for any other process, so `/workspace/output/link-to-etc/passwd` stats as a regular file — verified against a real daemon. Reads through such a path are not a privilege boundary being crossed, only the "stay inside the artifact directory" convention: ReadFile still runs as the sandbox account, so it returns what that account could have read anyway with shell_exec.
func (*DockerRemoteClient) WriteFile ¶
func (c *DockerRemoteClient) WriteFile( ctx context.Context, handle RemoteSandboxHandle, filePath string, content []byte, ) error
WriteFile writes one file through exec as remoteFileUser(ctx).
Read/Stat and the other file operations use the same exec path for explicit identity, bounded execution and activity tracking. Archive endpoints run in the daemon and do not honor these per-exec settings.
The default account is root. Exec still follows intermediate symlinks, and lexical path guards do not confine access to /workspace or protect /root and /etc inside this container. Host and cross-session isolation depend on the container and its mount configuration, not in-container ownership. Root cannot bypass a read-only mount. A future file API requiring path containment must enforce it during resolution and use, not rely on root exec or a separate prefix/Stat check.
type E2BRemoteClient ¶
type E2BRemoteClient struct {
// contains filtered or unexported fields
}
E2BRemoteClient implements RemoteSandboxClient on top of the go-e2b client.
func NewE2BRemoteClient ¶
func NewE2BRemoteClient(cfg *Config) (*E2BRemoteClient, error)
NewE2BRemoteClient builds an E2B-backed RemoteSandboxClient from Config. The API key is required; the caller may leave APIURL / SandboxDomain empty to fall back to the SDK's built-in defaults.
func NewE2BRemoteClientWithPool ¶
func NewE2BRemoteClientWithPool( cfg *Config, pool *SandboxGatewayTransportPool, ) (*E2BRemoteClient, error)
NewE2BRemoteClientWithPool builds the client on top of the shared gateway pool. It is what self-hosted E2B-compatible control planes need: the pool keeps control-plane traffic on the process-wide transport while dialling data-plane traffic at the configured gateway (see gateway_transport.go). A nil pool falls back to the SDK defaults.
func NewE2BRemoteClientWithTransport ¶
func NewE2BRemoteClientWithTransport( cfg *Config, transport *http.Transport, ) (*E2BRemoteClient, error)
NewE2BRemoteClientWithTransport builds the client with an injected HTTP transport. Per-tenant clients are constructed per request, so sharing one transport across tenants is what preserves connection pooling: transports pool per host:port and the API key travels in per-request headers, making the sharing both safe and effective. A nil transport uses the http default.
func (*E2BRemoteClient) Capabilities ¶
func (c *E2BRemoteClient) Capabilities() RemoteSandboxCapabilities
func (*E2BRemoteClient) Connect ¶
func (c *E2BRemoteClient) Connect( ctx context.Context, request RemoteConnectRequest, ) (RemoteSandboxHandle, error)
func (*E2BRemoteClient) ConnectSession ¶
func (c *E2BRemoteClient) ConnectSession(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
ConnectSession reuses the connected SDK handle for the lifecycle probe.
func (*E2BRemoteClient) Create ¶
func (c *E2BRemoteClient) Create( ctx context.Context, request RemoteCreateRequest, ) (RemoteSandboxHandle, error)
func (*E2BRemoteClient) CreateSnapshot ¶
func (c *E2BRemoteClient) CreateSnapshot( ctx context.Context, sandboxID string, name string, ) (RemoteSnapshotRef, error)
CreateSnapshot snapshots a running sandbox. E2B exposes snapshots on the connected sandbox handle, so the adapter reattaches first like Delete does.
func (*E2BRemoteClient) Delete ¶
func (c *E2BRemoteClient) Delete(ctx context.Context, sandboxID string) error
func (*E2BRemoteClient) DeleteSnapshot ¶
func (c *E2BRemoteClient) DeleteSnapshot(ctx context.Context, snapshotID string) error
DeleteSnapshot removes a snapshot. go-e2b reports a missing snapshot as (false, nil), so cleanup stays idempotent while genuine provider errors still surface through the neutral error taxonomy.
func (*E2BRemoteClient) DeleteSupersededDesktopTemplates ¶
func (c *E2BRemoteClient) DeleteSupersededDesktopTemplates(ctx context.Context, keepID string) error
DeleteSupersededDesktopTemplates drops desktop templates other than keepID.
func (*E2BRemoteClient) DeleteSupersededStandardTemplates ¶
func (c *E2BRemoteClient) DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
DeleteSupersededStandardTemplates drops WeKnora templates other than keepID.
func (*E2BRemoteClient) DialDesktop ¶
func (c *E2BRemoteClient) DialDesktop( ctx context.Context, handle RemoteSandboxHandle, opts RemoteDesktopOptions, ) (*websocket.Conn, error)
DialDesktop opens a WebSocket to websockify inside the sandbox.
It goes through the pool-owned dialer rather than building a URL here, so the inbound-token registry, the gateway target, and the SSRF guard all come from the one place that knows which pool this client belongs to.
func (*E2BRemoteClient) EnsureDesktopTemplate ¶
func (c *E2BRemoteClient) EnsureDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
EnsureDesktopTemplate returns the cluster's WeKnora desktop template, building it when absent. It is a sibling of EnsureStandardTemplate: a cluster may hold both, and the admin picks which ID a config boots.
func (*E2BRemoteClient) EnsureStandardTemplate ¶
func (c *E2BRemoteClient) EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
EnsureStandardTemplate returns the cluster's WeKnora template, building it when absent. A failed or untagged template is not returned as is: it can never spawn a sandbox, so it falls through to the build below. E2B resolves the build by name, so that is a rebuild of the same template rather than a second entry in the catalog.
func (*E2BRemoteClient) Exec ¶
func (c *E2BRemoteClient) Exec( ctx context.Context, handle RemoteSandboxHandle, request RemoteExecRequest, ) (*RemoteExecResult, error)
func (*E2BRemoteClient) Get ¶
func (c *E2BRemoteClient) Get( ctx context.Context, sandboxID string, ) (*RemoteSandboxSummary, error)
Get returns a single sandbox summary through Connect and the info endpoint. Session execution uses ConnectSession to retain that handle after probing.
func (*E2BRemoteClient) Health ¶
func (c *E2BRemoteClient) Health(ctx context.Context) error
Health probes the control plane by listing sandboxes. The protocol has no dedicated health endpoint, and a list is the smallest authenticated call that detects a bad API key or a dead API.
It deliberately uses the v2 listing rather than the legacy one: v2 is what every other call in this client already depends on, and E2B-compatible control planes (Agent-Sandbox, for one) implement only that one — probing the legacy path would report a perfectly healthy backend as unreachable.
func (*E2BRemoteClient) KeepAlive ¶ added in v0.9.0
func (c *E2BRemoteClient) KeepAlive(ctx context.Context, handle RemoteSandboxHandle, ttl time.Duration) error
KeepAlive moves the sandbox's timeout to ttl from now. E2B does not count commands, file access or URL requests as activity on a running sandbox; only SetTimeout resets it.
func (*E2BRemoteClient) List ¶
func (c *E2BRemoteClient) List( ctx context.Context, filter RemoteListFilter, ) ([]RemoteSandboxSummary, error)
func (*E2BRemoteClient) ListDir ¶
func (c *E2BRemoteClient) ListDir( ctx context.Context, handle RemoteSandboxHandle, path string, ) ([]RemoteDirEntry, error)
func (*E2BRemoteClient) ListSnapshots ¶
func (c *E2BRemoteClient) ListSnapshots( ctx context.Context, sandboxID string, ) ([]RemoteSnapshotRef, error)
ListSnapshots pages through every snapshot, optionally filtered by source sandbox. Used only by the skill-image orphan-reconciliation task.
func (*E2BRemoteClient) ListTemplates ¶
func (c *E2BRemoteClient) ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
func (*E2BRemoteClient) MakeDir ¶
func (c *E2BRemoteClient) MakeDir( ctx context.Context, handle RemoteSandboxHandle, dir string, ) error
func (*E2BRemoteClient) OpenTerminal ¶
func (c *E2BRemoteClient) OpenTerminal( ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions, ) (RemoteTerminalSession, error)
OpenTerminal opens an interactive shell PTY inside the sandbox behind handle. The returned session keeps streaming until the shell exits, the context passed to OpenTerminal is cancelled (the session derives its own lifetime from it), or Close is called.
func (*E2BRemoteClient) Provider ¶
func (c *E2BRemoteClient) Provider() RemoteProvider
func (*E2BRemoteClient) ReadFile ¶
func (c *E2BRemoteClient) ReadFile( ctx context.Context, handle RemoteSandboxHandle, path string, ) ([]byte, error)
func (*E2BRemoteClient) Remove ¶
func (c *E2BRemoteClient) Remove( ctx context.Context, handle RemoteSandboxHandle, path string, ) error
func (*E2BRemoteClient) ReplaceDesktopTemplate ¶
func (c *E2BRemoteClient) ReplaceDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
ReplaceDesktopTemplate starts a new desktop-template build from the current spec. Same persist-then-delete contract as ReplaceStandardTemplate.
func (*E2BRemoteClient) ReplaceStandardTemplate ¶
func (c *E2BRemoteClient) ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
ReplaceStandardTemplate starts a new WeKnora-template build from the current spec. E2B resolves builds by name, so this is often a rebuild of the same ID. The previous template stays listed until the caller has persisted a spawnable replacement and called DeleteSupersededStandardTemplates: deleting first left stored template_ids pointing at a missing template, and retrying a refused rebuild by deleting the live ID did the same.
func (*E2BRemoteClient) StartDesktopTTLRefresh ¶
func (c *E2BRemoteClient) StartDesktopTTLRefresh(ctx context.Context, handle RemoteSandboxHandle)
StartDesktopTTLRefresh extends the E2B sandbox idle timeout while the desktop relay is open. ctx must be the relay lifetime (WithoutCancel), not DialDesktop's request context.
func (*E2BRemoteClient) Stat ¶
func (c *E2BRemoteClient) Stat( ctx context.Context, handle RemoteSandboxHandle, path string, ) (*RemoteStatEntry, error)
func (*E2BRemoteClient) WriteFile ¶
func (c *E2BRemoteClient) WriteFile( ctx context.Context, handle RemoteSandboxHandle, path string, content []byte, ) error
Filesystem operations name DefaultSandboxExecUser explicitly rather than relying on the daemon's default account. Naming the user is required for interoperability: E2B Cloud falls back to "user" when the request omits it, while other E2B-compatible control planes reject the call outright. The default account is root, which matches what scripts run as; under one-session-one-sandbox there is no shared volume here to defend with file-mode ownership.
type ExecuteConfig ¶
type ExecuteConfig struct {
// Script is the absolute path to the script file
Script string
// Args are command-line arguments to pass to the script
Args []string
// WorkDir is the working directory for script execution
WorkDir string
// Timeout is the maximum execution time (0 = use default)
Timeout time.Duration
// Env is additional environment variables
Env map[string]string
// AllowNetwork enables network access (Docker only)
AllowNetwork bool
// MemoryLimit is the maximum memory in bytes (Docker only)
MemoryLimit int64
// CPULimit is the maximum CPU cores (Docker only)
CPULimit float64
// ReadOnlyRootfs makes the root filesystem read-only (Docker only)
ReadOnlyRootfs bool
// Stdin provides input to the script
Stdin string
// SkipValidation skips security validation (use with caution, only for trusted scripts)
SkipValidation bool
// ScriptContent is the script content for validation (optional, will be read from file if not provided)
ScriptContent string
// SessionID scopes the execution to a per-session persistent sandbox.
// Honoured by Cube, E2B and Docker. When empty, those backends fall back
// to an ephemeral (one-shot) sandbox created and torn down inside the
// single Execute call.
SessionID string
// RemoteScriptPath is an absolute path to a script that already exists
// inside the sandbox. When set, the executor skips the upload step and
// runs it in place. Accepted locations:
// - an installed skill file under SkillsImageRoot (bundle vetted at install)
// - a session-writable file under /workspace, not under /workspace/input,
// which also requires SkillDir so the skill's interpreter is used
RemoteScriptPath string
// SkillDir is the installed skill directory whose venv/node_modules
// should run RemoteScriptPath. Required when RemoteScriptPath sits under
// /workspace. Image-skill paths derive the directory from the script and
// ignore this field.
SkillDir string
}
ExecuteConfig contains configuration for script execution
type ExecuteResult ¶
type ExecuteResult struct {
// Stdout is the standard output from the script
Stdout string
// Stderr is the standard error from the script
Stderr string
// ExitCode is the process exit code
ExitCode int
// Duration is the actual execution time
Duration time.Duration
// Killed indicates if the process was killed (e.g., timeout)
Killed bool
// Error contains any execution error
Error string
}
ExecuteResult contains the result of script execution
func (*ExecuteResult) IsSuccess ¶
func (r *ExecuteResult) IsSuccess() bool
IsSuccess returns true if the script executed successfully
type InboundTokenRegistry ¶
type InboundTokenRegistry struct {
// contains filtered or unexported fields
}
InboundTokenRegistry maps a sandbox ID to its inbound credential.
func NewInboundTokenRegistry ¶
func NewInboundTokenRegistry() *InboundTokenRegistry
NewInboundTokenRegistry returns an empty registry.
func (*InboundTokenRegistry) Delete ¶
func (r *InboundTokenRegistry) Delete(sandboxID string)
Delete forgets sandboxID. Called when the sandbox is destroyed.
func (*InboundTokenRegistry) Get ¶
func (r *InboundTokenRegistry) Get(sandboxID string) string
Get returns the token for sandboxID, or "" when none is known.
func (*InboundTokenRegistry) Put ¶
func (r *InboundTokenRegistry) Put(sandboxID, token string)
Put records the token for sandboxID. An empty token is not stored (Docker, or a handle that omitted the credential).
type Manager ¶
type Manager interface {
// Execute runs a script using the configured sandbox
Execute(ctx context.Context, config *ExecuteConfig) (*ExecuteResult, error)
// Cleanup releases all sandbox resources
Cleanup(ctx context.Context) error
// GetSandbox returns the active sandbox
GetSandbox() Sandbox
// GetType returns the current sandbox type
GetType() SandboxType
}
Manager provides a unified interface for sandbox operations It handles sandbox selection and fallback logic
func NewDisabledManager ¶
func NewDisabledManager() Manager
NewDisabledManager creates a manager that rejects all execution requests
func NewManager ¶
NewManager creates a new sandbox manager with the given configuration
func NewManagerFromType ¶
NewManagerFromType creates a sandbox manager with the specified type. dockerImage is optional; if empty, the default image is used.
Session-scoped backends (Cube, E2B, Docker) route to SessionBoundManager, which keeps one persistent sandbox per SessionID; Disabled routes to DefaultManager. Both satisfy Manager.
type MemoryActivityRegistry ¶ added in v0.9.0
type MemoryActivityRegistry struct {
// contains filtered or unexported fields
}
MemoryActivityRegistry shares the binding store's background leases and lock, so single-process deployments also protect live work from rebuild and rewind.
func NewMemoryActivityRegistry ¶ added in v0.9.0
func NewMemoryActivityRegistry(store *MemorySessionSandboxBindingStore) *MemoryActivityRegistry
NewMemoryActivityRegistry returns the activity registry of store.
func (*MemoryActivityRegistry) Add ¶ added in v0.9.0
func (r *MemoryActivityRegistry) Add( ctx context.Context, s interfaces.SessionRef, a interfaces.BackgroundActivity, lease time.Duration, kindCap int, ) error
Add implements interfaces.ActivityRegistry.
func (*MemoryActivityRegistry) List ¶ added in v0.9.0
func (r *MemoryActivityRegistry) List( ctx context.Context, s interfaces.SessionRef, ) ([]interfaces.BackgroundActivity, error)
List implements interfaces.ActivityRegistry.
func (*MemoryActivityRegistry) Remove ¶ added in v0.9.0
func (r *MemoryActivityRegistry) Remove(ctx context.Context, s interfaces.SessionRef, id string) error
Remove implements interfaces.ActivityRegistry.
func (*MemoryActivityRegistry) Renew ¶ added in v0.9.0
func (r *MemoryActivityRegistry) Renew( ctx context.Context, s interfaces.SessionRef, id string, lease time.Duration, ) (bool, error)
Renew implements interfaces.ActivityRegistry.
type MemorySessionSandboxBindingStore ¶
type MemorySessionSandboxBindingStore struct {
// contains filtered or unexported fields
}
MemorySessionSandboxBindingStore is a process-local implementation intended for tests and explicitly configured single-process deployments.
func NewMemorySessionSandboxBindingStore ¶
func NewMemorySessionSandboxBindingStore() *MemorySessionSandboxBindingStore
NewMemorySessionSandboxBindingStore creates an empty in-memory store.
func (*MemorySessionSandboxBindingStore) BeginTurn ¶
func (s *MemorySessionSandboxBindingStore) BeginTurn( ctx context.Context, key SessionSandboxKey, ) error
BeginTurn opens a chat-turn lease. The first increment of a session's refcount allows the next resolve to rebuild a stale sandbox.
func (*MemorySessionSandboxBindingStore) ConsumeTurnRebuild ¶
func (s *MemorySessionSandboxBindingStore) ConsumeTurnRebuild( ctx context.Context, key SessionSandboxKey, ) error
ConsumeTurnRebuild spends the one rebuild allowed for the current turn.
func (*MemorySessionSandboxBindingStore) Create ¶
func (s *MemorySessionSandboxBindingStore) Create( ctx context.Context, key SessionSandboxKey, binding SessionSandboxBinding, ) (bool, error)
Create stores a validated current-schema binding only if key is unbound.
func (*MemorySessionSandboxBindingStore) DeleteIfMatch ¶
func (s *MemorySessionSandboxBindingStore) DeleteIfMatch( ctx context.Context, key SessionSandboxKey, provider RemoteProvider, sandboxID string, ) (bool, error)
DeleteIfMatch deletes only the expected provider and sandbox ID.
func (*MemorySessionSandboxBindingStore) EndTurn ¶
func (s *MemorySessionSandboxBindingStore) EndTurn( _ context.Context, key SessionSandboxKey, ) error
EndTurn releases one chat-turn lease. The last release drops the lease so a later resolve may rebuild a stale sandbox immediately.
func (*MemorySessionSandboxBindingStore) Get ¶
func (s *MemorySessionSandboxBindingStore) Get( ctx context.Context, key SessionSandboxKey, ) (*SessionSandboxBinding, error)
Get returns the current binding, or nil when the session is unbound.
func (*MemorySessionSandboxBindingStore) HasRewindLock ¶
func (s *MemorySessionSandboxBindingStore) HasRewindLock( ctx context.Context, key SessionSandboxKey, ) (bool, error)
HasRewindLock reports whether rewind currently holds key.
func (*MemorySessionSandboxBindingStore) InvalidateByConfig ¶
func (s *MemorySessionSandboxBindingStore) InvalidateByConfig( ctx context.Context, tenantID uint64, configID string, ) (int, error)
InvalidateByConfig marks this process's bindings of one config stale.
func (*MemorySessionSandboxBindingStore) ReplaceTrafficTokenIfMatch ¶
func (s *MemorySessionSandboxBindingStore) ReplaceTrafficTokenIfMatch( ctx context.Context, key SessionSandboxKey, expected SessionSandboxBinding, token string, ) (bool, error)
ReplaceTrafficTokenIfMatch patches the inbound credential without disturbing the rest of the binding, including a concurrent StaleAt mark.
func (*MemorySessionSandboxBindingStore) TryLockRewind ¶
func (s *MemorySessionSandboxBindingStore) TryLockRewind( ctx context.Context, key SessionSandboxKey, ) (func(), error)
TryLockRewind takes a process-local exclusive rewind lock for key.
func (*MemorySessionSandboxBindingStore) TurnState ¶
func (s *MemorySessionSandboxBindingStore) TurnState( ctx context.Context, key SessionSandboxKey, ) (bool, bool, error)
TurnState reports whether a chat turn is open and whether its first resolve may still rebuild a stale sandbox.
func (*MemorySessionSandboxBindingStore) WithLifecycleLock ¶
func (s *MemorySessionSandboxBindingStore) WithLifecycleLock( ctx context.Context, key SessionSandboxKey, fn func(context.Context) error, ) error
WithLifecycleLock runs fn while holding the process-local lock for key.Machine().
type OrphanReaperDeps ¶
type OrphanReaperDeps struct {
Client orphanReaperClient
// BoundIDs is the set of sandbox IDs the binding store still considers
// owned. Anything outside this set is a deletion candidate.
BoundIDs map[string]struct{}
TenantID uint64
// ConfigID scopes the sweep to one sandbox config. Empty means the
// deployment default config. Sweeping by tenant alone would delete
// sandboxes belonging to a sibling config on the same provider account.
ConfigID string
// Grace protects freshly created sandboxes that may not be bound yet.
// Pass 0 when reaping deliberately (e.g. before a backend switch), where
// every sandbox on the old backend is known to be abandoned.
Grace time.Duration
Now func() time.Time
}
OrphanReaperDeps configures one reconciliation pass for a single tenant.
type OutboundURLPolicy ¶
type OutboundURLPolicy struct {
// AllowPrivate permits loopback and RFC1918 addresses. It never permits
// link-local (169.254.0.0/16), which carries the cloud metadata service.
AllowPrivate bool
}
OutboundURLPolicy decides which addresses tenant-supplied endpoints may resolve to.
func DefaultOutboundURLPolicy ¶
func DefaultOutboundURLPolicy() OutboundURLPolicy
DefaultOutboundURLPolicy is the fail-closed policy used by callers that do not carry a workspace configuration.
func (OutboundURLPolicy) DialControl ¶
DialControl refuses connections to forbidden addresses. It inspects the concrete address the kernel is about to connect to, which is what makes it immune to DNS rebinding.
func (OutboundURLPolicy) Validate ¶
func (p OutboundURLPolicy) Validate(raw string) error
Validate reports whether raw is an acceptable tenant-supplied endpoint. It rejects non-HTTP schemes and any host that resolves to a forbidden address.
Callers must ALSO install DialControl on the dialer they use; Validate alone cannot close the DNS-rebinding window.
type OutputFileStamp ¶ added in v0.9.0
type OutputFileStamp struct {
Path string `json:"path"`
Size int64 `json:"size"`
ModUnixNano int64 `json:"mod_unix_nano"`
}
OutputFileStamp is one file in the output directory before a job starts. A later listing is compared with it to find deliverables.
func StampsFromEntries ¶ added in v0.9.0
func StampsFromEntries(entries []RemoteDirEntry) (stamps []OutputFileStamp, ok bool)
StampsFromEntries keeps file entries only, capped so the job meta stays small. ok is false when the listing is too large to compare reliably.
type PermissiveSessionExistenceChecker ¶
type PermissiveSessionExistenceChecker struct{}
PermissiveSessionExistenceChecker accepts every session. It is safe in deployments where WeKnora's own DestroySession is the only session-delete path (single-process memory binding store); the Redis-authoritative deployment must inject a real checker consulting the session repository.
func (PermissiveSessionExistenceChecker) SessionExists ¶
func (PermissiveSessionExistenceChecker) SessionExists( context.Context, SessionSandboxKey, ) (bool, error)
SessionExists always returns true.
type RedisActivityRegistry ¶ added in v0.9.0
type RedisActivityRegistry struct {
// contains filtered or unexported fields
}
RedisActivityRegistry is the multi-replica ActivityRegistry. It shares the binding store's client and hash tag so the turn-lease scripts can read it.
func NewRedisActivityRegistry ¶ added in v0.9.0
func NewRedisActivityRegistry(store *RedisSessionSandboxBindingStore) *RedisActivityRegistry
NewRedisActivityRegistry returns a registry backed by store's Redis client.
func (*RedisActivityRegistry) Add ¶ added in v0.9.0
func (r *RedisActivityRegistry) Add( ctx context.Context, s interfaces.SessionRef, a interfaces.BackgroundActivity, lease time.Duration, kindCap int, ) error
Add implements interfaces.ActivityRegistry.
func (*RedisActivityRegistry) List ¶ added in v0.9.0
func (r *RedisActivityRegistry) List( ctx context.Context, s interfaces.SessionRef, ) ([]interfaces.BackgroundActivity, error)
List implements interfaces.ActivityRegistry.
func (*RedisActivityRegistry) Remove ¶ added in v0.9.0
func (r *RedisActivityRegistry) Remove(ctx context.Context, s interfaces.SessionRef, id string) error
Remove implements interfaces.ActivityRegistry.
func (*RedisActivityRegistry) Renew ¶ added in v0.9.0
func (r *RedisActivityRegistry) Renew( ctx context.Context, s interfaces.SessionRef, id string, lease time.Duration, ) (bool, error)
Renew implements interfaces.ActivityRegistry.
func (*RedisActivityRegistry) WithAllocations ¶ added in v0.9.0
func (r *RedisActivityRegistry) WithAllocations(allocations AllocationDirectory) *RedisActivityRegistry
WithAllocations uses each session's recorded placement to address shared machine leases. Configure it before using the registry.
type RedisSessionSandboxBindingStore ¶
type RedisSessionSandboxBindingStore struct {
// contains filtered or unexported fields
}
RedisSessionSandboxBindingStore is the authoritative distributed store for persistent remote-session bindings.
func NewRedisSessionSandboxBindingStore ¶
func NewRedisSessionSandboxBindingStore( client redis.UniversalClient, namespace string, ) (*RedisSessionSandboxBindingStore, error)
NewRedisSessionSandboxBindingStore creates a fail-closed Redis store.
func (*RedisSessionSandboxBindingStore) BeginTurn ¶
func (s *RedisSessionSandboxBindingStore) BeginTurn( ctx context.Context, key SessionSandboxKey, ) error
BeginTurn opens a chat-turn lease. The first increment of a session's refcount allows the next resolve to rebuild a stale sandbox.
func (*RedisSessionSandboxBindingStore) ConsumeTurnRebuild ¶
func (s *RedisSessionSandboxBindingStore) ConsumeTurnRebuild( ctx context.Context, key SessionSandboxKey, ) error
ConsumeTurnRebuild spends the one rebuild allowed for the current turn.
func (*RedisSessionSandboxBindingStore) Create ¶
func (s *RedisSessionSandboxBindingStore) Create( ctx context.Context, key SessionSandboxKey, binding SessionSandboxBinding, ) (bool, error)
Create stores a validated current-schema binding with SET NX and no expiration.
func (*RedisSessionSandboxBindingStore) DeleteIfMatch ¶
func (s *RedisSessionSandboxBindingStore) DeleteIfMatch( ctx context.Context, key SessionSandboxKey, provider RemoteProvider, sandboxID string, ) (bool, error)
DeleteIfMatch atomically deletes only the expected provider and sandbox ID.
func (*RedisSessionSandboxBindingStore) EndTurn ¶
func (s *RedisSessionSandboxBindingStore) EndTurn( ctx context.Context, key SessionSandboxKey, ) error
EndTurn releases one chat-turn lease. The last release drops the lease so a later resolve may rebuild a stale sandbox immediately.
func (*RedisSessionSandboxBindingStore) Get ¶
func (s *RedisSessionSandboxBindingStore) Get( ctx context.Context, key SessionSandboxKey, ) (*SessionSandboxBinding, error)
Get returns the current binding, or nil when the session is unbound.
func (*RedisSessionSandboxBindingStore) HasRewindLock ¶
func (s *RedisSessionSandboxBindingStore) HasRewindLock( ctx context.Context, key SessionSandboxKey, ) (bool, error)
HasRewindLock reports whether rewind currently holds key.
func (*RedisSessionSandboxBindingStore) InvalidateByConfig ¶
func (s *RedisSessionSandboxBindingStore) InvalidateByConfig( ctx context.Context, tenantID uint64, configID string, ) (int, error)
InvalidateByConfig marks every binding of one workspace's config stale.
func (*RedisSessionSandboxBindingStore) ReplaceTrafficTokenIfMatch ¶
func (s *RedisSessionSandboxBindingStore) ReplaceTrafficTokenIfMatch( ctx context.Context, key SessionSandboxKey, expected SessionSandboxBinding, token string, ) (bool, error)
ReplaceTrafficTokenIfMatch patches the inbound credential only while the stored binding still names expected's provider and sandbox.
func (*RedisSessionSandboxBindingStore) TryLockRewind ¶
func (s *RedisSessionSandboxBindingStore) TryLockRewind( ctx context.Context, key SessionSandboxKey, ) (func(), error)
TryLockRewind takes a distributed exclusive rewind lock for key. The lock fails if a chat-turn lease already exists, and is renewed until the returned unlock runs so a slow reset+cleanup cannot expire it.
func (*RedisSessionSandboxBindingStore) TurnState ¶
func (s *RedisSessionSandboxBindingStore) TurnState( ctx context.Context, key SessionSandboxKey, ) (bool, bool, error)
TurnState reports whether a chat turn is open and whether its first resolve may still rebuild a stale sandbox. Live background work counts as an open turn that may not rebuild: it runs outside any turn, and a resolve that replaced the sandbox under it would destroy it.
func (*RedisSessionSandboxBindingStore) WithLifecycleLock ¶
func (s *RedisSessionSandboxBindingStore) WithLifecycleLock( ctx context.Context, key SessionSandboxKey, fn func(context.Context) error, ) error
WithLifecycleLock serializes create, recover, replace, and delete transitions across all WeKnora processes sharing Redis.
type RemoteConnectRequest ¶
RemoteConnectRequest re-attaches to an existing sandbox. TrafficAccessToken is the inbound credential recovered from the session binding; adapters apply it when the provider's connect response omits it, which is the normal case because the token is only ever issued at create time.
type RemoteCreateRequest ¶
type RemoteCreateRequest struct {
// TemplateID references the pre-baked sandbox template. Required.
TemplateID string
// FromSnapshot is set when TemplateID is a skill or fork snapshot rather
// than a base template. Aliyun Agent Sandbox rejects create-from-snapshot
// requests that resend env vars or function-config overrides.
FromSnapshot bool
// Timeout controls the idle-timeout policy.
Timeout RemoteTimeoutPolicy
// Metadata is a small key/value bag the provider stores alongside the
// sandbox. WeKnora uses this to recover ownership of stray sandboxes
// after a restart. Adapters that cannot persist metadata return
// RemoteErrorKindUnsupported when non-empty metadata is supplied.
Metadata map[string]string
// EnvVars is baked into the sandbox at creation time. Optional.
EnvVars map[string]string
// Network is the neutral outbound-network policy for the new sandbox.
// A zero-value policy asks the adapter to apply the provider's default
// behaviour (see RemoteNetworkPolicy for the exact semantics).
Network RemoteNetworkPolicy
// VolumeMounts specifies volumes to mount into the sandbox at creation
// time. Optional. When non-empty, the adapter maps each entry to the
// provider-native volume mount API (e2b.VolumeMount for E2B, etc.).
VolumeMounts []RemoteVolumeMount
}
RemoteCreateRequest holds the neutral parameters for spawning a new sandbox.
type RemoteCubeEgressRule ¶
type RemoteCubeEgressRule struct {
Name string
Scheme string
SNI string
Host string
Methods []string
Path string
Allow bool
Audit string
Inject []RemoteHeaderInject
}
RemoteCubeEgressRule is one CubeEgress L7 rule in neutral form. Allow is phrased positively here even though the stored config says Deny: adapters map onto provider payloads whose field is also an allow flag, and having the negation happen exactly once (in ResolveEffectiveConfig) is what keeps a double negative from creeping in.
type RemoteDesktopManager ¶
type RemoteDesktopManager interface {
// DialDesktop opens a WebSocket to websockify inside the sandbox behind
// handle. The returned connection carries raw RFB bytes in binary frames.
DialDesktop(ctx context.Context, handle RemoteSandboxHandle, opts RemoteDesktopOptions) (*websocket.Conn, error)
}
RemoteDesktopManager is the optional capability interface for backends that can relay a WebSocket to the desktop port of a running sandbox. Mirrors RemoteTerminalManager: advertised via RemoteSandboxCapabilities (SupportsDesktop) and narrowed with DesktopManagerFrom.
func DesktopManagerFrom ¶
func DesktopManagerFrom(client RemoteSandboxClient) (RemoteDesktopManager, bool)
DesktopManagerFrom narrows a client to its desktop capability.
Both signals must agree, exactly as in TerminalManagerFrom: the type assertion finds the method, and SupportsDesktop is the advertised capability. Either one alone is not enough — a decorator can carry the method while the inner backend cannot serve it.
type RemoteDesktopOptions ¶
type RemoteDesktopOptions struct {
// BasicAuthUser / BasicAuthPassword authenticate against websockify's
// BasicHTTPAuth plugin. The password is read out of the sandbox at
// DesktopSecretPath and MUST NOT be sent to the browser: it is a
// backend-to-sandbox credential only.
// An empty user falls back to DesktopBasicAuthUser.
BasicAuthUser string
BasicAuthPassword string
}
RemoteDesktopOptions carries the neutral parameters for dialling a desktop.
type RemoteDesktopTTLRefresher ¶
type RemoteDesktopTTLRefresher interface {
StartDesktopTTLRefresh(ctx context.Context, handle RemoteSandboxHandle)
}
RemoteDesktopTTLRefresher extends a desktop backend with provider idle-TTL refresh. DialDesktop must not start that loop: its ctx is the HTTP request, which is cancelled on handshake abort. The WebSocket handler starts refresh on the relay ctx (WithoutCancel) after upgrade, matching OpenTerminal.
func DesktopTTLRefresherFrom ¶
func DesktopTTLRefresherFrom(client RemoteSandboxClient) (RemoteDesktopTTLRefresher, bool)
DesktopTTLRefresherFrom narrows a client to desktop TTL refresh.
SupportsTimeoutRefresh is the advertised capability (Docker is false: it has no SetTimeout). The type assertion finds the method through wrappers such as Langfuse, the same two-signal pattern as DesktopManagerFrom.
type RemoteDesktopTemplateCatalog ¶
type RemoteDesktopTemplateCatalog interface {
// EnsureDesktopTemplate returns the cluster's WeKnora desktop template,
// building it when absent.
EnsureDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
// ReplaceDesktopTemplate applies the current spec to the desktop
// template. Callers persist a READY replacement first, then
// DeleteSupersededDesktopTemplates.
ReplaceDesktopTemplate(ctx context.Context) (*RemoteTemplate, error)
// DeleteSupersededDesktopTemplates removes desktop templates other than
// keepID after that ID is spawnable and stored on the config.
DeleteSupersededDesktopTemplates(ctx context.Context, keepID string) error
}
RemoteDesktopTemplateCatalog is the desktop sibling of RemoteTemplateCatalog. Cube and E2B implement it; Docker does not (SupportsDesktop stays false).
type RemoteDirEntry ¶
type RemoteDirEntry struct {
Name string
Path string
Type RemoteDirEntryType
Size int64
ModTime time.Time
}
RemoteDirEntry describes one entry inside a directory listing.
func EntriesFromStamps ¶ added in v0.9.0
func EntriesFromStamps(stamps []OutputFileStamp) []RemoteDirEntry
EntriesFromStamps restores stamps for the same comparison shell_exec uses.
type RemoteDirEntryType ¶
type RemoteDirEntryType string
RemoteDirEntryType is the coordinator-facing entry kind.
const ( RemoteEntryFile RemoteDirEntryType = "file" RemoteEntryDir RemoteDirEntryType = "dir" // RemoteEntryOther covers symlinks, sockets, devices, etc. WeKnora // artifact code treats these as opaque and skips them. RemoteEntryOther RemoteDirEntryType = "other" )
type RemoteE2BHostRule ¶
RemoteE2BHostRule is one E2B per-host request transform.
type RemoteError ¶
type RemoteError struct {
// Kind classifies the failure for coordinator decisions.
Kind RemoteErrorKind
// Provider identifies which backend produced the error, for logging.
Provider RemoteProvider
// Op names the RemoteSandboxClient operation (e.g. "Create", "Exec").
Op string
// Message is a human-readable summary. Provider-specific status codes
// or SDK error strings belong here.
Message string
// StatusCode is the provider's HTTP status when the failure came from an
// HTTP response, or 0 when it did not. Kind deliberately collapses statuses
// that call for the same lifecycle decision, so diagnostics that must tell
// those statuses apart read this instead of re-parsing Message.
StatusCode int
// Cause is the original provider-side error, retained for
// errors.Unwrap so callers can still errors.Is / errors.As it.
Cause error
}
RemoteError is the wire-agnostic error type returned by every RemoteSandboxClient method.
func NewRemoteError ¶
func NewRemoteError(provider RemoteProvider, op string, kind RemoteErrorKind, message string, cause error) *RemoteError
NewRemoteError builds a RemoteError. Convenience for adapters.
func (*RemoteError) Error ¶
func (e *RemoteError) Error() string
Error implements the error interface.
func (*RemoteError) Unwrap ¶
func (e *RemoteError) Unwrap() error
Unwrap exposes the wrapped provider-native error for errors.Is / errors.As.
type RemoteErrorKind ¶
type RemoteErrorKind string
RemoteErrorKind is the stable, provider-neutral classification of a RemoteSandboxClient failure.
const ( // RemoteErrorKindNotFound: sandbox / file / path does not exist. RemoteErrorKindNotFound RemoteErrorKind = "not_found" // RemoteErrorKindTerminal: sandbox is permanently gone (killed, // crashed, terminated). Semantically equivalent to NotFound for // lifecycle purposes but distinct for observability. RemoteErrorKindTerminal RemoteErrorKind = "terminal" // RemoteErrorKindAuthentication: bad credentials, expired token, etc. RemoteErrorKindAuthentication RemoteErrorKind = "authentication" // RemoteErrorKindInvalidRequest: caller-side error (bad template ID, // bad path, oversized payload). Not retryable. RemoteErrorKindInvalidRequest RemoteErrorKind = "invalid_request" // RemoteErrorKindUnsupported: provider does not implement the requested // capability (e.g. metadata, "never" timeout). RemoteErrorKindUnsupported RemoteErrorKind = "unsupported" // RemoteErrorKindConflict: concurrent modification (e.g. sandbox state // changed under us, template being built). RemoteErrorKindConflict RemoteErrorKind = "conflict" // RemoteErrorKindCapacity: quota / rate-limit / out-of-capacity. RemoteErrorKindCapacity RemoteErrorKind = "capacity" // RemoteErrorKindTimeout: request exceeded its deadline before the // provider responded. Distinct from execution timeout, which is a // normal RemoteExecResult with Killed=true. RemoteErrorKindTimeout RemoteErrorKind = "timeout" // error, control-plane unreachable). RemoteErrorKindUnavailable RemoteErrorKind = "unavailable" // RemoteErrorKindInternal: catch-all for unclassified failures. // Adapters SHOULD narrow this to a more specific kind when possible. RemoteErrorKindInternal RemoteErrorKind = "internal" )
type RemoteExecRequest ¶
type RemoteExecRequest struct {
// OnOutput receives stdout/stderr chunks while the command runs. It is an
// observation hook only; callers must not retain the supplied bytes.
OnOutput func(stream string, chunk []byte) `json:"-"`
// Command is the executable name (Shell=false) or the shell expression
// (Shell=true).
Command string
// Args are argv[1:] when Shell=false; must be empty when Shell=true.
Args []string
// Shell selects between direct exec (false) and shell interpretation
// (true). RemoteSandboxClient implementations must reject requests that
// combine Shell=true with a non-empty Args.
Shell bool
// Stdin is written to the process before it starts reading.
Stdin string
// Env is merged into the process environment.
Env map[string]string
// WorkDir is the process working directory. Empty means "provider
// default".
WorkDir string
// User is the OS user the process runs as. Empty means "provider
// default". Both backends support selecting it (E2B WithUser, Cube
// CommandOptions.User).
//
// The default is root: each chat session owns its own sandbox
// (one-session-one-sandbox, single tenant), so there is no shared-volume
// tenant boundary inside a sandbox for file-mode isolation to defend.
// Cross-tenant and host isolation live at the container boundary, not in
// the exec account. Callers that still rely on in-container filesystem
// permissions should not treat mode bits as a root isolation boundary.
// Enforce read-only access at the mount level instead.
User string
// Timeout bounds a single exec call. Zero means "use provider default".
Timeout time.Duration
}
RemoteExecRequest describes a single command invocation. See the RemoteSandboxClient.Exec contract for how Shell interacts with Args.
type RemoteExecResult ¶
type RemoteExecResult struct {
Stdout string
Stderr string
ExitCode int
Duration time.Duration
Killed bool
}
RemoteExecResult is the neutral shape returned by Exec.
type RemoteHeaderInject ¶
RemoteHeaderInject is one credential header injected by the egress proxy. Format defaults to "${SECRET}" provider-side when empty.
type RemoteInboundTokenCarrier ¶
type RemoteInboundTokenCarrier interface {
TrafficAccessToken() string
}
RemoteInboundTokenCarrier is implemented by handles whose provider issues a per-sandbox inbound traffic token. Cube and E2B always do; Docker does not. It is an optional capability in the same spirit as RemoteSnapshotManager.
type RemoteListFilter ¶
type RemoteListFilter struct {
// Metadata: only return sandboxes whose metadata contains all these
// key/value pairs. Adapters that cannot filter server-side may filter
// client-side and MUST return the same set.
Metadata map[string]string
// States restricts the response to the given normalized states. Empty
// means "any state".
States []RemoteSandboxState
}
RemoteListFilter narrows a List call. Empty fields mean "no filter".
type RemoteNetworkPolicy ¶
type RemoteNetworkPolicy struct {
AllowInternetAccess *bool
AllowPublicTraffic *bool
AllowOut []string
DenyOut []string
// CubeRules / E2BHostRules are the provider-specific L7 extensions. Each
// adapter consumes its own and ignores the other's, so this type stays a
// superset rather than a lowest common denominator.
CubeRules []RemoteCubeEgressRule
E2BHostRules []RemoteE2BHostRule
}
RemoteNetworkPolicy is the provider-neutral outbound-network policy for a new sandbox. The fields both Cube and E2B accept are shared; the two L7 extensions they do not share are carried here as separate slices rather than merged, because their shapes have nothing in common. Each adapter consumes its own and ignores the other's. Extensions with no admin-facing surface yet (egress proxies, masked host names) still stay in the adapters.
Field semantics match the underlying providers:
- AllowInternetAccess: top-level egress switch. Both adapters materialise nil as true rather than leaving it unset, because the provider default is the template's rather than the protocol's. ResolveEffectiveConfig always fills it, so nil reaches an adapter only from a hand-built Config. When false the sandbox has no default egress; specific hosts must appear in AllowOut.
- AllowPublicTraffic: whether the sandbox is reachable from the public internet by URL. ResolveEffectiveConfig always sets this to false (credential required). nil reaches an adapter only from a hand-built Config and is materialised as false there too. true is leftover for tests; production create never sends it.
- AllowOut / DenyOut: CIDR blocks or domain names. Cube treats these as L3/L4 filters; E2B applies domain-level filtering only on HTTP(S). Both providers require that specific domain allow-lists be paired with a deny-all; types.ValidateSandboxNetworkPolicy enforces that at save time, so the adapters never see the provider-native error.
func (RemoteNetworkPolicy) DeniesEgressByDefault ¶
func (p RemoteNetworkPolicy) DeniesEgressByDefault() bool
DeniesEgressByDefault reports whether outbound traffic falls back to deny, leaving only AllowOut (and L7 rule targets) reachable.
It accepts both spellings the drawer and the validator accept: the top-level switch turned off, or a deny-all entry in DenyOut. A caller that checks only AllowInternetAccess misreads a strict-but-valid config as an open one, which is how the deep connectivity check came to report a correct deny-all policy as a hard egress failure.
type RemoteProvider ¶
type RemoteProvider = SandboxType
RemoteProvider identifies a remote sandbox backend. Values match the stored SandboxType strings so wiring/logging stays uniform.
type RemoteSandbox ¶
type RemoteSandbox struct {
// contains filtered or unexported fields
}
RemoteSandbox implements Sandbox on top of a RemoteSandboxClient. It is intentionally stateless: no local mutexes, no cached handles. Higher layers decide whether a call allocates a fresh sandbox or reuses an existing one.
func NewRemoteSandbox ¶
func NewRemoteSandbox(client RemoteSandboxClient, createRequest RemoteCreateRequest) *RemoteSandbox
NewRemoteSandbox builds a stateless RemoteSandbox. createRequest is the template used for ephemeral Execute calls. Callers that only use ExecuteOnHandle may pass a zero createRequest.
func (*RemoteSandbox) Cleanup ¶
func (s *RemoteSandbox) Cleanup(context.Context) error
Cleanup is a no-op: RemoteSandbox holds no long-lived state. Session cleanup lives in SessionBoundManager, which owns the authoritative binding.
func (*RemoteSandbox) Execute ¶
func (s *RemoteSandbox) Execute(ctx context.Context, cfg *ExecuteConfig) (*ExecuteResult, error)
Execute allocates a fresh sandbox, runs cfg's script, and always tears the sandbox down. It is the ephemeral path used when a caller provides no SessionID.
func (*RemoteSandbox) ExecuteOnHandle ¶
func (s *RemoteSandbox) ExecuteOnHandle( ctx context.Context, handle RemoteSandboxHandle, cfg *ExecuteConfig, ) (*ExecuteResult, error)
ExecuteOnHandle runs cfg's script on an existing handle; uploads land in /workspace and on-disk scripts run from there.
func (*RemoteSandbox) ExecuteOnHandleIn ¶ added in v0.9.0
func (s *RemoteSandbox) ExecuteOnHandleIn( ctx context.Context, handle RemoteSandboxHandle, cfg *ExecuteConfig, workDir, scriptDir string, ) (*ExecuteResult, error)
ExecuteOnHandleIn is ExecuteOnHandle with explicit directories. A shared machine passes the session root and a script directory inside it, so two sessions uploading run.py never overwrite each other.
func (*RemoteSandbox) IsAvailable ¶
func (s *RemoteSandbox) IsAvailable(ctx context.Context) bool
IsAvailable probes the provider's control plane. Returns true only when Health succeeds within ctx.
func (*RemoteSandbox) Type ¶
func (s *RemoteSandbox) Type() SandboxType
Type mirrors the underlying provider so callers can gate provider-specific behaviour (see agent_service capability registration).
type RemoteSandboxCapabilities ¶
type RemoteSandboxCapabilities struct {
// SupportsReconnect is true when Connect can recover an operable handle
// from a provider-scoped sandbox ID after a WeKnora process restart.
SupportsReconnect bool
// SupportsMetadata is true when Create+List preserve the Metadata bag,
// enabling orphan-sandbox recovery after a WeKnora restart.
SupportsMetadata bool
// SupportsListSandboxes is true when List enumerates existing sandboxes
// (independent of metadata support).
SupportsListSandboxes bool
// SupportsPauseResume signals that idle sandboxes can be paused and
// resumed instead of destroyed. Purely informational for now; the
// current SessionBoundManager does not itself pause/resume.
SupportsPauseResume bool
// SupportsTimeoutRefresh indicates the provider can extend a sandbox's
// idle timeout after creation. Informational.
SupportsTimeoutRefresh bool
// SupportsFilesystemEnumeration is true when the provider implements
// ListDir, MakeDir, Stat, and Remove. SessionBoundManager only
// advertises the SessionFileStore capability when this is true — the
// application layer then knows to deregister list/read/attachment
// staging tools that would otherwise fail at request time.
SupportsFilesystemEnumeration bool
// SupportsSnapshots reports whether the provider can snapshot a running
// sandbox into a reusable image. Snapshot IDs double as template IDs on
// Cube, E2B, and Docker, which is what makes skill images work.
SupportsSnapshots bool
// SupportsVolumes is true when the provider can mount a named volume into
// a sandbox at creation time (RemoteCreateRequest.VolumeMounts). Callers
// use this to tell an operator up front that a backend cannot serve
// volume-based features, instead of failing later at first use.
SupportsVolumes bool
// SupportsTerminals is true when the provider can open interactive PTYs
// inside a running sandbox (RemoteTerminalManager). Callers use this to
// reject terminal features with an unsupported-backend error instead of
// failing after the WebSocket is upgraded.
SupportsTerminals bool
// SupportsDesktop is true when the provider can relay a WebSocket to a
// non-envd data-plane port inside the sandbox, which is what the VNC
// desktop needs (websockify on 6080). It is separate from
// SupportsTerminals: the terminal rides envd's PTY service, the desktop
// rides a raw port through the gateway. Docker is false — not because it
// cannot, but because it is not scheduled.
SupportsDesktop bool
}
RemoteSandboxCapabilities advertises which optional operations a client supports natively. SessionBoundManager reads this to skip provider-specific paths (e.g. metadata-based recovery) on backends that do not support them.
Missing metadata/list capabilities use less-optimal but correct behaviour (rely on the binding store instead of scanning provider metadata). SupportsReconnect is required for persistent session lifecycle management.
type RemoteSandboxClient ¶
type RemoteSandboxClient interface {
// Provider identifies the backend. Used by the binding schema to detect
// provider mismatches after a mode switch.
Provider() RemoteProvider
// Capabilities returns the static capability set of this client. It is
// safe to call before Health succeeds.
Capabilities() RemoteSandboxCapabilities
// Health probes the provider's control plane. Returns nil when reachable.
Health(ctx context.Context) error
// Create spawns a new sandbox and returns an opaque handle. The handle
// is owned by the caller; Delete must eventually be called.
Create(ctx context.Context, req RemoteCreateRequest) (RemoteSandboxHandle, error)
// Connect re-attaches to an already-running sandbox. Adapters that
// cannot support reconnect must return RemoteErrorKindUnsupported here
// and set SupportsReconnect=false.
Connect(ctx context.Context, req RemoteConnectRequest) (RemoteSandboxHandle, error)
// Get fetches a single sandbox summary by ID. Returns nil summary and
// RemoteErrorKindNotFound when the sandbox is gone.
Get(ctx context.Context, sandboxID string) (*RemoteSandboxSummary, error)
// List enumerates sandboxes visible to this client, optionally filtered.
List(ctx context.Context, filter RemoteListFilter) ([]RemoteSandboxSummary, error)
// Delete destroys a sandbox. Deleting a non-existent sandbox returns
// RemoteErrorKindNotFound; callers typically treat that as success.
Delete(ctx context.Context, sandboxID string) error
// Exec runs one command inside the sandbox. See RemoteExecRequest for
// the Shell/Args contract.
Exec(ctx context.Context, handle RemoteSandboxHandle, req RemoteExecRequest) (*RemoteExecResult, error)
WriteFile(ctx context.Context, handle RemoteSandboxHandle, path string, content []byte) error
ReadFile(ctx context.Context, handle RemoteSandboxHandle, path string) ([]byte, error)
ListDir(ctx context.Context, handle RemoteSandboxHandle, path string) ([]RemoteDirEntry, error)
// MakeDir creates path, including parents. A directory that already
// exists is success: envd's MakeDir is not mkdir -p, and adapters must
// hide that so writing a second file into the same folder (or seeding
// SKILL.md after resetSkillDir) does not fail.
MakeDir(ctx context.Context, handle RemoteSandboxHandle, path string) error
Remove(ctx context.Context, handle RemoteSandboxHandle, path string) error
Stat(ctx context.Context, handle RemoteSandboxHandle, path string) (*RemoteStatEntry, error)
}
RemoteSandboxClient is the contract SessionBoundManager talks to. All backends (Cube, E2B, ...) must satisfy this interface via a thin adapter.
Concurrency: implementations MUST be safe for concurrent use.
Cancellation: every method must honour ctx.Done. Cancellation returns a RemoteError whose Kind is RemoteErrorKindTimeout when the deadline elapsed server-side, or the wrapped ctx.Err() otherwise.
func NewRemoteClientForCheck ¶
func NewRemoteClientForCheck(cfg *Config) (RemoteSandboxClient, error)
NewRemoteClientForCheck builds a throwaway client for connectivity probes. It is never cached and never handed to a manager.
Both providers get a guarded transport. The endpoints being probed come straight off an admin's unsaved form, so save-time ValidateOutboundURL has not necessarily run against the address this client will actually dial, and even when it has, a hostname can resolve to a public address during validation and to 169.254.169.254 at dial time.
type RemoteSandboxHandle ¶
type RemoteSandboxHandle interface {
// ID returns the provider-scoped sandbox identifier.
ID() string
// Provider returns the backend that issued this handle.
Provider() RemoteProvider
// Metadata returns the metadata originally recorded with the sandbox on
// creation, when the provider preserves it. Returns nil when the provider
// does not support metadata recovery.
Metadata() map[string]string
}
RemoteSandboxHandle is an opaque, provider-issued reference to a live sandbox. Adapters may wrap their SDK-specific object (e.g. *cubesandbox.Sandbox, *e2b.Sandbox) inside the concrete handle type; the manager only reads the stable identifiers exposed here.
type RemoteSandboxKeepAliver ¶ added in v0.9.0
type RemoteSandboxKeepAliver interface {
KeepAlive(ctx context.Context, handle RemoteSandboxHandle, ttl time.Duration) error
}
RemoteSandboxKeepAliver refreshes a sandbox's provider idle timer. Background work runs outside chat turns, so nothing else tells the provider the sandbox is still in use. Cube and E2B move their timeout to ttl from now; Docker runs a no-op exec, which refreshes the activity marker its idle sweeper reads.
type RemoteSandboxState ¶
type RemoteSandboxState string
RemoteSandboxState is the coordinator-facing lifecycle state.
const ( // RemoteStateRunning: sandbox is up and reachable. RemoteStateRunning RemoteSandboxState = "running" // RemoteStatePaused: sandbox is paused; resumable. RemoteStatePaused RemoteSandboxState = "paused" // RemoteStateTransitioning: sandbox is in a transient lifecycle state // (pausing, resuming, provisioning, ...). Treated as "still owned" by // SessionBoundManager but not immediately usable. RemoteStateTransitioning RemoteSandboxState = "transitioning" // RemoteStateTerminal: sandbox is gone. Bindings referencing this state // can be replaced. RemoteStateTerminal RemoteSandboxState = "terminal" // RemoteStateUnknown: adapter could not classify the raw state. Treated // as transient (do not replace the binding). RemoteStateUnknown RemoteSandboxState = "unknown" )
type RemoteSandboxSummary ¶
type RemoteSandboxSummary struct {
// ID is the provider-scoped sandbox identifier.
ID string
// TemplateID is the template the sandbox was created from.
TemplateID string
// State is the normalized lifecycle state. See RemoteSandboxState.
State RemoteSandboxState
// RawState is the provider-native state string, retained for diagnostics
// only. SessionBoundManager must not branch on RawState.
RawState string
// Metadata is the sandbox metadata bag. May be nil when the provider does
// not support metadata.
Metadata map[string]string
// StartedAt records when the sandbox was created; zero value when the
// provider does not report it.
StartedAt time.Time
// EndAt records when the sandbox was terminated; zero when unknown or
// still running.
EndAt time.Time
}
RemoteSandboxSummary is the neutral view of a sandbox listing / probe.
func ListConfigSandboxes ¶
func ListConfigSandboxes( ctx context.Context, client ConfigSandboxLister, tenantID uint64, configID string, ) ([]RemoteSandboxSummary, error)
ListConfigSandboxes returns the sandboxes a config currently owns.
type RemoteSessionConnector ¶
type RemoteSessionConnector interface {
ConnectSession(context.Context, RemoteConnectRequest) (RemoteSandboxHandle, error)
}
RemoteSessionConnector connects and checks lifecycle state using the same provider handle. It avoids Get (which itself connects on Cube/E2B) followed by another Connect. Terminal/missing sandboxes must return classified errors; transient failures must never authorize replacement of a session's sandbox.
type RemoteSnapshotManager ¶
type RemoteSnapshotManager interface {
// CreateSnapshot snapshots a running sandbox. An empty name lets the
// provider generate one. The provider pauses the sandbox while the
// snapshot is taken.
CreateSnapshot(ctx context.Context, sandboxID string, name string) (RemoteSnapshotRef, error)
// DeleteSnapshot removes a snapshot. A missing snapshot is NOT an error:
// both SDKs treat delete as idempotent and so must every adapter.
DeleteSnapshot(ctx context.Context, snapshotID string) error
// ListSnapshots lists snapshots. An empty sandboxID lists all of them.
ListSnapshots(ctx context.Context, sandboxID string) ([]RemoteSnapshotRef, error)
}
RemoteSnapshotManager is an optional capability used only by the skill install/remove paths. Session execution never touches it.
func SnapshotManagerFrom ¶
func SnapshotManagerFrom(client RemoteSandboxClient) (RemoteSnapshotManager, bool)
SnapshotManagerFrom narrows a client to its snapshot capability. It returns false for providers that cannot snapshot, so callers can fall back to the base template instead of failing.
Both signals must agree: the type assertion finds the methods, and SupportsSnapshots is the advertised capability. A wrapper that happens to embed snapshot methods must not be treated as snapshot-capable when the flag is off.
type RemoteSnapshotRef ¶
RemoteSnapshotRef identifies one provider-side snapshot. ID can be passed straight back as RemoteCreateRequest.TemplateID: Cube and E2B store snapshots as templates, and Docker stores them as local image tags.
type RemoteStatEntry ¶
type RemoteStatEntry struct {
Path string
Type RemoteDirEntryType
Size int64
ModTime time.Time
}
RemoteStatEntry is the neutral shape returned by Stat.
type RemoteTemplate ¶
type RemoteTemplate struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status,omitempty"`
Version string `json:"version,omitempty"`
Image string `json:"image,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
Standard bool `json:"standard"`
// Desktop marks the XFCE sibling of Standard. The two coexist in one
// cluster; the admin picks which ID this config boots. Not a second
// boot-target field on the config.
Desktop bool `json:"desktop,omitempty"`
// Error carries the provider's own explanation for a failed build. Without
// it a failed template is a red badge with no way to tell a registry
// credential problem from an out-of-disk node.
Error string `json:"error,omitempty"`
// Cube reports these on GET /templates; other backends leave them empty
// and the settings list simply omits the corresponding rows.
InstanceType string `json:"instance_type,omitempty"`
NetworkType string `json:"network_type,omitempty"`
AllowInternetAccess *bool `json:"allow_internet_access,omitempty"`
}
RemoteTemplate is the provider-neutral template projection returned to the settings UI. IDs remain opaque; users choose a readable name and status.
type RemoteTemplateCatalog ¶
type RemoteTemplateCatalog interface {
ListTemplates(ctx context.Context) ([]RemoteTemplate, error)
EnsureStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
// ReplaceStandardTemplate applies the current spec to the cluster's
// WeKnora template (DNS, image). A READY template cannot pick those up
// any other way. It must not delete a usable template: callers persist
// the replacement ID first, then DeleteSupersededStandardTemplates.
ReplaceStandardTemplate(ctx context.Context) (*RemoteTemplate, error)
// DeleteSupersededStandardTemplates removes WeKnora templates other than
// keepID. Call only after keepID is spawnable and has been written onto
// every config that still pointed at the previous standard template.
DeleteSupersededStandardTemplates(ctx context.Context, keepID string) error
}
RemoteTemplateCatalog is an optional provider capability used by the configuration flow. It stays separate from RemoteSandboxClient because the session lifecycle never needs template administration.
type RemoteTerminalEvent ¶
type RemoteTerminalEvent struct {
// Data is raw PTY output bytes. Nil for non-data events.
Data []byte
// Exited is true when the remote shell process terminated. ExitCode is
// meaningful only then.
Exited bool
// ExitCode is the shell's exit code (may be unavailable; -1 fallback).
ExitCode int
// Err is set when the stream ended abnormally (transport failure,
// provider error). A normal process exit leaves it nil.
Err error
}
RemoteTerminalEvent is one event on a terminal's output stream. The channel returned by RemoteTerminalSession.Output closes when the terminal session ends (process exit, disconnect, or provider error).
type RemoteTerminalManager ¶
type RemoteTerminalManager interface {
// OpenTerminal opens a new PTY inside the sandbox behind handle.
OpenTerminal(ctx context.Context, handle RemoteSandboxHandle, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
}
RemoteTerminalManager is the optional capability interface for backends that can open interactive PTYs inside a running sandbox. Mirrors RemoteSnapshotManager: advertised via RemoteSandboxCapabilities (SupportsTerminals) and narrowed with TerminalManagerFrom.
func TerminalManagerFrom ¶
func TerminalManagerFrom(client RemoteSandboxClient) (RemoteTerminalManager, bool)
TerminalManagerFrom narrows a client to its terminal capability. It returns false for providers that cannot stream PTYs, so callers can report an unsupported-backend error instead of failing mid-session.
Both signals must agree: the type assertion finds the methods, and SupportsTerminals is the advertised capability (see SnapshotManagerFrom for the rationale).
type RemoteTerminalOptions ¶
type RemoteTerminalOptions struct {
// Cols/Rows is the initial terminal size. Zero falls back to 80x24.
Cols uint32
Rows uint32
// Cwd is the shell's working directory. Empty means the sandbox
// default (/workspace).
Cwd string
// User is the account the shell runs as. Empty means
// DefaultSandboxExecUser.
User string
// Envs are extra environment variables. TERM is always forced to a
// 256-colour value by the adapters so full-screen programs work.
Envs map[string]string
// AttachPID, when non-zero, asks the provider to reattach to an
// already-running PTY instead of creating a new shell. A failed
// reattach falls back to Create.
AttachPID uint32
// AllowResume lets OpenSessionTerminal Connect (and therefore wake) a
// paused sandbox. Lookup-only opens leave this false so opening a
// panel cannot resume a paused microVM; the confirmed-create path
// sets it.
AllowResume bool
}
RemoteTerminalOptions carries the neutral parameters for opening a PTY. Adapters translate these into their provider SDK's equivalent knobs.
type RemoteTerminalSession ¶
type RemoteTerminalSession interface {
// Output streams PTY output events. The channel is closed exactly once
// when the terminal session ends; consumers must not send to it.
Output() <-chan RemoteTerminalEvent
// PID is the remote shell's process ID, when the provider exposes one.
// It doubles as the reattach handle: 0 means a reconnect cannot resume
// this shell and the browser must not redial on its own.
PID() uint32
// Write feeds raw bytes (keystrokes) into the PTY.
Write(ctx context.Context, data []byte) error
// Resize changes the PTY window size.
Resize(ctx context.Context, cols, rows uint32) error
// Close disconnects WeKnora from the PTY. A backend that can re-attach
// (Cube/E2B, via envd) leaves the remote shell running so a reconnect
// resumes it; a backend that cannot (Docker) terminates it, because the
// disconnect would otherwise strand the shell and its jobs. Safe to call
// more than once.
Close() error
}
RemoteTerminalSession is a neutral handle to one live PTY. Implementations MUST be safe for concurrent Write/Resize calls alongside the Output consumer goroutine.
type RemoteTimeoutAction ¶
type RemoteTimeoutAction string
RemoteTimeoutAction is the provider-side action taken when idle timeout elapses.
const ( // RemoteOnTimeoutPause pauses the sandbox and preserves its filesystem // state so it can be resumed later. RemoteOnTimeoutPause RemoteTimeoutAction = "pause" // RemoteOnTimeoutKill destroys the sandbox and releases all resources. RemoteOnTimeoutKill RemoteTimeoutAction = "kill" )
type RemoteTimeoutMode ¶
type RemoteTimeoutMode string
RemoteTimeoutMode describes how the remote provider should treat the requested idle timeout.
const ( // RemoteTimeoutServerDefault leaves the timeout unspecified so the // provider applies its configured default. RemoteTimeoutServerDefault RemoteTimeoutMode = "server" // RemoteTimeoutExplicit uses RemoteTimeoutPolicy.Value verbatim. A zero // value asks for immediate on-timeout action; a negative value asks for // "never" when the provider supports it (adapters return // RemoteErrorKindUnsupported otherwise). RemoteTimeoutExplicit RemoteTimeoutMode = "explicit" )
type RemoteTimeoutPolicy ¶
type RemoteTimeoutPolicy struct {
Mode RemoteTimeoutMode
Value time.Duration
Action RemoteTimeoutAction
// AutoResume asks the provider to resume a paused sandbox on the next
// Connect. Adapters that cannot honour this must return
// RemoteErrorKindUnsupported at Create time.
AutoResume bool
}
RemoteTimeoutPolicy is the provider-neutral timeout configuration.
type RemoteVolumeMount ¶
type RemoteVolumeMount struct {
// Name identifies the volume. Required.
Name string
// Path is the mount point inside the sandbox. Required.
Path string
}
RemoteVolumeMount describes a volume to mount into the sandbox at creation time. Both Cube and E2B support named-volume mounts, making this a provider-neutral concept. Each entry references a pre-created volume by Name and the sandbox-internal Path at which it should appear.
type ResolvedTenantSandboxConfig ¶
type ResolvedTenantSandboxConfig struct {
// Config is nil when configID was empty (use the global default).
Config *types.TenantSandboxConfig
// Found is false when a non-empty configID matched no row.
Found bool
// Cordoned is true while the config's credentials are being replaced.
Cordoned bool
}
ResolvedTenantSandboxConfig is what the loader reports about one config.
type Sandbox ¶
type Sandbox interface {
// Execute runs a script in an isolated environment
Execute(ctx context.Context, config *ExecuteConfig) (*ExecuteResult, error)
// Cleanup releases sandbox resources
Cleanup(ctx context.Context) error
// Type returns the sandbox type
Type() SandboxType
// IsAvailable checks if the sandbox is available for use
IsAvailable(ctx context.Context) bool
}
Sandbox defines the interface for isolated script execution
type SandboxGatewayTransportPool ¶
type SandboxGatewayTransportPool struct {
// contains filtered or unexported fields
}
SandboxGatewayTransportPool owns the transports handed to per-request remote clients. One instance lives for the process; clients built from it come and go.
func NewSandboxGatewayTransportPool ¶
func NewSandboxGatewayTransportPool(control http.RoundTripper) *SandboxGatewayTransportPool
NewSandboxGatewayTransportPool returns a pool whose control plane rides control. A nil control transport installs a guarded one.
func NewSandboxGatewayTransportPoolWithPolicy ¶
func NewSandboxGatewayTransportPoolWithPolicy( control http.RoundTripper, policy OutboundURLPolicy, ) *SandboxGatewayTransportPool
func (*SandboxGatewayTransportPool) InboundTokens ¶
func (p *SandboxGatewayTransportPool) InboundTokens() *InboundTokenRegistry
InboundTokens is the registry the data-plane transport consults to attach a sandbox's inbound credential. Adapters register on create / connect.
func (*SandboxGatewayTransportPool) RoundTripperFor ¶
func (p *SandboxGatewayTransportPool) RoundTripperFor(cfg *Config) http.RoundTripper
RoundTripperFor returns the transport a client built from cfg should use. Configs without a usable gateway URL keep every request on the control transport, matching the SDKs' behaviour when no gateway is configured.
func (*SandboxGatewayTransportPool) WebsocketDialerFor ¶
func (p *SandboxGatewayTransportPool) WebsocketDialerFor(cfg *Config) *WebsocketDialer
WebsocketDialerFor returns the dialer a client built from cfg must use to reach a sandbox data-plane port. It is the ws:// sibling of RoundTripperFor and MUST stay the only place that knows how: same gateway target, same SafeDialControlForPolicy guard, same inbound-token registry.
type SandboxIdentity ¶
type SandboxIdentity struct {
Provider string
AllowPrivateEndpoints bool
// Control plane - whether an existing sandbox can still be reclaimed.
APIURL string
APIKey string
// Data plane - whether an existing sandbox can still be used.
SandboxDomain string
ProxyURL string
}
SandboxIdentity is the comparable projection of a config: two configs with equal identities can operate each other's sandboxes.
func IdentityOf ¶
func IdentityOf(tenantCfg *types.TenantSandboxConfig) SandboxIdentity
IdentityOf projects a stored config onto its identity. Only the active provider's fields are read: a sub-struct left behind by an earlier provider switch says nothing about where today's sandboxes live.
Because named configs inherit nothing, the stored row is the whole picture and no deployment baseline is consulted. Unknown type strings are compared verbatim rather than rejected: a typo yields an identity that matches nothing, which errs toward refusing the edit, and ParseSandboxType reports it on save.
This runs no SSRF guard and returns no error on purpose. It answers "would this edit strand anything", a question that must stay answerable when the OLD endpoint no longer resolves — precisely the situation in which an admin needs to re-point the config. Validating the incoming URLs is the save path's job.
type SandboxType ¶
type SandboxType string
SandboxType represents the type of sandbox environment
const ( // SandboxTypeDocker runs each session in its own long-lived Docker // container, driven through the Docker Engine API. Like the MicroVM // backends it keeps session state between executions; unlike them it // shares the host kernel and lives on a single daemon. SandboxTypeDocker SandboxType = "docker" // SandboxTypeCube uses Tencent CubeSandbox (E2B-compatible) MicroVM for isolation. // Like Docker and E2B it keeps session-scoped persistent sandboxes: multiple // executions bound to the same SessionID share one instance and preserve // installed packages, created files, running services, etc. SandboxTypeCube SandboxType = "cube" // SandboxTypeE2B uses E2B's hosted MicroVM sandbox service. SandboxTypeE2B SandboxType = "e2b" // SandboxTypeHost runs commands on the user's own machine under // OS-enforced restrictions (macOS Seatbelt, Windows restricted token). // It is deliberately not named "local": the removed Local backend ran // bare host processes with no isolation at all, and reusing that name // would misrepresent this one. SandboxTypeHost SandboxType = "host" // SandboxTypeDisabled means script execution is disabled SandboxTypeDisabled SandboxType = "disabled" )
func ParseSandboxType ¶
func ParseSandboxType(raw string) (SandboxType, error)
ParseSandboxType maps a stored string onto a SandboxType. Unknown values are rejected so a typo surfaces when the admin saves the config, instead of silently disabling that tenant's sandbox at first use.
type ScriptValidator ¶
type ScriptValidator struct {
// contains filtered or unexported fields
}
ScriptValidator validates scripts and arguments for security
func NewScriptValidator ¶
func NewScriptValidator() *ScriptValidator
NewScriptValidator creates a new validator with default security rules
func (*ScriptValidator) ValidateAll ¶
func (v *ScriptValidator) ValidateAll(scriptContent string, args []string, stdin string) *ValidationResult
ValidateAll performs comprehensive validation on script, args, and stdin
func (*ScriptValidator) ValidateArgs ¶
func (v *ScriptValidator) ValidateArgs(args []string) *ValidationResult
ValidateArgs validates command-line arguments for injection attempts
func (*ScriptValidator) ValidateScript ¶
func (v *ScriptValidator) ValidateScript(content string) *ValidationResult
ValidateScript validates script content for dangerous patterns
func (*ScriptValidator) ValidateStdin ¶
func (v *ScriptValidator) ValidateStdin(stdin string) *ValidationResult
ValidateStdin validates stdin content for injection attempts
type SessionBootstrapper ¶
type SessionBootstrapper interface {
// TemplateOverride returns a template ID to create this session's sandbox
// from instead of the config's. An empty string means "no override".
//
// An error aborts the create: an implementation that cannot tell whether
// an override applies must not be silently treated as "no override",
// because that would boot a plain sandbox for a session the user expects
// to carry forked state.
TemplateOverride(ctx context.Context, key SessionSandboxKey) (string, error)
// AfterCreate runs once, immediately after the binding is written.
//
// Returning an error makes the lifecycle destroy the sandbox and delete
// the binding. That severity is deliberate: a half-bootstrapped fork looks
// completely normal but has the wrong baseline, and that silent wrongness
// is far worse than an honest failure the next resolve can retry from a
// clean slate.
AfterCreate(ctx context.Context, key SessionSandboxKey, handle RemoteSandboxHandle) error
}
SessionBootstrapper customises how ONE session's first sandbox is created. It exists for session fork: a forked session boots from a snapshot of its parent's sandbox and then rolls /workspace back to the fork point.
It is optional. A nil bootstrapper leaves the lifecycle's behaviour unchanged, which is what every ordinary session gets.
The lifecycle calls both methods inside the per-session lifecycle lock, so implementations need no locking of their own and cannot race with a concurrent resolve of the same session.
type SessionBootstrapperWithClient ¶
type SessionBootstrapperWithClient interface {
WithClient(client RemoteSandboxClient) SessionBootstrapper
}
SessionBootstrapperWithClient rebinds a bootstrapper to the client that created the sandbox handle. AfterCreate runs under the session lifecycle lock, which is not reentrant, so implementations must talk to `client` (and the handle) directly instead of calling Resolve.
type SessionBoundManager ¶
type SessionBoundManager struct {
// contains filtered or unexported fields
}
SessionBoundManager is a sandbox.Manager that binds one remote sandbox per tenant session. Concrete provider work is delegated to RemoteSandboxClient; this type owns validation and the mapping between application concepts (ExecuteConfig, session-scoped shell/file APIs) and the provider-neutral RemoteSandboxClient contract.
func NewSessionBoundManager ¶
func NewSessionBoundManager(deps SessionBoundManagerConfig) (*SessionBoundManager, error)
NewSessionBoundManager wires the manager with an explicit RemoteSandboxClient backend, binding store, and session existence checker. Every persistent operation flows through these three dependencies; the manager never keeps authoritative session→sandbox state locally.
Provider identity comes from deps.Client.Provider() — not Config.Type — so test harnesses and custom wiring that inject a different client backend always project the correct template, TTL, and health timeout.
func (*SessionBoundManager) AcquireSessionExclusive ¶ added in v0.9.0
func (m *SessionBoundManager) AcquireSessionExclusive( ctx context.Context, sessionID string, ) (context.Context, func(), error)
AcquireSessionExclusive is acquireExclusive for one user-mode session. Use the returned context for the exclusive work; it is cancelled with admission.ErrLeaseLost if the window is lost. Call the function to end the window. Session-mode sessions get ctx back unchanged.
func (*SessionBoundManager) BeginSessionTurn ¶
func (m *SessionBoundManager) BeginSessionTurn(ctx context.Context, sessionID string) (context.Context, func(), error)
BeginSessionTurn opens one chat-turn hold for sessionID. The first resolve after this may rebuild a stale image; later resolves of the same turn keep the sandbox. end closes exactly this hold.
func (*SessionBoundManager) BeginWorkspaceFork ¶ added in v0.9.0
func (m *SessionBoundManager) BeginWorkspaceFork(ctx context.Context, sourceSessionID string) (WorkspaceFork, error)
BeginWorkspaceFork opens a fork of sourceSessionID on its shared machine. It never creates the machine: a source without one has nothing to copy.
func (*SessionBoundManager) BoundSandboxID ¶
func (m *SessionBoundManager) BoundSandboxID( ctx context.Context, sessionID string, ) (string, bool)
BoundSandboxID returns the ID of the sandbox currently bound to sessionID. It never provisions and never Connects: a session with no live binding reports ok=false, which callers treat as "nothing to check point".
func (*SessionBoundManager) Cleanup ¶
func (m *SessionBoundManager) Cleanup(_ context.Context) error
Cleanup marks the manager closed. Session sandboxes are not force-deleted here: their lifecycle is authoritative in the binding store and would leak to any other WeKnora replica if this replica reaped them on shutdown. Providers reclaim idle sandboxes via their own timeout/pause policies.
func (*SessionBoundManager) CreateForkSnapshot ¶
func (m *SessionBoundManager) CreateForkSnapshot( ctx context.Context, sessionID, name string, ) (string, error)
CreateForkSnapshot snapshots the session's already-bound sandbox. It never provisions: an unbound session or a backend without snapshots returns an error so fork can degrade.
func (*SessionBoundManager) CreateSnapshot ¶
func (m *SessionBoundManager) CreateSnapshot( ctx context.Context, sessionID string, name string, ) (RemoteSnapshotRef, error)
CreateSnapshot forwards provider snapshot creation for the live sandbox bound to sessionID. Session execution never uses this optional capability; it is reserved for skill image maintenance.
func (*SessionBoundManager) DeleteForkSnapshot ¶
func (m *SessionBoundManager) DeleteForkSnapshot( ctx context.Context, _, snapshotID string, ) error
DeleteForkSnapshot removes a fork snapshot. sessionID is accepted so the method matches SessionForkSandboxPort; deletion is by snapshot ID.
func (*SessionBoundManager) DeleteSessionWorkspaces ¶ added in v0.9.0
func (m *SessionBoundManager) DeleteSessionWorkspaces( ctx context.Context, machine allocation.UserMachine, sessionIDs []string, ) ([]string, error)
DeleteSessionWorkspaces removes the work trees and checkpoint repositories of retired sessions from their shared machine. It registers on the machine first, then takes each session's scope exclusively so work admitted before the retirement finishes before its directory goes; a session whose operations do not leave in time is skipped and reported as not deleted. Every hold is bound into the command's context, so a lost hold stops the deletion (C3). A machine whose binding was lost is re-adopted by metadata, never created; only a provider that lists nothing proves it is gone (C5). Connect resumes a paused machine: a deleted session's files must not linger on its disk.
func (*SessionBoundManager) DeleteSnapshot ¶
func (m *SessionBoundManager) DeleteSnapshot(ctx context.Context, snapshotID string) error
DeleteSnapshot forwards provider snapshot deletion. The skill install path uses it to abandon an orphan when the pointer switch fails; the reaper uses it to prune superseded snapshots that have aged past retention.
func (*SessionBoundManager) DesktopMachine ¶ added in v0.9.0
func (m *SessionBoundManager) DesktopMachine(ctx context.Context, sessionID string) (string, string, error)
DesktopMachine names the physical machine behind sessionID's desktop and the sandbox it currently is. Sessions on one shared machine contend for one control slot; a rebuilt machine, or a sandbox of another deployment, provider or config, never shares a key. Call it while holding the session's operation so the binding cannot change underneath the caller.
func (*SessionBoundManager) DestroySession ¶
func (m *SessionBoundManager) DestroySession(ctx context.Context, sessionID string) error
DestroySession removes the remote sandbox bound to sessionID (if any) and the authoritative binding. Idempotent: succeeds on absent sessions.
func (*SessionBoundManager) EnsureSessionDir ¶
func (m *SessionBoundManager) EnsureSessionDir(ctx context.Context, sessionID, dir string) error
EnsureSessionDir creates dir inside the session's live sandbox when one is bound. It is a no-op when the session has no live binding; the skill framework will materialise the directory during the next Execute call.
func (*SessionBoundManager) ExecShellCommand ¶
func (m *SessionBoundManager) ExecShellCommand( ctx context.Context, sessionID string, command string, workDir string, timeout time.Duration, env map[string]string, ) (*ExecuteResult, error)
ExecShellCommand runs a shell one-liner inside the session's persistent sandbox. work_dir may name any absolute directory inside that sandbox.
func (*SessionBoundManager) ExecShellCommandWithOptions ¶
func (m *SessionBoundManager) ExecShellCommandWithOptions( ctx context.Context, sessionID string, command string, opts ShellExecOptions, ) (*ExecuteResult, error)
ExecShellCommandWithOptions runs a shell command with install-only options. Fallback is explicitly refused so even privileged installer calls never escape onto the WeKnora host machine.
func (*SessionBoundManager) ExecShellCommandWithOutputSnapshot ¶
func (m *SessionBoundManager) ExecShellCommandWithOutputSnapshot( ctx context.Context, sessionID, command string, opts ShellExecOptions, outputDir string, ) (*ExecuteResult, *ShellOutputSnapshot, error)
ExecShellCommandWithOutputSnapshot resolves the session once for the command and both artifact probes. Handles never escape this call or survive a rebuild.
func (*SessionBoundManager) Execute ¶
func (m *SessionBoundManager) Execute(ctx context.Context, cfg *ExecuteConfig) (*ExecuteResult, error)
Execute is the shared entry point used by the DefaultManager compatibility layer, the skills manager, and the ephemeral tool path. It applies script security validation, then dispatches to the session-bound path (non-empty SessionID) or the ephemeral path.
func (*SessionBoundManager) GetSandbox ¶
func (m *SessionBoundManager) GetSandbox() Sandbox
GetSandbox exposes a diagnostic Sandbox for callers that need to inspect availability. Returns a stateless RemoteSandbox surface for the current provider.
func (*SessionBoundManager) GetType ¶
func (m *SessionBoundManager) GetType() SandboxType
GetType reports the current effective sandbox type.
func (*SessionBoundManager) HasActiveTurn ¶
HasActiveTurn reports whether an agent turn currently holds the session's sandbox lease. A store without turn-lease support is treated as not busy so fork can proceed.
func (*SessionBoundManager) HasRewindLock ¶
HasRewindLock reports whether rewind currently holds sessionID.
func (*SessionBoundManager) HoldSessionOperation ¶ added in v0.9.0
func (m *SessionBoundManager) HoldSessionOperation( ctx context.Context, sessionID, kind string, ) (context.Context, func(), error)
HoldSessionOperation registers a long-lived operation (desktop relay) of sessionID; ctx is cancelled if the lease is lost. A no-op for per-session machines.
func (*SessionBoundManager) InvalidateConfigSandboxes ¶
func (m *SessionBoundManager) InvalidateConfigSandboxes( ctx context.Context, tenantID uint64, configID string, ) (int, error)
InvalidateConfigSandboxes marks every session sandbox this config owns stale, so each session rebuilds its sandbox from the config's current image on its next use, and reports how many bindings were marked.
It is the image-maintenance counterpart to DestroySession: nothing is torn down here, so marking cannot delete a sandbox that is executing right now. The replacement happens at the session's next resolve, which may be the next operation of a turn already in flight; see resolveLocked for that limitation.
func (*SessionBoundManager) InvalidateConfigSandboxesForMachines ¶ added in v0.9.0
func (m *SessionBoundManager) InvalidateConfigSandboxesForMachines( ctx context.Context, configID string, machines []allocation.UserMachine, ) (int, error)
InvalidateConfigSandboxesForMachines marks the listed user machines stale.
func (*SessionBoundManager) KeepAliveSession ¶ added in v0.9.0
func (m *SessionBoundManager) KeepAliveSession(ctx context.Context, sessionID string) (time.Duration, error)
KeepAliveSession refreshes the provider idle timer of the sandbox bound to sessionID and returns how long the caller may wait before the next refresh. It is lookup-only: it never provisions, never rebuilds and never connects to anything but the bound sandbox, so a session with nothing bound, or a provider with nothing to refresh, returns (0, nil).
func (*SessionBoundManager) KeepAliveSessionExpected ¶ added in v0.9.0
func (m *SessionBoundManager) KeepAliveSessionExpected( ctx context.Context, sessionID, expectedSandboxID string, ) (time.Duration, error)
KeepAliveSessionExpected is KeepAliveSession for callers that selected a specific sandbox. Lookup is only against expectedSandboxID: a missing binding or a different sandbox returns (0, nil) and never allocates.
func (*SessionBoundManager) ListSessionFiles ¶
func (m *SessionBoundManager) ListSessionFiles( ctx context.Context, sessionID, dir string, ) ([]RemoteDirEntry, error)
ListSessionFiles walks dir under the session's live sandbox recursively. Returns nil (no error) when the session has no bound sandbox so callers can treat "no sandbox" and "empty output" uniformly.
func (*SessionBoundManager) ListSnapshots ¶
func (m *SessionBoundManager) ListSnapshots( ctx context.Context, sandboxID string, ) ([]RemoteSnapshotRef, error)
ListSnapshots forwards provider snapshot listing for audit and later cleanup tasks.
func (*SessionBoundManager) LockRewindSession ¶ added in v0.9.0
func (m *SessionBoundManager) LockRewindSession( ctx context.Context, sessionID string, ) (context.Context, func(), error)
LockRewindSession takes the session's rewind lock and, on a shared machine, the session's exclusive window, so no other operation of the session touches its work tree during the reset. Run the reset with the returned context. On a per-session sandbox the context is ctx itself.
func (*SessionBoundManager) Namespace ¶ added in v0.9.0
func (m *SessionBoundManager) Namespace() string
Namespace is the deployment namespace user machine digests use.
func (*SessionBoundManager) OpenSessionDesktop ¶
func (m *SessionBoundManager) OpenSessionDesktop( ctx context.Context, sessionID string, opts RemoteDesktopOptions, ) (*SessionDesktopConn, error)
OpenSessionDesktop dials the desktop of the sandbox currently bound to the session.
Unlike OpenSessionTerminal there is no AllowResume branch: by the time this runs, SandboxDesktopService has already driven the same resolveSandboxForExecution path a chat turn uses, so the sandbox is live and — critically — its inbound token is registered on THIS replica. Reaching here with no binding means the sandbox went away in between, which is an error, not a reason to provision.
func (*SessionBoundManager) OpenSessionTerminal ¶
func (m *SessionBoundManager) OpenSessionTerminal( ctx context.Context, sessionID string, opts RemoteTerminalOptions, ) (RemoteTerminalSession, error)
OpenSessionTerminal opens a PTY on the sandbox currently bound to the session. It is strictly lookup-only: with no live binding it returns ErrNoLiveSessionSandbox instead of provisioning, because the terminal entry point lacks the config-pin context that agent-driven creation relies on. A bound sandbox that is not confirmed running returns ErrSandboxPaused unless opts.AllowResume is set — Connect would wake a paused instance. A backend that cannot stream PTYs returns ErrTerminalUnsupported, not "no sandbox".
func (*SessionBoundManager) ReadSessionFile ¶
func (m *SessionBoundManager) ReadSessionFile( ctx context.Context, sessionID, filePath string, ) ([]byte, error)
ReadSessionFile downloads a file from the session's live sandbox. Errors when no sandbox is bound for the same reason as StatSessionFile.
func (*SessionBoundManager) ReclaimUserMachine ¶ added in v0.9.0
func (m *SessionBoundManager) ReclaimUserMachine( ctx context.Context, machine allocation.UserMachine, check func(context.Context) (bool, error), ) (bool, error)
ReclaimUserMachine deletes a shared user machine; see remoteSessionLifecycle.reclaimUserMachine for the safety rules.
func (*SessionBoundManager) RemoveSessionInputPath ¶
func (m *SessionBoundManager) RemoveSessionInputPath( ctx context.Context, sessionID, targetPath string, ) error
RemoveSessionInputPath deletes a staged attachment. It is a no-op when the session has no live sandbox and never provisions one.
func (*SessionBoundManager) RequireRunningSessionSandbox ¶
func (m *SessionBoundManager) RequireRunningSessionSandbox( ctx context.Context, sessionID string, ) error
RequireRunningSessionSandbox reports ErrNoLiveSessionSandbox or ErrSandboxPaused without Connect. Lookup-only terminal and desktop opens use it so opening a panel cannot resume (and re-bill) a paused instance. Only a List-confirmed running sandbox is safe: paused/transitioning Connect resumes, and a list miss with a stale binding is not "no sandbox".
func (*SessionBoundManager) SessionDesktopManager ¶
func (m *SessionBoundManager) SessionDesktopManager() SessionDesktopManager
SessionDesktopManager advertises the graphical-desktop capability while a real remote backend is active, the provider can relay a data-plane WebSocket (E2B and Cube do; Docker is not scheduled), and this config's base image is a desktop template. DesktopEnabled is the stored bit that survives skill snapshots replacing template_id with a UUID; without it the tab would Exec ensure.sh on a CLI image and only then report unsupported.
func (*SessionBoundManager) SessionFileStore ¶
func (m *SessionBoundManager) SessionFileStore() SessionFileStore
SessionFileStore advertises the session-scoped filesystem capability while a real remote backend is active and the provider implements the enumeration operations (ListDir / Stat / MakeDir / Remove).
func (*SessionBoundManager) SessionInstallShellExecutor ¶
func (m *SessionBoundManager) SessionInstallShellExecutor() SessionInstallShellExecutor
SessionInstallShellExecutor advertises the privileged install-mode shell.
func (*SessionBoundManager) SessionShellExecutor ¶
func (m *SessionBoundManager) SessionShellExecutor() SessionShellExecutor
SessionShellExecutor advertises the shell-execution capability while the manager is open.
func (*SessionBoundManager) SessionTerminalManager ¶
func (m *SessionBoundManager) SessionTerminalManager() SessionTerminalManager
SessionTerminalManager advertises the interactive-terminal capability while a real remote backend is active and the provider implements PTY streaming (E2B, Cube and Docker all do).
func (*SessionBoundManager) SessionWorkspaceLayout ¶
func (m *SessionBoundManager) SessionWorkspaceLayout(ctx context.Context, sessionID string) (WorkspaceLayout, error)
SessionWorkspaceLayout reports where sessionID's files live. A session-owned machine keeps the /workspace contract (a validated WEKNORA_SKILL_OUTPUT_DIR overlays OutputDir). A shared machine reports that session's directory. Lookups do not admit and do not require a tenant: tool descriptions call this before any provider operation.
func (*SessionBoundManager) StatSessionFile ¶
func (m *SessionBoundManager) StatSessionFile( ctx context.Context, sessionID, filePath string, ) (*RemoteStatEntry, error)
StatSessionFile returns metadata for a single file without downloading contents. Returns an error when no sandbox is bound: callers of this method already hold a path from a prior ListSessionFiles call and should not race with reaper/destroy.
func (*SessionBoundManager) TerminalIdleDisconnect ¶
func (m *SessionBoundManager) TerminalIdleDisconnect() time.Duration
TerminalIdleDisconnect is how long an open PTY or desktop relay may sit idle before the WebSocket is closed. Missing or out-of-range workspace values are clamped onto the built-in default so a stored 0 still disconnects.
func (*SessionBoundManager) TryLockRewind ¶
func (m *SessionBoundManager) TryLockRewind(ctx context.Context, sessionID string) (func(), error)
TryLockRewind takes an exclusive rewind lock for sessionID. Stores that do not implement rewind locking succeed as a no-op so local tests without a lease store still rewind.
func (*SessionBoundManager) UserMachineKey ¶ added in v0.9.0
func (m *SessionBoundManager) UserMachineKey(machine allocation.UserMachine) SessionSandboxKey
UserMachineKey is the machine-level key of a shared user machine.
func (*SessionBoundManager) WriteSessionFile ¶
func (m *SessionBoundManager) WriteSessionFile( ctx context.Context, sessionID, filePath string, content []byte, ) error
WriteSessionFile writes an install/maintenance file into the session's live sandbox. It is deliberately narrower than a general remote write: only the tenant skills image root is accepted, because ordinary attachments must keep using WriteSessionInputFile and its /workspace/input guard.
func (*SessionBoundManager) WriteSessionInputFile ¶
func (m *SessionBoundManager) WriteSessionInputFile( ctx context.Context, sessionID, filePath string, content []byte, ) error
WriteSessionInputFile writes a durable attachment path into the session's remote sandbox, provisioning the sandbox on first call. It is refused when the manager has fallen back to Local (writing to the host would leak attachments outside the tenant's isolation boundary).
func (*SessionBoundManager) WriteSessionWorkspaceFile ¶
func (m *SessionBoundManager) WriteSessionWorkspaceFile( ctx context.Context, sessionID, filePath string, content []byte, ) error
WriteSessionWorkspaceFile writes a model-authored file into the session's remote sandbox, provisioning the sandbox on first call. Paths must sit inside the session sandbox and must not land in /workspace/input.
func (*SessionBoundManager) WriteSessionWorkspaceFiles ¶
func (m *SessionBoundManager) WriteSessionWorkspaceFiles( ctx context.Context, sessionID string, files []SessionWorkspaceFile, ) error
WriteSessionWorkspaceFiles prepares the session workspace once, then writes every file. Staging a host skill tree must not re-run directory bootstrap or walk the parent path for each entry.
type SessionBoundManagerConfig ¶
type SessionBoundManagerConfig struct {
Config *Config
Client RemoteSandboxClient
Store SessionSandboxBindingStore
Checker SessionExistenceChecker
// ConfigID identifies the tenant sandbox config this manager serves. It is
// stamped onto sandbox metadata so cleanup can target one config without
// touching another that shares the same provider account.
ConfigID string
// ConfigTenantID is the workspace that owns ConfigID. The resolver fills
// it so provider creates register on that config's admission scope.
ConfigTenantID uint64
// SkipHealthProbe skips the construction-time Health() round-trip.
// Set by the per-tenant resolver, which builds a manager per request.
// See NewSessionBoundManager.
SkipHealthProbe bool
// Bootstrapper customises the first sandbox create of individual sessions
// (session fork). Optional: nil is the ordinary path.
Bootstrapper SessionBootstrapper
// Allocations, Admission and Namespace enable shared user machines. All
// three are optional; a user-mode assignment is refused without them.
Allocations AllocationDirectory
Admission admission.Registry
Namespace string
}
SessionBoundManagerConfig bundles the wired dependencies. Test helpers and the production container use it so callers only have to name the moving parts they actually override.
type SessionCapabilityProvider ¶
type SessionCapabilityProvider interface {
SessionShellExecutor() SessionShellExecutor
SessionFileStore() SessionFileStore
}
SessionCapabilityProvider is implemented by managers that MAY offer session-scoped capabilities. Accessors return nil when the current runtime configuration cannot support that capability. Application code should gate feature registration on non-nil accessor returns.
type SessionCreateFailureHandler ¶
type SessionCreateFailureHandler interface {
OnCreateFailed(ctx context.Context, key SessionSandboxKey, createErr error)
}
SessionCreateFailureHandler is an optional SessionBootstrapper extension.
AfterCreate only runs when Create succeeded. A fork snapshot that the provider will never boot (deleted, unknown template) would otherwise stay on the session forever: every Resolve retries the same dead ID. The lifecycle calls this when Create used a TemplateOverride and still failed, so the bootstrapper can retire that snapshot and let the next Resolve use the ordinary template.
type SessionDesktopConn ¶
type SessionDesktopConn struct {
Conn *websocket.Conn
SandboxID string
// StartTTLRefresh extends the provider idle timeout for as long as ctx
// is live. The WebSocket handler must pass the relay ctx (WithoutCancel),
// not the HTTP request ctx used to dial. Nil when the backend has no
// timeout to refresh (Docker).
StartTTLRefresh func(ctx context.Context)
}
SessionDesktopConn is one dialled desktop leg.
SandboxID is the sandbox this connection actually reached. It is returned rather than looked up afterwards because a skill install landing between the dial and the lookup would report the new sandbox for a connection held on the old one, and the handler's rebuild check would silently miss.
type SessionDesktopManager ¶
type SessionDesktopManager interface {
// OpenSessionDesktop dials the desktop port of the session's currently
// bound sandbox. It is lookup-only by design: the caller (the desktop
// service) has already provisioned and started the desktop through the
// normal execution path, so a missing binding here means the sandbox
// disappeared between the two steps, not "please create one".
// ErrNoLiveSessionSandbox says exactly that; a backend that cannot relay
// desktops returns ErrDesktopUnsupported.
OpenSessionDesktop(ctx context.Context, sessionID string, opts RemoteDesktopOptions) (*SessionDesktopConn, error)
}
SessionDesktopManager relays a WebSocket to the graphical desktop of the sandbox bound to a session. Like the terminal it is provider-neutral: the WebSocket handler bridges browser RFB frames to it without knowing whether E2B or Cube serves the session.
type SessionDesktopProvider ¶
type SessionDesktopProvider interface {
SessionDesktopManager() SessionDesktopManager
}
SessionDesktopProvider is implemented by managers that MAY offer a graphical desktop. The accessor returns nil when the current runtime cannot honour the capability.
type SessionDestroyer ¶
SessionDestroyer releases the remote sandbox bound to a session, leaving the session record itself alone. Like RemoteSnapshotManager it is an optional capability: stateless backends have nothing to release.
type SessionExistenceChecker ¶
type SessionExistenceChecker interface {
SessionExists(context.Context, SessionSandboxKey) (bool, error)
}
SessionExistenceChecker checks the tenant-scoped durable session record.
type SessionFileReader ¶
type SessionFileReader interface {
ReadSessionFile(ctx context.Context, sessionID, path string) ([]byte, error)
}
SessionFileReader reads one file out of a session's sandbox. It is the single-method slice of SessionFileStore that callers which only ever read need, so a manager offering just this much is enough for them.
type SessionFileStore ¶
type SessionFileStore interface {
// EnsureSessionDir creates dir in the session's live sandbox. Silent
// no-op when no sandbox is bound yet; the next Execute call will
// materialise the directory during script upload.
EnsureSessionDir(ctx context.Context, sessionID, dir string) error
// ListSessionFiles walks dir recursively and returns file entries.
// Returns nil (no error) when the session has no live sandbox so
// callers can treat "no sandbox" and "empty output" uniformly.
ListSessionFiles(ctx context.Context, sessionID, dir string) ([]RemoteDirEntry, error)
// StatSessionFile returns metadata for a single file. Errors when the
// session has no bound sandbox — callers of this method already hold a
// path from a prior ListSessionFiles call.
StatSessionFile(ctx context.Context, sessionID, path string) (*RemoteStatEntry, error)
// ReadSessionFile downloads a file's contents. Same "no sandbox
// bound" contract as StatSessionFile.
ReadSessionFile(ctx context.Context, sessionID, path string) ([]byte, error)
// WriteSessionInputFile writes a durable attachment path into the
// session's remote sandbox, provisioning the sandbox on first call.
WriteSessionInputFile(ctx context.Context, sessionID, filePath string, content []byte) error
// WriteSessionWorkspaceFile writes a model-authored file inside the
// current session sandbox. Relative paths resolve from /workspace;
// /workspace/input stays read-only (attachments).
WriteSessionWorkspaceFile(ctx context.Context, sessionID, filePath string, content []byte) error
// WriteSessionWorkspaceFiles writes many workspace files after preparing
// the session layout once. Host-skill staging must use this instead of
// looping WriteSessionWorkspaceFile.
WriteSessionWorkspaceFiles(ctx context.Context, sessionID string, files []SessionWorkspaceFile) error
// RemoveSessionInputPath deletes a staged attachment. No-op when the
// session has no live sandbox.
RemoveSessionInputPath(ctx context.Context, sessionID, targetPath string) error
}
SessionFileStore is the effective per-session filesystem view a manager offers callers that need to inspect, stage, or clean up files inside the session's remote sandbox. It is intentionally provider-neutral: entries use RemoteDirEntry / RemoteStatEntry, so E2B and Cube can implement it without touching the caller.
type SessionInstallCapabilityProvider ¶
type SessionInstallCapabilityProvider interface {
SessionInstallShellExecutor() SessionInstallShellExecutor
}
SessionInstallCapabilityProvider is implemented by managers that can run install-mode shell commands. Like the other accessors it returns nil when the current runtime cannot honour the capability.
type SessionInstallShellExecutor ¶
type SessionInstallShellExecutor interface {
ExecShellCommandWithOptions(
ctx context.Context,
sessionID string,
command string,
opts ShellExecOptions,
) (*ExecuteResult, error)
}
SessionInstallShellExecutor runs install/maintenance shell commands with their own bootstrap and working-directory scope. Ordinary shell execution stays inside its session sandbox: anywhere in a remote container, and the layout's writable roots on a host workspace.
type SessionSandboxBinding ¶
type SessionSandboxBinding struct {
Version int `json:"version"`
Provider RemoteProvider `json:"provider,omitempty"`
TenantID uint64 `json:"tenant_id"`
SessionID string `json:"session_id"`
SandboxID string `json:"sandbox_id"`
TemplateID string `json:"template_id"`
CreatedAt time.Time `json:"created_at"`
// ConfigID is the sandbox config the sandbox was created from. It is what
// makes "every sandbox of this config" answerable from the binding store:
// the sandbox itself carries the same value in provider metadata, but a
// staleness mark must not depend on the provider being reachable.
//
// Bindings written before this field existed carry an empty value. They are
// deliberately still valid — refusing them would break every live session
// on upgrade — and simply match no config until they are rebuilt.
ConfigID string `json:"config_id,omitempty"`
// StaleAt marks a binding whose sandbox boots an image the config has
// since replaced. The sandbox keeps serving until the session's next
// resolve, which destroys and recreates it; see InvalidateByConfig.
StaleAt *time.Time `json:"stale_at,omitempty"`
// TrafficAccessToken is the per-sandbox inbound credential issued at
// create time for Cube and E2B. Inbound is always credential-required.
//
// Cube may reissue it on Connect after pause/resume; the lifecycle writes
// the fresh value back here. E2B issues it only at create. Either way this
// binding is the only place the credential survives a WeKnora restart.
//
// Stored as-is. It is a bearer credential, so the Redis instance holding
// these bindings must be access-controlled — it already holds the
// session-to-sandbox ownership anyway. Never log the binding payload.
//
// Empty for Docker (no such credential) and for bindings written before
// this field existed; both are valid.
TrafficAccessToken string `json:"traffic_access_token,omitempty"`
// OwnerID and ConfigTenantID complete a user machine's identity
// (version 2). Empty on per-session bindings.
OwnerID string `json:"owner_id,omitempty"`
ConfigTenantID uint64 `json:"config_tenant_id,omitempty"`
}
SessionSandboxBinding records the remote sandbox assigned to a session.
func (SessionSandboxBinding) Validate ¶
func (b SessionSandboxBinding) Validate(key SessionSandboxKey) error
Validate checks a binding against its schema and authoritative key.
type SessionSandboxBindingStore ¶
type SessionSandboxBindingStore interface {
Get(context.Context, SessionSandboxKey) (*SessionSandboxBinding, error)
Create(context.Context, SessionSandboxKey, SessionSandboxBinding) (bool, error)
DeleteIfMatch(
context.Context,
SessionSandboxKey,
RemoteProvider,
string,
) (bool, error)
// ReplaceTrafficTokenIfMatch writes token onto the binding only while it
// still names expected's provider and sandbox. It patches that one field
// so a concurrent stale-mark cannot be wiped. Empty token is a no-op.
ReplaceTrafficTokenIfMatch(
ctx context.Context,
key SessionSandboxKey,
expected SessionSandboxBinding,
token string,
) (bool, error)
// WithLifecycleLock passes fn a request context carrying a separate
// ownership context. The ownership context survives caller cancellation
// but is canceled when exclusive lock ownership is lost.
WithLifecycleLock(context.Context, SessionSandboxKey, func(context.Context) error) error
// InvalidateByConfig marks every binding of one workspace's sandbox config
// stale and reports how many it marked. Already-marked bindings are not
// counted again.
InvalidateByConfig(ctx context.Context, tenantID uint64, configID string) (int, error)
}
SessionSandboxBindingStore persists bindings and serializes lifecycle transitions. Implementations must use create-if-absent and compare-delete semantics.
type SessionSandboxKey ¶
type SessionSandboxKey struct {
TenantID uint64
SessionID string
User allocation.UserMachine
}
SessionSandboxKey addresses persistent sandbox state.
With User zero it is today's per-session key. With User set and SessionID empty it addresses a shared user machine (binding, lifecycle lock); with both set it addresses one session's leases on that machine (turn lease, rewind lock).
func (SessionSandboxKey) IsUserMachine ¶ added in v0.9.0
func (k SessionSandboxKey) IsUserMachine() bool
IsUserMachine reports the machine-level key of a shared user machine.
func (SessionSandboxKey) IsUserScoped ¶ added in v0.9.0
func (k SessionSandboxKey) IsUserScoped() bool
IsUserScoped reports a key on a shared user machine.
func (SessionSandboxKey) Machine ¶ added in v0.9.0
func (k SessionSandboxKey) Machine() SessionSandboxKey
Machine is the key the binding and the lifecycle lock live under.
func (SessionSandboxKey) String ¶ added in v0.9.0
func (k SessionSandboxKey) String() string
String identifies the key in logs without the owner id.
func (SessionSandboxKey) Validate ¶
func (k SessionSandboxKey) Validate() error
Validate rejects keys that cannot address sandbox state.
type SessionShellExecutor ¶
type SessionShellExecutor interface {
ExecShellCommand(
ctx context.Context,
sessionID string,
command string,
workDir string,
timeout time.Duration,
env map[string]string,
) (*ExecuteResult, error)
}
SessionShellExecutor executes ad-hoc shell commands inside a session- scoped remote sandbox. SessionBoundManager surfaces it while Cube, E2B, or Docker is active.
type SessionStateReleaser ¶ added in v0.9.0
SessionStateReleaser forgets per-session approvals when a chat is deleted.
type SessionTerminalManager ¶
type SessionTerminalManager interface {
// OpenSessionTerminal connects to the session's currently bound sandbox
// and opens a PTY. It is lookup-only: when no live sandbox is bound it
// returns ErrNoLiveSessionSandbox instead of provisioning one, because
// the terminal entry point lacks the agent's config-pin context and
// must not create microVMs as a side effect. A bound sandbox that is
// not confirmed running returns ErrSandboxPaused unless opts.AllowResume
// is set, so a panel open cannot silently resume (and re-bill) a paused
// instance. A backend that cannot stream PTYs returns
// ErrTerminalUnsupported, not "no sandbox".
OpenSessionTerminal(ctx context.Context, sessionID string, opts RemoteTerminalOptions) (RemoteTerminalSession, error)
}
SessionTerminalManager opens interactive PTYs on the sandbox bound to a session. Like the file store it is provider-neutral: the WebSocket handler bridges browser terminal frames to it without knowing whether E2B or Cube serves the session.
type SessionTerminalProvider ¶
type SessionTerminalProvider interface {
SessionTerminalManager() SessionTerminalManager
}
SessionTerminalProvider is implemented by managers that MAY offer interactive terminals. The accessor returns nil when the current runtime cannot honour the capability.
type SessionTurnHolder ¶
type SessionTurnHolder interface {
// BeginSessionTurn opens one turn hold. Run the turn with the returned
// context: on a shared machine it carries this hold's admission member,
// so the turn's own tool calls do not wait for it, and it ends if the
// registration is lost. end closes exactly this hold; call it once.
BeginSessionTurn(ctx context.Context, sessionID string) (turnCtx context.Context, end func(), err error)
}
SessionTurnHolder marks one chat turn on a session's sandbox.
type SessionWorkspaceFile ¶
SessionWorkspaceFile is one path/content pair for WriteSessionWorkspaceFiles.
type SessionWorkspaceLayoutProvider ¶
type SessionWorkspaceLayoutProvider interface {
SessionWorkspaceLayout(ctx context.Context, sessionID string) (WorkspaceLayout, error)
}
SessionWorkspaceLayoutProvider is implemented by managers that can describe their session workspace. Tools resolve it once per Execute from the session sandbox, not from a tool-instance field.
type ShellExecOptions ¶
type ShellExecOptions struct {
// ExpectedSandboxID makes maintenance lookup-only: never allocate/rebuild,
// and fail if the binding changed since the caller selected this sandbox.
ExpectedSandboxID string
OnOutput func(stream string, chunk []byte)
WorkDir string
Timeout time.Duration
Env map[string]string
// AllowSkillsRoot selects the installer workspace/skills working-directory scope.
// See cleanSessionWorkDir for why the work_dir allowlist is lexical only.
// Never set this from a model-authored tool such as shell_exec.
AllowSkillsRoot bool
// AsRoot forces root and selects the maintenance bootstrap: only WorkDir
// is prepared, without requiring /workspace/input or /workspace/output.
// The default account is already root, but the bootstrap still differs.
// AllowSkillsRoot separately selects the installer working-directory scope;
// it is not a filesystem boundary for commands running as root.
AsRoot bool
// SkipWorkspacePrep omits prepareSessionDirs. Desktop maintenance and
// workspace checkpoints operate on an existing layout. Never set this from a model-authored tool
// such as shell_exec — agents still need the workspace contract.
SkipWorkspacePrep bool
}
ShellExecOptions carries per-call shell execution knobs. The install-only flags select the installer working-directory allowlist and bootstrap. Both ordinary and install calls currently execute as root.
type ShellJobMarkerStream ¶ added in v0.9.0
type ShellJobMarkerStream struct {
// contains filtered or unexported fields
}
ShellJobMarkerStream strips the trailing marker line and everything after it from forwarded output, holding back at most len(marker)-1 bytes so a marker split across chunks is never leaked into the preview or the result body.
func NewShellJobMarkerStream ¶ added in v0.9.0
func NewShellJobMarkerStream(marker string) *ShellJobMarkerStream
NewShellJobMarkerStream splits forwarded output on marker.
func (*ShellJobMarkerStream) Feed ¶ added in v0.9.0
func (s *ShellJobMarkerStream) Feed(chunk []byte) (body, tail []byte)
Feed returns output that is safe to forward and any bytes still held because they might be the start of the marker.
func (*ShellJobMarkerStream) Final ¶ added in v0.9.0
func (s *ShellJobMarkerStream) Final() (body, epilogue string)
Final returns the held body when no marker arrived, or the epilogue after it.
type ShellJobMeta ¶ added in v0.9.0
type ShellJobMeta struct {
ID string `json:"work_id"`
TenantID uint64 `json:"tenant_id"`
SandboxTenantID uint64 `json:"sandbox_tenant_id"` // config owner, NOT the binding-key owner
SessionID string `json:"session_id"`
Root string `json:"root"`
Command string `json:"command"` // masked; credentials never persist
WorkDir string `json:"work_dir"`
SandboxID string `json:"sandbox_id"`
CreatedAt time.Time `json:"created_at"`
YieldUntil time.Time `json:"yield_until"`
ParentMessageID string `json:"parent_message_id"`
ToolCallID string `json:"tool_call_id"`
Title string `json:"title"`
OutputDir string `json:"output_dir,omitempty"`
OutputSeen bool `json:"output_seen,omitempty"`
OutputBefore []OutputFileStamp `json:"output_before,omitempty"`
}
ShellJobMeta is the persisted identity of one shell job.
type ShellJobOffsets ¶ added in v0.9.0
ShellJobOffsets is an absolute byte cursor into the two output streams.
type ShellJobRead ¶ added in v0.9.0
type ShellJobRead struct {
ID string `json:"id"`
From *ShellJobOffsets `json:"from,omitempty"`
OutputBudget int `json:"output_budget"`
Preview bool `json:"preview"`
}
ShellJobRead selects a job directory and how much of each stream to return.
type ShellJobSnapshot ¶ added in v0.9.0
type ShellJobSnapshot struct {
Meta ShellJobMeta `json:"meta"`
Status ShellJobStatus `json:"status"`
Stdout ShellJobStream `json:"stdout"`
Stderr ShellJobStream `json:"stderr"`
WatcherAlive bool `json:"watcher_alive"`
Missing bool `json:"missing"`
Preview string `json:"preview,omitempty"`
}
ShellJobSnapshot is a point-in-time read of one job directory.
func ParseShellJobBatch ¶ added in v0.9.0
func ParseShellJobBatch(stdout string) []ShellJobSnapshot
ParseShellJobBatch decodes one JSON snapshot per non-empty line. Lines that are not objects are skipped. Missing records are explicit; an absent or malformed line is a read failure for the caller. Slice fields are assembled with the same head/tail rules as ReadShellJob.
func ReadShellJob ¶ added in v0.9.0
func ReadShellJob(root string, req ShellJobRead) (ShellJobSnapshot, error)
ReadShellJob reads one job directory with bounded IO. A nil cursor is the first read of a run, or an out-of-run "查看输出" read: head plus tail, with the tail padded from the end of .1 when the current segment is short (shell 稿 §9.1, §6.3). A cursor returns only bytes after that absolute offset. A cursor that falls in bytes rotation already discarded starts at the earliest byte still on disk, and OmittedBytes counts the gap.
type ShellJobStatus ¶ added in v0.9.0
type ShellJobStatus struct {
State string `json:"state"`
WatcherPID int `json:"watcher_pid"`
PGID int `json:"pgid"`
TouchPID int `json:"touch_pid,omitempty"`
ProcStartedAt int64 `json:"proc_started_at,omitempty"`
ExitCode *int `json:"exit_code,omitempty"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
}
ShellJobStatus is the watcher's latest state for one shell job.
type ShellJobStream ¶ added in v0.9.0
type ShellJobStream struct {
Text string `json:"text"`
FromBytes int64 `json:"from_bytes"`
TotalBytes int64 `json:"total_bytes"`
OmittedBytes int64 `json:"omitted_bytes"`
Truncated bool `json:"truncated"`
// NextBytes is the absolute end of the bytes consumed by this view, before
// UTF-8 sanitization. Nil is reserved for older snapshot producers.
NextBytes *int64 `json:"next_bytes,omitempty"`
}
ShellJobStream is one bounded view of a job output stream.
type ShellOutputSnapshot ¶
type ShellOutputSnapshot struct {
Before []RemoteDirEntry
After []RemoteDirEntry
}
ShellOutputSnapshot contains two complete metadata listings of the same sandbox. A nil snapshot means inspection failed; an empty snapshot means no output files.
type TenantSandboxConfigLoader ¶
type TenantSandboxConfigLoader interface {
Load(ctx context.Context, tenantID uint64, configID string) (ResolvedTenantSandboxConfig, error)
}
TenantSandboxConfigLoader fetches one stored sandbox config. Implemented in the application layer so this package stays free of repository dependencies.
type TenantSandboxResolver ¶
type TenantSandboxResolver interface {
// Resolve builds the manager for configID. An empty configID selects the
// deployment-wide default manager.
Resolve(ctx context.Context, tenantID uint64, configID string) (Manager, error)
}
TenantSandboxResolver produces the Manager for a (tenant, config) pair.
func NewTenantSandboxResolver ¶
func NewTenantSandboxResolver(deps TenantSandboxResolverDeps) (TenantSandboxResolver, error)
NewTenantSandboxResolver validates the wiring and returns a resolver.
type TenantSandboxResolverDeps ¶
type TenantSandboxResolverDeps struct {
// GlobalConfig supplies only built-in runtime tuning defaults.
GlobalConfig *Config
// DefaultManager serves tenants without their own configuration, which
// preserves the pre-feature behaviour for existing deployments.
DefaultManager Manager
Loader TenantSandboxConfigLoader
Store SessionSandboxBindingStore
Checker SessionExistenceChecker
// Bootstrapper customises the first sandbox create of individual sessions
// (session fork). Optional: nil is the ordinary path.
Bootstrapper SessionBootstrapper
// Allocations, Admission and Namespace are passed to every session-bound
// manager; see SessionBoundManagerConfig.
Allocations AllocationDirectory
Admission admission.Registry
Namespace string
// guarded transport is installed when nil.
SharedTransport *http.Transport
}
TenantSandboxResolverDeps bundles the resolver's wiring.
type ValidationError ¶
type ValidationError struct {
Type string // "dangerous_command", "dangerous_pattern", "arg_injection", "shell_injection"
Pattern string // The pattern that matched
Context string // Where it was found
Message string // Human-readable description
}
ValidationError represents a security validation failure
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
type ValidationResult ¶
type ValidationResult struct {
Valid bool
Errors []*ValidationError
}
ValidationResult contains all validation errors found
type WebsocketDialer ¶
type WebsocketDialer struct {
// contains filtered or unexported fields
}
WebsocketDialer dials one data-plane port of one sandbox over WebSocket. Build it with SandboxGatewayTransportPool.WebsocketDialerFor.
func (*WebsocketDialer) Dial ¶
func (d *WebsocketDialer) Dial( ctx context.Context, sandboxID string, port int, path string, extraHeader http.Header, ) (*websocket.Conn, *http.Response, error)
Dial opens a WebSocket to port/path of sandboxID.
extraHeader carries credentials the caller owns — for the desktop that is Authorization: Basic for websockify, and the handle's inbound traffic token when the registry has not been written yet (Cube Create/Connect historically skipped that Put). The registry fills the inbound header only if missing.
The negotiated subprotocol is pinned to "binary" and verified after the handshake. If an upstream ever selected base64, the bytes on the sandbox side would be base64 text and every byte offset the RFB parser computes would be wrong — silently, and only under load.
type WorkspaceFork ¶ added in v0.9.0
type WorkspaceFork interface {
SandboxID() string
// Context is cancelled with admission.ErrLeaseLost once the hold is
// lost. Run every step of the fork with it, database steps included.
Context() context.Context
// Copy materializes sha of the source's checkpoint repository as
// targetSessionID's workspace and reports which of candidates are sha
// or its ancestors, i.e. which checkpoints exist in the new repository.
Copy(targetSessionID, sha string, candidates []string) (map[string]bool, error)
// Remove deletes targetSessionID's work tree and repository.
Remove(targetSessionID string) error
Release()
}
WorkspaceFork is an open fork of a session on a shared machine. It holds the machine's and the source session's admission until Release, so the machine named by SandboxID is the one every step runs on. Its steps fail with admission.ErrLeaseLost once that hold is lost.
type WorkspaceLayout ¶
WorkspaceLayout is the set of roots a session may read and write. The type lives in the workspace subpackage; this alias keeps existing callers compiling.
func FailedHostWorkspaceLayout ¶
func FailedHostWorkspaceLayout() WorkspaceLayout
FailedHostWorkspaceLayout is what tools and prompts use when a layout provider exists but errors: do not fall back to /workspace.
func RemoteWorkspaceLayout ¶
func RemoteWorkspaceLayout() WorkspaceLayout
RemoteWorkspaceLayout is the /workspace layout of a session-owned machine.
func UserWorkspaceLayout ¶ added in v0.9.0
func UserWorkspaceLayout(sessionID string) (WorkspaceLayout, error)
UserWorkspaceLayout is sessionID's layout on a machine its owner shares.
type WorkspaceOrigin ¶
WorkspaceOrigin is where a workspace path is rooted. The type lives in the workspace subpackage; this alias keeps existing callers compiling.
Source Files
¶
- activity_registry_memory.go
- activity_registry_redis.go
- capabilities.go
- command_approval.go
- command_output.go
- config_identity.go
- config_required.go
- config_sandboxes.go
- cube_dns.go
- cube_remote_client.go
- cube_terminal.go
- desktop.go
- desktop_scripts.go
- docker_enabled.go
- docker_engine.go
- docker_host.go
- docker_idle_sweeper.go
- docker_remote_client.go
- docker_rpc_timeout.go
- docker_snapshot.go
- docker_template_catalog.go
- docker_terminal.go
- e2b_remote_client.go
- e2b_rpc_timeout.go
- e2b_terminal.go
- envd_compat_transport.go
- filesystem_identity.go
- gateway_transport.go
- inbound_token_registry.go
- keepalive.go
- langfuse.go
- manager.go
- mkdir.go
- orphan_reaper.go
- pty_input_coalesce.go
- remote_client.go
- remote_errors.go
- remote_sandbox.go
- runtime_defaults.go
- sandbox.go
- session_binding.go
- session_binding_redis.go
- session_bootstrapper.go
- session_connect.go
- session_file_operation.go
- session_lifecycle.go
- session_manager.go
- shell_job.go
- shell_job_scripts.go
- shell_quote.go
- skill_image.go
- skill_paths.go
- template_catalog.go
- tenant_config.go
- tenant_resolver.go
- terminal.go
- url_guard.go
- validator.go
- websocket_dialer.go
- workspace_layout.go
- workspace_path.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package admission tracks operations in flight on a shared machine and on each session of it, so a rebuild, a reclaim, a rewind or a workspace delete can stop new work and wait for running work instead of cutting it off.
|
Package admission tracks operations in flight on a shared machine and on each session of it, so a rebuild, a reclaim, a rewind or a workspace delete can stop new work and wait for running work instead of cutting it off. |
|
Package allocation is the domain model for placing a session on a remote sandbox: which mode it uses, whether its owner may share a machine, and the identity of a shared machine.
|
Package allocation is the domain model for placing a session on a remote sandbox: which mode it uses, whether its owner may share a machine, and the identity of a shared machine. |
|
Package workspace describes where one session's files live inside a remote sandbox and the lexical path rules the manager and the agent tools share.
|
Package workspace describes where one session's files live inside a remote sandbox and the lexical path rules the manager and the agent tools share. |