package context import ( "context" "github.com/yaoapp/gou/runtime/v8/bridge" openapiSandbox "github.com/yaoapp/yao/openapi/sandbox" infraSandbox "github.com/yaoapp/yao/sandbox" "rogchap.com/v8go" ) // SandboxExecutor defines the interface for sandbox operations // This interface is implemented by agent/sandbox.Executor // It's defined here to avoid import cycles type SandboxExecutor interface { // Filesystem operations ReadFile(ctx context.Context, path string) ([]byte, error) WriteFile(ctx context.Context, path string, content []byte) error ListDir(ctx context.Context, path string) ([]infraSandbox.FileInfo, error) // Command execution Exec(ctx context.Context, cmd []string) (string, error) // Workspace info GetWorkDir() string // Sandbox identification GetSandboxID() string // VNC access (returns empty string if not available) GetVNCUrl() string } // SetSandboxExecutor sets the sandbox executor for this context // This should be called before hooks are executed func (ctx *Context) SetSandboxExecutor(executor SandboxExecutor) { ctx.sandboxExecutor = executor } // GetSandboxExecutor returns the sandbox executor if available func (ctx *Context) GetSandboxExecutor() SandboxExecutor { return ctx.sandboxExecutor } // HasSandbox returns true if sandbox executor is available func (ctx *Context) HasSandbox() bool { return ctx.sandboxExecutor != nil } // newSandboxObject creates the ctx.sandbox JavaScript object // Returns nil if sandbox executor is not available func (ctx *Context) newSandboxObject(iso *v8go.Isolate) *v8go.ObjectTemplate { if ctx.sandboxExecutor == nil { return nil } sandboxObj := v8go.NewObjectTemplate(iso) // Set methods sandboxObj.Set("ReadFile", ctx.sandboxReadFileMethod(iso)) sandboxObj.Set("WriteFile", ctx.sandboxWriteFileMethod(iso)) sandboxObj.Set("ListDir", ctx.sandboxListDirMethod(iso)) sandboxObj.Set("Exec", ctx.sandboxExecMethod(iso)) sandboxObj.Set("GetVNCUrl", ctx.sandboxGetVNCUrlMethod(iso)) sandboxObj.Set("GetSandboxID", ctx.sandboxGetSandboxIDMethod(iso)) return sandboxObj } // createSandboxInstance creates the sandbox object instance with workdir property func (ctx *Context) createSandboxInstance(v8ctx *v8go.Context) *v8go.Value { if ctx.sandboxExecutor == nil { return nil } sandboxTemplate := ctx.newSandboxObject(v8ctx.Isolate()) if sandboxTemplate == nil { return nil } // Set workdir as a property sandboxTemplate.Set("workdir", ctx.sandboxExecutor.GetWorkDir()) // Set sandbox_id as a property sandboxID := ctx.sandboxExecutor.GetSandboxID() sandboxTemplate.Set("sandbox_id", sandboxID) // Set vnc_url as a property (empty string if not available) // GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise vncSandboxID := ctx.sandboxExecutor.GetVNCUrl() if vncSandboxID != "" { sandboxTemplate.Set("vnc_url", openapiSandbox.GetVNCClientURL(vncSandboxID)) } else { sandboxTemplate.Set("vnc_url", "") } instance, err := sandboxTemplate.NewInstance(v8ctx) if err != nil { return nil } return instance.Value } // sandboxReadFileMethod implements ctx.sandbox.ReadFile(path) func (ctx *Context) sandboxReadFileMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() args := info.Args() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } if len(args) < 1 { return bridge.JsException(v8ctx, "ReadFile requires path parameter") } path := args[0].String() content, err := ctx.sandboxExecutor.ReadFile(context.Background(), path) if err != nil { return bridge.JsException(v8ctx, err.Error()) } // Return as string jsVal, err := v8go.NewValue(iso, string(content)) if err != nil { return bridge.JsException(v8ctx, err.Error()) } return jsVal }) } // sandboxWriteFileMethod implements ctx.sandbox.WriteFile(path, content) func (ctx *Context) sandboxWriteFileMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() args := info.Args() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } if len(args) < 2 { return bridge.JsException(v8ctx, "WriteFile requires path and content parameters") } path := args[0].String() content := args[1].String() err := ctx.sandboxExecutor.WriteFile(context.Background(), path, []byte(content)) if err != nil { return bridge.JsException(v8ctx, err.Error()) } // Return undefined on success return v8go.Undefined(iso) }) } // sandboxListDirMethod implements ctx.sandbox.ListDir(path) func (ctx *Context) sandboxListDirMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() args := info.Args() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } if len(args) < 1 { return bridge.JsException(v8ctx, "ListDir requires path parameter") } path := args[0].String() files, err := ctx.sandboxExecutor.ListDir(context.Background(), path) if err != nil { return bridge.JsException(v8ctx, err.Error()) } // Convert to JavaScript array of objects result := make([]map[string]interface{}, len(files)) for i, f := range files { result[i] = map[string]interface{}{ "name": f.Name, "size": f.Size, "is_dir": f.IsDir, } } jsVal, err := bridge.JsValue(v8ctx, result) if err != nil { return bridge.JsException(v8ctx, err.Error()) } return jsVal }) } // sandboxExecMethod implements ctx.sandbox.Exec(cmd) func (ctx *Context) sandboxExecMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() args := info.Args() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } if len(args) < 1 { return bridge.JsException(v8ctx, "Exec requires cmd parameter (array of strings)") } // Parse command array cmdArg := args[0] if !cmdArg.IsArray() { return bridge.JsException(v8ctx, "Exec requires cmd to be an array of strings") } cmdObj, err := cmdArg.AsObject() if err != nil { return bridge.JsException(v8ctx, "failed to parse cmd array: "+err.Error()) } // Get array length lengthVal, err := cmdObj.Get("length") if err != nil { return bridge.JsException(v8ctx, "failed to get cmd array length: "+err.Error()) } length := int(lengthVal.Integer()) // Build command slice cmd := make([]string, length) for i := 0; i < length; i++ { itemVal, err := cmdObj.GetIdx(uint32(i)) if err != nil { return bridge.JsException(v8ctx, "failed to get cmd array element: "+err.Error()) } cmd[i] = itemVal.String() } output, err := ctx.sandboxExecutor.Exec(context.Background(), cmd) if err != nil { return bridge.JsException(v8ctx, err.Error()) } jsVal, err := v8go.NewValue(v8ctx.Isolate(), output) if err != nil { return bridge.JsException(v8ctx, err.Error()) } return jsVal }) } // sandboxGetVNCUrlMethod implements ctx.sandbox.GetVNCUrl() func (ctx *Context) sandboxGetVNCUrlMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } // GetVNCUrl returns sandbox ID if VNC is supported, empty otherwise vncSandboxID := ctx.sandboxExecutor.GetVNCUrl() vncUrl := "" if vncSandboxID != "" { vncUrl = openapiSandbox.GetVNCClientURL(vncSandboxID) } jsVal, err := v8go.NewValue(iso, vncUrl) if err != nil { return bridge.JsException(v8ctx, err.Error()) } return jsVal }) } // sandboxGetSandboxIDMethod implements ctx.sandbox.GetSandboxID() func (ctx *Context) sandboxGetSandboxIDMethod(iso *v8go.Isolate) *v8go.FunctionTemplate { return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value { v8ctx := info.Context() if ctx.sandboxExecutor == nil { return bridge.JsException(v8ctx, "sandbox executor not available") } sandboxID := ctx.sandboxExecutor.GetSandboxID() jsVal, err := v8go.NewValue(iso, sandboxID) if err != nil { return bridge.JsException(v8ctx, err.Error()) } return jsVal }) }