# 03 · 功能说明 > 本篇是**功能清单与行为契约**:有哪些面板、数据从哪来、文案与配色怎么走、安全边界在哪、能配什么。 ## 一、面板与标签页总表 「来源」列:**上游** = 来自 `dsh-better-sidebar`;**宿主** = DSH 自身提供并占用该槽位;**本 fork** = 本项目的增量。 | id | 名称 | order | 来源 | 数据 / 行为 | | --- | --- | --- | --- | --- | | `editor` | 文件 | 10 | 上游 | 文件树 + 编辑器(CodeMirror 系,懒加载 `client-editor`) | | `git` | 文件变动 | 20 | 上游 | 工作区变更、单个 diff 视图 | | `subagent` | 子代理 | 30 | 上游 | 会话的子代理视图 | | `sidechat` | 侧边对话 | 35 | 上游 | 会话内旁路对话 | | `terminal` | 终端 | 40 | 上游 | PTY 终端(懒加载 `client-terminal`;v0.24.1 线起由宿主内置终端承担) | | `browser` | 浏览器 | 50 | 上游 | 内嵌浏览器/HTML 预览宿主 | | `diff` | (内部) | -1 | 上游 | 非用户可见,供内部打开 diff | | **`apes:tasks`** | **任务** | **55** | **本 fork** | 会话列表(当前 / 运行中 / 子智能体标记,`showInactive` 开关;角标 = 运行中会话数) | | **`apes:agents`** | **智能体** | **60** | **本 fork** | 主代理 + 直系子代理拓扑(角标 = 直系子代理数) | | **`apes:apps`** | **应用** | **65** | **本 fork** | 当前会话所属工作区(宿主暂无「应用」feed) | | **`apes:messages`** | **消息** | **67** | **本 fork** | 消息流占位(宿主暂无消息流 feed) | | **`apes:profile`** | **画像** | **70** | **本 fork** | 当前会话摘要(标题 / cwd / 运行态) | > order 10–50 是上游/宿主既定带宽,apes 信息层取 55/60/65/67/70 留出余量,避免与原生面板抢占位置。 ## 二、信息层五 tab(本 fork 的增量) 五个 tab 全部是**单实例**(`single: true`),且都按 DSH 标准 `TabDescriptor` 注册, 因此自动获得原生侧边栏的全部待遇:进入原生 guide、tab 芯片带图标、可开合与分栏。 | tab | 展示内容 | 数据来源 | 空态 | | --- | --- | --- | --- | | `apes:tasks` | 会话行;当前会话排首位,其次运行中、再次空闲;行上带「当前 / 运行中 / 子智能体」标记;点击切换会话 | `ctx.sessions.list` 快照(`byId` + `current`) | 「当前没有会话」提示 | | `apes:agents` | 根(主代理)卡片 + 其直系子代理卡片;每行显示标签、`one-shot`/`continuable`、运行态圆点 | ① `ctx.sessions.list` 的 `byId`(durable 会话摘要);② 宿主的会话投影 `projectionsBySession[sessionId].values.subagentCatalog`(直系子代理清单) | 「当前会话没有子代理」提示 | | `apes:apps` | 当前会话的工作区(cwd) | `byId[scope.sessionId].cwd`,回退 `scope.cwd` | 明确写出「宿主今日无应用 feed」 | | `apes:messages` | 仅标题与说明(同 `apes:apps` 的占位策略) | —— | 明确写出「宿主今日无消息流 feed」 | | `apes:profile` | 会话标题、运行/空闲、cwd | `byId[scope.sessionId]` | 「无 cwd」提示 | **行为契约**:`apes:tasks` / `apes:agents` 声明 `badge`(运行中会话数 / 直系子代理数,为 0 时不渲染角标), 其余三个 tab 不发角标;`apes:tasks` 额外声明 `showInactive` 开关,读写落在 `prefs.pluginSettings['apes:tasks']` (缺省显示全部,值非布尔时按缺省处理)。隐藏的 tab 不订阅会话 feed(`visible` 门控), 因此后台 tab 不会被会话事件唤醒。 **实现要点**:两个来源被归一化成同一种行形状 `{ id, label, mode }`,**活动态统一从 `byId` 读**, 于是「投影已就绪」与「仅有 durable 摘要」两种情况下渲染一致;这也是上游 0.1.6→0.1.7 会话 feed 迁移(`subagentsByParent` → `projectionsBySession`、`list.current` 删除)后的落地方式。 ## 三、文件预览器 | id | 适用 | 说明 | | --- | --- | --- | | `image` | 图片 | 走媒体路由,受 `mediaLimit` 限制 | | `pdf` | PDF | 内嵌查看 | | `markdown` | Markdown | 渲染(Mermaid 走懒加载 chunk,可手动刷新) | | `html` | HTML | **不透明源沙箱 iframe**,见下节 | | `code` | 代码 | 高亮查看(编辑器懒加载) | | `binary-download` | 二进制 | 只提供下载 | ### 3.1 HTML 预览的交付方式(重要) ``` 路由 /sidebar/html// ← 路径围栏 + 体积上限(readLimit) │ ├─ 主框架 fetch(唯一同时被宿主围栏信任、又能带上 Electron 头的角色) │ ├─ 注入 ← 让 ./style.css 等相对引用解析回本文件所在目录 │ └─ iframe srcDoc=<文档> + sandbox=HTML_IFRAME_SANDBOX ``` | 状态 | 自包含 HTML | 多文件 HTML(外链 CSS/JS/图片) | | --- | --- | --- | | 沙箱态(默认) | ✅ 完整可用 | 正文可用,**相对资源不可用**(iframe 源为 `null`,子请求同样被宿主围栏拒绝) | | 临时解锁(无 sandbox 属性) | ✅ | ✅ **相对资源可用**(iframe 继承 GUI 源,成为普通同源请求) | 设计取舍:**沙箱令牌一个字符都不放宽**。多文件 HTML 想要完整效果需用户显式点「临时解锁」—— 这是把「放宽边界」的决定权留给用户的显式动作,而不是插件默认降级。 ## 四、安全边界(三层) | 层 | 机制 | 说明 | | --- | --- | --- | | 预览内容 | `sandbox="allow-scripts allow-popups allow-downloads allow-modals"` | **不含 `allow-same-origin`**:预览文档运行在不透明源里。宿主路由另有 CSP `sandbox` 兜底 | | 插件路由 | 请求信任围栏(`src/trust-fence.ts`) | 校验 Host 头/令牌,拒绝跨站或非授权来源对 `/sidebar/*` 的访问 | | 桌面壳 | Electron 注入的 `x-dsh-desktop-renderer` 头 | 扩展模式下所有路由的前置围栏;只发给主框架与同源子框架。这正是「沙箱 iframe 发不出请求」的根源 | ## 五、i18n 与主题 - **语言包**:`src/client/locales.ts` + 19 个 `src/client/locales-*.ts` 覆盖文件;默认 `zh`,缺省回退 `en`。 - **本 fork 文案**:集中在 `src/client/apes/locales.ts`,每个 key 同时提供 `zh` / `en`。 - **硬性约定**:界面里**不得出现裸字符串**,一律 `t('key')`。 - **配色**:只用宿主设计令牌 `--dsw-alias-*`(bg / border / label / state / interactive 系列), 写法为 `var(--dsw-alias-xxx, <字面回退>)`,保证在非 DSH 环境(单元测试)也能渲染。 **无公开的 `--ds-*` 几何令牌**,字号/间距/圆角内联即可。 ## 六、配置面(挂载行 `config`) 配置写在插件挂载行上(profile 的 `cordis.patch.yml` 或 bundle patch 的 insert 行),设置页写的也是它: | 字段 | 默认 | 含义 | | --- | --- | --- | | `readLimit` | `512 KiB` | 单文件读取上限(超出截断并标记 `truncated`) | | `mediaLimit` | `20 MiB` | 媒体(图片等)读取上限 | | `uploadLimit` | `128 MiB` | 上传上限 | | `listLimit` | `1000` | 目录列举条目上限 | | `terminalsPerSession` | `3` | 每会话终端数上限 | | `shell` / `shellArgs` | 自动解析 | 终端 shell 与其启动参数(`shellArgs` 非空时替换默认 `-l`) | 例:在挂载行里指定 shell ```yaml - insert: - id: better-sidebar name: 'dsh-apes-sidebar' config: shell: /bin/zsh shellArgs: ['--noprofile', '--no-rc'] ```