- Implemented a new endpoint to retrieve the absolute path of a workspace's root directory. - Enhanced the workspace interface with a GetRoot method to facilitate this functionality. - Updated the workspace manager to utilize the new method for improved path resolution. Made-with: Cursor
239 lines
6.3 KiB
Go
239 lines
6.3 KiB
Go
package context
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/yaoapp/gou/runtime/v8/bridge"
|
|
infraV2 "github.com/yaoapp/yao/sandbox/v2"
|
|
"github.com/yaoapp/yao/tai/workspace"
|
|
"rogchap.com/v8go"
|
|
)
|
|
|
|
// SetComputer sets the V2 computer and its workspace for this context.
|
|
// Should be called after Runner.Prepare succeeds in initSandboxV2.
|
|
func (ctx *Context) SetComputer(computer infraV2.Computer) {
|
|
ctx.computer = computer
|
|
if computer != nil {
|
|
ctx.workspace = computer.Workplace()
|
|
}
|
|
}
|
|
|
|
// SetWorkspace sets the workspace FS directly without requiring a Computer.
|
|
// Use this when the user selected a workspace but no sandbox is configured.
|
|
func (ctx *Context) SetWorkspace(ws workspace.FS) {
|
|
ctx.workspace = ws
|
|
}
|
|
|
|
// GetComputer returns the V2 computer if available.
|
|
func (ctx *Context) GetComputer() infraV2.Computer {
|
|
return ctx.computer
|
|
}
|
|
|
|
// GetWorkspace returns the V2 workspace FS if available.
|
|
func (ctx *Context) GetWorkspace() workspace.FS {
|
|
return ctx.workspace
|
|
}
|
|
|
|
// HasComputer returns true if V2 computer is available.
|
|
func (ctx *Context) HasComputer() bool {
|
|
return ctx.computer != nil
|
|
}
|
|
|
|
// HasWorkspace returns true if workspace FS is available.
|
|
func (ctx *Context) HasWorkspace() bool {
|
|
return ctx.workspace != nil
|
|
}
|
|
|
|
// createComputerInstance creates the ctx.computer JavaScript object.
|
|
func (ctx *Context) createComputerInstance(v8ctx *v8go.Context) *v8go.Value {
|
|
if ctx.computer == nil {
|
|
return nil
|
|
}
|
|
|
|
iso := v8ctx.Isolate()
|
|
objTpl := v8go.NewObjectTemplate(iso)
|
|
|
|
info := ctx.computer.ComputerInfo()
|
|
id := info.BoxID
|
|
if id == "" {
|
|
id = info.NodeID
|
|
}
|
|
objTpl.Set("id", id)
|
|
|
|
objTpl.Set("Exec", ctx.computerExecMethod(iso))
|
|
objTpl.Set("VNC", ctx.computerVNCMethod(iso))
|
|
objTpl.Set("Proxy", ctx.computerProxyMethod(iso))
|
|
objTpl.Set("Info", ctx.computerInfoMethod(iso))
|
|
|
|
instance, err := objTpl.NewInstance(v8ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return instance.Value
|
|
}
|
|
|
|
// computerExecMethod implements ctx.computer.Exec(cmd)
|
|
// cmd can be a string or an array of strings.
|
|
// Returns: { stdout, stderr, exit_code }
|
|
func (ctx *Context) computerExecMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
v8ctx := info.Context()
|
|
args := info.Args()
|
|
|
|
if ctx.computer == nil {
|
|
return bridge.JsException(v8ctx, "computer not available")
|
|
}
|
|
if len(args) < 1 {
|
|
return bridge.JsException(v8ctx, "Exec requires a command argument")
|
|
}
|
|
|
|
cmd, err := parseCommandArg(v8ctx, args[0])
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, err.Error())
|
|
}
|
|
|
|
result, err := ctx.computer.Exec(context.Background(), cmd)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, "Exec failed: "+err.Error())
|
|
}
|
|
|
|
res := map[string]interface{}{
|
|
"stdout": result.Stdout,
|
|
"stderr": result.Stderr,
|
|
"exit_code": int32(result.ExitCode),
|
|
}
|
|
jsVal, err := bridge.JsValue(v8ctx, res)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, err.Error())
|
|
}
|
|
return jsVal
|
|
})
|
|
}
|
|
|
|
// computerVNCMethod implements ctx.computer.VNC()
|
|
// Returns the VNC URL string.
|
|
func (ctx *Context) computerVNCMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
v8ctx := info.Context()
|
|
|
|
if ctx.computer == nil {
|
|
return bridge.JsException(v8ctx, "computer not available")
|
|
}
|
|
|
|
url, err := ctx.computer.VNC(context.Background())
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, "VNC failed: "+err.Error())
|
|
}
|
|
|
|
jsVal, err := v8go.NewValue(iso, url)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, err.Error())
|
|
}
|
|
return jsVal
|
|
})
|
|
}
|
|
|
|
// computerProxyMethod implements ctx.computer.Proxy(port, path?)
|
|
// Returns the proxy URL string.
|
|
func (ctx *Context) computerProxyMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
v8ctx := info.Context()
|
|
args := info.Args()
|
|
|
|
if ctx.computer == nil {
|
|
return bridge.JsException(v8ctx, "computer not available")
|
|
}
|
|
if len(args) < 1 || !args[0].IsNumber() {
|
|
return bridge.JsException(v8ctx, "Proxy requires a port number")
|
|
}
|
|
|
|
port := int(args[0].Integer())
|
|
path := ""
|
|
if len(args) >= 2 && args[1].IsString() {
|
|
path = args[1].String()
|
|
}
|
|
|
|
url, err := ctx.computer.Proxy(context.Background(), port, path)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, "Proxy failed: "+err.Error())
|
|
}
|
|
|
|
jsVal, err := v8go.NewValue(iso, url)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, err.Error())
|
|
}
|
|
return jsVal
|
|
})
|
|
}
|
|
|
|
// computerInfoMethod implements ctx.computer.Info()
|
|
// Returns a JS object with computer identity and system information.
|
|
func (ctx *Context) computerInfoMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
v8ctx := info.Context()
|
|
|
|
if ctx.computer == nil {
|
|
return bridge.JsException(v8ctx, "computer not available")
|
|
}
|
|
|
|
ci := ctx.computer.ComputerInfo()
|
|
result := map[string]interface{}{
|
|
"kind": ci.Kind,
|
|
"node_id": ci.NodeID,
|
|
"tai_id": ci.TaiID,
|
|
"status": ci.Status,
|
|
"system": map[string]interface{}{
|
|
"os": ci.System.OS,
|
|
"arch": ci.System.Arch,
|
|
"hostname": ci.System.Hostname,
|
|
"num_cpu": int32(ci.System.NumCPU),
|
|
"shell": ci.System.Shell,
|
|
},
|
|
}
|
|
if ci.BoxID != "" {
|
|
result["box_id"] = ci.BoxID
|
|
result["container_id"] = ci.ContainerID
|
|
result["image"] = ci.Image
|
|
result["policy"] = string(ci.Policy)
|
|
}
|
|
|
|
jsVal, err := bridge.JsValue(v8ctx, result)
|
|
if err != nil {
|
|
return bridge.JsException(v8ctx, err.Error())
|
|
}
|
|
return jsVal
|
|
})
|
|
}
|
|
|
|
// parseCommandArg converts a JS value (string or string array) to []string.
|
|
func parseCommandArg(v8ctx *v8go.Context, val *v8go.Value) ([]string, error) {
|
|
if val.IsString() {
|
|
raw := val.String()
|
|
return strings.Fields(raw), nil
|
|
}
|
|
|
|
if val.IsArray() {
|
|
obj, err := val.AsObject()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lengthVal, err := obj.Get("length")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
length := int(lengthVal.Integer())
|
|
cmd := make([]string, length)
|
|
for i := 0; i < length; i++ {
|
|
item, err := obj.GetIdx(uint32(i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd[i] = item.String()
|
|
}
|
|
return cmd, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("command must be a string or array of strings")
|
|
}
|