Skip to content

[RFC]: Add JavaScript compute runner sandbox #445

Description

@hiqiancheng

Summary

Add a model-facing JavaScript compute runner built on QuickJS for short, bounded, side-effect-free calculations inside TouchAI conversations.

Motivation

TouchAI currently has Bash for broad local command execution, but many model tasks only need a narrow compute surface for arithmetic, statistics, simulation, and JSON transformation. A dedicated runner can let the model verify intermediate calculations without requesting user approval for every safe computation and without exposing filesystem, network, shell, DOM, Tauri, or database capabilities.

Affected boundaries

  • AgentService
  • conversation runtime
  • tool execution
  • context construction

Proposed design

Implement a built-in tool tentatively named builtin__run_javascript. The tool accepts a small JavaScript snippet, JSON input, a retention scope (once, reply, or session), an optional state key, and optional small libraries.

QuickJS is the primary sandbox boundary. TouchAI should inject only JSON input, JSON state, console log capture, simple-statistics as ss, decimal.js as Decimal, and optionally d3-array as array. The runner should deny network, file, shell, DOM, Tauri, database, dynamic import, and WebAssembly access. State is stored as JSON snapshots only, not as a preserved VM heap. Reply state clears when the assistant response finishes or aborts. Session state is in-memory only and does not survive app restart.

The initial library set should include simple-statistics and decimal.js by default. d3-array can be optional. mathjs and @stdlib/stats should not be default dependencies because of size and dependency weight.

Alternatives and trade-offs

  • Browser worker or WebView-only sandbox: simpler, but exposes more browser surface and requires more host-global removal.
  • Tauri capability plus QuickJS: useful defense in depth, but not necessary as the core boundary when QuickJS receives no Tauri or browser API references.
  • Deno runtime: mature permission model, but heavier than needed for small model-owned calculations.
  • Node vm or vm2: rejected because they are not suitable security boundaries for untrusted code.
  • Persistent notebook-style VM heap: rejected for v1 because JSON state snapshots are easier to bound, inspect, clear, and test.

Upstream references

  • Design draft: docs/superpowers/specs/2026-06-09-javascript-compute-runner-design.md
  • QuickJS / quickjs-emscripten
  • simple-statistics
  • decimal.js
  • d3-array

Testing and rollout

Use TDD. Cover successful calculations, once/reply/session retention, state cleanup, library injection, unavailable library errors, timeout/interruption, output/log/state limits, forbidden global access, syntax/runtime errors, built-in tool registration, tool logging, and disabled-tool behavior.

Implementation PRs should link this RFC and run pnpm test:pr. Native or Rust-side integration should also run the relevant Rust checks and tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:rfcArchitecture or cross-cutting design discussionstatus:acceptedConfirmed and ready for contribution

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions