npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gestaltrun/dsh-pet

v0.3.21-gestaltrun.0

Published

Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score

Readme

dsh-pet — 多宠物伴侣插件

English | 中文

一个注册表驱动的桌面伴侣:内置鲸鱼娘,也接受你放入的任何宠物。

模型思考时你在等待,你的宠物在游动。它跟随官方会话活动,在等待、思考、调用工具、整理回复、庆祝完成、报告失败时切换动画;你还可以摸摸它的头、喂它小鱼干,看着亲密度一点点成长。宠物是注册表条目而不是代码:每只宠物只需一份 pet.json manifest 加一张图集,宿主启动时自动发现。

从 Codex 桌面应用的宠物功能重新实现,采用官方 DSH 插件形态(cordis bundle:host 半区 + client 半区,一个包)。

功能

| 功能 | 说明 | |---|---| | 多宠物注册表 | 宿主扫描内置 assets/、hatch-pet 自定义宠物目录和组合配置条目;每只宠物 = manifest + 图集 | | 设置中选择宠物 | 插件设置卡片列出所有已注册宠物(内置资产 + 用户目录,即已安装集合);切换即持久化,精灵立即更换。卡片位于一级设置分区「宠物」 | | 每只宠物独立命名 | 在悬浮面板改名;每只宠物保存自己的名字(按宠物 id 存储,旧版平铺名字自动迁移) | | 状态动画 | 官方会话活动 → manifest 定义的 9 态轨道序列;每条轨道播完自身完整时长后切换,整条序列循环 | | 摸头互动 | 点击宠物 → 气泡反馈 + 亲密度 +1(10s 冷却) | | 喂食 | 悬浮面板 喂食 → 消耗 1 条小鱼干 + 亲密度 +5(30s 冷却) | | 小鱼干经济 | 库存(上限 20):每工作 30 轮 +1,每 300 分钟(5 小时)+1 —— 获取难度为原来的 10 倍 | | 亲密度 | 每完成一轮 +1;9 级:幼鲸 → 伙伴 → 挚友 → 深海羁绊 → 心有灵犀 → 传说羁绊 → 神话羁绊 → 永恒之契 → 鲸生共渡(上限 999,999,999) | | 拖动 | 按住拖动宠物换位置;位置持久化 | | 隐藏/召唤 | 悬浮面板位于宠物下方(下方空间不足时上移到状态气泡之上)并提供 隐藏;隐藏后出现 召唤{name} 按钮 | | 妙语库 | 内置默认妙语库(每类事件 10 句)+ 宠物自定义台词;成功文案按持久化成功次数轮换,冷却文案按持久化拒绝次数轮换 | | 状态气泡 | 默认只有一个顶层会话说话——上报 GUI 当前会话时优先它,否则是最近活动的一个——其余会话收进主气泡右上角的 +N 角标,不再叠出一长列;悬停气泡(触屏点按角标)即可向上展开所有会话的气泡,点击任一气泡跳转到对应会话;子代理会话借由其发起会话体现,不占用独立气泡;瞬时互动反馈临时优先。气泡文案按场景准备了大量轮换词库(等待 / 思考 / 整理 / 完成 / 失败……),工具调用按工具族映射俏皮文案并带上真实参数(如 跑跑 npm test),同一场景持续数秒会自动换一种说法 | | 碎碎念 | 会话流式输出期间,宠物会偶尔借该会话自己的气泡说出内心独白——新鲜的碎碎念接管所属会话的气泡并以「」引号标记——与状态气泡共用同一片 DeepSeek 蓝黑玻璃,气泡栈内颜色统一不再色差——不再叠出第二只气泡——分类台词由「正在干什么」触发(思考 / 写回复 / 正在跑的工具族),结果台词只由结构化结果触发(测试工具通过 → 全绿、工具失败 → 报错、回合完成 → 收工)——决不读模型说的话,讨论里提到关键词也不会误触发心境;碎碎念也从不引用真实内容(无工具名、路径、模型原文);分类冷却 9 秒、结果冷却 5 秒,展示 8 秒后气泡恢复状态文案 | | 多会话活动 | 宠物是宿主全局的:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话用自己的气泡报告各自状态;每个会话(含子代理)完成的轮次都计入亲密度与小鱼干 | | 语音包与面板 DIY | 宠物目录 voice.json + 全局 $DSH_HOME/pets/.voice.json 覆盖气泡全部文案与悬浮面板(按钮标签/统计格式/按钮显隐);合并优先级 宠物自带 > 全局 > 内置,坏包警告不拒载 |

宠物契约

一只宠物 = 一个目录 + 一份 pet.json manifest + 一张图集。除此之外什么都不需要——不用改任何宿主或客户端代码。

{
  "id": "whale-girl",                     // 唯一的小写 kebab id
  "displayName": "鲸鱼娘",                 // 显示在设置选择器与面板上
  "description": "一只软萌治愈的鲸鱼娘。",     // 可选
  "spritesheetPath": "spritesheet.webp",   // 图集,相对 manifest 所在目录
  "cell": { "width": 192, "height": 208 }, // 可选;默认 Codex 契约
  "columns": 8,                            // 可选;默认 8
  "spriteVersionNumber": 1,                // 可选;2 表示 11 行 v2 图集(9 行动画行 + 2 行视线跟随行)
  "frames": [6, 8, 8, 4, 5, 8, 6, 6, 6],   // 可选的每行帧数
  "tracks": {                              // 可选的每轨节奏覆盖
    "idle": { "durations": [400, 400, 500, 400, 400, 500] }
  },
  "sequences": {                           // 可选的每场景轨道序列(每条至少 5 项)
    "thinking": ["running", "running-right", "running", "running-left", "waiting"]
  },
  "remarks": {                             // 可选妙语(每个槽位一行或多行)
    "pet": "摸摸水獭的头~",
    "feed": ["小鱼干真香", "再来一条~"]
  }
}
  • 图集是 8 列 × 9 行网格(默认 192×208 单元格);行序固定:0 idle、1 running-right、2 running-left、3 waving、4 jumping、5 failed、6 waiting、7 running、8 review。未使用的格子保持全透明。v2(Codex)图集在 manifest 里声明 "spriteVersionNumber": 2,共 11 行——同样的 9 行动画行外加末尾 2 行视线跟随行;插件渲染这 9 行动画行、忽略视线行。
  • 可选 remarks 块覆盖宠物在 pet(摸头)/ petCooldown / feed(喂食)/ feedCooldown / noTreats(缺粮)事件上的气泡台词。每个槽位接受一句或一组台词;声明过的槽位只替换该槽位的内置默认池。成功与冷却池使用对应的持久化成功或拒绝次数,noTreats 则独立轮询。社区贡献就是这样给自家宠物配上专属妙语的。
  • frames 记录每行用到的列数(缺省按 hatch-pet 契约表 [6, 8, 8, 4, 5, 8, 6, 6, 6]);tracks 按动画覆盖每帧时长(按该行帧数循环补足)、loop 与 fallback(默认:全部循环;jumping 与 failed 停在最后一帧后回到 idle)。
  • sequences 可选地把活动场景(idle / waiting / thinking / tool / review / done / failed)映射到至少 5 条动画轨道。每项按 tracks 中的时长播完所有帧后进入下一项,整条序列循环;未声明的场景保持标准单轨播放。

清单 v2(宠物中心,#623)

宠物目录的 pet.json 在 v2 中显式声明渲染器:

  • petManifestVersion: 2(缺省 = v1,按 sprite2d 兼容读并给出迁移提示);
  • renderer:"sprite2d"(上文图集契约)、"live2d" 或 "frames2d";
  • license(v2 必填):资产授权标识——社区宠物必须携带来源声明;
  • 渲染器专属块:sprite2d(spritesheetPath/cell/columns/atlasRows/frames/tracks)、live2d(model/motions/expressions/hitAreas/scale/translate)或 frames2d(dir/defaultFrameMs/tracks/phases——目录式帧序列)。

校验纪律:结构 fail-closed(未知字段或未知渲染器直接拒载并给出诊断),sequences/remarks 内容维持 warn-and-drop。机器可读 schema 见 contracts/pet-manifest-v2.schema.json,权威校验器为 src/manifest-v2.ts。迁移 v1 清单:node scripts/dsh-pet-migrate-v2.mjs <dir> --write(默认 dry-run;保留 pet.json.v1.bak)。

宠物的来源(后注册的来源在同 id 冲突时覆盖前者):

  1. 内置:本包 assets/<dir>/pet.json。
  2. legacy 自定义宠物:${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json —— hatch-pet 流水线把产物放在这里,孵化的宠物无需任何接线即可出现在选择器里。
  3. 宠物中心用户目录:$DSH_HOME/pets/<id>/——推荐安放位(见下方 CLI)。
  4. 组合注入:嵌入应用通过 PetConfig.pets 传入的 manifest 条目。

用 CLI 校验并安装宠物目录(零构建、零发布):

node scripts/dsh-pet validate <dir>           # 清单 + 资产 + Live2D 引用闭包 + voice.json
node scripts/dsh-pet install <dir>            # 校验通过后拷入 $DSH_HOME/pets/<id>/
node scripts/dsh-pet install <dir> --force    # 覆盖同名已安装宠物

非法条目永不覆盖可用宠物:它们被跳过并在设置(宠物栏目)给出诊断。注册表在宿主启动时构建一次;新增或修改宠物后重启 dsh web 生效。

语音包与面板定制(voice.json,宠物中心 M4,#677)

气泡里的状态/工具/碎碎念文案与悬浮面板的按钮/统计文案,都可以被一只宠物(或你本人)整体替换——宠物不再只能换图,还能换「话」与「面板」。载体是宠物目录内的可选 voice.json(随宠物分发),以及全局覆盖文件 $DSH_HOME/pets/.voice.json(不改宠物目录即可换词)。

{
  "voicePackVersion": 1,              // 可选;缺省视为 1
  "status": {                          // 状态池:键 = 场景 id,逐键覆盖内置池
    "done": ["搞定收工~", "交差!下一位"]
  },
  "tools": {                           // 工具池:键 = 工具族;允许 {tool} / {hint}
    "shell": ["跑跑 {hint}", "敲回车!{hint}"]
  },
  "toolRemaining": ["后台还有 {n} 位小助手"],   // 允许 {n}
  "whispers": {                        // 碎碎念:按键替换内置池
    "categories": {                    // 分类池;显式空数组 = 静音该分类
      "thinking": ["让我想想……"],
      "running": ["正在跑,我盯着"]
    },
    "results": {                       // 结果池(测试全绿 / 报错 / 完成)
      "pass": ["全绿!"]
    }
  },
  "panel": {                           // 悬浮面板(每槽未声明时回落插件 i18n 文案)
    "labels": { "feed": "投喂", "hide": "藏起来", "rename": "起名字", "confirm": "好的" },
    "stats": { "rank": "好感 {rank}", "treats": "鱼干 ×{n}", "points": "{points} 分" },
    "actions": ["feed", "rename", "hide"]  // 子集(按规范顺序);省略 = 全部;[] = 只显示统计行
  }
}
  • 合并优先级(逐槽):宠物自带 voice.json > 全局 .voice.json > 内置文案。status/tools/whispers 逐键合并、panel 逐槽合并,任何层缺失的槽位回落下一层。
  • 占位符白名单:tools 允许 {tool} / {hint};toolRemaining 允许 {n};panel.stats 允许 {rank} / {n} / {points};status、碎碎念与面板标签不允许任何占位符(含非法占位符的行被警告丢弃)。
  • 上限(warn-and-drop):每池 64 行以内、每行 160 字符以内;面板标签 40 以内、统计 80 字符以内。
  • 坏包不影响宠物:voice.json 不是合法 JSON 或根不是对象 → 警告并整体忽略;其余问题逐槽警告丢弃。诊断显示在设置 → 宠物目录诊断。node scripts/dsh-pet validate 会把结构错误判为安装失败、内容问题列为警告。
  • 语义细节:status/tools 的空池回落内置文案(场景行始终有话说);whispers 的显式空数组是静音(关掉该通道);面板 actions 为空数组 = 三个按钮全部隐藏;未覆盖的按钮/统计继续使用插件双语字典。旧的 whispers.generic / whispers.rules 字段已不再支持,会被忽略并给出警告。

Live2D 宠物(renderer: live2d)

Live2D 宠物经 PixiJS/WebGL 渲染:MIT 许可的 pixi.js + untitled-pixi-live2d-engine 栈以按需加载的 vendor 分包随插件内置,纯 sprite 用户永不下载、永不解析它。本插件永不内置、永不代下 Cubism Core 运行时——Live2D 专有许可不允许再分发。启用 Live2D 宠物:

  1. 自行从 Live2D 官方渠道获取 Cubism SDK for Web(你自行同意其许可),取得 live2dcubismcore.min.js;
  2. 放到 $DSH_HOME/pets/.runtime/live2dcubismcore.min.js——插件把它连同自带的 vendor 分包一起提供给页面;
  3. 安装 Live2D 宠物(含 pet.json v2、renderer: "live2d" 与模型文件的目录)。

core 缺失时,Live2D 宠物在渲染位置给出安装指引卡;sprite2d 宠物不受影响。法律提示:本插件属 Live2D 术语下的「可扩展性 APP」——你公开发布基于可加载用户模型的衍生作品时,可能不论规模都须与 Live2D 签订发行许可;发布前请自行评估义务。

Live2D 清单把七个活动相位映射到模型的动作组:

{
  "petManifestVersion": 2,
  "id": "my-live2d-pet",
  "displayName": "My Live2D Pet",
  "license": "CC0-1.0",
  "renderer": "live2d",
  "live2d": {
    "model": "model/my-pet.model3.json",
    "motions": { "idle": "Idle", "thinking": "Think", "failed": "TapBody" },
    "hitAreas": ["Body"]
  }
}
  • model:相对宠物目录的 .model3.json 路径。模型引用的一切文件(moc、贴图、动作、物理、姿态、表情)都必须放在目录内——宿主恰好只服务这个引用闭包。
  • motions(必填,且必须含 idle):相位 → 动作组。未映射的相位与模型缺失的组一律回退 idle;组内有多条动作时随机播放一条。官方 Cubism 示例模型只带 Idle 与 TapBody 两个组。
  • expressions(可选):相位 → 表情名,叠加在动作之上。
  • hitAreas(可选):点击落在列出的命中区时播放模型的 TapBody 组,播完回到当前相位的动作组。任何点击仍计入摸头——交互经济与 sprite2d 一样由 chrome 掌管。
  • scale / translate(可选):模型自动适配显示盒;scale 在适配结果上乘算(默认 1,范围 (0, 10]),translate 以中心为基准做像素偏移。

模型授权:Live2D 官方示例模型(Hiyori、Haru 等)仅供评估、禁止再分发——只发布你有权的模型(原创作品或宽松授权的模型)。

Frames2d 宠物与玩法(renderer: frames2d)

frames2d 宠物不用图集,直接交付目录式帧序列:thumb/<track>/<frame>.webp,帧时长取文件名 _<ms> 尾缀或轨道的 frameMs 列表(默认 200ms,范围 16–5000)。清单把七个活动相位映射到轨道;drag 轨道跟随外壳的拖拽手势;非循环轨道播完进入 fallback(默认 idle 轨道),入睡/睡熟这类"引子 + 循环"拆分(sleep-intro → sleep)就是纯清单数据。

frames2d 块还可声明 skins(皮肤):可选的 { id, label, idleTrack } 数组。选中某皮肤后,宠物"回待机"的目标(idle 相位、未映射相位、以及一切回 idle 的 fallback)都切到该皮肤的 idleTrack——休息外观随皮肤切换,而玩法轨道(shy/work/sleep…)仍挂在默认资产上。每个皮肤还可声明 clickActions——仅对该皮肤生效的概率掷骰点击反应({ track, probability, phrases? }):皮肤激活时点击按声明顺序依次掷骰,命中则播一次命中轨道,未命中回落普通点击加成(绝不落入默认触摸分区)。

frames2d 宠物可声明 gameplay 块——从 miku 桌宠泛化而来的可选玩法层:属性条(stats,按分钟衰减,另有打工中与空闲变体)、统一的小鱼干货币(玩法收入与商店支出都走宠物面板那条小鱼干库存,上限 20 条:work 成功、passiveIncome 与彩票奖品发放小鱼干,商品也以小鱼干标价扣款,无独立钱包页)、加权 idleDirector(每 intervalMs 掷骰演出小动作,连续落空 maxMiss 次必演)、hitBox 内的 touch 触摸分区(分支掷骰:效果 + 轨道保持 + 台词气泡)、work 打工循环(宿主裁决 tick,成功/失败结果轨道)、sleep 睡觉循环(惰性恢复)、passiveIncome 被动收入,以及 shop 商店(商品可带效果或分档抽奖)。所有掷骰与记账由宿主权威裁决(POST /api/pet/gameplay/*),状态按宠物持久化在 pet.json,沿用小鱼干经济的惰性结算纪律。浏览器半侧为声明了该块的宠物自动渲染玩法菜单卡(属性条、打工/睡觉开关、皮肤选择、商店网格)。精灵显示尺寸范围为 32–1024 px。

Miku 宠物(stushansusu 涂山苏苏以 MIT 贡献;初音未来角色权利归 Crypton Future Media,受 Piapro 角色许可约束——见 THIRD_PARTY_NOTICES.md)是 frames2d 玩法的参考实现。它只经创意工坊分发(不打进 npm 包):从工坊宠物列表安装后落在 $DSH_HOME/pets/miku/。

状态装饰(decoration.json,宠物中心 M5,#567)

宠物状态气泡里的文字之前可以有一个小的状态装饰(内置:喷水鲸鱼),由 ActivityPhase 流驱动换帧。装饰与宠物相互独立:独立描述符、独立 id、独立目录,换宠物不换装饰。入口资产只收 PNG/WebP 单行精灵条带(不收 SVG/CSS);气泡自身的 role=status/aria-live(或会话气泡按钮语义)永远保留,装饰 aria-hidden;prefers-reduced-motion 时停在帧段首帧,资产加载失败只消失装饰、文字照常。

{
  "decorationManifestVersion": 1,
  "id": "whale",                     // 唯一小写 kebab id
  "displayName": "喷水鲸鱼",           // 可选
  "license": "MIT",                   // 必填:资产授权(社区装饰必须携带来源声明)
  "entry": "whale-frames.png",        // PNG/WebP 单行条带,相对本目录
  "cell": { "width": 64, "height": 48 },
  "columns": 4,                       // 条带帧数(1..16)
  "frameMs": 160,                     // 常量帧时长;或用 "durations": [..] 逐帧覆盖
  "loop": true,
  "phases": {                         // ActivityPhase 七态 -> 帧段(含端点);"hide" = 不显示;缺省 = hide
    "idle": "hide",
    "waiting": { "from": 0, "to": 1 },
    "thinking": { "from": 0, "to": 3 },
    "done": { "from": 2, "to": 3 },
    "failed": { "from": 3, "to": 3 }
  }
}
  • 结构 fail-closed(未知字段、越界尺寸、非 PNG/WebP 入口直接拒载并进诊断),帧段内容 warn-and-drop。机器可读 schema 见 contracts/status-decoration-v1.schema.json,权威校验器为 src/decoration.ts。
  • 来源:内置 assets/decorations/ + 用户目录 $DSH_HOME/pets/decorations//(同 id 覆盖内置)。资产经 /api/pet/decoration// 路由,containment 与白名单与宠物资产同构。
  • 开关:设置 → 宠物 → 状态装饰(默认开)。内置鲸鱼素材派生自 DeepSeek wordmark(MIT),完整声明见 THIRD_PARTY_NOTICES.md。

公告气泡(pet.announce,dsh-usage 联动)

宿主侧的兄弟插件可通过 pet cordis 服务推送一条结构化公告(ctx.pet.announce({ source, kind, title, ... }));客户端半区将其渲染为一只专用、特别设计的气泡,挂在会话气泡栈顶部——独立玻璃样式与色调描边(ok/warn/low)、余额与今日消费类金额胶囊、套餐百分比窗口带微型计量条、展示重置时间。载荷经有界校验(src/announce.ts):未知字段丢弃、超长文本截断、TTL 收敛到 1 秒 - 2 小时(默认 10 秒;周期型发布方按自身轮询节奏声明 TTL,常驻气泡因此跨轮询连续,上限仍保证失活来源的气泡至多一个刷新周期内消失),畸形公告静默丢弃。公告仅存内存:最新一条生效,过期即不再渲染,不触碰 pet.json 与台账。刻意不提供 HTTP 面——按跨插件协作规则,API 就是进程内服务。

内置宠物

| 注册表 id | 选择器名称 | 来源 | |---|---|---| | blue-throated-bee-eater | 蓝喉蜂虎 | 贡献者以 Apache-2.0 贡献的伙伴插画(12 张同角色 AI 立绘素材,配色取蓝喉蜂虎皮肤同源;由 docs/archive/blue-throated-bee-eater-pet/gen-pet.py 组装,各轨道独立姿态:栖枝/飞行/正面悬停/挥翅/降落/垂头/歪头/昂首;饲料以「小蜜蜂」命名) | | jyn | 女仆鲸鱼娘 | frames2d 玩法宠物(stushansusu 以 MIT 贡献):带打工/睡觉/触摸玩法与三款可选皮肤(暗夜鎏金 / 蓝海霓裳 / 冰晶公主)的女仆鲸鱼同桌宠,每款皮肤各带一个概率掷骰点击动作;暗夜鎏金另将专属休息循环换入睡觉玩法 | (docs(dsh-pet): list three jyn skins in the registry rows and re-record pairing) | ouo-neko | OUO Neko | Pessimist0906 以 MIT 许可证贡献的粉色樱花猫耳伙伴 | | whale-girl | 鲸鱼娘(原版) | 仓库原有的鲸鱼娘图集 | | whale-girl-refined | 鲸鱼娘(精致版) | 以鲸鱼娘设计方向为基础,经 AI 辅助二次创作、修复和细节精修的衍生版本 |

Miku 宠物有意不随包内置:它是 frames2d 玩法宠物,经创意工坊按需安装(见上文 frames2d 一节)。星夜人偶(Starry Doll)(Theater-ahyeon 以 CC-BY-NC-SA-4.0 贡献)同样仅经创意工坊分发:sprite2d 哥特星夜人偶,从单张插画抠像后以剪纸变换生成动画(呼吸、蹦跳、挥手、沮丧、review 态放大镜道具),从创意工坊宠物列表安装后落在 $DSH_HOME/pets/starry-doll/。

精致版参考了 DreamSkin 的「DeepSeek-鲸鱼娘」主题。历史来源记录标注原主题作者为 powerdog996,并标注主题为 MIT:DreamSkin、仓库来源记录。此处用于记录素材来源与衍生关系;精致版不表述为原作者的官方作品,也不重新定义原始美术作品的授权范围。

动画预览

精灵宠物使用由 hatch-pet 流水线生成的 8 列图集,单元格为 192×208。经典图集包含 9 行动画;v2 图集额外增加 2 行,共提供 16 个观察方向。以下为标准动画状态预览:

| idle | waiting | running | jumping | |---|---|---|---| | idle | waiting | running | jumping |

| waving | review | failed | 左右移动 | |---|---|---|---| | waving | review | failed | running-left running-right |

内置的蓝喉蜂虎宠物随附同一套状态预览:

| idle | waiting | running | jumping | |---|---|---|---| | idle | waiting | running | jumping |

| waving | review | failed | 左右移动 | |---|---|---|---| | waving | review | failed | running-left running-right |

社区贡献的星夜人偶宠物(仅经创意工坊分发,Theater-ahyeon 以 CC-BY-NC-SA-4.0 贡献)随附同一套状态预览:

| idle | waiting | running | jumping | |---|---|---|---| | idle | waiting | running | jumping |

| waving | review | failed | 左右移动 | |---|---|---|---| | waving | review | failed | running-left running-right |

架构

dsh-pet/
|-- src/
|   |-- index.ts             # host 半区:插件入口(构建注册表、设置区、路由)
|   |-- registry.ts          # 多宠物契约:manifest 扫描 + 归一化(内置 + 自定义宠物)
|   |-- service.ts           # PetService:宠物选择 + 状态机 + 亲密度 + 配置
|   |-- state.ts             # 宠物状态机:会话活动投影 → 9 态动画
|   |-- remarks.ts           # 妙语库:内置默认池 + 每宠物覆盖 + 计数选取
|   |-- affinity.ts          # 亲密度账本(纯函数 + 冷却)
|   |-- treats.ts            # 小鱼干库存账本
|   |-- persist.ts           # 持久化($DSH_HOME/pet.json:选择 + 名字 + 互动计数)
|   |-- routes.ts            # /api/pet/* JSON API + /pet/<id>/* 静态资源路由
|   `-- client/             # 浏览器半区
|       |-- index.ts         # 全局挂载(createRoot → body)+ 注册表拉取 + 轮询 + 接线
|       |-- PetDockEntry.tsx # 全局浮层入口(document.body,始终显示)
|       |-- PetSprite.tsx    # 由定义驱动的浮层精灵(portal + rAF + 拖动)
|       |-- PetSettingsCard.tsx # 设置卡片:宠物选择器 + 显示布局
|       |-- sequences.ts     # 完整轨道场景序列计时
|       |-- spritesheet.ts   # 图集几何辅助 + 轨道裁剪
|       `-- pet.module.css
|-- assets/whale/            # 内置原版鲸鱼娘(manifest + 图集 + 预览)
|-- assets/whale-refined/    # 内置精致版鲸鱼娘注册表变体
|-- assets/ouo-neko/         # 内置 OUO Neko v2 宠物(11 行图集 + 预览)
|-- assets/blue-throated-bee-eater/  # 内置蓝喉蜂虎宠物(9 行图集 + 预览)
`-- cordis.patch.yml         # bundle 补丁:插入宠物插件行

数据流

官方会话事件(turn/step/chunk/tool)----\
                                                    > PetService(宿主)<-- 注册表(内置 + 自定义宠物)
可选兼容 activity/status ------------------/
                                                              | /api/pet/* JSON
全局 React 根(createRoot → document.body)<-- 2s 轮询 -- pet-client(浏览器)
                                                              |
                                       PetSprite 浮层(portal + rAF)
  • 状态来源:宿主把官方 turn/start、step/start、assistant/message、tool/call、tool/result、turn/end 事件与实时 agent/assistant-stream 增量投影为 waiting/thinking/tool/review/done/failed 状态。可选兼容 activity/status 事件仍作为输入。
  • 注册表:宿主把每份 manifest 归一化为完整渲染定义(几何、每行帧数、每轨时长),经 /api/pet/pets 下发;浏览器半区用该定义渲染任意条目,不携带任何宠物专属代码。
  • 选择与命名:petId 存于设置命名空间;每只宠物的名字存于 pet.json 的 names,通过悬浮面板对当前宠物改名编辑。旧版安装的平铺 name 自动迁移到鲸鱼娘名下。
  • 多会话语义:API 与浏览器挂载都是宿主全局的,不暴露前台会话身份。并行会话各自保留投影状态:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话在独立气泡里报告自己的阶段(state 视图的 sessions 列表,最多保留最近 12 个)。子代理会话仍参与动画、计奖与单一显示气泡,但不占独立气泡位——N 个对话不会变成"N + 子代理数"的气泡堆。每个会话完成的轮次仍独立计奖;销毁会话移除它的气泡,销毁当前显示会话则回退到最近仍在活动的会话。
  • 挂载点:document.body(全局 React 根,始终显示:无会话 / 新会话 / 会话中都可见——旧挂载点 conversation.composer.dock 只在活动会话里渲染,新会话里宠物消失);组件内部用 createPortal 渲染全局浮层。根容器随插件 fiber 生命周期走:fiber 销毁时卸载 React 根、移除容器并停止轮询与设置订阅;热重载或重复注入的新 bundle 接管页面级单挂载槽,页面始终只有一个 [data-dsh-pet-root](issue #785)。
  • 渲染:CSS 精灵(background-position)逐帧动画;帧时长和可选场景序列来自下发定义。悬浮面板锚定在宠物下方,间隙由指针桥接覆盖;当视口下方空间不足时,面板翻转到宠物上方并抬升到状态气泡栈之上,两者互不遮挡。
  • 通信:浏览器 ↔ 宿主走同源 /api/pet/* JSON 端点(state/pets/interact/set-visible/set-config/set-name/set-pet);每只宠物的图集从 /pet/<id>/<spritesheetPath> 加载——插件自给自足地提供自己的 API 与资源(与 dsh-remote-web-ui 的 /api/pair 同一模式)。

安装

安装聚合全家桶 @gestaltrun/dsh-web-all(全部插件与皮肤一次到位),或单独安装本插件:

### 从 npm 安装(推荐)
dsh plugin --profile web add @gestaltrun/dsh-pet@latest

### 从仓库安装(开发调试)
git clone https://github.com/gestaltrun/dsh-web.git
cd dsh-web
pnpm install && pnpm -r build
dsh plugin --profile web add link:$(pwd)/packages/dsh-pet

安装后重启 dsh web——你选择的宠物出现在界面右下角。link 模式下改代码后 pnpm build 并刷新页面即可,无需重装。

开发

pnpm build        # tsc -b(类型+声明)&& tsdown(node 半区 + 浏览器 bundle)
pnpm test         # vitest 单元/组件测试(注册表 / 事件投影 / 状态 / UI / 账本)
pnpm prepare      # 仅转译构建(不做类型检查,供消费者安装)
pnpm typecheck    # 仅类型检查

浏览器 bundle 走 window.__ModuleLoader__.load 契约;React/cordis 等从 loader 模块表解析(external);CSS Modules 由 lightningcss 以内联 <style data-plugin> 编译进 bundle。

精灵图与动画轨道校准

两套内置鲸鱼娘图集使用同一份 9 态 × 8 列契约:assets/whale/ 是原版,assets/whale-refined/ 是精致版。每张图集均为 1536×1872(8 列 × 9 行,192×208 单元格)。每行帧数、节奏与场景轮换写在各目录的 pet.json 中;未覆盖的宠物沿用 hatch-pet 契约节奏和标准单轨场景映射(行序:0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review)。

OUO Neko 使用扩展 v2 契约:图集为 1536×2288,在相同 9 行动画之后增加 2 行、共 16 帧观察方向。其清单声明 sprite2d.atlasRows: 11,渲染器把最后 16 个单元格作为顺时针观察方向循环。

安全模型

  • 全部 /api/pet/* 与 /pet/<id>/* 路由默认仅限 loopback(插件家族共享围栏:loopback 套接字 + Host 头 + 浏览器同源标记):未配对的局域网客户端在任何宠物状态或图集下发前即收到 403 forbidden: loopback-only。同时装了 dsh-remote-web-ui 时,有效的已配对设备 cookie 是额外放行路径(与 api/gate 检查同一枚 cookie);未配对与已撤销设备仍 403。宠物插件不硬依赖远程插件。
  • 资产服务对宠物目录与目标文件双双做 realpath 解析;symlink 越界一律拒绝(403)。文件读入内存前按类限大小(清单 64 KB、图像 20 MB;超限 413)。
  • Live2D 模型按闭包放行:仅清单、声明的主资产与 .model3.json 引用到的文件(引用先经穿越/绝对路径/URL 形态筛查)。
  • 插件从不下载可执行文件,也从不内置 Live2D Cubism Core。
  • 清单结构 fail-closed:未知字段或未知渲染器直接拒载,并在设置中给出诊断。

数据遥测

浏览器半区每个 UTC 日向 dsh-market.com 发送一次匿名安装心跳:仅含一个 localStorage 随机 ID 与本包名,无其他数据。服务端只存储该 ID 的加盐哈希,不存 IP,且只暴露聚合计数。完整契约见 docs/telemetry.md。

许可证

BSD-3-Clause