yao/sandbox/v2/jsapi/computer.go
Max 43fd532357 chore: update unit-test workflow to use latest Tai image
- Change Docker image references in the unit-test workflow from yaoapp/tai:1.2.0 to yaoapp/tai:latest for consistency and to ensure the latest features and fixes are utilized.
- Update related documentation to reflect the unified Computer interface in the sandbox, replacing Box and Host references with Computer.

Made-with: Cursor
2026-03-08 22:52:11 +08:00

138 lines
5 KiB
Go

package jsapi
import (
"rogchap.com/v8go"
)
// sbHost: `sandbox.Host(pool?)` → Computer (kind="host")
//
// Go: Manager.Host(ctx, pool) (*Host, error)
//
// Args:
//
// pool: string (optional) — pool name; empty = default pool
//
// Returns: Computer object (kind="host") if the pool has host_exec capability, otherwise throws.
func sbHost(info *v8go.FunctionCallbackInfo) *v8go.Value {
// TODO: Phase 2
// 1. pool := ""; if len(info.Args()) > 0 && info.Args()[0].IsString() { pool = info.Args()[0].String() }
// 2. host, err := sandbox.M().Host(ctx, pool)
// 3. if err != nil { throw in V8 }
// 4. Return NewComputerObject(v8ctx, "host", pool)
return v8go.Undefined(info.Context().Isolate())
}
// NewComputerObject creates a unified JS Computer object backed by either a Box or Host.
// The `kind` field ("box" or "host") determines which methods are available at runtime.
// Box-only methods (Attach, Info, Start, Stop, Remove) throw an error when called on a host.
//
// # Properties (read-only)
//
// pc.kind → string // "box" | "host" ← ComputerInfo().Kind
// pc.id → string // sandbox ID ← Box.ID() (empty for host)
// pc.owner → string // owner ← Box.Owner() (empty for host)
// pc.pool → string // pool name ← ComputerInfo().Pool
//
// # Methods — Computer interface (both box and host)
//
// pc.Exec(cmd, options?) → ExecResult
//
// Go: Computer.Exec(ctx, cmd []string, opts ...ExecOption) (*ExecResult, error)
//
// JS args:
// cmd: string[] → cmd []string
// options: { → ExecOption
// workdir: string, → WithWorkDir(dir)
// env: object, → WithEnv(map[string]string)
// stdin: string, → WithStdin([]byte)
// timeout: number, → WithTimeout(ms → time.Duration)
// max_output: number → WithMaxOutput(bytes int64)
// }
// JS returns: {
// exit_code: number, ← ExecResult.ExitCode
// stdout: string, ← ExecResult.Stdout
// stderr: string, ← ExecResult.Stderr
// duration_ms: number, ← ExecResult.DurationMs
// error: string, ← ExecResult.Error
// truncated: boolean ← ExecResult.Truncated
// }
//
// pc.Stream(cmd, callback) / pc.Stream(cmd, options, callback)
//
// Go: Computer.Stream(ctx, cmd []string, opts ...ExecOption) (*ExecStream, error)
//
// Blocks until the process exits. The last argument must be a JS function.
// Callback signature: function(type, data)
// type = "stdout" → data is string (chunk)
// type = "stderr" → data is string (chunk)
// type = "exit" → data is number (exit code)
//
// pc.VNC() → string
//
// Go: Computer.VNC(ctx) (string, error)
// Box: returns ws://host:port/vnc/{containerID}/ws
// Host: returns ws://host:port/vnc/__host__/ws
//
// pc.Proxy(port, path?) → string
//
// Go: Computer.Proxy(ctx, port int, path string) (string, error)
// Box: returns http://host:port/{containerID}:{port}/{path}
// Host: returns http://host:port/__host__:{port}/{path}
//
// pc.ComputerInfo() → ComputerInfo
//
// Go: Computer.ComputerInfo() ComputerInfo
// JS returns: { kind, pool, tai_id, machine_id, version, mode, status, capabilities,
// system: { os, arch, hostname, num_cpu, total_mem },
// box_id, container_id, owner, image, policy, labels }
//
// pc.BindWorkplace(workspaceID) → void
//
// Go: Computer.BindWorkplace(workspaceID string)
//
// pc.Workplace() → WorkspaceFS | null
//
// Go: Computer.Workplace() workspace.FS
// Returns WorkspaceFS if a workplace is bound, null otherwise.
//
// # Methods — Box-only (throw on host)
//
// pc.Attach(port, options?) → string
//
// Gets a WebSocket/SSE endpoint URL for a container service.
// JS args:
// port: number
// options: { protocol: "ws"|"sse", path: string }
// JS returns: string (URL)
//
// pc.Info() → BoxInfo
//
// Go: Box.Info(ctx) (*BoxInfo, error)
// JS returns: { id, container_id, pool, owner, status, image, vnc, policy,
// labels, created_at, last_active, process_count }
//
// pc.Start() → void
//
// Go: Box.Start(ctx) error
//
// pc.Stop() → void
//
// Go: Box.Stop(ctx) error
//
// pc.Remove() → void
//
// Go: Box.Remove(ctx) error
func NewComputerObject(v8ctx *v8go.Context, kind string, id string) (*v8go.Value, error) {
// TODO: Phase 2 implementation
// 1. Create JS object via v8go.NewObjectTemplate
// 2. Set read-only properties: kind, id, owner, pool
// - kind: "box" or "host"
// - id/owner: from sandbox.M().Get(id) for box; empty for host
// - pool: from ComputerInfo().Pool
// 3. Bind Computer interface methods:
// - Exec, Stream, VNC, Proxy, ComputerInfo, BindWorkplace, Workplace
// 4. Bind box-only methods with kind guard:
// - Attach, Info, Start, Stop, Remove
// - If kind == "host", these throw: "not supported: {method}() requires a box computer"
return nil, nil
}