# 小工具开发指南

> 使用 Beav Mini App 包、window.redbox SDK 和 Host 能力开发可安装、可授权、可升级的小工具。

- 原文: https://www.getbeav.com/docs/mini-app-development

Beav 小工具（Mini App）是安装在 Beav 中的本地沙箱应用。它使用静态 HTML、CSS 和 JavaScript 构建，通过 `window.redbox` SDK 保存界面状态，并在用户授权后调用 Beav 的采集、知识库、资产库、媒体、AI 和自动化能力。

这份指南同时面向开发者和负责开发小工具的 Agent。公开文档说明稳定的开发方法；当前设备真正支持的能力、参数和权限始终以本机运行时为准。

## 先理解两套工具

小工具开发涉及两套彼此独立的工具面，不能混用。

| 工具面          | 使用者               | 用途                    |
| ------------ | ----------------- | --------------------- |
| Agent 写包工具   | 负责创建或修改小工具的 Agent | 读取、写入和校验 Mini App 包文件 |
| Mini App SDK | 已打开的小工具页面         | 保存界面状态并调用 Host 能力     |

### Agent 写包工具

在 Beav 的小工具创建或编辑会话中，Agent 使用以下通用工具修改包：

* `workspace.read`：读取 manifest、当前版本文件和写入后的结果。
* `workspace.write`：写入完整文件；发布 manifest 时必须使用完整写入。
* `workspace.patch`：对新版本中的现有文本文件做局部修改。
* `image.generate`：用户明确需要生成式头像或图片素材时使用。

Agent 应先通过 `ui.capabilities.inspect(redbox.sandbox.web@1)` 读取当前设备的包和 SDK 契约。它不能用聊天 Agent 的 `tool_search` 猜测 Mini App 能力，也不能用 shell、浏览器自动化或 Tauri API 绕过包协议。

### 运行中的 Mini App SDK

小工具代码只使用注入页面的 `window.redbox`：

```js
const permissions = window.redbox.capabilities.list();

const state = window.redbox.state.get() || {};
window.redbox.state.set({ ...state, lastInput: 'example' });

const result = await window.redbox.invoke('knowledge.search', {
  query: '选题素材',
});

window.redbox.ui.notify('已完成');
window.redbox.ui.close();
```

* `capabilities.list()` 返回当前空间已经授予这个版本的能力。
* `state.get()` / `state.set()` 读写当前空间中的小工具界面状态。
* `invoke(action, payload)` 调用 manifest 已声明且用户已授权的 Host action。
* `ui.notify()` 显示短状态提示；`ui.close()` 关闭当前小工具窗口。

不要把 Agent 写包工具名称放进 manifest，也不要在小工具代码里调用它们。

## 最小可运行包

```text
miniapp://<app-id>/
  manifest.json
  host.json
  versions/
    1.0.0/
      index.html
      styles.css
      app.js
      icon.svg
      assets/
      tasks/
      skills/
```

* `manifest.json` 是当前已发布版本的指针，必须最后写入。
* `versions/<version>/` 是不可变版本目录。发布后不得原地修改。
* `host.json` 是 Host 维护的发布历史，开发者和 Agent 不得编辑。
* `window.redbox.state` 不属于包文件，由 Host 按空间隔离保存。

最小 manifest：

```json
{
  "schemaVersion": 2,
  "id": "topic-helper",
  "name": "选题助手",
  "description": "搜索并整理创作选题",
  "version": "1.0.0",
  "entry": "versions/1.0.0/index.html",
  "icon": "versions/1.0.0/icon.svg",
  "capabilities": ["knowledge.search"],
  "sdk": { "min": 2, "max": 2 },
  "stateSchemaVersion": 1,
  "privateSkills": [],
  "agentTasks": [],
  "status": "ready"
}
```

主要约束：

* `id` 只使用小写 ASCII 字母、数字、`-` 和 `_`，最大 64 个字符。
* `entry` 必须位于当前 `versions/<version>/` 目录内。
* `icon` 使用包内 PNG、JPG、WebP、GIF、SVG 或 AVIF，最大 2 MiB。
* `capabilities` 最多 64 项，必须使用下文列出的精确 action 名称。
* `privateSkills` 最多 8 个，只能包含声明式 Skill、references、templates 和静态素材。
* `agentTasks` 最多 16 个，必须声明输入、输出、能力上限和是否允许后台运行。
* manifest 最大 64 KiB，入口 HTML 最大 512 KiB，状态最大 256 KiB。
* `status: "ready"` 只表示包已完成并通过读回验证；未完成的草稿使用 `draft`。

## 版本与发布规范

除单纯改名外，任何界面、行为、图标、能力或包文件变化都必须创建新版本。

正确发布顺序：

1. 读取当前 `manifest.json`，保留相同的 `id`。
2. 创建新的 `versions/<next>/`，不要修改当前线上目录。
3. 写入新版本的 HTML、CSS、JavaScript、图标和声明资源。
4. 逐个读取文件，确认内容完整且路径正确。
5. 最后一次性写入完整 `manifest.json`，让 `version`、`entry` 和 `icon` 指向新版本。
6. 再次读取 manifest 和入口文件，确认发布指针已经切换。

只写入新版本目录而没有切换 manifest，仍然是未发布草稿。工具调用返回成功也不等于发布完成。

改名是唯一例外：只更新 manifest 的 `name`，保持版本、入口、图标、状态和能力不变，并在写入后读回。

## 全部运行时能力

manifest 只声明小工具实际会调用的能力。声明代表申请权限，不代表已经授权。参数 schema 可能随 Host 版本扩展，开发时应以当前设备的 `ui.capabilities.inspect` 结果为准。

### 采集

* `capture.collect`：采集公开内容或账号链接，可下载支持的平台媒体并选择是否写入知识库。
* `capture.status`：读取一次采集或研究任务的状态。

采集视频并继续处理时，应从返回值中使用 Host 资源引用，不要读取或拼接本地文件路径。

### 社交平台

* `social.capabilities`
* `social.profile.resolve`
* `social.profile.listContent`
* `social.search`
* `social.detail`
* `social.research`
* `social.collect`
* `social.subscription.create`
* `social.subscription.list`
* `social.subscription.update`
* `social.subscription.refresh`
* `social.subscription.sync`
* `social.job.get`

社交 action 用于读取平台资料、内容和研究结果。需要把链接内容或媒体保存下来时使用 `capture.collect`。

### 知识库

* `knowledge.search`
* `knowledge.list`
* `knowledge.read`
* `knowledge.create`
* `knowledge.update`
* `knowledge.delete`
* `knowledge.attach`
* `knowledge.inspectVisual`

知识库是 Host 的业务数据源。小工具自己的筛选条件、表单草稿和最近使用记录放进 `window.redbox.state`，不要复制一份知识库到状态中。

### 资产库

* `assets.list`
* `assets.search`
* `assets.get`
* `assets.readText`
* `assets.create`
* `assets.update`
* `assets.createText`
* `assets.patchText`
* `assets.createFolder`
* `assets.updateFolder`
* `assets.rename`
* `assets.move`
* `assets.setCover`
* `assets.import`
* `assets.trash`
* `assets.restore`
* `assets.delete`
* `assets.categories.list`
* `assets.categories.create`
* `assets.manage`
* `assets.generateCharacterCard`
* `assets.commitInitializationCandidates`

### 选题中心

* `topicCenter.read`
* `topicCenter.manage`

### 稿件

* `manuscripts.list`
* `manuscripts.read`
* `manuscripts.createProject`
* `manuscripts.write`
* `manuscripts.patch`
* `manuscripts.variants.ensure`
* `manuscripts.variants.read`
* `manuscripts.variants.save`
* `manuscripts.projects.promoteStandalone`
* `manuscripts.packages.begin`
* `manuscripts.packages.preflight`
* `manuscripts.packages.build`
* `manuscripts.packages.list`
* `manuscripts.packages.getPreview`

### 记忆

* `memory.list`
* `memory.search`
* `memory.recall`
* `memory.add`
* `memory.note`
* `memory.update`
* `memory.archive`
* `memory.manage`
* `memory.rebuildIndex`
* `memory.diagnostics`
* `memory.commitInitializationCandidates`

### 媒体处理

* `media.transcribe`：把 `assets://` 或 `miniapp-media://` 资源转成纯文字、SRT 或 VTT。
* `media.bind`
* `media.edit`

`media.import`、`media.search`、`media.get`、`media.inspect` 和 `video.analyze` 不是 Mini App action。不要让用户粘贴绝对路径，也不要把 `sourcePath`、`toolPath` 或输出目录传给 `media.transcribe`。

### 图片、视频与语音

* `image.generate`
* `video.generate`
* `voice.list`
* `voice.get`
* `voice.speech`
* `voice.clone`
* `voice.bindAsset`
* `voice.delete`

### Agent

* `agent.run`
* `agent.get`
* `agent.events`
* `agent.cancel`
* `agent.runTask`

`agent.run` 启动受 Host 管理的持久任务，`allowedCapabilities` 必须是 manifest 能力的子集。优先使用 `agent.runTask` 运行 manifest 中已经声明的不可变 Agent Task；Host 会校验输入 schema、能力上限、后台资格和绑定的 Skill。

### Direct AI 与任务结果

* `ai.generate`
* `ai.analyze`
* `jobs.list`
* `jobs.get`
* `jobs.events`
* `jobs.cancel`

Direct AI 适合一次模型调用，不启动 Agent，也不使用工具。它支持 prompt、system prompt、Host 资源引用、JSON response schema、reasoning effort 和受限生成参数。生成、转写和 Agent 任务会投影到 app-owned jobs，小工具只能读取和取消自己的执行。

### 事件与自动化

* `events.subscribe`
* `events.unsubscribe`
* `automations.preview`
* `automations.create`
* `automations.list`
* `automations.update`
* `automations.disable`
* `automations.runs`
* `automations.retry`

iframe 关闭后不会继续运行。需要监听知识库或后台执行时，应创建由 Host 持久化的自动化：先用 `automations.preview` 校验定义并获得短期 token，再由用户确认创建或更新。Package JavaScript 本身不能在后台常驻。

### Host UI 与资源交接

* `ui.notify`
* `ui.pickResources`
* `ui.openResource`
* `ui.previewResource`
* `ui.exportResource`
* `ui.copy`
* `ui.openExternal`

这些 action 负责由 Host 选择、预览、打开、复制或导出资源。使用 `knowledge://`、`assets://`、`miniapp-media://` 等规范引用，不向页面暴露物理路径。

## 授权与审批

小工具打开时，Host 根据 manifest 展示需要的能力，并把授权绑定到当前空间、app id 和版本。每次 invoke 时，Host 都会重新读取 manifest 和授权、校验 action schema，并过滤凭据和绝对路径。

* 普通读取在已授予 app 能力后执行。
* `capture.collect` 使用当前空间的 app 授权，不重复弹出相同的逐次授权。
* 付费生成、转写、导入、删除、外部采集、订阅、通用 manage、Agent 启动或取消等高影响操作可能要求 typed approval。
* 自动化只有在定义、版本、能力、预算和并发限制都匹配已批准范围时才能后台执行。
* manifest 新增能力后，旧授权不会自动扩大；用户必须重新确认。

不得通过重复请求、改 action 名称、直接网络访问或隐藏副作用绕过审批。

## 沙箱边界

Mini App 可以运行包内脚本和素材，但不能直接使用：

* Tauri API、Node.js、npm、shell 或子进程。
* 原始文件系统或用户电脑上的绝对路径。
* `fetch`、WebSocket 或其他直接网络请求。
* Host 凭据、Cookie、API Key 或环境变量。
* 远程 JavaScript、CSS、字体、图片、音视频或 iframe。
* popup、表单提交、浏览器下载或任意外部导航。
* 浏览器控制、插件控制、Team Runtime、聊天 Agent 工具或 `ui.surface.manage`。

需要外部内容或 Host 数据时，选择对应的 typed action。需要外部打开、资源导出或复制时，使用 Host UI action。

## 常用开发配方

### 链接转字幕

manifest 至少声明：

```json
{
  "capabilities": ["capture.collect", "media.transcribe"]
}
```

```js
const captured = await window.redbox.invoke('capture.collect', {
  url,
  platform: 'auto',
  downloadMedia: true,
  ingestToKnowledge: false,
});

const resourceRef = captured.items?.[0]?.evidenceRef
  || captured.knowledge?.entryIds?.[0];

const subtitles = await window.redbox.invoke('media.transcribe', {
  resourceRef,
  format: 'srt',
});
```

只有拿到可读取的媒体引用后才能开始转写。采集任务显示完成但没有返回可用资源时，不得显示“转写成功”。

### 搜索知识库

```js
const result = await window.redbox.invoke('knowledge.search', {
  query: input.value.trim(),
});
```

### 一次 Direct AI 调用

```js
const result = await window.redbox.invoke('ai.generate', {
  prompt: '把下面内容整理成三个选题',
  resourceRefs: selectedRefs,
  responseFormat: 'json',
  responseSchema: {
    type: 'object',
    properties: {
      topics: { type: 'array', items: { type: 'string' } }
    },
    required: ['topics']
  }
});
```

引用知识或资产内容时，还要声明对应的读取能力。JSON 结果只有通过 response schema 校验后才算成功。

## 状态与数据规范

* 包是全局安装的，但状态和授权按空间隔离。
* 运行上下文绑定 `app id + version + space`，不要在页面打开后自行推断当前空间。
* `window.redbox.state` 只保存小而明确的 UI 状态，例如输入草稿、模式、最近记录和任务引用。
* 知识、资产、稿件和媒体仍以 Host 数据库为真值，不复制到小工具状态中。
* 同一来源应复用已有记录，避免刷新、重试或重新打开时制造重复数据。
* 长任务保存 execution id，重新打开后通过 `jobs.get`、`agent.get` 或对应 status action 恢复。

## UI 规范

* 一个小工具只解决一个主要任务，并提供一个明显的主要操作。
* 优先适配约 280–420 px 的侧边栏宽度，再向更宽的弹窗响应式扩展。
* 使用系统字体、清晰对比度、可见键盘焦点和可操作的表单标签。
* 提供空闲、处理中、成功和失败状态；失败后保留输入并允许从同一按钮重试。
* 长任务显示简短状态和刷新入口，重新打开后能恢复正在运行的任务。
* 主要结果应可复制；确实存在第二种常用格式时再提供第二个复制入口。
* 不添加设置页、仪表盘、引导长文或与主要任务无关的实体。
* 不依赖 Host 的 Tailwind、React、lucide 或样式变量；样式必须放在包内。

## 调试

Host 会收集受限的 `console.*`、未捕获 JavaScript 错误、Promise rejection、SDK action、request id 和耗时。编辑已有小工具时，Agent 应读取任务上下文提供的 `miniApp.diagnosticsRef`，不要让用户复制日志或打开额外调试面板。

日志中不要输出凭据、完整源文档、供应商原始响应或二进制内容。错误 UI 只展示用户可以理解和恢复的信息。

## 完成检查清单

发布前逐项确认：

* manifest id 与现有小工具一致，版本目录是新的且不可变。
* 入口、脚本、样式、图标和声明资源都已写入并读回。
* manifest 只声明实际调用的精确 capability。
* 没有直接网络、绝对路径、远程依赖或未声明副作用。
* 空闲、处理中、成功、失败和重试状态完整。
* 长任务能够重新读取，重复操作不会创建重复业务数据。
* manifest 最后写入，并已读回正确的 version、entry、icon 和 `status: "ready"`。
* 实际打开小工具后，结果来自 Host 返回值或持久化资源，而不是页面伪造的成功状态。

只有完成写入、持久化和读回后，Agent 才能告诉用户“小工具已经发布”。
