@linxin666/dsh-pet 0.1.19 → 0.2.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/README.i18n.yaml +2 -2
- package/README.md +19 -12
- package/README.zh.md +19 -12
- package/assets/whale/pet.json +9 -0
- package/lib/client.js +202 -38
- package/lib/client.js.map +1 -1
- package/lib/index.js +897 -42
- package/lib/invariant.js +1 -1
- package/lib/{state-Diiq8vjk.js → state-Ch3f4luZ.js} +51 -23
- package/lib/types/affinity.d.ts +22 -18
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +32 -6
- package/lib/types/chatter.d.ts +111 -0
- package/lib/types/chatter.d.ts.map +1 -0
- package/lib/types/chatter.js +666 -0
- package/lib/types/client/PetDockEntry.d.ts +2 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -2
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +2 -2
- package/lib/types/client/PetSprite.d.ts +2 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +100 -25
- package/lib/types/client/PluginSettingsCard.d.ts +7 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +5 -3
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +31 -6
- package/lib/types/client/locales.d.ts +82 -80
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/sequences.d.ts +10 -0
- package/lib/types/client/sequences.d.ts.map +1 -0
- package/lib/types/client/sequences.js +20 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/spritesheet.d.ts +1 -1
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +3 -12
- package/lib/types/event-projection.d.ts +14 -1
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +38 -17
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +15 -13
- package/lib/types/invariant.js +2 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +18 -10
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +6 -4
- package/lib/types/registry.d.ts +7 -1
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +37 -1
- package/lib/types/remarks.d.ts +2 -0
- package/lib/types/remarks.d.ts.map +1 -1
- package/lib/types/remarks.js +5 -0
- package/lib/types/routes.js +2 -2
- package/lib/types/service.d.ts +16 -6
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -8
- package/lib/types/state.d.ts +7 -4
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +20 -20
- package/package.json +9 -9
- package/src/affinity.test.ts +4 -2
- package/src/affinity.ts +37 -6
- package/src/chatter.test.ts +154 -0
- package/src/chatter.ts +697 -0
- package/src/client/PetDockEntry.tsx +3 -0
- package/src/client/PetSprite.test.tsx +246 -4
- package/src/client/PetSprite.tsx +132 -36
- package/src/client/PluginSettingsCard.tsx +10 -0
- package/src/client/index.ts +26 -3
- package/src/client/locales.ts +2 -0
- package/src/client/pet-css.test.ts +42 -0
- package/src/client/pet.module.css +173 -22
- package/src/client/sequences.test.ts +33 -0
- package/src/client/sequences.ts +33 -0
- package/src/client/spritesheet.ts +3 -13
- package/src/event-projection.ts +49 -16
- package/src/index.ts +4 -1
- package/src/ledger.test.ts +42 -1
- package/src/ledger.ts +15 -7
- package/src/mount-once.ts +48 -0
- package/src/persist.test.ts +18 -1
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +50 -3
- package/src/registry.ts +47 -3
- package/src/remarks.ts +6 -0
- package/src/service.ts +42 -9
- package/src/state.test.ts +13 -0
- package/src/state.ts +24 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "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",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
61
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.
|
|
62
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.
|
|
66
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
67
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.
|
|
68
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.
|
|
61
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
|
|
62
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.7",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
|
|
66
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
|
|
67
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.7",
|
|
68
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
69
69
|
"@testing-library/dom": "^10.4.1",
|
|
70
70
|
"@testing-library/react": "^16.3.2",
|
|
71
71
|
"@types/node": "^22.20.0",
|
package/src/affinity.test.ts
CHANGED
|
@@ -26,8 +26,9 @@ describe('applyInteraction', () => {
|
|
|
26
26
|
const second = applyInteraction(first.affinity, 'pet', now + defaultAffinityConfig.petCooldownMs - 1)
|
|
27
27
|
expect(second.accepted).toBe(false)
|
|
28
28
|
expect(second.delta).toBe(0)
|
|
29
|
-
expect(second.affinity).toBe(first.affinity)
|
|
29
|
+
expect(second.affinity).not.toBe(first.affinity)
|
|
30
30
|
expect(second.affinity.pets).toBe(1)
|
|
31
|
+
expect(second.affinity.petRejects).toBe(1)
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
it('accepts a pet again after the cooldown elapsed', () => {
|
|
@@ -44,8 +45,9 @@ describe('applyInteraction', () => {
|
|
|
44
45
|
const second = applyInteraction(first.affinity, 'feed', now + defaultAffinityConfig.feedCooldownMs - 1)
|
|
45
46
|
expect(second.accepted).toBe(false)
|
|
46
47
|
expect(second.delta).toBe(0)
|
|
47
|
-
expect(second.affinity).toBe(first.affinity)
|
|
48
|
+
expect(second.affinity).not.toBe(first.affinity)
|
|
48
49
|
expect(second.affinity.feeds).toBe(1)
|
|
50
|
+
expect(second.affinity.feedRejects).toBe(1)
|
|
49
51
|
})
|
|
50
52
|
|
|
51
53
|
it('clamps points at AFFINITY_MAX', () => {
|
package/src/affinity.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module @linxin666/dsh-pet/affinity
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { builtinRemark } from './remarks.ts'
|
|
11
|
+
import { BUILTIN_REMARKS, builtinRemark } from './remarks.ts'
|
|
12
12
|
|
|
13
13
|
/** One interaction the user can perform on the pet. */
|
|
14
14
|
export type PetInteraction = 'pet' | 'feed'
|
|
@@ -25,6 +25,10 @@ export interface AffinityState {
|
|
|
25
25
|
pets: number
|
|
26
26
|
/** Total feed count (lifetime). */
|
|
27
27
|
feeds: number
|
|
28
|
+
/** Total pet attempts rejected by cooldown (lifetime). */
|
|
29
|
+
petRejects: number
|
|
30
|
+
/** Total feed attempts rejected by cooldown (lifetime). */
|
|
31
|
+
feedRejects: number
|
|
28
32
|
/** Total completed turns witnessed (lifetime). */
|
|
29
33
|
turns: number
|
|
30
34
|
}
|
|
@@ -75,7 +79,16 @@ export const defaultAffinityConfig: AffinityConfig = {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export function emptyAffinity(): AffinityState {
|
|
78
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
points: 0,
|
|
84
|
+
lastPetAt: 0,
|
|
85
|
+
lastFeedAt: 0,
|
|
86
|
+
pets: 0,
|
|
87
|
+
feeds: 0,
|
|
88
|
+
petRejects: 0,
|
|
89
|
+
feedRejects: 0,
|
|
90
|
+
turns: 0,
|
|
91
|
+
}
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
/** Outcome of one interaction. */
|
|
@@ -136,6 +149,12 @@ function clamp(points: number): number {
|
|
|
136
149
|
return Math.min(AFFINITY_MAX, Math.max(0, points))
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
/** Built-in reaction selected deterministically from a persisted counter. */
|
|
153
|
+
function countedRemark(kind: 'pet' | 'petCooldown' | 'feed' | 'feedCooldown', count: number): string {
|
|
154
|
+
const pool = BUILTIN_REMARKS[kind]
|
|
155
|
+
return pool[Math.max(0, Math.floor(count)) % pool.length] ?? builtinRemark(kind)
|
|
156
|
+
}
|
|
157
|
+
|
|
139
158
|
/**
|
|
140
159
|
* Apply one interaction to a copy of the state (immutable style: returns a
|
|
141
160
|
* new object; the caller replaces the persisted state). Cooldowns only
|
|
@@ -151,7 +170,13 @@ export function applyInteraction(
|
|
|
151
170
|
const next = { ...state }
|
|
152
171
|
if (kind === 'pet') {
|
|
153
172
|
if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) {
|
|
154
|
-
|
|
173
|
+
next.petRejects += 1
|
|
174
|
+
return {
|
|
175
|
+
affinity: next,
|
|
176
|
+
delta: 0,
|
|
177
|
+
reaction: countedRemark('petCooldown', state.petRejects),
|
|
178
|
+
accepted: false,
|
|
179
|
+
}
|
|
155
180
|
}
|
|
156
181
|
next.lastPetAt = nowMs
|
|
157
182
|
next.pets += 1
|
|
@@ -159,13 +184,19 @@ export function applyInteraction(
|
|
|
159
184
|
return {
|
|
160
185
|
affinity: next,
|
|
161
186
|
delta: config.petReward,
|
|
162
|
-
reaction:
|
|
187
|
+
reaction: countedRemark('pet', state.pets),
|
|
163
188
|
accepted: true,
|
|
164
189
|
}
|
|
165
190
|
}
|
|
166
191
|
if (kind === 'feed') {
|
|
167
192
|
if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) {
|
|
168
|
-
|
|
193
|
+
next.feedRejects += 1
|
|
194
|
+
return {
|
|
195
|
+
affinity: next,
|
|
196
|
+
delta: 0,
|
|
197
|
+
reaction: countedRemark('feedCooldown', state.feedRejects),
|
|
198
|
+
accepted: false,
|
|
199
|
+
}
|
|
169
200
|
}
|
|
170
201
|
next.lastFeedAt = nowMs
|
|
171
202
|
next.feeds += 1
|
|
@@ -173,7 +204,7 @@ export function applyInteraction(
|
|
|
173
204
|
return {
|
|
174
205
|
affinity: next,
|
|
175
206
|
delta: config.feedReward,
|
|
176
|
-
reaction:
|
|
207
|
+
reaction: countedRemark('feed', state.feeds),
|
|
177
208
|
accepted: true,
|
|
178
209
|
}
|
|
179
210
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chatter unit tests: the status voice (scene-stable lines, cadence rotation,
|
|
3
|
+
* per-tool families, argument hints) and the murmur engine (keyword moods,
|
|
4
|
+
* cooldown, output-volume budget). Everything is deterministic round-robin,
|
|
5
|
+
* so exact lines are asserted.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from 'vitest'
|
|
8
|
+
import {
|
|
9
|
+
STATUS_POOLS,
|
|
10
|
+
StatusVoice,
|
|
11
|
+
TOOL_POOLS,
|
|
12
|
+
toolArgHint,
|
|
13
|
+
toolCategory,
|
|
14
|
+
WHISPER_GENERIC_POOL,
|
|
15
|
+
WHISPER_RULES,
|
|
16
|
+
WhisperEngine,
|
|
17
|
+
} from './chatter.ts'
|
|
18
|
+
|
|
19
|
+
describe('StatusVoice', () => {
|
|
20
|
+
it('opens every scene with its legacy fixed line', () => {
|
|
21
|
+
// The first pool line is the wording the plugin has always shown.
|
|
22
|
+
expect(STATUS_POOLS.prepare[0]).toBe('准备开始')
|
|
23
|
+
expect(STATUS_POOLS.waiting[0]).toBe('等待模型响应')
|
|
24
|
+
expect(STATUS_POOLS.thinking[0]).toBe('正在思考')
|
|
25
|
+
expect(STATUS_POOLS.review[0]).toBe('整理回复中')
|
|
26
|
+
expect(STATUS_POOLS.toolResult[0]).toBe('处理工具结果')
|
|
27
|
+
expect(STATUS_POOLS.done[0]).toBe('完成啦')
|
|
28
|
+
expect(STATUS_POOLS.failed[0]).toBe('执行失败')
|
|
29
|
+
expect(STATUS_POOLS.toolFailed[0]).toBe('工具执行失败')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('keeps the line stable while a scene repeats within the rotation window', () => {
|
|
33
|
+
const voice = new StatusVoice()
|
|
34
|
+
// Streamed chunks re-emit the same scene many times a second: the copy
|
|
35
|
+
// must not flicker per chunk.
|
|
36
|
+
expect(voice.scene('thinking', 1000)).toBe('正在思考')
|
|
37
|
+
expect(voice.scene('thinking', 2000)).toBe('正在思考')
|
|
38
|
+
expect(voice.scene('thinking', 4999)).toBe('正在思考')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('advances the line once the scene persists past the rotation cadence', () => {
|
|
42
|
+
const voice = new StatusVoice()
|
|
43
|
+
expect(voice.scene('thinking', 1000)).toBe('正在思考')
|
|
44
|
+
expect(voice.scene('thinking', 5001)).toBe(STATUS_POOLS.thinking[1])
|
|
45
|
+
expect(voice.scene('thinking', 9002)).toBe(STATUS_POOLS.thinking[2])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('rotates round-robin when a scene is revisited after another scene', () => {
|
|
49
|
+
const voice = new StatusVoice()
|
|
50
|
+
expect(voice.scene('thinking', 0)).toBe(STATUS_POOLS.thinking[0])
|
|
51
|
+
expect(voice.scene('review', 100)).toBe(STATUS_POOLS.review[0])
|
|
52
|
+
expect(voice.scene('thinking', 200)).toBe(STATUS_POOLS.thinking[1])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('interpolates the tool name and argument hint into tool lines', () => {
|
|
56
|
+
const voice = new StatusVoice()
|
|
57
|
+
expect(voice.tool('bash', 'bash', 'npm test', 0)).toBe('正在使用 bash')
|
|
58
|
+
// Same family within the window: the pool line repeats but the CURRENT
|
|
59
|
+
// call's name/hint are interpolated, so copy never goes stale.
|
|
60
|
+
expect(voice.tool('bash', 'bash', 'pnpm build', 100)).toBe('正在使用 bash')
|
|
61
|
+
// Past the cadence the shell family rotates to a hint-carrying line.
|
|
62
|
+
const rotated = voice.tool('bash', 'bash', 'npm test', 5000)
|
|
63
|
+
expect(rotated).toBe(TOOL_POOLS.shell[1]!.replace('{hint}', 'npm test'))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('falls back to the tool name when no argument hint is available', () => {
|
|
67
|
+
const voice = new StatusVoice()
|
|
68
|
+
const line = voice.tool('grep', 'grep', undefined, 5000)
|
|
69
|
+
expect(line).not.toContain('{hint}')
|
|
70
|
+
expect(line).not.toContain('{tool}')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('interpolates the running count into the remaining-tools line', () => {
|
|
74
|
+
const voice = new StatusVoice()
|
|
75
|
+
expect(voice.toolRemaining(1, 0)).toBe('还有 1 个工具运行中')
|
|
76
|
+
expect(voice.toolRemaining(3, 5000)).toContain('3')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('toolCategory', () => {
|
|
81
|
+
it('maps the common tool vocabulary onto copy families', () => {
|
|
82
|
+
expect(toolCategory('read')).toBe('read')
|
|
83
|
+
expect(toolCategory('write')).toBe('write')
|
|
84
|
+
expect(toolCategory('edit')).toBe('edit')
|
|
85
|
+
expect(toolCategory('str_replace_editor')).toBe('edit')
|
|
86
|
+
expect(toolCategory('run_code')).toBe('shell')
|
|
87
|
+
expect(toolCategory('bash')).toBe('shell')
|
|
88
|
+
expect(toolCategory('grep')).toBe('grep')
|
|
89
|
+
expect(toolCategory('glob')).toBe('find')
|
|
90
|
+
expect(toolCategory('web_search')).toBe('webSearch')
|
|
91
|
+
expect(toolCategory('browserFetch')).toBe('webFetch')
|
|
92
|
+
expect(toolCategory('mcp__mem0__search_memories')).toBe('memory')
|
|
93
|
+
expect(toolCategory('subagent')).toBe('subagent')
|
|
94
|
+
expect(toolCategory('todo_write')).toBe('todo')
|
|
95
|
+
expect(toolCategory('some_future_tool')).toBe('generic')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('toolArgHint', () => {
|
|
100
|
+
it('extracts the shell command', () => {
|
|
101
|
+
expect(toolArgHint('bash', '{"command":"npm test"}')).toBe('npm test')
|
|
102
|
+
expect(toolArgHint('run_code', '{"code":"console.log(1)"}')).toBe('console.log(1)')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('reduces file paths to their basename', () => {
|
|
106
|
+
expect(toolArgHint('read', '{"file_path":"/repo/src/client/PetSprite.tsx"}')).toBe('PetSprite.tsx')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('extracts search patterns and web queries', () => {
|
|
110
|
+
expect(toolArgHint('grep', '{"pattern":"bubbleStack"}')).toBe('bubbleStack')
|
|
111
|
+
expect(toolArgHint('web_search', '{"query":"dsh 插件"}')).toBe('dsh 插件')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('stays hintless for unparseable or shapeless arguments', () => {
|
|
115
|
+
expect(toolArgHint('bash', 'not json')).toBeUndefined()
|
|
116
|
+
expect(toolArgHint('bash', '[]')).toBeUndefined()
|
|
117
|
+
expect(toolArgHint('bash', '{"timeout":1000}')).toBeUndefined()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('caps long hints so the bubble stays compact', () => {
|
|
121
|
+
const hint = toolArgHint('bash', JSON.stringify({ command: 'x'.repeat(60) }))
|
|
122
|
+
expect(hint).toBeDefined()
|
|
123
|
+
expect(hint!.length).toBeLessThanOrEqual(28)
|
|
124
|
+
expect(hint!.endsWith('...')).toBe(true)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('WhisperEngine', () => {
|
|
129
|
+
it('wakes a themed whisper from a keyword in the model output', () => {
|
|
130
|
+
const engine = new WhisperEngine()
|
|
131
|
+
expect(engine.feed('这里有个错误需要处理', 0)).toBe(WHISPER_RULES[1]!.pool[0])
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('suppresses whispers during the cooldown, then rotates the pool', () => {
|
|
135
|
+
const engine = new WhisperEngine()
|
|
136
|
+
expect(engine.feed('出现一个错误', 0)).toBe(WHISPER_RULES[1]!.pool[0])
|
|
137
|
+
expect(engine.feed('又一个错误', 1000)).toBeUndefined()
|
|
138
|
+
expect(engine.feed('还有一个错误', 9000)).toBe(WHISPER_RULES[1]!.pool[1])
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('earns an ambient whisper from output volume alone', () => {
|
|
142
|
+
const engine = new WhisperEngine(0, 10)
|
|
143
|
+
expect(engine.feed('aaaaaaaa', 0)).toBeUndefined()
|
|
144
|
+
expect(engine.feed('bbbb', 1)).toBe(WHISPER_GENERIC_POOL[0])
|
|
145
|
+
// The counter resets after speaking: another budget must accumulate.
|
|
146
|
+
expect(engine.feed('cccc', 2)).toBeUndefined()
|
|
147
|
+
expect(engine.feed('dddddddddd', 3)).toBe(WHISPER_GENERIC_POOL[1])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('ignores empty chunks', () => {
|
|
151
|
+
const engine = new WhisperEngine(0, 0)
|
|
152
|
+
expect(engine.feed('', 0)).toBeUndefined()
|
|
153
|
+
})
|
|
154
|
+
})
|