@linxin666/dsh-pet 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +73 -38
  3. package/README.zh.md +91 -55
  4. package/assets/whale/pet.json +12 -1
  5. package/cordis.patch.yml +4 -4
  6. package/lib/client.js +296 -347
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +975 -317
  9. package/lib/invariant.js +1 -1
  10. package/lib/{state-P-wsVJ6O.js → state-CFyJv0sQ.js} +15 -1
  11. package/lib/types/affinity.d.ts +15 -0
  12. package/lib/types/affinity.d.ts.map +1 -1
  13. package/lib/types/affinity.js +14 -0
  14. package/lib/types/client/PetDockEntry.d.ts +14 -12
  15. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  16. package/lib/types/client/PetDockEntry.js +13 -8
  17. package/lib/types/client/PetSettingsCard.d.ts +22 -9
  18. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/PetSettingsCard.js +42 -7
  20. package/lib/types/client/{WhalePet.d.ts → PetSprite.d.ts} +21 -20
  21. package/lib/types/client/PetSprite.d.ts.map +1 -0
  22. package/lib/types/client/{WhalePet.js → PetSprite.js} +44 -67
  23. package/lib/types/client/PluginSettingsCard.d.ts +26 -11
  24. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  25. package/lib/types/client/PluginSettingsCard.js +30 -8
  26. package/lib/types/client/index.d.ts +11 -8
  27. package/lib/types/client/index.d.ts.map +1 -1
  28. package/lib/types/client/index.js +32 -14
  29. package/lib/types/client/locales.d.ts +10 -10
  30. package/lib/types/client/locales.js +10 -10
  31. package/lib/types/client/pet-store.d.ts +5 -0
  32. package/lib/types/client/pet-store.d.ts.map +1 -1
  33. package/lib/types/client/pet-store.js +4 -0
  34. package/lib/types/client/settings-form.d.ts +76 -5
  35. package/lib/types/client/settings-form.d.ts.map +1 -1
  36. package/lib/types/client/settings-form.js +97 -20
  37. package/lib/types/client/spritesheet.d.ts +14 -48
  38. package/lib/types/client/spritesheet.d.ts.map +1 -1
  39. package/lib/types/client/spritesheet.js +13 -81
  40. package/lib/types/dsh-home.d.ts +17 -0
  41. package/lib/types/dsh-home.d.ts.map +1 -0
  42. package/lib/types/dsh-home.js +34 -0
  43. package/lib/types/event-projection.d.ts +36 -0
  44. package/lib/types/event-projection.d.ts.map +1 -0
  45. package/lib/types/event-projection.js +98 -0
  46. package/lib/types/index.d.ts +24 -11
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +53 -33
  49. package/lib/types/ledger.d.ts +79 -0
  50. package/lib/types/ledger.d.ts.map +1 -0
  51. package/lib/types/ledger.js +146 -0
  52. package/lib/types/persist.d.ts +15 -4
  53. package/lib/types/persist.d.ts.map +1 -1
  54. package/lib/types/persist.js +39 -8
  55. package/lib/types/registry.d.ts +158 -0
  56. package/lib/types/registry.d.ts.map +1 -0
  57. package/lib/types/registry.js +276 -0
  58. package/lib/types/routes.d.ts +10 -10
  59. package/lib/types/routes.d.ts.map +1 -1
  60. package/lib/types/routes.js +169 -51
  61. package/lib/types/service.d.ts +59 -57
  62. package/lib/types/service.d.ts.map +1 -1
  63. package/lib/types/service.js +111 -211
  64. package/package.json +2 -2
  65. package/src/affinity.ts +33 -0
  66. package/src/client/PetDockEntry.tsx +34 -26
  67. package/src/client/PetSettingsCard.tsx +67 -22
  68. package/src/client/{WhalePet.test.tsx → PetSprite.test.tsx} +56 -8
  69. package/src/client/{WhalePet.tsx → PetSprite.tsx} +55 -73
  70. package/src/client/PluginSettingsCard.tsx +90 -18
  71. package/src/client/index.ts +38 -16
  72. package/src/client/locales.ts +10 -10
  73. package/src/client/pet-store.ts +9 -0
  74. package/src/client/settings-card.module.css +9 -0
  75. package/src/client/settings-form.ts +153 -19
  76. package/src/client/spritesheet.ts +16 -92
  77. package/src/dsh-home.test.ts +35 -0
  78. package/src/dsh-home.ts +36 -0
  79. package/src/event-projection.ts +128 -0
  80. package/src/index.test.ts +23 -0
  81. package/src/index.ts +89 -45
  82. package/src/ledger.test.ts +69 -0
  83. package/src/ledger.ts +190 -0
  84. package/src/persist.test.ts +52 -3
  85. package/src/persist.ts +49 -11
  86. package/src/registry.test.ts +132 -0
  87. package/src/registry.ts +400 -0
  88. package/src/routes.ts +166 -53
  89. package/src/service.ts +153 -285
  90. package/lib/types/client/WhalePet.d.ts.map +0 -1
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 437e1068038cb013e2880441e69cf1c259f51e79
6
- README.zh.md: 46511480ae653cc8d6c9b164e9e84ff5645f5945
5
+ README.md: ea1c6ac0d9ff57a9072ac6872a8147e5dc389435
6
+ README.zh.md: 5146dc5a8fbe4861c1e2367546a5a592dd916476
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # dsh-pet — Whale-girl pet plugin
1
+ # dsh-pet — Multi-pet companion plugin
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- > A soft, healing whale-girl who works alongside you in DeepSeek Harness.
5
+ > A registry-driven desktop companion for DeepSeek Harness — the built-in whale girl plus any pet you drop in.
6
6
 
7
- While the model thinks, you wait — she swims. She follows official session activity and switches animations while waiting, thinking, using tools, composing a reply, celebrating completion, or reporting failure; you can also pat her head, feed her dried fish, and watch her grow from a baby whale into your deep-sea bond.
7
+ While the model thinks, you wait — your pet swims. It follows official session activity and switches animations while waiting, thinking, using tools, composing a reply, celebrating completion, or reporting failure; you can also pat its head, feed it dried fish, and watch its affinity grow. Pets are registry entries, not code: every pet is one `pet.json` manifest plus one atlas image, and the host discovers them at startup.
8
8
 
9
9
  Re-implemented from the pet feature of the Codex desktop app, as an official DSH plugin shape (cordis bundle: host half + client half in one package).
10
10
 
@@ -12,17 +12,49 @@ Re-implemented from the pet feature of the Codex desktop app, as an official DSH
12
12
 
13
13
  | Feature | Description |
14
14
  |---|---|
15
- | State animation | Official session activity → whale-girl animation: `thinking running`, `tool running-right`, `review review`, `waiting waiting`, `done jumping`, `failed failed` |
16
- | Head-pat interaction | Click the whale-girl bubble feedback + affinity +1 (10s cooldown) |
17
- | Feeding | Hover panel "喂食" (Feed) consumes 1 dried fish + affinity +5 (30s cooldown) |
18
- | Treat economy | Dried-fish stock (cap 20): +1 every 3 rounds of work, +1 every 30 minutes; when low it prompts "多陪鲸鱼娘工作一会儿" (Work with the whale-girl a bit more) |
19
- | Affinity | +1 per round completed; 4 levels: 幼鲸 (baby whale) 伙伴 (companion) 挚友 (close friend) → 深海羁绊 (deep-sea bond, capped at 100) |
20
- | Custom naming | Hover panel "改名" (Rename) → 1–20 characters, persisted, echoed in the summon button/panel |
21
- | Dragging | Hold and drag the whale-girl to reposition; position persisted |
22
- | Hide/Summon | Hover panel "隐藏" (Hide); after hiding, a "召唤{name}" (Summon {name}) button appears in the input selector row |
15
+ | Multi-pet registry | The host scans built-in `assets/`, the hatch-pet custom pets directory, and composed config entries; each pet is a manifest plus an atlas |
16
+ | Pet selection in settings | The plugin settings card lists every registered pet; switching persists and the sprite swaps immediately |
17
+ | Per-pet naming | Rename from the hover panel; each pet keeps its own name (stored per pet id, migrated from the legacy flat name) |
18
+ | State animation | Official session activity 9-state animation: `thinking running`, `tool running-right`, `review review`, `waiting waiting`, `done jumping`, `failed failed` |
19
+ | Head-pat interaction | Click the petbubble feedback + affinity +1 (10s cooldown) |
20
+ | Feeding | Hover panel 喂食 (Feed) → consumes 1 dried fish + affinity +5 (30s cooldown) |
21
+ | Treat economy | Dried-fish stock (cap 20): +1 every 3 rounds of work, +1 every 30 minutes |
22
+ | Affinity | +1 per round completed; 4 levels: 幼鲸 伙伴 挚友 深海羁绊 (capped at 100) |
23
+ | Dragging | Hold and drag the pet to reposition; position persisted |
24
+ | Hide/Summon | Hover panel 隐藏 (Hide); after hiding, a 召唤{name} (Summon {name}) button appears |
23
25
  | Status bubble | Shows the current session stage or tool name; transient interaction feedback temporarily takes priority |
24
26
  | Multi-session activity | The pet is host-global: the most recent meaningful event controls its display, while completed turns from every session contribute affinity and treats |
25
27
 
28
+ ## Pet contract
29
+
30
+ A pet is a directory holding one `pet.json` manifest and one atlas image. Nothing else is required — no host or client code changes.
31
+
32
+ ```jsonc
33
+ {
34
+ "id": "whale-girl", // unique lowercase kebab id
35
+ "displayName": "鲸鱼娘", // shown in the settings selector and panel
36
+ "description": "A soft healing whale-girl.", // optional
37
+ "spritesheetPath": "spritesheet.webp", // atlas, relative to the manifest
38
+ "cell": { "width": 192, "height": 208 }, // optional; defaults to the Codex contract
39
+ "columns": 8, // optional; default 8
40
+ "frames": [6, 8, 8, 4, 5, 8, 6, 6, 6], // optional per-row frame counts
41
+ "tracks": { // optional per-track rhythm overrides
42
+ "idle": { "durations": [400, 400, 500, 400, 400, 500] }
43
+ }
44
+ }
45
+ ```
46
+
47
+ - The atlas is an 8-column × 9-row grid (192×208 cells by default); rows are fixed in this order: 0 idle, 1 running-right, 2 running-left, 3 waving, 4 jumping, 5 failed, 6 waiting, 7 running, 8 review. Unused cells stay fully transparent.
48
+ - `frames` counts the used columns per row (defaults to the hatch-pet contract table `[6, 8, 8, 4, 5, 8, 6, 6, 6]`); `tracks` overrides per-frame durations (cycled to the row's frame count), `loop`, and `fallback` per animation (defaults: everything loops; `jumping` and `failed` hold their last frame, then fall back to `idle`).
49
+
50
+ Where pets come from (later sources override earlier ones on id collision):
51
+
52
+ 1. **Built-in**: `assets/<dir>/pet.json` in this package.
53
+ 2. **Custom pets**: `${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json` — the hatch-pet pipeline stages its output there, so a hatched pet appears in the selector with no further wiring.
54
+ 3. **Composed**: `PetConfig.pets` manifest entries passed to the plugin by the embedding application.
55
+
56
+ The registry is built once at host startup; add or change a pet, then restart `dsh web`.
57
+
26
58
  ## Animation preview
27
59
 
28
60
  The sprites are an 8-column × 9-row atlas (192×208 cells) generated by the [hatch-pet](https://github.com/dsh2026) pipeline; below are previews of each state:
@@ -37,53 +69,57 @@ The sprites are an 8-column × 9-row atlas (192×208 cells) generated by the [ha
37
69
 
38
70
  ## Architecture
39
71
 
40
- ```
72
+ ```text
41
73
  dsh-pet/
42
74
  |-- src/
43
- | |-- index.ts # host half: plugin entry (cordis apply, route registration)
44
- | |-- service.ts # PetService: pet state machine + affinity + config (HTTP API service face)
45
- | |-- state.ts # pet state machine: projected session activity → 9 state animations
46
- | |-- affinity.ts # affinity ledger (pure functions + cooldowns)
47
- | |-- treats.ts # dried-fish stock ledger
48
- | |-- persist.ts # persistence ($DSH_HOME/pet.json, atomic write)
49
- | |-- routes.ts # /api/pet/* JSON API + /pet/whale/* static asset routes
50
- | `-- client/ # browser half
51
- | |-- index.ts # global mount (createRoot → body) + polling (800ms) + interaction wiring (fetch)
52
- | |-- PetDockEntry.tsx # global floating entry (document.body, always shown: no session / new session / mid-session)
53
- | |-- WhalePet.tsx # floating component (portal + rAF frame animation + dragging)
54
- | |-- spritesheet.ts # atlas geometry + per-state animation tracks (frames/duration)
75
+ | |-- index.ts # host half: plugin entry (registry build, settings section, routes)
76
+ | |-- registry.ts # multi-pet contract: manifest scan + normalization (assets + custom pets)
77
+ | |-- service.ts # PetService: pet selection + state machine + affinity + config
78
+ | |-- state.ts # pet state machine: projected session activity → 9 state animations
79
+ | |-- affinity.ts # affinity ledger (pure functions + cooldowns)
80
+ | |-- treats.ts # dried-fish stock ledger
81
+ | |-- persist.ts # persistence ($DSH_HOME/pet.json: selection + per-pet names, atomic write)
82
+ | |-- routes.ts # /api/pet/* JSON API + /pet/<id>/* asset routes
83
+ | `-- client/ # browser half
84
+ | |-- index.ts # global mount (createRoot body) + registry fetch + polling + wiring
85
+ | |-- PetDockEntry.tsx # global floating entry (document.body, always shown)
86
+ | |-- PetSprite.tsx # definition-driven floating sprite (portal + rAF + dragging)
87
+ | |-- PetSettingsCard.tsx # settings card: pet selector + display layout
88
+ | |-- spritesheet.ts # atlas geometry helpers + track trimming
55
89
  | `-- pet.module.css
56
- |-- assets/whale/ # whale-girl assets (pet.json + spritesheet.webp + animation previews)
57
- `-- cordis.patch.yml # bundle patch: inserts the pet plugin row
90
+ |-- assets/whale/ # built-in whale-girl (pet.json + spritesheet.webp + previews)
91
+ `-- cordis.patch.yml # bundle patch: inserts the pet plugin row
58
92
  ```
59
93
 
60
94
  ### Data flow
61
95
 
62
- ```
96
+ ```text
63
97
  official session events (turn/step/chunk/tool) ----\
64
- > PetService (host)
98
+ > PetService (host) <-- registry (assets + custom pets)
65
99
  optional legacy activity/status ------------------/
66
100
  | /api/pet/* JSON
67
- global React root (createRoot → document.body) <-- polling 800ms -- pet-client (browser)
101
+ global React root (createRoot → document.body) <-- polling 2s -- pet-client (browser)
68
102
  |
69
- WhalePet floating layer (portal + rAF)
103
+ PetSprite floating layer (portal + rAF)
70
104
  ```
71
105
 
72
106
  - **Status source**: the host projects official `turn/start`, `step/start`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, and `turn/end` events into waiting/thinking/tool/review/done/failed states. Optional legacy `activity/status` events remain a compatibility input.
107
+ - **Registry**: the host normalizes every manifest into a full render definition (geometry, per-row frame counts, per-track durations) and serves it over `/api/pet/pets`; the browser half renders any entry from that definition and carries no per-pet code.
108
+ - **Selection & naming**: `petId` lives in the settings namespace; per-pet names live in `pet.json` under `names`, edited through the hover-panel rename of the active pet. Legacy installs migrate their flat `name` onto the whale girl.
73
109
  - **Multi-session semantics**: the API and browser mount are host-global and expose no foreground-session identity, so the most recent meaningful event wins the display. Every session's completed turns are still rewarded independently, and disposing a non-current session does not reset the visible state.
74
110
  - **Mount point**: `document.body` (global React root, always shown: no session / new session / mid-session — the old mount point `conversation.composer.dock` only rendered in an active session, hiding the pet in new sessions); the component uses `createPortal` internally to render the global floating layer.
75
- - **Rendering**: CSS sprite (background-position) per-frame animation, frame durations from the track definitions in `spritesheet.ts`.
76
- - **Communication**: browser ↔ host over the same-origin `/api/pet/*` JSON endpoints (state/interact/set-visible/set-config); the atlas loads from `/pet/whale/spritesheet.webp`both the RPC domain and the `/plugins/` static service are platform-registered, and the plugin self-sufficiently provides its own API and assets (the same pattern as dsh-remote-web-ui's `/api/pair`).
111
+ - **Rendering**: CSS sprite (background-position) per-frame animation; frame durations come from the served definition's tracks.
112
+ - **Communication**: browser ↔ host over the same-origin `/api/pet/*` JSON endpoints (state/pets/interact/set-visible/set-config/set-name/set-pet); each pet's atlas loads from `/pet/<id>/<spritesheetPath>` — the plugin self-sufficiently provides its own API and assets (the same pattern as dsh-remote-web-ui's `/api/pair`).
77
113
 
78
114
  ## Install
79
115
 
80
116
  Install the family aggregate package `@linxin666/dsh-web-ui-all` (all plugins and skins in one) or this plugin alone:
81
117
 
82
118
  ```sh
83
- ### npm 安装(推荐)
119
+ ### From npm (recommended)
84
120
  dsh plugin --profile web add @linxin666/dsh-pet
85
121
 
86
- ### 从仓库安装(开发调试)
122
+ ### From the repository (development)
87
123
  git clone https://github.com/zhu1090093659/dsh-web-ui.git
88
124
  cd dsh-web-ui
89
125
  pnpm install && pnpm -r build
@@ -91,13 +127,13 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-pet
91
127
 
92
128
  ```
93
129
 
94
- After installing, **restart `dsh web`** — the whale-girl appears at the bottom-right of the interface. In link mode, `pnpm build` and refresh the page after a code change; no reinstall needed.
130
+ After installing, **restart `dsh web`** — your selected pet appears at the bottom-right of the interface. In link mode, `pnpm build` and refresh the page after a code change; no reinstall needed.
95
131
 
96
132
  ## Development
97
133
 
98
134
  ```sh
99
135
  pnpm build # tsc -b (types+declarations) && tsdown (node half + browser bundle)
100
- pnpm test # vitest unit/component tests (event projection / state / UI / ledgers)
136
+ pnpm test # vitest unit/component tests (registry / event projection / state / UI / ledgers)
101
137
  pnpm prepare # transpile-only build (no type checking, for consumer installs)
102
138
  pnpm typecheck # type check only
103
139
  ```
@@ -106,9 +142,8 @@ The browser bundle rides the `window.__ModuleLoader__.load` contract; React/cord
106
142
 
107
143
  ## Sprites and animation-track calibration
108
144
 
109
- The whale-girl atlas is generated by the hatch-pet pipeline as 9 states × 8 columns: `assets/whale/spritesheet.webp` (1536×1872, 8 columns × 9 rows of 192×208 cells) + `assets/whale/pet.json`. The actual frame count and rhythm of each row are defined in `TRACKS` in `src/client/spritesheet.ts`. If the artwork is redone and the frame count changes, only that table needs updating (row-order contract: 0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review).
145
+ The built-in whale-girl atlas is generated by the hatch-pet pipeline as 9 states × 8 columns: `assets/whale/spritesheet.webp` (1536×1872, 8 columns × 9 rows of 192×208 cells) + `assets/whale/pet.json`. The frame count and rhythm of each row live in that manifest's `frames` and `tracks` fields the whale girl carries its own slower healing durations, while pets without overrides follow the hatch-pet contract rhythm. Redoing artwork therefore only edits `assets/whale/pet.json` (row-order contract: 0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review).
110
146
 
111
147
  ## License
112
148
 
113
149
  [BSD-3-Clause](LICENSE)
114
-
package/README.zh.md CHANGED
@@ -1,83 +1,119 @@
1
- # dsh-pet — 鲸鱼娘宠物插件
1
+ # dsh-pet — 多宠物伴侣插件
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- > 一只软萌治愈的鲸鱼娘,陪你在 DeepSeek Harness 里工作。
5
+ > 一个注册表驱动的桌面伴侣:内置鲸鱼娘,也接受你放入的任何宠物。
6
6
 
7
- 模型思考的时候你在等,她在游。她会跟随官方会话活动,在等待、思考、使用工具、整理回复、庆祝完成或报告失败时切换动画;你还可以摸头、喂小鱼干,看着她从幼鲸慢慢长成你的深海羁绊。
7
+ 模型思考时你在等待,你的宠物在游动。它跟随官方会话活动,在等待、思考、调用工具、整理回复、庆祝完成、报告失败时切换动画;你还可以摸摸它的头、喂它小鱼干,看着亲密度一点点成长。宠物是注册表条目而不是代码:每只宠物只需一份 `pet.json` manifest 加一张图集,宿主启动时自动发现。
8
8
 
9
- 复刻自 Codex 桌面版的宠物功能,以 DSH 官方插件形态实现(cordis bundle:host 半区 + client 半区单包)。
9
+ Codex 桌面应用的宠物功能重新实现,采用官方 DSH 插件形态(cordis bundle:host 半区 + client 半区,一个包)。
10
10
 
11
11
  ## 功能
12
12
 
13
13
  | 功能 | 说明 |
14
14
  |---|---|
15
- | 状态动画 | 官方会话活动 → 鲸鱼娘动画:`thinking → running`、`tool → running-right`、`review review`、`waiting waiting`、`done jumping`、`failed → failed` |
16
- | 摸头互动 | 点击鲸鱼娘 → 气泡反馈 + 亲密度 +1(10s 冷却) |
17
- | 喂食 | 悬浮面板「喂食」→ 消耗 1 条小鱼干 + 亲密度 +5(30s 冷却) |
18
- | 饲料经济 | 小鱼干库存(上限 20):工作每 3 回合 +1 条、每 30 分钟 +1 条;库存不足会提示「多陪鲸鱼娘工作一会儿」 |
19
- | 亲密度 | 每完成一个回合 +1;4 个等级:幼鲸 伙伴 挚友 深海羁绊(100 点封顶) |
20
- | 自定义命名 | 悬浮面板「改名」→ 1–20 字符,持久化,召唤按钮/面板同步显示 |
21
- | 拖动 | 按住鲸鱼娘拖动重新摆放,位置持久化 |
22
- | 隐藏/召唤 | 悬浮面板「隐藏」;隐藏后输入选择行出现「召唤{名字}」按钮 |
23
- | 状态气泡 | 显示当前会话阶段或工具名;短暂的交互反馈会临时优先显示 |
24
- | 多会话活动 | 宠物是 host 全局的:最近一条有效事件决定显示,所有会话的完成回合都会计入亲密度与小鱼干 |
25
-
26
- ## 动画演示
27
-
28
- 素材为 8 列 × 9 行图集(192×208 单元),由 [hatch-pet](https://github.com/dsh2026) 流水线生成,以下为各状态动画预览:
29
-
30
- | idle 待机 | waiting 等待 | running 干活 | jumping 庆祝 |
15
+ | 多宠物注册表 | 宿主扫描内置 `assets/`、hatch-pet 自定义宠物目录和组合配置条目;每只宠物 = manifest + 图集 |
16
+ | 设置中选择宠物 | 插件设置卡片列出所有已注册宠物;切换即持久化,精灵立即更换 |
17
+ | 每只宠物独立命名 | 在悬浮面板改名;每只宠物保存自己的名字(按宠物 id 存储,旧版平铺名字自动迁移) |
18
+ | 状态动画 | 官方会话活动 9 态动画:`thinking running`、`tool running-right`、`review review`、`waiting → waiting`、`done → jumping`、`failed → failed` |
19
+ | 摸头互动 | 点击宠物气泡反馈 + 亲密度 +1(10s 冷却) |
20
+ | 喂食 | 悬浮面板 喂食 → 消耗 1 条小鱼干 + 亲密度 +5(30s 冷却) |
21
+ | 小鱼干经济 | 库存(上限 20):每工作 3 轮 +1,每 30 分钟 +1 |
22
+ | 亲密度 | 每完成一轮 +1;4 级:幼鲸 → 伙伴 → 挚友 → 深海羁绊(上限 100) |
23
+ | 拖动 | 按住拖动宠物换位置;位置持久化 |
24
+ | 隐藏/召唤 | 悬浮面板 隐藏;隐藏后出现 召唤{name} 按钮 |
25
+ | 状态气泡 | 显示当前会话阶段或工具名;瞬时互动反馈临时优先 |
26
+ | 多会话活动 | 宠物是宿主全局的:最近一次有意义事件驱动显示,每个会话完成的轮次都计入亲密度与小鱼干 |
27
+
28
+ ## 宠物契约
29
+
30
+ 一只宠物 = 一个目录 + 一份 `pet.json` manifest + 一张图集。除此之外什么都不需要——不用改任何宿主或客户端代码。
31
+
32
+ ```jsonc
33
+ {
34
+ "id": "whale-girl", // 唯一的小写 kebab id
35
+ "displayName": "鲸鱼娘", // 显示在设置选择器与面板上
36
+ "description": "一只软萌治愈的鲸鱼娘。", // 可选
37
+ "spritesheetPath": "spritesheet.webp", // 图集,相对 manifest 所在目录
38
+ "cell": { "width": 192, "height": 208 }, // 可选;默认 Codex 契约
39
+ "columns": 8, // 可选;默认 8
40
+ "frames": [6, 8, 8, 4, 5, 8, 6, 6, 6], // 可选的每行帧数
41
+ "tracks": { // 可选的每轨节奏覆盖
42
+ "idle": { "durations": [400, 400, 500, 400, 400, 500] }
43
+ }
44
+ }
45
+ ```
46
+
47
+ - 图集是 8 列 × 9 行网格(默认 192×208 单元格);行序固定:0 idle、1 running-right、2 running-left、3 waving、4 jumping、5 failed、6 waiting、7 running、8 review。未使用的格子保持全透明。
48
+ - `frames` 记录每行用到的列数(缺省按 hatch-pet 契约表 `[6, 8, 8, 4, 5, 8, 6, 6, 6]`);`tracks` 按动画覆盖每帧时长(按该行帧数循环补足)、`loop` 与 `fallback`(默认:全部循环;`jumping` 与 `failed` 停在最后一帧后回到 `idle`)。
49
+
50
+ 宠物的来源(后注册的来源在同 id 冲突时覆盖前者):
51
+
52
+ 1. **内置**:本包 `assets/<dir>/pet.json`。
53
+ 2. **自定义宠物**:`${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json` —— hatch-pet 流水线把产物放在这里,孵化的宠物无需任何接线即可出现在选择器里。
54
+ 3. **组合注入**:嵌入应用通过 `PetConfig.pets` 传入的 manifest 条目。
55
+
56
+ 注册表在宿主启动时构建一次;新增或修改宠物后重启 `dsh web` 生效。
57
+
58
+ ## 动画预览
59
+
60
+ 精灵图是由 [hatch-pet](https://github.com/dsh2026) 流水线生成的 8 列 × 9 行图集(192×208 单元格);各状态预览:
61
+
62
+ | idle | waiting | running | jumping |
31
63
  |---|---|---|---|
32
64
  | ![idle](assets/whale/previews/idle.gif) | ![waiting](assets/whale/previews/waiting.gif) | ![running](assets/whale/previews/running.gif) | ![jumping](assets/whale/previews/jumping.gif) |
33
65
 
34
- | waving 挥手 | review 复盘 | failed 失败 | 左右移动 |
66
+ | waving | review | failed | 左右移动 |
35
67
  |---|---|---|---|
36
68
  | ![waving](assets/whale/previews/waving.gif) | ![review](assets/whale/previews/review.gif) | ![failed](assets/whale/previews/failed.gif) | ![running-left](assets/whale/previews/running-left.gif) ![running-right](assets/whale/previews/running-right.gif) |
37
69
 
38
70
  ## 架构
39
71
 
40
- ```
72
+ ```text
41
73
  dsh-pet/
42
74
  |-- src/
43
- | |-- index.ts # host 半区:插件入口(cordis apply,注册路由)
44
- | |-- service.ts # PetService:宠物状态机 + 亲密度 + 配置(HTTP API 服务面)
45
- | |-- state.ts # 宠物状态机:投影后的会话活动 9 状态动画
46
- | |-- affinity.ts # 亲密度账本(纯函数 + 冷却)
47
- | |-- treats.ts # 小鱼干库存账本
48
- | |-- persist.ts # 持久化($DSH_HOME/pet.json,原子写入)
49
- | |-- routes.ts # /api/pet/* JSON API + /pet/whale/* 素材静态路由
50
- | `-- client/ # 浏览器半区
51
- | |-- index.ts # 全局挂载(createRoot → body)+ 轮询(800ms)+ 交互接线(fetch)
52
- | |-- PetDockEntry.tsx # 全局浮层入口(document.body,无会话/新会话/会话中全程显示)
53
- | |-- WhalePet.tsx # 浮层组件(portal + rAF 帧动画 + 拖动)
54
- | |-- spritesheet.ts # 图集几何 + 每状态动画轨道(帧/时长)
75
+ | |-- index.ts # host 半区:插件入口(构建注册表、设置区、路由)
76
+ | |-- registry.ts # 多宠物契约:manifest 扫描 + 归一化(内置 + 自定义宠物)
77
+ | |-- service.ts # PetService:宠物选择 + 状态机 + 亲密度 + 配置
78
+ | |-- state.ts # 宠物状态机:会话活动投影 9 态动画
79
+ | |-- affinity.ts # 亲密度账本(纯函数 + 冷却)
80
+ | |-- treats.ts # 小鱼干库存账本
81
+ | |-- persist.ts # 持久化($DSH_HOME/pet.json:选择 + 每宠物名字,原子写入)
82
+ | |-- routes.ts # /api/pet/* JSON API + /pet/<id>/* 静态资源路由
83
+ | `-- client/ # 浏览器半区
84
+ | |-- index.ts # 全局挂载(createRoot → body)+ 注册表拉取 + 轮询 + 接线
85
+ | |-- PetDockEntry.tsx # 全局浮层入口(document.body,始终显示)
86
+ | |-- PetSprite.tsx # 由定义驱动的浮层精灵(portal + rAF + 拖动)
87
+ | |-- PetSettingsCard.tsx # 设置卡片:宠物选择器 + 显示布局
88
+ | |-- spritesheet.ts # 图集几何辅助 + 轨道裁剪
55
89
  | `-- pet.module.css
56
- |-- assets/whale/ # 鲸鱼娘素材(pet.json + spritesheet.webp + 动画预览)
57
- `-- cordis.patch.yml # bundle patch:插入 pet 插件行
90
+ |-- assets/whale/ # 内置鲸鱼娘(pet.json + spritesheet.webp + 预览)
91
+ `-- cordis.patch.yml # bundle 补丁:插入宠物插件行
58
92
  ```
59
93
 
60
94
  ### 数据流
61
95
 
62
- ```
63
- 官方会话事件(turn/step/chunk/tool) ----\
64
- > PetService(host)
65
- 可选的兼容事件 activity/status --------------/
96
+ ```text
97
+ 官方会话事件(turn/step/chunk/tool)----\
98
+ > PetService(宿主)<-- 注册表(内置 + 自定义宠物)
99
+ 可选兼容 activity/status ------------------/
66
100
  | /api/pet/* JSON
67
- global React root(createRoot → document.body <-- 轮询 800ms -- pet-client(浏览器)
101
+ 全局 React 根(createRoot → document.body)<-- 2s 轮询 -- pet-client(浏览器)
68
102
  |
69
- WhalePet 浮层(portal + rAF)
103
+ PetSprite 浮层(portal + rAF)
70
104
  ```
71
105
 
72
- - **状态源**:host 将官方 `turn/start`、`step/start`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result` 和 `turn/end` 事件投影为 waiting/thinking/tool/review/done/failed 状态。可选的旧版 `activity/status` 事件仍作为兼容输入。
73
- - **多会话语义**:API 和浏览器挂载是 host 全局的,也不提供前台会话标识,因此最近一条有效事件决定显示。各会话的完成回合仍独立奖励,销毁非当前会话不会重置可见状态。
74
- - **挂载点**:`document.body`(全局 React root,无会话/新会话/会话中全程显示——旧挂载点 `conversation.composer.dock` 只在活跃会话渲染,导致新会话界面看不到宠物),组件内部 `createPortal` 渲染全局浮层。
75
- - **渲染**:CSS sprite(background-position)逐帧动画,帧时长来自 `spritesheet.ts` 的轨道定义。
76
- - **通信**:浏览器 host 走同源 `/api/pet/*` JSON 端点(state/interact/set-visible/set-config),图集从 `/pet/whale/spritesheet.webp` 加载——RPC 域与 `/plugins/` 静态服务都是平台注册的,插件自足地提供自己的 API 与素材(与 dsh-remote-web-ui 的 `/api/pair` 同一模式)。
106
+ - **状态来源**:宿主把官方 `turn/start`、`step/start`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`turn/end` 事件投影为 waiting/thinking/tool/review/done/failed 状态。可选兼容 `activity/status` 事件仍作为输入。
107
+ - **注册表**:宿主把每份 manifest 归一化为完整渲染定义(几何、每行帧数、每轨时长),经 `/api/pet/pets` 下发;浏览器半区用该定义渲染任意条目,不携带任何宠物专属代码。
108
+ - **选择与命名**:`petId` 存于设置命名空间;每只宠物的名字存于 `pet.json` `names`,通过悬浮面板对当前宠物改名编辑。旧版安装的平铺 `name` 自动迁移到鲸鱼娘名下。
109
+ - **多会话语义**:API 与浏览器挂载都是宿主全局的,不暴露前台会话身份,最近一次有意义事件赢得显示;每个会话完成的轮次仍独立计奖,销毁非当前会话不会重置可见状态。
110
+ - **挂载点**:`document.body`(全局 React 根,始终显示:无会话 / 新会话 / 会话中都可见——旧挂载点 `conversation.composer.dock` 只在活动会话里渲染,新会话里宠物消失);组件内部用 `createPortal` 渲染全局浮层。
111
+ - **渲染**:CSS 精灵(background-position)逐帧动画;帧时长来自下发定义里的轨道表。
112
+ - **通信**:浏览器 ↔ 宿主走同源 `/api/pet/*` JSON 端点(state/pets/interact/set-visible/set-config/set-name/set-pet);每只宠物的图集从 `/pet/<id>/<spritesheetPath>` 加载——插件自给自足地提供自己的 API 与资源(与 dsh-remote-web-ui 的 `/api/pair` 同一模式)。
77
113
 
78
114
  ## 安装
79
115
 
80
- 推荐直接安装全家桶聚合包 `@linxin666/dsh-web-ui-all`(一个包装齐全部功能插件与皮肤),或单独安装本插件:
116
+ 安装聚合全家桶 `@linxin666/dsh-web-ui-all`(全部插件与皮肤一次到位),或单独安装本插件:
81
117
 
82
118
  ```sh
83
119
  ### 从 npm 安装(推荐)
@@ -91,23 +127,23 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-pet
91
127
 
92
128
  ```
93
129
 
94
- 安装后**重启 `dsh web`**,鲸鱼娘出现在界面右下角即生效。link 模式下改代码后重新 `pnpm build` 并刷新页面即可,无需重装。
130
+ 安装后**重启 `dsh web`**——你选择的宠物出现在界面右下角。link 模式下改代码后 `pnpm build` 并刷新页面即可,无需重装。
95
131
 
96
132
  ## 开发
97
133
 
98
134
  ```sh
99
135
  pnpm build # tsc -b(类型+声明)&& tsdown(node 半区 + 浏览器 bundle)
100
- pnpm test # vitest 单元/组件测试(事件投影 / state / UI / 账本)
101
- pnpm prepare # 仅转译构建(无类型检查,供消费者安装)
136
+ pnpm test # vitest 单元/组件测试(注册表 / 事件投影 / 状态 / UI / 账本)
137
+ pnpm prepare # 仅转译构建(不做类型检查,供消费者安装)
102
138
  pnpm typecheck # 仅类型检查
103
139
  ```
104
140
 
105
- 浏览器 bundle 走 `window.__ModuleLoader__.load` 契约,React/cordis 等由 loader 模块表解析(external);CSS Modules 由 lightningcss 内联为 `<style data-plugin>`。
141
+ 浏览器 bundle 走 `window.__ModuleLoader__.load` 契约;React/cordis 等从 loader 模块表解析(external);CSS Modules 由 lightningcss 以内联 `<style data-plugin>` 编译进 bundle。
106
142
 
107
- ## 素材与动画轨道校准
143
+ ## 精灵图与动画轨道校准
108
144
 
109
- 鲸鱼娘图集由 hatch-pet 流水线按 9 状态 × 8 列生成:`assets/whale/spritesheet.webp`(1536×1872,8 列 × 9 192×208 单元)+ `assets/whale/pet.json`。每行实际帧数与节奏在 `src/client/spritesheet.ts` `TRACKS` 中定义。若素材重做导致帧数变化,只需更新该表(行序契约:0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review)。
145
+ 内置鲸鱼娘图集由 hatch-pet 流水线生成,9 × 8 列:`assets/whale/spritesheet.webp`(1536×1872,8 列 × 9 行,192×208 单元格)+ `assets/whale/pet.json`。每行的帧数与节奏就写在该 manifest 的 `frames` `tracks` 字段里——鲸鱼娘带着自己更慢的治愈系时长,未覆盖的宠物沿用 hatch-pet 契约节奏。重做美术因此只需修改 `assets/whale/pet.json`(行序契约:0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review)。
110
146
 
111
- ## License
147
+ ## 许可证
112
148
 
113
149
  [BSD-3-Clause](LICENSE)
@@ -3,5 +3,16 @@
3
3
  "displayName": "鲸鱼娘",
4
4
  "description": "一只软萌治愈的鲸鱼娘,来自深海的陪伴小伙伴。",
5
5
  "spritesheetPath": "spritesheet.webp",
6
- "frames": [6, 8, 8, 4, 5, 8, 6, 6, 6]
6
+ "frames": [6, 8, 8, 4, 5, 8, 6, 6, 6],
7
+ "tracks": {
8
+ "idle": { "durations": [400, 400, 500, 400, 400, 500] },
9
+ "running-right": { "durations": [225, 225, 225, 225, 225, 225, 225, 225] },
10
+ "running-left": { "durations": [225, 225, 225, 225, 225, 225, 225, 225] },
11
+ "waving": { "durations": [350, 350, 350, 350] },
12
+ "jumping": { "durations": [300, 300, 300, 350, 350] },
13
+ "failed": { "durations": [450, 450, 450, 500, 550, 600, 450, 450] },
14
+ "waiting": { "durations": [450, 450, 500, 450, 450, 500] },
15
+ "running": { "durations": [250, 250, 250, 250, 250, 250] },
16
+ "review": { "durations": [550, 550, 550, 550, 550, 550] }
17
+ }
7
18
  }
package/cordis.patch.yml CHANGED
@@ -1,10 +1,10 @@
1
1
  # dsh-pet bundle patch: inserts its plugin row into the web plugin roster.
2
2
  # The bundle is the official dsh plugin shape — install it into a dsh profile
3
3
  # (`dsh plugin --profile <name> add link:/Users/zcl/code/dsh-web-ui/packages/dsh-pet`). The single
4
- # row mounts BOTH halves: the host side (pet state machine, affinity score,
5
- # pet.* RPC service) and the browser side (the modules node half scans
6
- # dsh.client packages into the web plugin roster, served from this package's
7
- # lib/client.js plus the whale assets under assets/whale).
4
+ # row mounts BOTH halves: the host side (pet registry, state machine,
5
+ # affinity score, pet.* RPC service) and the browser side (the modules node
6
+ # half scans dsh.client packages into the web plugin roster, served from this
7
+ # package's lib/client.js plus the built-in pet assets under assets/).
8
8
  - insert:
9
9
  - id: pet
10
10
  name: '@linxin666/dsh-pet'