Documentation
¶
Index ¶
- Constants
- Variables
- func CreateJSONRPCErrorInstance(code int64, message string, data object.Object) *object.Instance
- func IsJSONRPCError(obj object.Object) bool
- func NewSandboxLibrary(allowedPaths []string) *object.Library
- func Register(registrar object.LibraryRegistrar)
- func RegisterJSONRPC(registrar object.LibraryRegistrar)
- func RegisterMCP(registrar object.LibraryRegistrar)
- func RegisterPlugin(registrar object.LibraryRegistrar)
- func RegisterSandbox(registrar object.LibraryRegistrar, allowedPaths []string)
- func RegisterSync(registrar object.LibraryRegistrar)
- func ReleaseBackgroundTasks()
- func ResetRuntime()
- func SetBackgroundFactory(factory SandboxFactory)
- func SetMCPResourceNotifier(fn func(uri string))
- func SetSandboxFactory(factory SandboxFactory)
- func SetTaskErrorLogger(fn func(name string, err error))
- func WaitBackgroundTasks()
- func WithServerStart(ctx context.Context, ch chan struct{}) context.Context
- type Promise
- type RuntimeAtomic
- type RuntimeQueue
- type RuntimeShared
- type RuntimeWaitGroup
- type SandboxFactory
- type SandboxInstance
Constants ¶
const ( RuntimeLibraryName = "scriptling.runtime" RuntimeSyncLibraryName = "scriptling.runtime.sync" RuntimeSandboxLibraryName = "scriptling.runtime.sandbox" RuntimeJSONRPCLibraryName = "scriptling.runtime.jsonrpc" RuntimePluginLibraryName = "scriptling.runtime.plugin" )
Library names for the runtime core and the sub-libraries in this package. scriptling.runtime.kv and scriptling.runtime.http live in the extlibs/runtime/kv and extlibs/runtime/http packages (kv.LibraryName, http.LibraryName).
const ( RuntimeMCPLibraryName = "scriptling.runtime.mcp" // MCPRegistryVar is the environment variable name where the mcp.tool() // decorator records tool registrations. The folder scanner reads this after // evaluating a .py file to discover decorated tools. MCPRegistryVar = "__mcp_registry" )
Variables ¶
var JSONRPCErrorClass = &object.Class{
Name: "JSONRPCError",
}
JSONRPCErrorClass is the class for error objects produced by runtime.jsonrpc.error(). The stdio server recognises instances of this class and emits a JSON-RPC error response instead of a result.
var JSONRPCSubLibrary = func() *object.Library { functions := map[string]*object.Builtin{ "method": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } if len(args) == 1 { return makeJSONRPCDecorator(ctx, func(ref string) { registerJSONRPCMethod(name, ref) }) } handler, err := args[1].AsString() if err != nil { return err } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.jsonrpc.method %q registered after start_server() — method will not be served\n", name) } RuntimeState.JSONRPCMethods[name] = handler RuntimeState.Unlock() return &object.Null{} }, HelpText: `method(name, handler=None) - Register a JSON-RPC method handler, or use as decorator Decorator form: import scriptling.jsonrpc as jsonrpc @jsonrpc.method("echo") def echo(params): return params Imperative form: runtime.jsonrpc.method("echo", "handlers.echo") The handler receives the decoded JSON-RPC params as its single argument and returns a JSON-compatible result. Return runtime.jsonrpc.error(...) to produce an error response.`, }, "notification": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } if len(args) == 1 { return makeJSONRPCDecorator(ctx, func(ref string) { registerJSONRPCNotification(name, ref) }) } handler, err := args[1].AsString() if err != nil { return err } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.jsonrpc.notification %q registered after start_server() — notification will not be served\n", name) } RuntimeState.JSONRPCNotifications[name] = handler RuntimeState.Unlock() return &object.Null{} }, HelpText: `notification(name, handler=None) - Register a JSON-RPC notification handler, or use as decorator Decorator form: @jsonrpc.notification("updated") def on_updated(params): ... Imperative form: runtime.jsonrpc.notification("updated", "handlers.on_updated") Notifications are JSON-RPC requests without an id. The handler receives the decoded params and no response is written.`, }, "error": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 2); err != nil { return err } code, err := args[0].AsInt() if err != nil { return err } message, err := args[1].AsString() if err != nil { return err } var data object.Object if len(args) >= 3 { data = args[2] } return CreateJSONRPCErrorInstance(code, message, data) }, HelpText: `error(code, message, data=None) - Build a JSON-RPC error response Parameters: code (int): JSON-RPC error code (e.g. -32602 for invalid params) message (str): Human-readable error message data (any, optional): Optional structured data attached to the error Return this from a method handler to emit a JSON-RPC error response with a custom code. If omitted the response uses the given code and message. Example: def divide(params): if params["b"] == 0: return runtime.jsonrpc.error(-32602, "division by zero") return params["a"] / params["b"]`, }, } for name, builtin := range reqctx.RequestContextBuiltins() { functions[name] = builtin } functions["transport"] = reqctx.NewTransportBuiltin(`transport() - How the JSON-RPC server is being served: "http", "stdio" or None Lets one setup script work in every mode: over stdio the middleware never runs, so anything middleware would gate per user must be handled differently there. import scriptling.runtime as runtime if runtime.jsonrpc.transport() == "stdio": # No middleware over stdio: treat every caller alike. ... Returns "http" when serving at POST /json-rpc (also from method handlers mid-request), "stdio" for the --json-rpc stdio server, and None when the script is not being served at all.`) return object.NewLibrary(RuntimeJSONRPCLibraryName, functions, map[string]object.Object{ "JSONRPCError": JSONRPCErrorClass, }, "stdio JSON-RPC 2.0 server method and notification registration") }()
JSONRPCSubLibrary exposes runtime.jsonrpc for registering stdio JSON-RPC 2.0 method and notification handlers. Handlers are referenced by string ("library.function") and run on a fresh evaluator per request, matching the runtime.http / MCP / WebSocket concurrency model. When served over HTTP the originating request is available to handlers via get_request() / request_context().
var MCPSubLibrary = func() *object.Library { functions := map[string]*object.Builtin{ "tool": { Fn: mcpToolDecorator, HelpText: `tool(description, params=None, keywords=None, discoverable=False, ui=None, icons=None, title=None, read_only=None, destructive=None, idempotent=None, open_world=None) - Decorator for MCP tools Decorates a function to register it as an MCP tool. The function's parameters become the tool's input schema; the return value becomes the tool response. Parameters: description (str): Tool description shown to the AI params (dict, optional): Parameter metadata keyed by name. Each value is either a string (the description; type inferred from default or defaults to "string") or a dict with keys "type", "description", and optional "required". keywords (list, optional): Keywords for tool search/discovery discoverable (bool, optional): If True, tool is hidden from tools/list and only available via search (default: False) ui (dict, optional): Links this tool to a companion UI resource per the MCP Apps extension (https://github.com/modelcontextprotocol/ext-apps). A dict with an optional "resourceUri" (str, the ui:// resource — omit it for an "app"-only action tool with no view of its own, such as a form submission that's only ever called by a view that's already open) and optional "visibility" (list of "model" and/or "app"; defaults to both). At least one of "resourceUri" or "visibility" is required if "ui" is given at all. icons (list, optional): Visual identifiers for this tool's tools/list descriptor. Each element is a dict with a required "src" (str, an https:// URL or data: URI) and optional "mimeType", "sizes" (list of strings like "48x48"), and "theme" ("light" or "dark"). title (str, optional): Human-readable display name for the tool, shown by clients in place of the function name read_only (bool, optional): True if the tool does not modify its environment destructive (bool, optional): True if the tool may perform destructive updates (deleting or overwriting); False if it only adds idempotent (bool, optional): True if calling it again with the same arguments has no further effect open_world (bool, optional): True if the tool talks to external systems (the web, third-party APIs); False if it stays within a closed domain The four hints become the tool's annotations in tools/list. Leave one out (or pass None) and clients assume the spec default: not read-only, possibly destructive, not idempotent, open world. They tell clients how careful to be (e.g. confirm before a destructive call); they are not access control. Returns: A decorator function that registers the tool and returns the original function. Example: import scriptling.runtime.mcp as mcp @mcp.tool( description="Double a number", params={"n": {"type": "int", "description": "Number to double"}}, ) def calc(n): return f"{n} * 2 = {n * 2}" @mcp.tool(description="Greet someone", params={ "name": "Name of the person", "times": {"type": "int", "description": "Number of greetings"}, }) def greet(name, times=1): return "\n".join(f"Hello, {name}!" for _ in range(times)) @mcp.tool(description="Delete a note", title="Delete Note", destructive=True, idempotent=True, open_world=False) def delete_note(id): ... @mcp.tool(description="Get the sales report", ui={"resourceUri": "ui://sales-dashboard/dashboard.html"}, icons=[{"src": "https://example.com/sales.png", "mimeType": "image/png"}]) def sales_report(): return {"records": [...]} @mcp.tool(description="Add a sale record (called by the dashboard's own form, not the model)", ui={"visibility": ["app"]}) def add_sale(date, product, amount): return {"records": [...]}`, }, "resource": { Fn: mcpResourceDecorator, HelpText: `resource(uri, name="", description="", mime_type="", template=False, complete=None) - Decorator for MCP resources Decorates a function to register it as an MCP resource (or, with template=True, a URI template like "user://docs/{path}"). For a static resource the function takes no parameters; for a template its parameters are the URI's {var} variables. A string return is the content; a dict/list return is JSON encoded. The function runs on every resources/read, so content can change between reads. Parameters: uri (str): Resource URI, or the URI template when template=True name (str, optional): Human-readable resource name (defaults to the URI) description (str, optional): Resource description mime_type (str, optional): Content type (default "text/plain", or "application/json" for dict/list results). Ignored for a "ui://" uri — the MCP Apps extension MUSTs that exact mimeType, so it's always set for you template (bool, optional): Treat uri as a {var} URI template (default: False) complete (dict, optional): Completers for completion/complete, keyed by template variable: {"path": complete_path}. Each value is a function in this file (or its name), or a "module.function" string. A completer is called as fn(argument, value, context) — the variable's name, the partial value typed so far, and a dict of the arguments already filled in — and returns a list of suggestion strings (the client gets at most 100). Templates only. Returns: A decorator function that registers the resource and returns the original function. Example: import scriptling.runtime.mcp as mcp @mcp.resource("config://app", name="App config", mime_type="application/json") def app_config(): return {"version": "1.0", "debug": False} def complete_path(argument, value, context): return [p for p in ["intro", "install", "usage"] if p.startswith(value)] @mcp.resource("user://docs/{path}", template=True, mime_type="text/markdown", complete={"path": complete_path}) def user_doc(path): return "# doc " + path`, }, "prompt": { Fn: mcpPromptDecorator, HelpText: `prompt(description="", arguments=None, complete=None) - Decorator for MCP prompts Decorates a function to register it as an MCP prompt under the function's own name. The function's parameters become the prompt's arguments and are passed on every prompts/get. A string return is a single user message; a dict with a "messages" list of {"role": "user"|"assistant", "content": "..."} builds a multi-message prompt. Parameters: description (str, optional): Prompt description arguments (list, optional): Argument metadata dicts with "name", "description" and "required". Inferred from the function signature when omitted. complete (dict, optional): Completers for completion/complete, keyed by argument name: {"language": complete_language}. Each value is a function in this file (or its name), or a "module.function" string. A completer is called as fn(argument, value, context) — the argument's name, the partial value typed so far, and a dict of the arguments already filled in — and returns a list of suggestion strings (the client gets at most 100). An exception becomes an error for that completion request. Returns: A decorator function that registers the prompt and returns the original function. Example: import scriptling.runtime.mcp as mcp @mcp.prompt(description="Summarise a document") def summarise(text): return "Summarise the following:\n\n" + text def complete_language(argument, value, context): return [l for l in ["go", "python", "rust"] if l.startswith(value)] @mcp.prompt(description="Review code", complete={"language": complete_language}) def review(language, code): return {"messages": [ {"role": "user", "content": "Review this " + language + " code:"}, {"role": "assistant", "content": code}, ]}`, }, "skill": { Fn: mcpSkillDecorator, HelpText: `skill(files=None) - Decorator for MCP skills Decorates a function to register it as an MCP skill (the Agent Skills format) under the function's own name. The function takes no parameters and returns the SKILL.md content, including its YAML frontmatter; the frontmatter's name must match the function name and its description seeds the skill's listing, exactly as for a SKILL.md file in the skills folder. The function runs once when the server starts (skills are static content; they do not reload). Parameters: files (dict, optional): Supporting files mapping file name to content string, served alongside SKILL.md as skill://<name>/<file> Returns: A decorator function that registers the skill and returns the original function. Example: import scriptling.runtime.mcp as mcp @mcp.skill(files={"regions.md": "eu-west: Europe\n"}) def region-guide(): return """--- name: region-guide description: How to identify a region and set it --- # Region guide Read regions.md for the list of regions. """`, }, } for name, builtin := range requestRegistrationBuiltins() { functions[name] = builtin } functions["notify_resource_updated"] = notifyResourceUpdatedBuiltin() functions["transport"] = reqctx.NewTransportBuiltin(`transport() - How the MCP server is being served: "http", "stdio" or None Lets one setup script work in every mode: over stdio the middleware never runs, so registrations that middleware would gate per user must be made unconditionally instead. import scriptling.runtime.mcp as mcp if mcp.transport() == "stdio": # No middleware over stdio: expose the extra tools to everyone. ... Returns "http" when serving over HTTP (also from middleware and tool handlers mid-request), "stdio" for the MCP stdio server, and None when the script is not being served at all.`) return object.NewLibrary(RuntimeMCPLibraryName, functions, nil, "MCP tool, resource, and prompt registration via decorators") }()
MCPSubLibrary is the scriptling.runtime.mcp sub-library. It provides decorator functions for defining MCP tools (and in future, resources and prompts) from script code, plus the register_request_* functions middleware uses to expose entries for the life of a single request. Decorator registrations are recorded per-interpreter in __mcp_registry; request registrations in the per-request accumulator carried on the context.
var PluginSubLibrary = object.NewLibrary(RuntimePluginLibraryName, map[string]*object.Builtin{ "serve": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } version := "" if len(args) >= 2 { v, e := args[1].AsString() if e != nil { return e } version = v } description := "" if len(args) >= 3 { d, e := args[2].AsString() if e != nil { return e } description = d } // Optional opaque host-defined manifest data: metadata={...}. A // host reads it verbatim from the plugin handshake to learn // plugin-specific declarations without running plugin code. var metadata map[string]any if mObj := kwargs.Get("metadata"); mObj != nil { dict, ok := mObj.(*object.Dict) if !ok { return errors.NewError("serve: metadata must be a dict") } metadata = make(map[string]any, len(dict.Pairs)) for _, pair := range dict.Pairs { key, e := pair.Key.AsString() if e != nil { return errors.NewError("serve: metadata keys must be strings") } metadata[key] = pluginMetadataValue(pair.Value) } } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.plugin.serve() called after start_server() — plugin identity will not be used\n") } RuntimeState.PluginName = name RuntimeState.PluginVersion = version RuntimeState.PluginDescription = description RuntimeState.PluginMetadata = metadata RuntimeState.Unlock() return &object.Null{} }, HelpText: `serve(name, version="", description="") - Declare this script as a plugin server When runtime.start_server() is called in stdio mode the server serves the full Scriptling plugin protocol (scriptling.handshake, function.call, etc.) instead of the plain JSON-RPC loop. Clients can then load the script as a plugin peer with scriptling=True and get auto-generated proxy libraries. Parameters: name (str): Library name (e.g. "myservice"). Clients import it as plugin.<name>. version (str): Optional version string (e.g. "1.0.0"). description (str): Optional human-readable description. metadata (dict): Optional opaque host-defined manifest data, carried verbatim in the plugin handshake. Scriptling never interprets it; a host reads it to learn plugin-specific declarations. Example: import scriptling.runtime.plugin as plugin_srv plugin_srv.serve("calculator", "1.0", "Basic arithmetic operations") plugin_srv.register_function("add", "handlers.add") import scriptling.runtime as runtime runtime.start_server()`, }, "register_function": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } if fn, ok := args[0].(*object.Function); ok { env := evaluator.GetEnvFromContext(ctx) ref := libutil.ModuleRef(env, fn.Name) if ref == "" { return errors.NewError("cannot determine module name for @plugin.register_function decorator") } RuntimeState.Lock() RuntimeState.PluginFunctions[fn.Name] = ref RuntimeState.Unlock() return fn } name, err := args[0].AsString() if err != nil { return err } if len(args) == 1 { env := evaluator.GetEnvFromContext(ctx) return &object.Builtin{ Fn: func(_ context.Context, _ object.Kwargs, dArgs ...object.Object) object.Object { if len(dArgs) == 0 { return errors.NewError("decorator requires a function") } fn, ok := dArgs[0].(*object.Function) if !ok { return errors.NewError("decorated value must be a function, got %s", dArgs[0].Type()) } ref := libutil.ModuleRef(env, fn.Name) if ref == "" { return errors.NewError("cannot determine module name for decorator") } RuntimeState.Lock() RuntimeState.PluginFunctions[name] = ref RuntimeState.Unlock() return fn }, } } handler, err := args[1].AsString() if err != nil { return err } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_function %q registered after start_server() — function will not be served\n", name) } RuntimeState.PluginFunctions[name] = handler RuntimeState.Unlock() return &object.Null{} }, HelpText: `register_function(name, handler=None) - Register a function for the plugin server, or use as decorator Decorator form: import scriptling.runtime.plugin as plugin @plugin.register_function("add") def add(a, b): return a + b # Bare form uses the function name as the plugin name @plugin.register_function def greet(name): return "hello " + name Imperative form: plugin.register_function("add", "handlers.add")`, }, "register_fetcher": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 2); err != nil { return err } scheme, err := args[0].AsString() if err != nil { return err } read, err := args[1].AsString() if err != nil { return err } glob := "" if len(args) >= 3 { glob, err = args[2].AsString() if err != nil { return err } } if scheme == "" || scheme == "http" || scheme == "https" || scheme == "file" { return errors.NewError("register_fetcher: scheme must not be empty or a built-in (http, https, file)") } for i, r := range scheme { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z': case i > 0 && (r >= '0' && r <= '9' || r == '+' || r == '-' || r == '.'): default: return errors.NewError("register_fetcher: invalid scheme %q", scheme) } } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_fetcher called after start_server() — fetcher will not be served\n") } RuntimeState.PluginFetchScheme = scheme RuntimeState.PluginFetchRead = read RuntimeState.PluginFetchGlob = glob RuntimeState.Unlock() return &object.Null{} }, HelpText: `register_fetcher(scheme, read_handler, glob_handler=None) - Serve sources from this plugin server Registers a fetcher so the host can ask this peer for files on demand — how a script peer serves a host's declared assets (an icon, a logo) from strings or bytes inside the script itself, the scriptling equivalent of a Go peer's embedded assets. The host calls the handlers with the full source string and a slash path relative to it. Parameters: scheme (str): The source scheme to serve, e.g. "notes" (the host asks for notes://<path>). Must not be http, https or file. read_handler (str): A "library.function" ref called as fn(source, path). Return the file's contents (string or bytes); return None for a miss (not found); any other error fails the read. glob_handler (str): Optional "library.function" ref called as fn(source, pattern); return a list of {name, is_dir} dicts. Without it the fetcher reports no glob matches. Example: import scriptling.runtime.plugin as plugin_srv plugin_srv.register_fetcher("notes", "impl.fetch_read")`, }, "register_constant": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 2); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_constant %q registered after start_server() — constant will not be served\n", name) } RuntimeState.PluginConstants[name] = args[1] RuntimeState.Unlock() return &object.Null{} }, HelpText: `register_constant(name, value) - Register a constant exported by the plugin server Parameters: name (str): Constant name exposed to plugin clients. value (any): Value — any type that the plugin transport can encode (bool, int, float, string, list, dict, None). Constants are included in the handshake schema so clients can read them directly as attributes of the plugin library. Example: import scriptling.runtime.plugin as plugin_srv plugin_srv.register_constant("VERSION", "1.0.0") plugin_srv.register_constant("MAX_RETRIES", 5)`, }, "register_class": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } if cls, ok := args[0].(*object.Class); ok { env := evaluator.GetEnvFromContext(ctx) ref := libutil.ModuleRef(env, cls.Name) if ref == "" { return errors.NewError("cannot determine module name for @plugin.register_class decorator") } RuntimeState.Lock() RuntimeState.PluginClasses[cls.Name] = ref RuntimeState.Unlock() return cls } handler, err := args[0].AsString() if err != nil { return err } name := handler for i := len(handler) - 1; i >= 0; i-- { if handler[i] == '.' { name = handler[i+1:] break } } RuntimeState.Lock() if RuntimeState.ServerStarted { fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_class %q registered after start_server() — class will not be served\n", name) } RuntimeState.PluginClasses[name] = handler RuntimeState.Unlock() return &object.Null{} }, HelpText: `register_class(handler) - Register a class exported by the plugin server, or use as bare decorator Decorator form: import scriptling.runtime.plugin as plugin @plugin.register_class class Config: def __init__(self): self.version = "1.0" Imperative form: plugin.register_class("handlers.Config") The exposed class name is derived from the class name (decorator) or the last segment of the handler ref (imperative).`, }, }, nil, "Scriptling plugin server — declare this script as a plugin peer with full handshake support")
PluginSubLibrary exposes runtime.plugin for declaring a script as a Scriptling plugin server. When start_server() is called the CLI replaces the plain JSON-RPC loop with a full plugin.Server that handles the plugin handshake, function.call, and object lifecycle. Available in the agent variant of scriptling only.
var RuntimeLibraryFunctions = map[string]*object.Builtin{ "background": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 2); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } handler, err := args[1].AsString() if err != nil { return err } env := libutil.EnvFromContext(ctx) eval := evaliface.FromContext(ctx) shared := false if v := kwargs.Get("shared"); v != nil { if b, e := v.AsBool(); e == nil { shared = b } } daemon := false if v := kwargs.Get("daemon"); v != nil { if b, e := v.AsBool(); e == nil { daemon = b } } handlerKwargs := make(map[string]object.Object, len(kwargs.Kwargs)) for k, v := range kwargs.Kwargs { if k == "shared" || k == "daemon" { continue } handlerKwargs[k] = v } kwargs = object.NewKwargs(handlerKwargs) promise, claimed := claimTaskName(name) if !claimed { return promiseObject(promise) } handedOff := false defer func() { if !handedOff { releaseTaskName(promise) } }() end, taskErr := env.ResourceGuard().StartTask() if taskErr != nil { if goerrors.Is(taskErr, object.ErrMemoryLimitExceeded) { return errors.NewMemoryLimitStopError() } return &object.Error{Message: taskErr.Error(), ExceptionType: object.ExceptionTypeRuntimeError} } promise.endTask = end if shared { handedOff = true liveArgs := make([]object.Object, len(args)-2) copy(liveArgs, args[2:]) return startSharedTask(ctx, handler, liveArgs, kwargs.Kwargs, env, eval, daemon, promise) } for i, a := range args[2:] { if err := object.ValidateTransferable(a); err != nil { return errors.NewError("background arg %d: %s", i, err) } } for k, v := range kwargs.Kwargs { if err := object.ValidateTransferable(v); err != nil { return errors.NewError("background kwarg '%s': %s", k, err) } } fnArgs := make([]object.Object, len(args[2:])) for i, a := range args[2:] { fnArgs[i] = object.CloneObject(a) } fnKwargs := make(map[string]object.Object, len(kwargs.Kwargs)) for k, v := range kwargs.Kwargs { fnKwargs[k] = object.CloneObject(v) } RuntimeState.Lock() backgroundReady := RuntimeState.BackgroundReady factory := RuntimeState.BackgroundFactory if !backgroundReady && factory != nil { RuntimeState.Backgrounds[name] = handler RuntimeState.BackgroundArgs[name] = fnArgs RuntimeState.BackgroundKwargs[name] = fnKwargs RuntimeState.BackgroundEnvs[name] = env RuntimeState.BackgroundEvals[name] = eval RuntimeState.BackgroundCtxs[name] = ctx RuntimeState.BackgroundDaemons[name] = daemon RuntimeState.BackgroundPromises[name] = promise } RuntimeState.Unlock() if backgroundReady || factory == nil { handedOff = true return startBackgroundTask(promise, handler, fnArgs, fnKwargs, env, eval, factory, ctx, daemon) } handedOff = true return promiseObject(promise) }, HelpText: `background(name, handler, *args, **kwargs) - Start a fire-and-forget background task Starts a background task in a goroutine and returns immediately. Returns a promise (call .get() or .wait() to await the result), or an error if the handler is not found. In server mode the task is queued until the server starts; the promise resolves once it runs. Both handler patterns run in isolated environments with no access to the calling script's data. Sibling functions, imported modules, and module-level constants (int, float, bool, str) are copied; other module-level variables (lists, dicts, objects, logger instances) are NOT visible — pass them as arguments or via runtime.sync. A task that fails is reported to stderr with its name. The name identifies the task: calling background() with the name of a task that is already queued or running starts nothing and returns the running task's promise. This keeps a module that registers both a request handler and a task from spawning another copy each time it is re-imported to serve a request. Once a task ends its name is free to reuse. Parameters: name (string): Unique name for the background task handler (string): Function name or "library.function" "func_name" - runs in isolated env with import support and sibling functions "lib.func" - loads library.function in a new Scriptling instance *args: Positional arguments to pass to the function **kwargs: Keyword arguments to pass to the function shared (bool): Run in the caller's environment with live shared state instead of an isolated copy (default False) daemon (bool): Do not wait for this task at script exit (default False) Arguments must be transferable types — only simple values and recursively transferable containers are allowed: - Scalars: None, bool, int, float, str - Containers: list, dict, set, tuple (elements must also be transferable) - Not allowed: instances, classes, functions, builtins, or any other runtime-backed objects Arguments are deep-copied before the task starts so the caller and task cannot race on shared state. Returns: Promise: wait() blocks until the task finishes and get() returns its result; starting a task under a name that is still live returns that task's promise. Raises RuntimeError at --max-background-tasks, and an error if handler validation fails. Background tasks are fire-and-forget. For coordination between tasks use runtime.sync primitives (Shared, Atomic, Queue, WaitGroup). Access panels via console.Console().panel("name") from background tasks. The scriptling CLI waits for outstanding background tasks before exiting, so a finishing script does not kill them mid-flight and their output and logging are not lost. Pass daemon=True for long-running tasks that must not hold the process open. `, }, "start_server": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { wait := true if v := kwargs.Get("wait"); v != nil { if b, e := v.AsBool(); e == nil { wait = b } } RuntimeState.Lock() if staleServerContext(ctx) { RuntimeState.Unlock() return &object.Null{} } if !RuntimeState.ServerStarted && RuntimeState.ServerStartCh != nil { RuntimeState.ServerStarted = true if RuntimeState.ServerCollect != nil { RuntimeState.ServerCollect() } close(RuntimeState.ServerStartCh) } RuntimeState.Unlock() if wait { object.RunBlocking(ctx, func() { RuntimeState.RLock() ch := RuntimeState.ServerRunningCh RuntimeState.RUnlock() if ch != nil { <-ch } }) } return &object.Null{} }, HelpText: `start_server(wait=True) - Signal the server to start accepting requests Signals the server to collect registered routes/methods and begin listening for requests. Call this after all routes are registered. Parameters: wait (bool, default True): If True, blocks until the server shuts down. If False, returns immediately so the script can continue running (e.g. to maintain gossip state or run a polling loop). When wait=True the call blocks until the server receives a shutdown signal (SIGTERM / Ctrl-C). Use wait=False combined with a server_running() loop to stay alive while performing other work: runtime.start_server(wait=False) while runtime.server_running(): yield_now() Backward compatibility: scripts that exit without calling start_server() continue to work — the server starts automatically after the setup script finishes. `, }, "server_running": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { RuntimeState.RLock() ch := RuntimeState.ServerRunningCh stale := staleServerContext(ctx) RuntimeState.RUnlock() if ch == nil || stale { return object.NewBoolean(false) } select { case <-ch: return object.NewBoolean(false) default: return object.NewBoolean(true) } }, HelpText: `server_running() - Returns True while the server is running Returns True as long as the server has not received a shutdown signal. Returns False once the server is shutting down or if called outside of server mode. Typical usage with start_server(wait=False): runtime.start_server(wait=False) while runtime.server_running(): yield_now() # release GIL on each iteration `, }, }
RuntimeLibraryFunctions contains the core runtime functions (background)
var RuntimeState = struct { sync.RWMutex // JSON-RPC stdio methods and notifications (name -> "library.function") JSONRPCMethods map[string]string JSONRPCNotifications map[string]string // Background tasks Backgrounds map[string]string // name -> "function_name" BackgroundArgs map[string][]object.Object // name -> args BackgroundKwargs map[string]map[string]object.Object // name -> kwargs BackgroundEnvs map[string]*object.Environment // name -> environment BackgroundEvals map[string]evaliface.Evaluator // name -> evaluator BackgroundFactory SandboxFactory // Factory to create new Scriptling instances BackgroundCtxs map[string]context.Context // name -> context BackgroundReady bool // If true, start tasks immediately BackgroundDaemons map[string]bool // name -> daemon flag (queued tasks) BackgroundPromises map[string]*Promise // name -> promise returned to the setup script (queued tasks) // ActiveTasks holds the promise of every background task that is currently // queued or running, keyed by the task name. A task name identifies one // task, so background() with a name that is already active starts nothing // and hands back the running task's promise. The entry is dropped when the // task ends, so a name can be reused for a later task. // // This matters because a module body is re-executed whenever one of its // functions is imported to serve a request: without this, a setup script // that registers both a route handler and a background task in the same // file would spawn a fresh copy of that task on every request. ActiveTasks map[string]*Promise // name -> promise (queued or running) // TaskWG tracks outstanding non-daemon background tasks. Hosts that run // scripts to completion (the CLI's plain-run modes) wait on it before // exiting so fire-and-forget tasks are not killed mid-flight. It is // deliberately NOT reset by ResetRuntime: a WaitGroup's counter cannot be // reset, and Done() calls from still-running tasks of an earlier run must // land on the same counter or they would panic. TaskWG sync.WaitGroup // Sync primitives WaitGroups map[string]*RuntimeWaitGroup Queues map[string]*RuntimeQueue Atomics map[string]*RuntimeAtomic Shareds map[string]*RuntimeShared // Server lifecycle channels (nil in script mode) ServerStartCh chan struct{} // closed by start_server() to signal server is ready ServerRunningCh chan struct{} // closed by server on shutdown ServerStarted bool // prevents double-close of ServerStartCh ServerCollect func() // set by NewServer; called inside start_server() to snapshot routes atomically // Plugin server registration (set via runtime.plugin, agent variant only) PluginName string PluginVersion string PluginDescription string PluginMetadata map[string]any // opaque host-defined manifest data (runtime.plugin.serve metadata=) PluginFunctions map[string]string // function name → "library.function" handler PluginConstants map[string]object.Object // constant name → value PluginClasses map[string]string // exposed class name → "library.ClassName" handler // Plugin fetcher registration (runtime.plugin.register_fetcher): the // scheme the peer serves sources under, plus the "library.function" // handlers answering read (required) and glob (optional; empty serves // no matches). Set together, before start_server. PluginFetchScheme string PluginFetchRead string PluginFetchGlob string }{ JSONRPCMethods: make(map[string]string), JSONRPCNotifications: make(map[string]string), Backgrounds: make(map[string]string), BackgroundArgs: make(map[string][]object.Object), BackgroundKwargs: make(map[string]map[string]object.Object), BackgroundEnvs: make(map[string]*object.Environment), BackgroundEvals: make(map[string]evaliface.Evaluator), BackgroundFactory: nil, BackgroundCtxs: make(map[string]context.Context), BackgroundReady: false, BackgroundDaemons: make(map[string]bool), BackgroundPromises: make(map[string]*Promise), ActiveTasks: make(map[string]*Promise), WaitGroups: make(map[string]*RuntimeWaitGroup), Queues: make(map[string]*RuntimeQueue), Atomics: make(map[string]*RuntimeAtomic), Shareds: make(map[string]*RuntimeShared), ServerStartCh: nil, ServerRunningCh: nil, ServerStarted: false, PluginFunctions: make(map[string]string), PluginConstants: make(map[string]object.Object), PluginClasses: make(map[string]string), }
RuntimeState holds all runtime state
var SyncSubLibrary = object.NewLibrary(RuntimeSyncLibraryName, map[string]*object.Builtin{ "WaitGroup": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } RuntimeState.Lock() wg, exists := RuntimeState.WaitGroups[name] if !exists { wg = &RuntimeWaitGroup{} RuntimeState.WaitGroups[name] = wg } RuntimeState.Unlock() return &object.Builtin{ Attributes: map[string]object.Object{ "add": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { delta := int64(1) if len(args) > 0 { if d, err := args[0].AsInt(); err == nil { delta = d } } wg.wg.Add(int(delta)) return &object.Null{} }, HelpText: "add(delta=1) - Add to the wait group counter", }, "done": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { wg.wg.Done() return &object.Null{} }, HelpText: "done() - Decrement the wait group counter", }, "wait": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { object.RunBlocking(ctx, func() { wg.wg.Wait() }) return &object.Null{} }, HelpText: "wait() - Block until counter reaches zero", }, }, HelpText: "WaitGroup - Go-style synchronization primitive", } }, HelpText: `WaitGroup(name) - Get or create a named wait group Parameters: name (string): Unique name for the wait group (shared across environments) Example: wg = runtime.sync.WaitGroup("tasks") def worker(id): print(f"Worker {id}") wg.done() for i in range(10): wg.add(1) runtime.run(worker, i) wg.wait()`, }, "Queue": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } maxsize := 0 if len(args) > 1 { if m, err := args[1].AsInt(); err == nil { maxsize = int(m) } } if m, ok := kwargs.Kwargs["maxsize"]; ok { if mInt, err := m.AsInt(); err == nil { maxsize = int(mInt) } } RuntimeState.Lock() queue, exists := RuntimeState.Queues[name] if !exists { queue = newRuntimeQueue(maxsize) RuntimeState.Queues[name] = queue } RuntimeState.Unlock() return &object.Builtin{ Attributes: map[string]object.Object{ "put": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.ExactArgs(args, 1); err != nil { return err } if err := queue.put(ctx, args[0]); err != nil { return errors.NewError("queue error: %v", err) } return &object.Null{} }, HelpText: "put(item) - Add item to queue (blocks if full, respects context timeout)", }, "get": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { item, err := queue.get(ctx) if err != nil { return errors.NewError("queue error: %v", err) } return item }, HelpText: "get() - Remove and return item from queue (blocks if empty, respects context timeout)", }, "size": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { return object.NewInteger(int64(queue.size())) }, HelpText: "size() - Return number of items in queue", }, "close": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { queue.close() return &object.Null{} }, HelpText: "close() - Close the queue", }, }, HelpText: "Queue - Thread-safe queue for producer-consumer patterns", } }, HelpText: `Queue(name, maxsize=0) - Get or create a named queue Parameters: name (string): Unique name for the queue (shared across environments) maxsize (int): Maximum queue size (0 = unbounded) Example: queue = runtime.sync.Queue("jobs", maxsize=100) def producer(): for i in range(10): queue.put(i) def consumer(): for i in range(10): item = queue.get() print(item) runtime.run(producer) runtime.run(consumer)`, }, "Atomic": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } initial := int64(0) if len(args) > 1 { if i, err := args[1].AsInt(); err == nil { initial = i } } if i := kwargs.Get("initial"); i != nil { if iVal, err := i.AsInt(); err == nil { initial = iVal } } RuntimeState.Lock() atomic, exists := RuntimeState.Atomics[name] if !exists { atomic = &RuntimeAtomic{value: initial} RuntimeState.Atomics[name] = atomic } RuntimeState.Unlock() return &object.Builtin{ Attributes: map[string]object.Object{ "add": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { delta := int64(1) if len(args) > 0 { if d, err := args[0].AsInt(); err == nil { delta = d } else { return errors.NewTypeError("INTEGER", args[0].Type().String()) } } newVal := atomic.add(delta) return object.NewInteger(newVal) }, HelpText: "add(delta=1) - Atomically add delta and return new value", }, "get": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { return object.NewInteger(atomic.get()) }, HelpText: "get() - Atomically read the value", }, "set": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.ExactArgs(args, 1); err != nil { return err } if val, err := args[0].AsInt(); err == nil { atomic.set(val) return &object.Null{} } return errors.NewTypeError("INTEGER", args[0].Type().String()) }, HelpText: "set(value) - Atomically set the value", }, }, HelpText: "Atomic integer - lock-free operations", } }, HelpText: `Atomic(name, initial=0) - Get or create a named atomic counter Parameters: name (string): Unique name for the counter (shared across environments) initial (int): Initial value (only used if creating new counter) Example: counter = runtime.sync.Atomic("requests", initial=0) counter.add(1) # Atomic increment counter.add(-5) # Atomic add counter.set(100) # Atomic set value = counter.get() # Atomic read`, }, "Shared": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } name, err := args[0].AsString() if err != nil { return err } var initial object.Object = &object.Null{} if len(args) > 1 { initial = args[1] } if i := kwargs.Get("initial"); i != nil { initial = i } RuntimeState.Lock() shared, exists := RuntimeState.Shareds[name] if !exists { shared = &RuntimeShared{value: initial} RuntimeState.Shareds[name] = shared } RuntimeState.Unlock() return &object.Builtin{ Attributes: map[string]object.Object{ "get": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { return shared.get() }, HelpText: "get() - Get the current value (thread-safe read)", }, "set": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.ExactArgs(args, 1); err != nil { return err } shared.set(args[0]) return &object.Null{} }, HelpText: "set(value) - Set the value (thread-safe write)", }, "update": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.ExactArgs(args, 1); err != nil { return err } fn := args[0] result := shared.update(func(current object.Object) object.Object { eval := evaliface.FromContext(ctx) if eval == nil { return current } env := libutil.EnvFromContext(ctx) return eval.CallObjectFunction(ctx, fn, []object.Object{current}, nil, env) }) return result }, HelpText: "update(fn) - Atomically read-modify-write: fn receives current value, returns new value", }, }, HelpText: "Shared variable - thread-safe access with get()/set()/update()", } }, HelpText: `Shared(name, initial) - Get or create a named shared variable Parameters: name (string): Unique name for the variable (shared across environments) initial: Initial value (only used if creating new variable) Note: Values should be treated as immutable. Use set() to replace, or update() for atomic read-modify-write operations. Example: counter = runtime.sync.Shared("counter", 0) def increment(current): return current + 1 # Atomic increment using update() counter.update(increment) # Simple get/set for immutable values counter.set(42) value = counter.get()`, }, }, nil, "Cross-environment named concurrency primitives")
Functions ¶
func CreateJSONRPCErrorInstance ¶
CreateJSONRPCErrorInstance creates a JSON-RPC error object from Go code.
func IsJSONRPCError ¶
IsJSONRPCError reports whether obj is a JSONRPCError instance.
func NewSandboxLibrary ¶
NewSandboxLibrary creates a new sandbox library with the given allowed paths. If allowedPaths is nil, all paths are allowed (no restrictions). If allowedPaths is empty slice, no paths are allowed (deny all).
func Register ¶
func Register(registrar object.LibraryRegistrar)
Register registers the scriptling.runtime library: background tasks and the server lifecycle (start_server, server_running). Sub-libraries need their own Register call (RegisterSync, RegisterSandbox, RegisterJSONRPC, RegisterMCP, RegisterPlugin, kv.Register, http.Register); each one registered with the same registrar, before or after this call, is also reachable as an attribute of scriptling.runtime when the registrar can look libraries up (scriptling.Scriptling can).
func RegisterJSONRPC ¶
func RegisterJSONRPC(registrar object.LibraryRegistrar)
RegisterJSONRPC registers the scriptling.runtime.jsonrpc sub-library.
func RegisterMCP ¶
func RegisterMCP(registrar object.LibraryRegistrar)
RegisterMCP registers the scriptling.runtime.mcp sub-library.
func RegisterPlugin ¶
func RegisterPlugin(registrar object.LibraryRegistrar)
RegisterPlugin registers the scriptling.runtime.plugin sub-library. Like the other sub-libraries it is also exposed as runtime.plugin when scriptling.runtime is registered with the same registrar, so `import scriptling.runtime as rt; rt.plugin.serve(...)` works.
func RegisterSandbox ¶
func RegisterSandbox(registrar object.LibraryRegistrar, allowedPaths []string)
RegisterSandbox registers the sandbox library with the specified allowed paths. If allowedPaths is nil, all paths are allowed (no restrictions). If allowedPaths is empty slice, no paths are allowed (deny all).
func RegisterSync ¶
func RegisterSync(registrar object.LibraryRegistrar)
RegisterSync registers the scriptling.runtime.sync sub-library.
func ReleaseBackgroundTasks ¶
func ReleaseBackgroundTasks()
ReleaseBackgroundTasks sets BackgroundReady=true and starts all queued tasks
func ResetRuntime ¶
func ResetRuntime()
ResetRuntime clears all runtime state (for testing or re-initialization). It first runs the cleanup functions libraries registered with libutil.RegisterCleanup; they stay registered for the next reset. That is how the separately packaged sub-libraries reset: scriptling.runtime.http clears its routes and closes its WebSocket connections, and scriptling.runtime.kv closes its default and named stores.
func SetBackgroundFactory ¶
func SetBackgroundFactory(factory SandboxFactory)
SetBackgroundFactory sets the factory function for creating Scriptling instances in background tasks. Deprecated: Use SetSandboxFactory instead, which sets the factory for both sandbox and background use.
func SetMCPResourceNotifier ¶
func SetMCPResourceNotifier(fn func(uri string))
SetMCPResourceNotifier installs fn as the target of notify_resource_updated(); nil removes it, making the call a no-op.
func SetSandboxFactory ¶
func SetSandboxFactory(factory SandboxFactory)
SetSandboxFactory sets the factory function for creating sandbox instances. Must be called before sandbox.create() is used in scripts.
Example:
runtime.SetSandboxFactory(func() runtime.SandboxInstance {
p := scriptling.New()
setupMyLibraries(p)
return p
})
func SetTaskErrorLogger ¶
SetTaskErrorLogger replaces the handler invoked when a background task fails (its promise resolves with an error). Pass nil to restore the default stderr reporter. The handler must be safe to call from task goroutines.
func WaitBackgroundTasks ¶
func WaitBackgroundTasks()
WaitBackgroundTasks blocks until every outstanding non-daemon background task has finished. Hosts that run scripts to completion (the CLI's plain-run modes) call it before exiting so fire-and-forget tasks are not killed mid-flight; long-running hosts (servers) do not.
func WithServerStart ¶
WithServerStart marks ctx as belonging to the server whose start channel is ch. start_server() and server_running() under a context whose server has since been replaced (a setup script left over from an earlier server) neither start nor report the newer server.
Types ¶
type Promise ¶
type Promise struct {
// contains filtered or unexported fields
}
Promise represents an async operation result
type RuntimeAtomic ¶
type RuntimeAtomic struct {
// contains filtered or unexported fields
}
RuntimeAtomic is a named atomic counter
func (*RuntimeAtomic) Value ¶
func (a *RuntimeAtomic) Value() int64
Value returns the current counter value. Exported for hosts and tests that need to observe a sync.Atomic created by script code.
type RuntimeQueue ¶
type RuntimeQueue struct {
// contains filtered or unexported fields
}
RuntimeQueue is a named thread-safe queue
type RuntimeShared ¶
type RuntimeShared struct {
// contains filtered or unexported fields
}
RuntimeShared is a named shared value. Values stored should be treated as immutable. Use set() to replace. For atomic read-modify-write, use update() with a callback.
type RuntimeWaitGroup ¶
type RuntimeWaitGroup struct {
// contains filtered or unexported fields
}
RuntimeWaitGroup is a named wait group
type SandboxFactory ¶
type SandboxFactory func() SandboxInstance
SandboxFactory creates new Scriptling instances for sandbox execution. Must be set by the host application before sandbox.create() can be used. The factory should return a fully configured instance with all required libraries registered and import paths configured.
func BackgroundFactory ¶
func BackgroundFactory() SandboxFactory
BackgroundFactory returns the currently configured background factory, or nil. Hosts that layer behaviour onto it (a server adding its package loader, say) read it, wrap it, and install the wrapper.
func GetSandboxFactory ¶
func GetSandboxFactory() SandboxFactory
GetSandboxFactory returns the currently configured sandbox factory. Returns nil if no factory has been set.
type SandboxInstance ¶
type SandboxInstance interface {
SetObjectVar(name string, obj object.Object) error
GetVarAsObject(name string) (object.Object, error)
EvalWithContext(ctx context.Context, input string) (object.Object, error)
SetSourceFile(name string)
LoadLibraryIntoEnv(name string, env *object.Environment) error
SetOutputWriter(w io.Writer)
}
SandboxInstance is the minimal interface a sandbox environment needs. This matches the Scriptling public API without importing the scriptling package. It is also used by the background task factory in scriptling.runtime.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package http provides the scriptling.runtime.http library: route, static file and WebSocket registration for the scriptling server, plus response helpers.
|
Package http provides the scriptling.runtime.http library: route, static file and WebSocket registration for the scriptling server, plus response helpers. |
|
Package kv provides the scriptling.runtime.kv library: a thread-safe key-value store (the process-wide default store plus named stores opened with kv.open), backed by snapshotkv.
|
Package kv provides the scriptling.runtime.kv library: a thread-safe key-value store (the process-wide default store plus named stores opened with kv.open), backed by snapshotkv. |