# Whale Trail 开发与实现说明 本文对应 **v1.0.0**。用户功能与安装入口见 [README](../README.md);本页说明证据口径、宿主接入、外观配置与迁移边界。 ## 环境与构建 开发要求 **Node.js 24 或更高版本**。宿主 SDK 从本机已安装的 Harness 发行包只读准备,不随公开仓库或插件包分发。 ```sh npm ci --legacy-peer-deps --ignore-scripts node scripts/prepare-local-dev.mjs npm test npm run typecheck npm run build npm pack --ignore-scripts ``` `prepare-local-dev.mjs` 当前从 macOS 已安装的 DeepSeek Harness 应用读取依赖,保存到忽略提交的 `vendor/`,并建立开发依赖链接;不会修改应用安装包。已有 `vendor/` 时不自动覆盖,因此切换宿主版本后需要主动核对开发 SDK,不能把旧缓存当成当前发行版。 `npm test` 会构建生产包和测试用 UI,再运行自动测试。覆盖范围包括完整回合计数、成功工具归因、历史去重、旧版迁移、备份原子合并、章节奖励、外观约束、隐藏信息隔离、菜单注入、页面几何降级、动画清理和名片导出。自动测试不替代真实宿主的点击、布局与下载验收。 Host 构建为 ESM,Client 为宿主支持的 lazy-CJS factory;Client 的外部运行依赖限于 React、React DOM 与 Zod。CSS 作为文本打包,品牌 PNG 内嵌为 data URL。`lib/` 是生成产物,`lib/build-info.json` 记录构建信息与 SDK 契约目标。 图像工具使用开发依赖 `sharp`。`node scripts/materials.mjs` 从原始 PNG 生成 WebP、256px PNG 和物料总览。 ## 功能与源文件 | 功能 | 主要位置 | | --- | --- | | 67 枚徽章目录、年度日历 | `assets/catalog.json`、`assets/calendar-2026.json` | | 最小事实、计数、授予与迁移 | `src/engine.ts` | | 九章节、奖励归属、偏好校验 | `src/collection.ts` | | Host 订阅、历史只读扫描、RPC | `src/index.ts`、`src/remote.ts` | | 四个标签页与菜单入口 | `src/client/BadgeDialog.tsx`、`src/client/index.tsx` | | 可选宿主能力、页面矩形、本地 modal 与偏好事件 | `src/client/platform.tsx` | | 36 件原创字符图、确定性场景与 Canvas 绘制 | `src/aquarium.ts`、`src/client/Aquarium.tsx` | | 名片画面、中文数字、内嵌离线 HTML | `src/namecard.ts`、`src/client/NamecardEditor.tsx` | | 上游研究与许可记录 | [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) | ## 宿主兼容与界面接入 安装包通过 `dsh.bundle` 声明 `cordis.patch.yml`,CLI 将插件加入指定 profile。Host 订阅正式 Session 事件,通过 SessionPersistence 只读扫描可用历史;Client 通过严格的 Typert 请求交换快照、偏好与备份数据。 v1.0.0 不再声明 `engines.dsh`,宿主协议 peer 不精确锁定单一发行版本。兼容依据是实际能力与数据契约,不是“允许安装就等于支持所有版本”。已读取 **0.2.1-rc.2 与 0.2.1-rc.3 发行 SDK**,并加入契约测试;macOS 0.2.1-rc.3 已完成安装、四个功能页、外观保存和名片导出的实际界面验证;不据此宣称所有系统与宿主版本均兼容。 ### 账户菜单 当前使用公共 `settings.launcher` 插槽,包装已注册的账户菜单函数,保留原菜单操作、注入的业务对象和 observable,再增加「我的徽章」。等待真实 occupant 出现,原 occupant 撤销后清理适配器。 不再因未知组件类型直接抛错。遇到不支持的表示方式,保留原生菜单并省略鲸迹菜单扩展;不会直接调用未知 class、memo 或 forwardRef 组件来猜测内部结构。菜单适配有单独能力边界,不能因为可选背景接口缺失而阻断加载。 ### 页面水族馆 背景通过公共 **`shell.overlay` 列表插槽**挂载,贡献项使用唯一 `id: whale-trail.aquarium`,满足发行 SDK 的列表注册要求。仅读取以下布局属性与矩形,不读取聊天正文、不修改宿主 DOM,也不调用私有 renderer API: | 已核对的布局契约 | 用途 | | --- | --- | | `[data-slot="main.conversation"] > [data-phase]` | 定位唯一可见主对话面板,区分 `hero`、`active`、`settling` | | `[data-composer-card]` | 确定输入框本体边界 | | `[data-composer-seat]` | 在新对话中避开标题和输入区前景 | | `[data-slot="conversation.header"]` | 避开面板头部 | 新对话使用主面板留白,裁掉前景区域;已有对话只在输入框底部显示渐变,绘制区域高度不超过输入框本体,且最多 96px。所有装饰均为 `pointer-events: none`,不接收焦点。 布局变化采用节流观察,页面阶段切换时立即重新判断;卸载移除 observer、监听器与定时任务。页面隐藏时动画暂停,恢复后继续;静水模式和系统减少动态设置会停止连续动画。偏好使用共享的首次快照与保存事件更新,不逐帧调用 RPC。 缺少 `shell.overlay`、布局锚点、可靠页面观察能力或必要裁切能力时,背景隐藏;`settling`、多个候选主面板、无效或越界矩形也按隐藏处理。未来宿主改变这些契约需要重新适配;该降级策略不承诺未知破坏性接口永久兼容。 **不要覆盖 `main.conversation` 或 `conversation.composer.bar` 来接管整棵宿主树。** 发行 SDK 的原 entry 拥有子插槽声明和渲染授权:不复制声明会丢失 `renderSlot`,直接复制又会遇到重复声明。当前方案保留原组件及其所有权。 ### 配置 Host 配置示例: ```yaml - id: whale-trail config: profileId: personal timezone: Asia/Shanghai scanHistory: true ``` `dataRoot` 可指定成就数据目录;默认位于用户主目录的 `.dsh-whale-trail` 下,再按 `profileId` 分开保存。档案与成就时区写入账本后需保持一致;一台 Host 不会根据登录 UID 自动切换个人统计档案,因此不适用于多人共用同一 Host 且要求账户级隔离的场景。 验证独立环境时,既要隔离 Harness profile,也要显式指定独立的 `dataRoot` 与 `profileId`,并按验证范围决定是否关闭历史扫描。只隔离 `DSH_HOME` 不会改变插件默认的成就目录。 ## 事件与计数 所有成就从最小事实推导,无打卡、领取、任务完成确认或撤销完成 RPC。 - **有效使用日**:顶层会话的明确人类非空输入,与实际文本回复或成功工具结果配对;按输入时间决定自然日,跨午夜回复不额外增加一天。后台输入与子代理不计。 - **完整对话轮次**:同一顶层 `turn/start` 中,人类有效直接输入获得可见 Agent 文本回复,并有匹配的 `turn/end reason.kind=completed`。一个回合最多计一次,排队合并输入不拆分;失败、中止、无可见回复不计。 - **Token**:读取 `assistant/message`、`assistant/attempt`、`compaction/summary` 的正式用量。优先采用完整 total,否则组合不重叠的输入、缓存读写与输出;不重复叠加 reasoning,不估算缺失用量,不读取 Token Meter 压力代替实际消耗。 - **日期与连续使用**:按固定时区计算累计、当前连续和最长连续;未来时间异常记录暂缓参与日期授予。当前连续允许从昨天接续。 - **节日**:使用离线年度文化日历,当前为 2026 年;不以调休或假期范围替代文化节日当天,后续年份需要更新。详见[日历来源](calendar-sources.md)。 事实按来源会话与原始序号去重,fork 继承、重复扫描和重复备份导入不会叠加计数。历史缺少完成状态或成功坐标时,只读回扫可补全;没有来源历史不能猜测。允许未知特征补全为已知;已知证据发生冲突则拒绝合并。 ### 探索航迹:8 枚新增徽章 探索仅使用已正常闭合、由人类发起的顶层完整对话。对应规则为: | 徽章 ID | 阈值与证据 | | --- | --- | | `exploration.tools.1/.3/.8` | 累计成功使用 1 / 3 / 8 种不同名称的工具 | | `exploration.sessions.3/.10` | 在 3 / 10 个不同会话中完成正常对话 | | `exploration.revisit.3/.7` | 同一会话中 3 / 7 个不同自然日的正常对话;无需连续 | | `exploration.compaction.1` | 同会话成功压缩后,再次发生新的直接人类输入并正常完成对话 | 工具成功要求调用与结果唯一匹配 `callId`、回合和步骤,调用先于结果、位于完整回合内,且有明确成功标记、无错误。兼容已核对的 rc.2 嵌套 `tool-result` 与 rc.3 原生 `role: tool` 结果形态。未知名称、仅一次调用、错误结果、鲸迹自身工具、子代理与继承记录不能贡献探索进度。 压缩要求同一 `compactionId` 的开始、摘要和成功结束证据完整,所有者与顺序一致;随后是新的直接人类输入及完整回复。仅有摘要 Token 或后台续跑不构成该徽章的证据。 ### 九章节与装饰 章节由累计有效使用日与完整对话轮次共同推导,门槛依次为: ```text [1, 1] → [3, 10] → [7, 20] → [14, 50] → [30, 100] → [60, 200] → [100, 500] → [180, 1000] → [365, 1000] ``` 每章四件、共 36 件唯一原创 ASCII 装饰。双方门槛同时达标即自动完成;不依赖 Token、节日或隐藏章,不提供领取动作。章节不以徽章目录数量作为进度,新目录不会重置既有航程。 ## 外观偏好与动态渲染 `Preferences` 分为两组,持久化在本地账本配置中: - `aquarium`:启用状态、已选装饰、密度 1–100、速度 0.1–3、十六进制颜色、减少动态。 - `card`:精选徽章 ID 与签名,签名上限 120 字符。 页面水族馆默认关闭、默认未选择装饰。编辑请求拒绝锁定装饰;精选名片必须为五枚不同的已获得徽章,尚未配置时允许为空。保存偏好会返回权威快照,页面背景共享更新;外观本身不提供解锁证据。 `drawAquarium` 使用确定性的时间与配置生成场景,页面、编辑器和名片采用同一套字符图与画法。Canvas 透明、尺寸受限,使用本地系统等宽字体,无独立桌宠进程、额外动画引擎或网络字体。组件在卸载时取消动画、断开尺寸观察并移除事件监听;无动画帧接口时可保留静态画面。 ## 航海名片与导出 名片包含恰好五枚不同的已获得徽章、个人签名、获得徽章数、累计天数及中文单位 Token。数字格式支持「万、百万、千万、亿、十亿、百亿、千亿、万亿」;这只是展示换算,不修改账本数值。 PNG 与动态 HTML 共享 `paintNamecard`: - **PNG**:1200×800,确定性时刻的一帧;徽章原图和品牌图加载完成后绘制。 - **HTML**:单个自包含文件,图片以 data URL 内嵌,携带同一名片绘制器和水族馆运行代码;无需联网,需在允许 JavaScript 的浏览器中打开。签名和嵌入数据做转义,尊重系统减少动态与页面隐藏状态。 - **分享**:浏览器支持 `navigator.canShare` / `navigator.share` 文件分享时唤起系统面板,否则下载 PNG。保存、导出与分享前均验证并保存当前名片配置;不自动上传或发帖。 官方 **Powered by dsh** 原图来自 Harness 的 `dsh-skill-badge`,原始尺寸 726×120,保持 121:20 比例、不改颜色与标识。页面、导出按相同比例缩放,完整 MIT 许可及出处见[品牌素材说明](../assets/brand/README.md)。品牌图用于平台署名,不表示 DeepSeek 官方认证、背书或赞助鲸迹。 ## 账本、迁移与备份 SQLite 保存最小事实、来源身份、规则版本、档案与时区、设备身份、最早授予时间和外观偏好。事实不保留提示词、回复正文、工具参数、附件或凭据;签名是用户主动保存的展示内容。目录与文件使用收紧的权限。 v1.0.0 `.dshbadge` 使用 **`schema: 4` / `rulesVersion: 4`**,包含年度日历、目录哈希、最小事实、授予缓存、外观偏好、五枚精选与签名,旧手动操作字段保持为空。外层为带 SHA-256 完整性校验的明文逻辑快照,校验和不构成加密或防作弊认证。 导入在暂存数据库先预演,用户确认后保存本地恢复快照,并在事务内合并和重算。损坏校验、无效结构、档案/时区/规则或已知事实冲突,不部分提交。重复导入保持幂等。 ### 从旧版本升级 - 支持 schema 1 / 2 / 3 备份,按其冻结的旧目录与日历校验,再从可核实事实推导新结果;不得用当前目录哈希直接验证所有旧备份。 - 旧账本迁移至规则 4 前创建一致性 SQLite 快照。保留真实日期、用量事实和设备身份;旧手动确认与任务缓存不转换为完整对话,隐藏、对话和探索授予缓存也不能代替证据。 - 旧 10K / 100K Token 徽章不再展示、计数或写入新授予缓存,其真实使用事实继续保留。 - 旧事实缺少工具成功坐标时,保留为未知,后续来源历史可补全,不凭旧缓存授予探索徽章。 ### 外观导入的合并规则 schema 4 导入会检查外观引用与实际已解锁结果;无效或尚未解锁的装饰被剔除,未获得徽章不能进入精选。有效精选不足五枚时按未配置处理。 只有本机尚未保存 `preferences` 时,才采纳备份中的有效外观。已有本机偏好保留;schema 1 / 2 / 3 没有外观字段,不会清空偏好或章节进度。导入外观不能解锁任何奖励。 日常跨设备迁移使用 `.dshbadge`,不要直接同步正在运行的 SQLite 文件。当前没有自动云同步或加密增量备份。 ## 隐藏徽章与已知边界 未获得的隐藏徽章,在 Client 快照和界面中只提供名称与通用问号图,不提供真实图案或解锁条件。仓库和原始物料公开,因此这是产品展示隔离,不是对源代码阅读者的密码学保密。 六枚隐藏设计中,四枚可从已验证行为自动识别;另外两枚依赖尚未可靠适配的子代理完整结算或跨 Host 来源,暂不授予。复制聊天、fork 或同步徽章不能冒充这些行为。 缺少模型用量、回合闭合、工具配对或压缩证据的历史不会补造结果。年度节日日历、多用户账户隔离与未知宿主破坏性接口也不在自动保证范围内。兼容测试中的本地 SDK 检查在未提供相应发行包时会跳过,跳过不代表验证通过;实机验收需另行记录宿主版本、页面行为、偏好重载与实际导出结果。 ## 来源与许可 ASCII 研究参考 [dsh-ascii-whales](https://github.com/Chael-Chael/dsh-ascii-whales)、[ascii-sharks](https://github.com/stackedlol/ascii-sharks)、[ascii-kanji-sharks](https://github.com/michikusa404/ascii-kanji-sharks)。36 件字符图和渲染逻辑为独立实现,不分发上述项目的代码、轮廓、字体、素材或运行时。 [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) 保留来源文件、读取记录、许可文本及历史 vendored 快照的许可差异,不能将某个上游当前分支的 MIT 声明套用到所有旧快照。 鲸迹包声明为 `UNLICENSED`,不因公开源码而自动授予开源使用许可;第三方材料与官方品牌图分别保留自己的许可。