dsh-villager-hmm 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-villager-hmm contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ NOTICE ON THIRD-PARTY ASSETS
26
+
27
+ This package bundles and redistributes no third-party assets.
28
+
29
+ scripts/fetch-assets.mjs downloads the Minecraft villager sounds and the
30
+ villager skin onto the operator's own machine, at the operator's direction,
31
+ into a local cache outside this package. Those files are the property of
32
+ Mojang Studios / Microsoft and are not covered by the MIT grant above; they
33
+ are never part of this package's source tree, tarball, or repository.
34
+
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # dsh-villager-hmm
2
+
3
+ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin.
4
+ Whenever the model goes *"hmm"* — inside its chain of thought, or in the reply
5
+ itself — a Minecraft villager hums back at you.
6
+
7
+ A small villager sits in the top-right corner, bobs on every hit, and carries
8
+ the controls: pause, volume, which stream to watch, and the matcher itself.
9
+
10
+ **Drag the title bar to move it; double-click the bar to send it back to the
11
+ corner.** The position is remembered in `localStorage`. It defaults to the
12
+ top-right rather than the bottom-right on purpose — the composer is full-width,
13
+ so a bottom-anchored overlay sits on top of the send button.
14
+
15
+ ```
16
+ 🧑‍🌾 村民 hmm 音效 触发 12 · 播放 12 -
17
+ 思维链 1284 字 正文 356 字 音效 2 段
18
+ [ 监听中 · 点击暂停 ] [ 试听一次 ] 音量 ────●────
19
+ 检测源 [ 仅思维链 ▾ ]
20
+ 匹配式 [(?<![0-9A-Za-z])(?:h+m+|mhm+|m{2,})|[嗯唔哼呃] ] [应用] [默认]
21
+ 最近命中 hmm 嗯 唔
22
+ ```
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ # 1. install the package into your profile
28
+ dsh plugin --profile web add dsh-villager-hmm
29
+ ```
30
+
31
+ ```jsonc
32
+ // 2. register it as a profile bundle — add the name to the
33
+ // `dsh.profile.bundles` array in $DSH_HOME/profiles/web/package.json
34
+ "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-villager-hmm"] } }
35
+ ```
36
+
37
+ ```sh
38
+ # 3. fetch the sounds and the villager texture (see Assets below)
39
+ npx dsh-villager-hmm-assets
40
+ ```
41
+
42
+ The package ships its own `cordis.patch.yml`, so step 2 is what actually
43
+ inserts the plugin row. Restart the profile after editing.
44
+
45
+ Skip step 2 and nothing happens; skip step 3 and the plugin loads, shows its
46
+ panel, and tells you which command to run — it never fails silently.
47
+
48
+ <details>
49
+ <summary>Installing from a local checkout instead of npm</summary>
50
+
51
+ ```sh
52
+ git clone https://github.com/nagatoquin33/dsh-villager-hmm && cd dsh-villager-hmm
53
+ dsh plugin --profile web add "$PWD"
54
+ ```
55
+
56
+ Note that a path containing spaces breaks pnpm's argument handling — install
57
+ from a space-free directory.
58
+ </details>
59
+
60
+ ## Assets
61
+
62
+ **This package ships no Minecraft material.** The two villager sounds and the
63
+ villager skin are fetched on your machine into a per-user cache:
64
+
65
+ ```
66
+ $DSH_HOME/.dsh-villager-hmm/assets/
67
+ idle1.ogg villager "hmm"
68
+ idle2.ogg villager "hmm", second take
69
+ villager.png the 64x64 villager skin; the panel crops the head front in CSS
70
+ ```
71
+
72
+ `npx dsh-villager-hmm-assets` (or `node scripts/fetch-assets.mjs`) populates it.
73
+ Two sources are supported:
74
+
75
+ | Flag | Meaning |
76
+ | --- | --- |
77
+ | *(none)* | Download from a public mirror of the vanilla 1.21.4 client assets |
78
+ | `--from <dir>` | Copy from an asset tree you extracted yourself — **the option that matches the game's terms** |
79
+ | `--base <url>` | Your own mirror with the same layout |
80
+ | `--dir <path>` | Use a different cache directory |
81
+ | `--force` | Re-fetch files that are already there |
82
+
83
+ ```sh
84
+ # from your own extraction
85
+ npx dsh-villager-hmm-assets --from "C:/mc-extract/assets/minecraft"
86
+ ```
87
+
88
+ The cache lives outside `node_modules`, so it survives plugin upgrades. The
89
+ host half re-reads it per request, which means running the fetch script takes
90
+ effect on a page reload — no harness restart.
91
+
92
+ ## Configuration
93
+
94
+ Defaults live in the row. To override them, replace the insert in your own
95
+ `$DSH_HOME/profiles/web/cordis.patch.yml`:
96
+
97
+ ```yaml
98
+ - insert:
99
+ - id: dsh-villager-hmm
100
+ name: 'dsh-villager-hmm'
101
+ config:
102
+ enabled: true
103
+ mode: both # 'reasoning' (chain of thought only) | 'both'
104
+ pattern: '(?<![0-9A-Za-z])(?:h+m+|mhm+|m{2,})|[嗯唔哼呃]'
105
+ assetDir: 'D:/my-assets'
106
+ ```
107
+
108
+ Everything except `assetDir` is also editable from the floating panel.
109
+
110
+ `mode` defaults to `both` on purpose: plenty of models expose no separate
111
+ reasoning channel, and a plugin that silently does nothing looks broken. Switch
112
+ to `reasoning` once you have confirmed your model streams `reasoning-delta` —
113
+ the panel's **思维链** counter tells you: if it climbs, you have a reasoning
114
+ channel and can narrow the scope.
115
+
116
+ ## The matcher
117
+
118
+ The default pattern is
119
+ `(?<![0-9A-Za-z])(?:h+m+|mhm+|m{2,})|[嗯唔哼呃]`.
120
+
121
+ It uses a **lookbehind rather than `\b`**, for two reasons:
122
+
123
+ - `\b` is ASCII-only in JavaScript, so it behaves unpredictably next to CJK
124
+ characters — `嗯hmm` would not match.
125
+ - The CJK branch deliberately has no boundary at all, because those characters
126
+ are always standalone interjections.
127
+
128
+ `h+m+` covers `hm`, `hmm`, `hmmm…`; `mhm+` covers the closed-mouth variant;
129
+ `m{2,}` covers a bare `mm`. None of them fire inside ordinary words — `comma`,
130
+ `comment`, `command` and `ahmm` all stay silent.
131
+
132
+ Write your own in the panel's **匹配式** field and hit **应用**. A pattern that
133
+ does not compile is reported inline instead of being swallowed.
134
+
135
+ ## How it works
136
+
137
+ The host half listens to the `agent/assistant-stream` event and scans
138
+ `reasoning-delta` (and optionally `text-delta`) chunks against the matcher. The
139
+ browser half polls that counter over the plugin's own HTTP prefix and plays a
140
+ sound per hit.
141
+
142
+ Two details are worth knowing if you touch the scanner:
143
+
144
+ **`frame.revision` is not a stream identity.** It increments on *every* frame
145
+ (`dsh-agent-loop`, `() => ++this.assistantStreamRevision`). Keying the scan
146
+ buffer by it resets the buffer on every delta, so the matcher only ever sees one
147
+ character and never matches. The buffer is keyed by `attemptId:turn:step`.
148
+
149
+ **The scan is inline, not debounced.** Deltas arrive far faster than any debounce
150
+ window, so every new delta cancels the pending timer before it can fire — on a
151
+ real stream, 1623 schedules produced 5 fires. Because the matcher keeps a
152
+ consumed-offset cursor, scanning on every delta is amortised O(n).
153
+
154
+ A match that ends exactly at the buffer end is deferred unless the attempt is
155
+ finishing, so `hm` is never counted before the next delta can turn it into
156
+ `hmm`; the block-end/end frames flush whatever is left.
157
+
158
+ ## Layout
159
+
160
+ ```
161
+ lib/index.js host half — event listener + HTTP routes
162
+ client/client.js browser half — prebuilt __ModuleLoader__ bundle
163
+ scripts/fetch-assets.mjs the asset fetcher (also the package's bin)
164
+ cordis.patch.yml the row this package inserts
165
+ test/self-test.mjs unit + integration tests
166
+ ```
167
+
168
+ ## Routes
169
+
170
+ The plugin owns one HTTP prefix, `/dsh-villager-hmm`:
171
+
172
+ | Route | Purpose |
173
+ | --- | --- |
174
+ | `GET /state?cursor=N` | counters, asset status, and triggers newer than `N` |
175
+ | `GET /config?enabled&mode&pattern` | change settings at runtime |
176
+ | `GET /sound/<n>.ogg` | the audio, read from the cache |
177
+ | `GET /texture.png` | the villager skin, read from the cache |
178
+
179
+ ## Tests
180
+
181
+ ```sh
182
+ npm test # 24 hermetic cases, no dependencies
183
+ npm run test:boot # mounts the host half in a real cordis Context
184
+ ```
185
+
186
+ `npm test` covers the scanner against character-by-character streams with
187
+ `revision` incrementing per frame, boundary and false-positive cases,
188
+ chunk-size invariance, config changes, the route surface, a populated and an
189
+ empty asset cache, that no assets are bundled, and the client bundle's
190
+ materialization.
191
+
192
+ `npm run test:boot` is the guard against a harness update breaking the row. It
193
+ imports and applies the real host half inside a live cordis `Context` and
194
+ checks that a missing `webServer`, a throwing `register()`, a malformed request
195
+ URL, and unknown chunk and frame kinds all degrade instead of failing the boot.
196
+ Cordis is an optional peer, so the suite skips cleanly when it is not
197
+ resolvable — point `DSH_CORDIS` at a deployment's copy to run it without
198
+ installing anything:
199
+
200
+ ```sh
201
+ DSH_CORDIS=/path/to/node_modules/@deepseek-ai/cordis/lib/index.js npm run test:boot
202
+ ```
203
+
204
+ ## Releasing
205
+
206
+ CI (`.github/workflows/ci.yml`) runs the tests on every push and pull request
207
+ against Node 22 and 24. It also fails the build if a binary asset is ever
208
+ tracked, or if `dsh.bundle.patch` stops resolving — the two things this
209
+ repository must not get wrong.
210
+
211
+ Publishing is tag-driven:
212
+
213
+ ```sh
214
+ npm version patch # or minor / major — commits and creates the tag
215
+ git push --follow-tags
216
+ ```
217
+
218
+ `.github/workflows/release.yml` then checks the tag against `package.json`,
219
+ runs the tests, asserts the tarball carries no Minecraft assets, publishes to
220
+ npm, and opens a GitHub release.
221
+
222
+ **One-time setup — trusted publishing.** No npm token is stored anywhere. The
223
+ workflow authenticates with a GitHub OIDC token that npm exchanges for a
224
+ short-lived registry token, and provenance is attached automatically because
225
+ both the repository and the package are public:
226
+
227
+ ```sh
228
+ npm login
229
+ npm trust github dsh-villager-hmm --file release.yml --repo nagatoquin33/dsh-villager-hmm -y
230
+ npm trust list dsh-villager-hmm # verify
231
+ ```
232
+
233
+ If `npm trust` refuses because the package does not exist yet, publish once by
234
+ hand (`npm publish`) and run the command above afterwards — every later release
235
+ goes through CI.
236
+
237
+ Prefer a long-lived token instead? Drop the `id-token: write` permission, add
238
+ an automation token as the `NPM_TOKEN` repository secret, and uncomment the
239
+ `env:` block on the Publish step.
240
+
241
+ ## License
242
+
243
+ The code is MIT (see `LICENSE`).
244
+
245
+ No third-party assets are bundled or redistributed: `scripts/fetch-assets.mjs`
246
+ downloads them on the operator's machine, at the operator's direction, into a
247
+ local cache. Minecraft is a trademark of Mojang Studios; this project is not
248
+ affiliated with or endorsed by Mojang or Microsoft.