跳到主要内容

Mini-app 沙箱

Mini-app(迷你应用)是 Agent Spaces 提供的一种轻量应用形态。每个 Mini-app 是一个独立、自包含的 React 或 HTML 项目,自带源码、配置、数据与(可选)SQLite 数据库,可以在平台内独立编辑、预览和运行,也可以挂载 Agent 运行时让 Agent 帮你编写和增强它。

什么是 Mini-app?

一个 Mini-app 由以下要素组成:

  • 项目类型 reacthtml,入口文件分别是 index.jsxindex.html
  • 源码目录 src/,存放所有用户编写的代码与资源。
  • 配置目录 configs/,存放 JSON 形式的应用配置(由沙箱服务端读写,对项目内所有客户端实时同步)。
  • 数据目录 data/,存放应用落盘的二进制或文本数据文件。
  • SQLite 数据库 data/db/<dbName>.sqlite,每个项目可按需创建多个命名数据库(基于 better-sqlite3)。
  • Agent 配置(可选)agents.json,声明项目内可对话的多个 Agent 角色。
  • manifest.json,记录项目元数据(名称、类型、入口文件、启用的插件、头像等)。

所有 Mini-app 项目存放在 ~/.agent-spaces-data/mini-apps/{projectId}/ 下,由 packages/server/src/storage/mini-app-store.ts 管理目录结构与越界保护。项目 id 由名称经清洗生成(替换文件系统/URL 非法字符为 _),创建后固定,改名只更新 manifest.name

创建与编辑

1. 创建项目

通过 POST /api/mini-apps 创建项目,请求体需要 nametypereacthtml,否则返回 400)。后端会按类型生成默认入口文件:

  • React 类型:生成 index.jsx(用 window.AgentSpacesUI 解构出 ButtonCard 等宿主组件)和一份自动生成的 CLAUDE.md 项目说明模板。
  • HTML 类型:生成一份最小 index.html 与同名 CLAUDE.md

名称必须全局唯一(大小写敏感、trim 后精确匹配),重名返回 409

2. 编辑源码

编辑入口在前端 packages/web/src/app/mini-apps/[id]/,集成 Monaco 编辑器与 TypeScript LSP。后端提供一组文件管理 REST 路由:

  • GET /api/mini-apps/:id/files — 文件树
  • GET /api/mini-apps/:id/files/manifest — 扁平文件清单 + mtimeMs,前端据此做增量刷新
  • GET|PUT /api/mini-apps/:id/files/content?path= — 读取/写入文件内容
  • POST /api/mini-apps/:id/files/rename — 重命名或移动
  • POST /api/mini-apps/:id/files/folder — 创建空目录
  • POST /api/mini-apps/:id/files/upload — multipart 批量上传(单文件上限 500MB)
  • DELETE /api/mini-apps/:id/files — 删除文件或目录

文件路径全部经过 safeSrcPath 越界校验,禁止绝对路径与 .. 逃逸 src/ 目录。所有写入操作都会更新 manifest 的 updatedAt 时间戳。

3. 导入与导出

  • GET /api/mini-apps/:id/export — 用 archiver 打包为 ZIP(含 manifest.json、src/、icon 与 avatar)。
  • POST /api/mini-apps/import — 接收 base64 编码的 ZIP,用 yauzl 解压。导入时会自动定位内容根(兼容 src/ 子目录或扁平布局,最多下钻 4 层),并解压时校验条目路径安全(拒绝 .. 与绝对路径)。

预览与运行

预览入口在前端 packages/web/src/app/mini-apps-preview/[id]/。预览页会一次性加载项目所有源码文件,按 manifest.mainFile 解析入口;若入口文件不在文件树中(通常是导入时多余顶层目录没剥掉),会显式报错而非静默 fallback 到 tree[0],避免渲染错误的入口导致模块解析错乱。

React 模式下,宿主通过 window.AgentSpacesUI 注入一批 React 组件与 lucide-react 图标供应用解构使用;HTML 模式下则运行普通 HTML/CSS/JS,window.AgentSpacesUIwindow.AgentSpaces 同样可用。

沙箱服务编译机制

Mini-app 允许在项目 src/services/*.js 中定义服务端可调用的服务处理函数。服务编译由 packages/server/src/services/mini-app-services.tscompileService() 实现,机制是:

  1. 剥离 import 行 — 用正则移除所有 import ... 语句(服务不依赖外部模块)。
  2. ESM → CJS 转换 — 把 export default 替换成 module.exports =
  3. 沙箱求值 — 用 new Function('module', 'exports', stripped) 构造函数并在沙箱中执行,默认导出应为 { eventName: handler } 形式的方法表。

服务文件按项目维度缓存到内存 registry(registries Map),首次访问或显式 reloadServices() 时重新加载。项目删除时通过 unloadServices() 清理缓存。

每个 handler 被调用时获得一个 MiniAppServiceContext,提供:

  • readConfig(path) / writeConfig(path, value) / updateConfig(path, updater) — 读写 configs/,写入后向该 projectId 频道广播 miniApp.configChanged 事件。
  • listRunningTasks() — 返回当前进程内的运行中任务(含 executorId)。
  • broadcast(event, data) — 向该 projectId 频道广播任意事件。

服务通过 POST /api/mini-apps/:id/services/invoke 调用,请求体为 { name, payload }。服务是 configs/ 的唯一写入方,保证状态变更集中、可广播。

Agent 运行时与工具注入

Mini-app 内置独立的 Agent 对话能力,运行链路封装在 packages/server/src/services/mini-app-agent.tsrunMiniAppAgent() 中。它自包含执行,不依赖 workspace 系统:读取 agents.json → 解析凭据 → 组装 functionTools → 注入 systemPrompt → 执行 → 落盘消息。

运行时选择

当前 Mini-app 内置 Agent 固定使用 langchain 运行时(runtimeConfig.kind = 'langchain'),并解析凭据优先级为:

  1. agentId 指向的 Agent preset 作为默认(modelProvider/modelId/apiKey/apiBase/systemPrompt/temperature/maxTokens)。
  2. Agent 本地字段覆盖 preset 值。
  3. systemPrompt 本地优先,缺失才用 preset。
  4. 全都没有则调用方走服务端默认模型兜底。

凭据解析后,可通过 providerIdapiBase+apiKey 匹配已配置的 provider。

函数工具注入

agent.tools 配置允许时(默认 { api: true, plugin: true }),运行时注入以下工具:

  • plugin 工具(由 createMiniAppFunctionTools() 提供,定义在 packages/server/src/services/builtin-tools/mini-app-tools.ts):
    • list_agent_spaces_ui_components — 按分类列出 window.AgentSpacesUI 暴露的宿主 React 组件(共约 9 个分类:actions / forms / layout / navigation / overlays / feedback / data-display / media / utilities)。
    • list_plugin_tools — 列出项目已启用插件(enabledPlugins)注册的所有 tool 与内置 tool 的轻量摘要。
    • get_plugin_tool_detail — 查看指定插件 tool 的完整 input_schema
    • execute_plugin_tool — 执行插件 tool,内置虚拟插件 @agent-spaces/builtin 提供 list_agent_presetsagent_run 两个内置工具。
  • api.js 方法工具:项目 src/api.js 编译出的方法表(编译机制与 services 一致),每个方法被包装成一个 function tool;同时注入一个 get_mini_app_tools 元工具,让 Agent 查询 src/tools.js 里声明的工具元数据(描述与 inputSchema)后再调用项目自定义方法。

systemPrompt 注入

运行时会拼接出 systemPrompt,包含:Agent 自带 systemPrompt、当前路由(route)、当前 projectId、可用 api.js 方法名清单(提示先调 get_mini_app_tools 看描述),以及启用的插件清单(提示用 list_plugin_tools 等三个工具)。

SSE 流式对话

对话通过 POST /api/mini-apps/:id/agents/:agentId/chat 触发,以 Server-Sent Events 流式返回以下事件:

  • reasoning — 思考过程(含 status)
  • tool_use — 工具调用(id / name / input)
  • tool_result — 工具结果(toolUseId / result)
  • text — 输出文本行
  • message_saved — 对话结束,落盘的用户与 agent 消息
  • done / error

用户消息时间戳在执行捕获,agent 消息时间戳在执行后生成,保证严格 user 早于 agent,避免同毫秒落盘导致历史排序错乱。agent 消息会附带 toolCalls 数组(name / input / result)。

Agent 配置管理

  • GET /api/mini-apps/:id/agents — 脱敏返回 agents 清单与 enableAgents 开关(不含 apiKey)。
  • GET /api/mini-apps/:id/agents/:agentId — 返回完整 agent config(含 apiKey,仅供编辑器加载)。
  • PUT /api/mini-apps/:id/agents/:agentId — 整条替换 agent config;写入时会剥离 apiKey/apiBase/baseURL,按 provider 解析后只存非敏感字段。
  • GET /api/mini-apps/:id/agents/chat?sessionId=&agentId= — 拉取历史消息(按 timestamp 升序)。
  • DELETE /api/mini-apps/:id/agents/chat?sessionId=&agentId= — 清空 session(可按 agentId 过滤)。

服务器启动时会调用 ensureAgentsConfigs():若某项目的 agents.json 尚不存在但 manifest 声明了 agents 种子数组,则把种子写入 agents.json;已存在的 agents.json 一律跳过,绝不覆盖(哪怕文件损坏)。

客户端 RPC

Mini-app 支持服务端向项目内预览客户端发起请求-响应 RPC,由 packages/server/src/services/mini-app-client-rpc.ts 实现:

  • 服务端通过 requestMiniAppClient(projectId, type, payload, timeoutMs=5000) 发起请求,生成随机 requestId,向该 projectId 频道广播 miniApp.clientRequest 事件(携带 requestId / type / payload)。
  • 客户端处理后将结果通过 WebSocket miniApp.clientResponse 事件回传(携带 requestId / ok / result / error),由 handleMiniAppClientResponse() 配对解决或拒绝 Promise。
  • 超时默认 5 秒,超时则 reject。

Agent 的 api.js 方法可通过 ApiCtx.requestClient() 复用这条 RPC 通道向预览客户端要数据。

SQLite 数据持久化

每个 Mini-app 可拥有多个命名 SQLite 数据库,由 packages/server/src/storage/mini-app-db.ts 管理(better-sqlite3):

  • 数据库文件位于 ~/.agent-spaces-data/mini-apps/{projectId}/data/db/{dbName}.sqlite
  • 连接按 projectId/dbName 复用(连接池),首次打开设置 journal_mode = WALbusy_timeout = 5000
  • 项目删除时 closeProjectDbs() 关闭并清理该项目所有连接。

REST 路由:

  • POST /api/mini-apps/:id/db/:dbName — 单条语句执行,body 为 { sql, params?, mode }mode 取值 all|get|run|exec
  • POST /api/mini-apps/:id/db/:dbName/transaction — 批量语句原子执行(statements: [{sql, params?}]),任一抛错则整个事务回滚。

所有 SQL 经 checkSql 安全校验,参数通过 bindArgs 绑定,all 模式结果超过 MAX_ROWS 上限会抛错。writeDataFilePUT /api/mini-apps/:id/data/content)则用于将文本或 base64 二进制写入 data/ 目录。

任务缓存

Mini-app 维护一份进程内任务状态缓存(packages/server/src/services/mini-app-tasks.ts),按 projectId 维护任务列表,供多客户端通过 WS 频道同步任务状态。startTask() 登记 running 任务(同 taskId 已为 running 时幂等保留),finishTask() / failTask() 标记终态;终态任务保留 TTL(10 分钟)后由 listTasks() 触发清理,running 任务永不清理。

WebSocket 事件

Mini-app 相关事件遵循 domain.action 命名约定:

  • miniApp.configChanged — 沙箱服务写入 configs 后广播(含 path / value)。
  • miniApp.clientRequest — 服务端向客户端发起 RPC 请求(含 requestId / type / payload)。
  • miniApp.clientResponse — 客户端回传 RPC 响应(含 requestId / ok / result / error)。
  • miniApp.taskSnapshot — 客户端连入时推送当前任务快照(断线/刷新/新标签恢复视图用)。
  • miniApp.configSnapshot — 客户端连入时推送配置全量快照(UI 据此建立内存缓存,不再直接 readConfig)。

模板

平台内置少量 Mini-app 模板(位于 packages/templates/mini-app/,含 minimax_tts 等),通过模板索引 index.json 注册。模板含 manifest.jsonsrc/index.jsx 与可选 avatar.png,可用于快速创建带预设功能(如语音合成)的项目。

相关链接

  • 关于 Issue 自动化与 Workflow 调度:议题管理Workflow 编辑器
  • 关于插件系统与可注入的工具:插件
  • 设计与实现细节参见 docs/mini-app-agent.mddocs/mini-app-preview-agent.mddocs/mini-app-renderer.mddocs/mini-app-state-sync-ws-plan.md