# dsh-web-icon-indicator

> 📖 [English](README.md) · [中文文档](README.zh.md) · 📝 [更新记录](CHANGELOG.md) · [Releases](https://github.com/waknow/dsh-web-icon-indicator/releases) · 🎨 [在线演示](https://waknow.github.io/dsh-web-icon-indicator/)

[![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![npm version](https://img.shields.io/npm/v/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
[![npm downloads](https://img.shields.io/npm/dm/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2**（设置卡片所用的 settings 服务 API）。已针对当前最高测试版本 **DSH 0.1.2-rc.1** 构建并验证。在 **DSH < 0.1.2** 上 favicon 仍可用，但设置页（**设置 → 插件 → 插件配置**）不可用。

浏览器标签页 favicon 实时反映 DSH 会话状态——`待机` / `运行中` / `提问` / `完成`——让你在标签页置于后台时也能一眼看出是否有会话需要处理。

> 🎨 **在线演示** — <https://waknow.github.io/dsh-web-icon-indicator/> · 在浏览器里直接体验四种状态、多 Agent 计数与全部特效，无需安装。试玩间还能驱动演示页自身标签页的真实 favicon——正如插件在 DSH 页面里做的那样。

## ✨ 功能特性

- **标签页 favicon 实时反映会话状态** —— 浏览器标签页图标同步 `idle` / `running` / `asking` / `done`（聚合优先级：`asking` > `running` > `done` > `idle`），后台标签页也能一眼看清 agent 们在做什么——包括 `ask_user_question` 提问，以及审批 / 沙箱提权等待（这两种情况会把图标钉在 `asking` 态）。
- **单个 SVG，浏览器内上色与动画** —— 只内置一个鲸鱼模板（[`icons/base.svg`](./icons/base.svg)）；每个状态、颜色、每一帧都在客户端渲染为 `data:image/svg+xml` URI，不再有按颜色拆分的图标文件。
- **六种内置特效** —— `static`（静止）、`blink`（闪烁）、`breath`（呼吸）、`rainbow`（彩虹）、`heartbeat`（心跳）、`bounce`（跳动），全部由 JavaScript 驱动（favicon 不会播放 SVG CSS 动画）。
- **完全可配置、即时生效** —— 每个状态的颜色、特效、周期，以及提问 / 完成驻留时长，改动约 1 秒内同步到已打开的标签页——无需刷新、无需重启。
- **内置配置 UI，无需手写 YAML** —— DSH 设置页里的 *标签页图标指示器* 卡片可编辑整套配置，带实时色块预览，保存后自动写入 `settings.yaml`（路径见下）。
- **后台标签页与重启抗性** —— 隐藏标签页中 `requestAnimationFrame` 被暂停时，动画态会按墙钟时间补帧；状态轮询还能扛住 host 重启、后端停止：故障期间标签页图标绝不丢失（还原启动时缓存的原始图标 `data:`-URI 副本，或保留最后一帧插件图标），端点恢复后自动回到实时状态。切回前台时会立刻触发一次状态拉取并重绘——后台标签页的定时器会被浏览器节流，轮询可能滞后，所以回到标签页的瞬间就刷新最新状态（比如 `done` 保持期在隐藏期间过期、图标应退回 `idle` 的情况）。
- **多 agent 一目了然** —— 当同时有**超过一个**活动 agent（非待机：`asking` / `running` / `done`）时，favicon 从鲸鱼切换为**占满整帧的数字块**，实时显示活动数（上限 `99+`），颜色与动画和该状态下鲸鱼完全一致；活动数回到 0–1 时恢复鲸鱼。（视觉与 [`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道一致。）

### 🛠 配置界面——怎么找到它

| # | 步骤 |
| --- | --- |
| 1 | 打开 DSH Web GUI，进入 **设置**。 |
| 2 | 在 **插件** 选项卡中，打开 **插件配置**。 |
| 3 | 找到 **标签页图标指示器（Favicon indicator）** 卡片。 |
| 4 | 先用 **默认图标颜色** 设置待机鲸鱼的颜色（这是待机唯一的设置项——待机只画一种颜色、不做动画），再展开状态行（`running` / `asking` / `done`）编辑 **特效**、**颜色**（每个色块即原生取色器），以及**周期（毫秒）**——仅动画状态显示，静态状态无周期；用 **提问驻留** / **完成驻留** 调整两个时长。 |

改动会通过 settings 传输层持久化到 profile 的 `settings.yaml`，约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。

## 🎬 默认配置，可视化

四个默认状态在浏览器标签页中的实际效果（`asking` 那条鲸鱼真的在闪烁）：

<p align="center">
  <img src="assets/states-default.svg" width="420" alt="默认状态：idle 深色鲸鱼、running 黄色、asking 红/黄闪烁、done 绿色">
</p>

| 状态 | 颜色（默认） | 特效（默认） |
| --- | --- | --- |
| `idle` 待机 | `#1a1a1a`——深色鲸鱼（可用 `defaultColor` 替换） | `static` |
| `running` 运行中 | `#FACC15`——黄色 | `static` |
| `asking` 提问 | `#E5484D` ⇄ `#FACC15`——红/黄 | `blink`（400ms） |
| `done` 完成 | `#22A06B`——绿色 | `static`，保持 `doneHoldMs` 后回到 `idle` |

### 多 agent 可视化

当多个 agent 同时运行时，favicon 本身就是计数器：只要活动 agent **超过一个**
（非待机：`asking` / `running` / `done`，含短暂的 `done` 保持期），鲸鱼就换成
占满整帧的数字块——实时显示 `active` 计数，底色取聚合状态色、特效与鲸鱼完全
同源（照常闪烁 / 呼吸 / 彩虹）；活动数回到 0–1 时恢复鲸鱼。

<p align="center">
  <img src="assets/multi-agent-count.svg" width="576" alt="active=0 深色鲸鱼、active=1 黄色鲸鱼、active=2/3 黄色数字块、active=3 asking 红色块（闪烁）、active=100 显示 99+">
</p>

| `active`（非待机 agent 数） | favicon |
| --- | --- |
| `0` | 深色 `idle` 鲸鱼 |
| `1` | 对应状态的鲸鱼（`running` 黄色，…） |
| `2`–`99` | 占满整帧的数字块，数字高度约占图标的 31%–52%（1 位 26、2 位 20、3+ 位 15.5）——16px 与固定标签页都可读 |
| `100`+ | `99+` |

状态优先级不变：`asking` 仍以红 ⇄ 黄 400ms 闪烁接管（闪烁的是数字块），
`done` 按其颜色驻留 `doneHoldMs`，计数随状态轮询实时刷新（约 1 秒）。
视觉与 [`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道一致。

## ✨ 全部特效，动画演示

下面每个预览都是真实的鲸鱼路径，按插件实际渲染方式做动画（预览是自包含的动画 SVG，在浏览器里直接播放）：

| 特效 | 效果 | 预览 |
| --- | --- | --- |
| `static` | 纯色单帧，无动画——使用 `colors[0]` | <img src="assets/effects/static.svg" width="56" alt="static 特效预览"> |
| `blink` | 在 `colors[0]` ⇄ `colors[1]` 之间按 `speed` 切换（缺省时自动推导更深的第二色） | <img src="assets/effects/blink.svg" width="56" alt="blink 特效预览"> |
| `breath` | 在 `colors[0]` 与 `colors[1]` 之间平滑呼吸过渡（缺省时推导） | <img src="assets/effects/breath.svg" width="56" alt="breath 特效预览"> |
| `rainbow` | 以 `colors[0]` 为起始色相，在 `speed` 内绕色轮循环 | <img src="assets/effects/rainbow.svg" width="56" alt="rainbow 特效预览"> |
| `heartbeat` | 在 `speed` 内做「lub-dub」式的尖锐缩放脉冲——颜色为 `colors[0]` | <img src="assets/effects/heartbeat.svg" width="56" alt="heartbeat 特效预览"> |
| `bounce` | 鲸鱼在 `speed` 内上下跳动——颜色为 `colors[0]` | <img src="assets/effects/bounce.svg" width="56" alt="bounce 特效预览"> |

想改颜色并实时观察标签页 favicon 变化？打开自包含 demo（[`demo/dynamic-color.html`](./demo/dynamic-color.html)）——选择状态 + 特效并实时改色，favicon 即时更新（无构建、无依赖）。

## 安装

这是一个标准 DSH bundle 插件。安装到 `web` profile（GUI/TUI profile 会自动通过 cordis patch 层加载）：

从 npm 安装（**推荐**）：

```bash
dsh plugin --profile web add dsh-web-icon-indicator@latest
```

从 Git 源码安装：

```bash
dsh plugin --profile web add github:waknow/dsh-web-icon-indicator
```

或从本地目录 / tarball 安装：

```bash
dsh plugin --profile web add <路径或tarball>
```

或将目录放进 `~/.dsh/profiles/web/node_modules/<name>/`，并附带与包内一致的 `cordis.patch.yml`。

## 配置

所有键均可选，默认值如下。`statusPath` 与 `iconPathPrefix` 是**注册期**键：
只能在合成条目（composition entry）里设置——它们在插件挂载时就被烘进路由表与注入
脚本，因此刻意**不**进入设置面（`settings.yaml`）。

| 键 | 默认值 | 含义 |
| --- | --- | --- |
| `iconsDir` | `<package>/icons/` | 单个 `base.svg` 所在目录 |
| `statusPath` | `/dsh-web-icon-status.json` | JSON 状态端点 —— **注册期（仅合成条目）** |
| `iconPathPrefix` | `/dsh-web-icon-indicator` | `base.svg` 的 URL 前缀 —— **注册期（仅合成条目）** |
| `askingHoldMs` | `3500` | 提问状态的最小保持时长 |
| `doneHoldMs` | `5000` | 完成状态保持时长，随后回到 idle |
| `defaultColor` | *（未设置）* | 默认图标颜色（待机鲸鱼的主色）—— 用于区分多个 DSH 实例；与其它状态颜色过于接近时会告警 |
| `states` | 见下 | 每个状态的视觉配置 |

`states` 中每个状态是一个对象：`{ effect, colors[], speed? }`：

```yaml
config:
  states:
    idle:    { effect: static,    colors: ['#1a1a1a'] }
    running: { effect: static,    colors: ['#FACC15'] }
    asking:  { effect: blink,     colors: ['#E5484D', '#FACC15'], speed: 400 }
    done:    { effect: static,    colors: ['#22A06B'] }
```

- **`effect`** — 取 `static | blink | breath | rainbow | heartbeat | bounce` 之一。
- **`colors`** — **数组**，多个 hex 颜色（`#rgb` / `#rrggbb`；非法项会被逐项忽略，全部无效时才回退到该状态的内置颜色）。`colors[0]` 为主色。多色特效读取更多项：`blink` 用 `colors[0]`⇄`colors[1]`，`breath` 在 `colors[0]`⇄`colors[1]` 间过渡（缺省时自动推导更深的第二色），`rainbow` 仅用 `colors[0]` 作起始色相。
- **`speed`** — 可选，该状态的周期（ms），也是 `blink` 的切换间隔。默认 `1200`。

`idle` 比较特殊：它的颜色就是 `defaultColor` 键，设置卡片**不为它提供状态条目**
（一种颜色、不做动画、也没有周期）。若 `states.idle` 来自合成条目或手写的
`settings.yaml`，仍然会被沿用——这属于向后兼容，只是无法在卡片里编辑。

每个状态条目会在默认值之上做浅合并，因此只需覆盖少量状态。示例：

```yaml
- id: dsh-web-icon-indicator
  name: 'dsh-web-icon-indicator'
  config:
    states:
      running: { effect: breath,    colors: ['#FF9900', '#FFD9A0'], speed: 900 }
      asking:  { effect: rainbow,   colors: ['#FF0000'] }
      done:    { effect: heartbeat, colors: ['#2ECC71'] }
```

### 区分多个实例（`defaultColor`）

同时开多个 DSH 实例（不同项目 / profile / 端口）时，给每个实例设一个自己的默认图标
颜色，浏览器标签页就能一眼区分，不必去改整套状态配色：

```yaml
- id: dsh-web-icon-indicator
  name: 'dsh-web-icon-indicator'
  config:
    defaultColor: '#5B8DEF'
```

- `defaultColor` 就是**待机鲸鱼的主色**。它会被折叠进 `states.idle.colors[0]`，
  因此 idle 仍保留自己配置的特效与第二色，其它状态的颜色语义（黄=运行、红/黄=提问、
  绿=完成）不受影响；不设置（默认）即等于「沿用 idle 自己的颜色」，行为与之前完全一致。
- 这是**按 DSH 实例**生效的设置，不是按标签页：同一实例的所有标签页共用它；另一个实例
  （自己的 profile / `settings.yaml`，例如 `dsh web --port 3081`）可以用另一种颜色。
- **恢复默认**会把你的覆盖清回合成条目。当颜色本来就来自合成条目（`base` 层，
  用户层的 `unset` 触及不到）时，卡片改为写入 idle 自己的颜色——这样「恢复默认」真的
  能让图标回到朴素的鲸鱼色，而合成条目里配置的值仍可通过「清除覆盖」一键取回。
- **相似度告警**：当默认颜色与其它状态颜色在感知上过于接近时，你会收到告警 —— 设置卡片里
  实时显示（保存前即可见）、host 日志里记录、状态端点 `warnings` 中返回 —— 但该颜色
  仍然会被应用（告警绝不阻断保存）。度量方式是 **CIELAB 中的 CIE76 ΔE**：`ΔE < 25`
  告警，`ΔE < 12` 视为几乎相同（ΔE 2.3 是人眼恰可分辨的阈值）。比较覆盖每个状态实际
  会画出的所有填充：`asking` 闪烁的两种颜色、`breath` 的插值中间色；而 `rainbow`
  会扫过所有色相，因此任何有色默认色都会被标记。出厂默认中 `running` 与 `asking` 刻意
  共用 `#FACC15`，所以**状态之间互不比较**，只把默认色与它们逐一比较。格式非法的值会被
  忽略并上报，而不会被画到图标上。

### 设置页与 `settings.yaml`（DSH ≥ 0.1.2）

插件把上面整套配置注册进 DSH settings 服务，命名空间为 `web-icon-indicator`
（schema 为 `lib/index.js` 中的 schemastery schema）：

- **Web GUI：** 打开 **设置 → 插件 → 插件配置**，会出现 *标签页图标指示器*
  卡片，可编辑：提问/完成驻留、**默认图标颜色**（带配色一览与相似度告警），以及
  `running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
  各占一行可折叠条目，行首是一个**色块**——多色状态（asking）会左右分格同时显示
  红黄两色，旁边是一行「特效 · 周期」摘要（如 `Blink · 400ms`）。点击色块即打开
  系统取色弹窗（十六进制值在那里显示/输入），卡片本身不打印颜色代码——唯一例外是相似度
  告警，它会给出具体的 hex 以便定位（例如「与「运行中」的颜色 #FACC15 几乎相同（ΔE 0）」）。
  展开该行后只列出当前
  特效**实际会用到**的颜色：`blink` / `breath` 在还有空位时才多出一个 `+` 色块
  （它默认停在浏览器本来就会推导的那个更深的第二色上，且不会重复添加已有颜色），第一个
  之后的色块都可以删除；单色特效则只显示、也只保存一个色块，不会留下看不见的第二色。
  `rainbow` 不携带颜色列表：字段与色块都显示彩虹色环，色环旁另有一个**可选的起始色相**
  色块（也就是存下的 `colors[0]`）。`idle` 刻意**不占
  一行**——它只画一种颜色、不做动画，默认图标颜色就是它的全部配置。所有修改都通过
  settings 传输层暂存并保存。
- **持久化：** 值写入 profile 的 `settings.yaml`（默认 `~/.dsh/settings.yaml`）
  的 `web-icon-indicator:` 段。合成条目仍是 `base` 层；解析顺序为 schema 默认值
  → 合成条目 → 设置文档用户层。
- **无需重启服务器、无需刷新标签页**即可让设置卡片的修改生效：`askingHoldMs` /
  `doneHoldMs` 在主机侧即时生效；各状态的视觉配置（特效 / 颜色 / 周期）会随状态
  轮询同步进正在运行的标签页，约 1 秒内生效。只有改 `lib/index.js` 里的代码级
  默认值才需要重载标签页（或重新构建 DSH Web）。
- **路由路径不是设置项。** `statusPath` / `iconPathPrefix` 是注册期键，已被烘进
  路由表与注入脚本，因此只存在于合成条目（见上方表格），改动需要重启。它们刻意
  不在设置 schema 与 `settings.yaml` 中：若在那里生效，浏览器会去请求服务器根本
  没有提供的路径。
- 因此设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir` 与 `states`。
  `iconsDir` 没有 schema 默认值，用户未设置时不会出现在设置文档中。
- 浏览器半区是手写的 `lib/client.js`（ModuleLoader factory 格式——无构建步骤、
  无额外运行期依赖，仅用 shell 自带的 `react`）。DSH 客户端扫描器会在下次启动
  profile 时识别新的 `dsh.client` 声明。
- 未组合 settings 服务的部署不受影响：插件回退到直接读取合成条目，行为与之前完全一致。

## 实现原理

- Host 插件 + 一个小型浏览器半区：在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`（鲸鱼模板），以及一个 `tapIndex` 向每个 `index.html` 注入小段浏览器脚本。整套配置已注册进 DSH settings 服务（`web-icon-indicator` 命名空间）用于校验、持久化与设置页卡片（见上）。
- 状态按 `agents.list()` 聚合，优先级 `asking > running > done > idle`。每次请求都会执行一次 `reconcile()` 检测 running → idle 的转换，因为 `agent/status` 的 idle 事件在回合结束时并不保证送达。状态端点还会上报 `active`——非待机 agent 数——当该数 **> 1** 时，注入脚本改为渲染占满整帧的数字块（[`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道：圆角色块，填充色与鲸鱼同源的逐帧状态色/特效，白色粗体数字约占图标高度 31%–52%，上限 `99+`），而不是鲸鱼，这样即使在 16px 的固定标签页里也能一眼看出同时有几个 agent 在忙。
- `ask_user_question` 工具调用（通过 `tools/pre-execute` / `tools/result`）把会话置为 `asking`，带可配置的最小保持时长，即使你立刻回答，图标也会保持可见。
- 权限 / **沙箱拦截**等待同样会显示为 `asking`：当 agent 命中沙箱拒绝并请求提权（`sandbox_permissions` + `justification`），或其他工具需要征得同意时，审批服务会先写入一条 `approval/asked` 会话事件并阻塞 agent，直到你做出决定。插件监听 `session/event`（并以实时会话日志的权威折叠作为兜底）在整个等待期间将会话置为 `asking` 状态，收到 `approval/decided` 后清除。
- 浏览器脚本每秒轮询 `/dsh-web-icon-status.json`（轮询间隔在注入脚本里固定为 1000 ms，不是配置项），首次获取 `base.svg`，然后每个 `requestAnimationFrame` 周期把 favicon 重建为 `data:image/svg+xml,…` URI——把 `__COLOR__` 占位符替换为状态配置的颜色，并应用该状态配置的特效。状态响应还会携带当前的每状态视觉配置，因此设置保存后约 1 秒内（下一个轮询 tick）即同步到已打开的标签页，无需刷新。浏览器不会播放 SVG favicon 的 CSS 动画，所以一切动画都由 JS 驱动。由于浏览器在**隐藏（后台）标签页会暂停 `requestAnimationFrame`**，轮询还会为动画态补绘一帧按墙钟时间计算的画面——后台标签页保持粗粒度动画（约每 1 秒）而不会冻结，切回前台后恢复满速动画。回到前台时还会通过 `visibilitychange` 立即触发一次状态拉取并重绘——后台定时器会被节流，轮询可能滞后，所以切回标签页的瞬间就能看到最新状态（例如隐藏期间 `done` 保持期已过、图标应退回 `idle`）。轮询还能**扛住 host 重启 / 后端停止**：启动时会把原始 favicon 缓存为离线安全的 `data:`-URI 副本，请求失败时还原该副本（副本未取到则保留最后一帧插件图标）——绝不写回原始的服务端 URL（后端停止时它恰恰不可达，写回正是「图标丢失」的根因）；每个 tick 持续重试，端点恢复后第一个成功轮询即换回实时图标（SPA 原地重连，无需手动刷新）。

## 浏览器支持与已知限制

favicon 本质是一张图片，浏览器不会在标签页 UI 里运行 SVG 自带的 CSS/JS 动画——每一帧都在本插件里由 JavaScript 生成。

| 浏览器 | SVG favicon | 逐帧换色 / 换特效 | 说明 |
| --- | --- | --- | --- |
| Chrome / Edge | ✅ | ✅ 顺滑 | 实时重读 `<link rel=icon>`；`data:` URI 的 SVG 可用。 |
| Firefox | ✅ | ✅ 顺滑 | 对 SVG favicon 支持良好（且会响应其 `prefers-color-scheme`，本插件未使用）。 |
| Safari（macOS） | ✅ 渲染为静态图 | ⚠️ 尽力而为 | 忽略 SVG 内嵌 CSS；favicon 缓存激进。 |
| Safari（iOS） | ✅ 渲染为静态图 | ⚠️ 基本不刷 | 通常需重新访问标签页才刷新。 |

已知限制（截至 Safari 26.3）：

- **favicon 有专属缓存。** Chrome 用 favicon 数据库、Firefox 用 `favicons.sqlite`、**Safari 用系统级图标缓存**——清普通缓存都清不掉，WebKit 甚至会把「无图标」这一状态也缓存起来。这就是改了图标后，已打开的标签页还可能显示旧图标的原因。本插件已通过「给 `base.svg` 与状态端点设置 `Cache-Control: no-store`、请求携带 freshness 参数（`?t=Date.now()`）、每次切换状态时重建 `<link rel=icon>` 节点」来缓解。
- **Safari 渲染 SVG favicon，但忽略其内部 CSS**——不支持 `@media`、`prefers-color-scheme`、CSS 动画。所以所有上色必须烘焙进每一帧的标记（本插件正是这么做的），而不能依赖 CSS 变量。
- **`data:` URI 的 SVG favicon 在 Safari 不可靠**（WebKit bug 236616，仍未关闭；Safari 17.6 复现）。本插件当前每帧都生成 `data:image/svg+xml` URI，因此在 Safari 上标签页图标可能完全不显示——这是最大的已知缺口。
- **Safari 的动态 JS 更新为 hit-or-miss**，可能需要刷新一次；Safari 会「锁定」它首次看到的图标。目前没有保证可靠的、符合规范的手段能在 Safari 中实时更换 favicon。
- **固定标签页图标（`<link rel="mask-icon">`）使用独立缓存**，与普通 favicon 分开；它是靠 `color` 属性着色的**单色剪影**——仅 macOS + 固定标签页、页面加载时读取一次、并非实时。

完整机制与来源（WebKit bugs、Stack Overflow、浏览器工程博客）以及让 Safari 更顺滑变色/切换的推荐路径见 [`docs/safari-favicon-research.md`](./docs/safari-favicon-research.md)。

## 已知限制

- favicon 的 SVG CSS 动画在浏览器标签页 UI 中不会运行——所有特效都由 JavaScript 每帧重建 data-URI 实现，这是零依赖设计的刻意取舍。（本文档中的动画预览只是演示素材——真实 favicon 的动画始终由 JS 驱动。）
- favicon 行为因浏览器而异，其中 Safari 限制最多——见 [浏览器支持与已知限制](#浏览器支持与已知限制)。
- `base.svg` 模板必须保留 `#p { fill: … }` 规则中的 `__COLOR__` 占位符；浏览器会替换该标记为每帧上色。
- 插件运行在 **host** 平面，必须挂载进 profile 的组合配置，不能作为会话级 agent preset。
- 文件读取走 `fs` 服务，以配置的 `iconsDir` 为 `cwd`。请确保该路径在部署环境的沙箱策略下可读。

## 许可

MIT
