@linxin666/dsh-pet 0.3.4 → 0.3.5
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 +13 -10
- package/README.zh.md +13 -10
- package/contracts/voice-pack-v1.schema.json +23 -28
- package/lib/client.js +23 -15
- package/lib/client.js.map +1 -1
- package/lib/index.js +344 -461
- package/lib/types/chatter.d.ts +68 -46
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +243 -301
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +16 -17
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +26 -9
- package/lib/types/event-projection.d.ts +9 -4
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +46 -14
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +8 -3
- package/lib/types/service.d.ts +7 -11
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +27 -19
- package/lib/types/voice-pack.d.ts +6 -7
- package/lib/types/voice-pack.d.ts.map +1 -1
- package/lib/types/voice-pack.js +44 -47
- package/package.json +1 -1
- package/src/chatter.test.ts +68 -38
- package/src/chatter.ts +269 -316
- package/src/client/PetSprite.test.tsx +32 -15
- package/src/client/PetSprite.tsx +21 -24
- package/src/client/index.test.tsx +10 -1
- package/src/client/index.ts +29 -10
- package/src/event-projection.ts +57 -16
- package/src/routes.ts +9 -4
- package/src/service.ts +32 -29
- package/src/voice-pack.test.ts +48 -25
- package/src/voice-pack.ts +50 -47
package/src/voice-pack.test.ts
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
normalizePanel,
|
|
13
13
|
normalizePool,
|
|
14
14
|
normalizeVoicePack,
|
|
15
|
-
|
|
15
|
+
normalizeWhisperCategories,
|
|
16
|
+
normalizeWhisperResults,
|
|
16
17
|
PANEL_ACTIONS,
|
|
17
18
|
PANEL_LABEL_KEYS,
|
|
18
19
|
PANEL_STAT_KEYS,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
VOICE_PACK_V1,
|
|
22
23
|
WHISPER_KEYS,
|
|
23
24
|
} from './voice-pack.ts'
|
|
24
|
-
import { STATUS_SCENES, TOOL_CATEGORIES } from './chatter.ts'
|
|
25
|
+
import { STATUS_SCENES, TOOL_CATEGORIES, WHISPER_CATEGORIES, WHISPER_RESULTS } from './chatter.ts'
|
|
25
26
|
import { petPackageRoot } from './registry.ts'
|
|
26
27
|
|
|
27
28
|
function collectWarnings(pack: unknown): { pack: ReturnType<typeof normalizeVoicePack>; warnings: string[] } {
|
|
@@ -85,31 +86,40 @@ describe('normalizePool', () => {
|
|
|
85
86
|
})
|
|
86
87
|
|
|
87
88
|
it('preserves an explicit empty array (mute semantics) but not absent', () => {
|
|
88
|
-
expect(normalizePool([], '
|
|
89
|
-
expect(normalizePool(undefined, '
|
|
89
|
+
expect(normalizePool([], 'whisperCategory')).toEqual([])
|
|
90
|
+
expect(normalizePool(undefined, 'whisperCategory')).toBeUndefined()
|
|
90
91
|
})
|
|
91
92
|
})
|
|
92
93
|
|
|
93
|
-
describe('
|
|
94
|
-
it('
|
|
94
|
+
describe('normalizeWhisperCategories', () => {
|
|
95
|
+
it('keeps known categories, keeps an explicit empty pool (mute) and warns on unknown keys', () => {
|
|
95
96
|
const warnings: string[] = []
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{ keywords: ['有词'], pool: [] },
|
|
100
|
-
], m => warnings.push(m))
|
|
101
|
-
expect(rules).toEqual([{ keywords: ['测试通过'], pool: ['全绿'] }])
|
|
102
|
-
expect(warnings.length).toBeGreaterThan(0)
|
|
97
|
+
const pools = normalizeWhisperCategories({ thinking: ['自定义思考'], running: [], bogus: ['x'] }, m => warnings.push(m))
|
|
98
|
+
expect(pools).toEqual({ thinking: ['自定义思考'], running: [] })
|
|
99
|
+
expect(warnings.join('\n')).toContain('unknown whisper category')
|
|
103
100
|
})
|
|
104
101
|
|
|
105
|
-
it('
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
expect(
|
|
102
|
+
it('returns undefined for an absent or non-object section', () => {
|
|
103
|
+
expect(normalizeWhisperCategories(undefined)).toBeUndefined()
|
|
104
|
+
const warnings: string[] = []
|
|
105
|
+
expect(normalizeWhisperCategories(['x'], m => warnings.push(m))).toBeUndefined()
|
|
106
|
+
expect(warnings.join('\n')).toContain('must be an object')
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('normalizeWhisperResults', () => {
|
|
111
|
+
it('keeps known outcomes, keeps an explicit empty pool (mute) and warns on unknown keys', () => {
|
|
112
|
+
const warnings: string[] = []
|
|
113
|
+
const pools = normalizeWhisperResults({ pass: ['自定义全绿'], done: [], bogus: ['x'] }, m => warnings.push(m))
|
|
114
|
+
expect(pools).toEqual({ pass: ['自定义全绿'], done: [] })
|
|
115
|
+
expect(warnings.join('\n')).toContain('unknown whisper result')
|
|
109
116
|
})
|
|
110
117
|
|
|
111
|
-
it('returns
|
|
112
|
-
expect(
|
|
118
|
+
it('returns undefined for an absent or non-object section', () => {
|
|
119
|
+
expect(normalizeWhisperResults(undefined)).toBeUndefined()
|
|
120
|
+
const warnings: string[] = []
|
|
121
|
+
expect(normalizeWhisperResults('x', m => warnings.push(m))).toBeUndefined()
|
|
122
|
+
expect(warnings.join('\n')).toContain('must be an object')
|
|
113
123
|
})
|
|
114
124
|
})
|
|
115
125
|
|
|
@@ -153,12 +163,22 @@ describe('mergeVoicePacks', () => {
|
|
|
153
163
|
expect(merged?.panel?.labels).toEqual({ feed: '宠物投喂', hide: '宠物藏' })
|
|
154
164
|
})
|
|
155
165
|
|
|
156
|
-
it('lets the pet pack replace whisper
|
|
157
|
-
const global = normalizeVoicePack({ whispers: {
|
|
158
|
-
const pet = normalizeVoicePack({ whispers: {
|
|
166
|
+
it('lets the pet pack replace whisper keys while the global stays', () => {
|
|
167
|
+
const global = normalizeVoicePack({ whispers: { categories: { thinking: ['全局思考'] }, results: { pass: ['全局全绿'] } } })
|
|
168
|
+
const pet = normalizeVoicePack({ whispers: { categories: { thinking: ['宠物思考'] } } })
|
|
159
169
|
const merged = mergeVoicePacks(global, pet)
|
|
160
|
-
expect(merged?.overrides.whispers?.
|
|
161
|
-
expect(merged?.overrides.whispers?.
|
|
170
|
+
expect(merged?.overrides.whispers?.categories?.thinking).toEqual(['宠物思考'])
|
|
171
|
+
expect(merged?.overrides.whispers?.results?.pass).toEqual(['全局全绿'])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('warns and ignores the legacy generic / rules whisper fields', () => {
|
|
175
|
+
const { pack, warnings } = collectWarnings({
|
|
176
|
+
whispers: { generic: ['老环境池'], rules: [{ keywords: ['测试'], pool: ['老全绿'] }], categories: { thinking: ['新思考'] } },
|
|
177
|
+
})
|
|
178
|
+
expect(pack?.overrides.whispers?.categories?.thinking).toEqual(['新思考'])
|
|
179
|
+
expect(pack?.overrides.whispers?.categories?.generic).toBeUndefined()
|
|
180
|
+
expect(warnings.join('\n')).toContain('generic')
|
|
181
|
+
expect(warnings.join('\n')).toContain('rules')
|
|
162
182
|
})
|
|
163
183
|
|
|
164
184
|
it('returns undefined when every layer is empty', () => {
|
|
@@ -200,7 +220,10 @@ describe('voice-pack schema file drift lock', () => {
|
|
|
200
220
|
const props = schema.properties
|
|
201
221
|
expect(new Set(Object.keys(props.status?.properties ?? {}))).toEqual(new Set(STATUS_SCENES))
|
|
202
222
|
expect(new Set(Object.keys(props.tools?.properties ?? {}))).toEqual(new Set(TOOL_CATEGORIES))
|
|
203
|
-
|
|
223
|
+
const whispers = props.whispers?.properties ?? {}
|
|
224
|
+
expect(new Set(Object.keys(whispers))).toEqual(WHISPER_KEYS)
|
|
225
|
+
expect(new Set(Object.keys((whispers.categories as { properties?: Record<string, unknown> })?.properties ?? {}))).toEqual(new Set(WHISPER_CATEGORIES))
|
|
226
|
+
expect(new Set(Object.keys((whispers.results as { properties?: Record<string, unknown> })?.properties ?? {}))).toEqual(new Set(WHISPER_RESULTS))
|
|
204
227
|
const panel = props.panel?.properties ?? {}
|
|
205
228
|
const labels = (panel.labels as { properties?: Record<string, unknown> } | undefined)?.properties ?? {}
|
|
206
229
|
const stats = (panel.stats as { properties?: Record<string, unknown> } | undefined)?.properties ?? {}
|
package/src/voice-pack.ts
CHANGED
|
@@ -31,8 +31,11 @@
|
|
|
31
31
|
import {
|
|
32
32
|
STATUS_SCENES,
|
|
33
33
|
TOOL_CATEGORIES,
|
|
34
|
+
WHISPER_CATEGORIES,
|
|
35
|
+
WHISPER_RESULTS,
|
|
34
36
|
type VoicePackOverrides,
|
|
35
|
-
type
|
|
37
|
+
type WhisperCategory,
|
|
38
|
+
type WhisperResult,
|
|
36
39
|
} from './chatter.ts'
|
|
37
40
|
|
|
38
41
|
/** Schema version this module normalizes (optional field; missing = 1). */
|
|
@@ -71,9 +74,6 @@ export interface VoicePack {
|
|
|
71
74
|
/** Hard caps shared by every pool slot (mirrors the remarks discipline). */
|
|
72
75
|
export const VOICE_POOL_LINES_MAX = 64
|
|
73
76
|
export const VOICE_LINE_MAX = 160
|
|
74
|
-
export const VOICE_KEYWORDS_PER_RULE_MAX = 16
|
|
75
|
-
export const VOICE_KEYWORD_MAX = 40
|
|
76
|
-
export const VOICE_RULES_MAX = 32
|
|
77
77
|
export const VOICE_LABEL_MAX = 40
|
|
78
78
|
export const VOICE_STAT_MAX = 80
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ const PLACEHOLDER_WHITELIST: Partial<Record<PoolKind, readonly string[]>> = {
|
|
|
87
87
|
stat: ['{rank}', '{n}', '{points}'],
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
type PoolKind = 'status' | 'tools' | 'toolRemaining' | '
|
|
90
|
+
type PoolKind = 'status' | 'tools' | 'toolRemaining' | 'whisperCategory' | 'whisperResult' | 'label' | 'stat'
|
|
91
91
|
|
|
92
92
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
93
93
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
@@ -151,51 +151,48 @@ export function normalizePool(
|
|
|
151
151
|
return pool
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
/** Normalize
|
|
155
|
-
export function
|
|
154
|
+
/** Normalize whisper category pools; a key replaces that category's built-in pool (an explicit empty pool mutes it). */
|
|
155
|
+
export function normalizeWhisperCategories(
|
|
156
156
|
raw: unknown,
|
|
157
157
|
onWarning: (message: string) => void = () => {},
|
|
158
|
-
):
|
|
158
|
+
): Partial<Record<WhisperCategory, readonly string[]>> | undefined {
|
|
159
159
|
if (raw === undefined) return undefined
|
|
160
|
-
if (!
|
|
161
|
-
onWarning('whispers.
|
|
160
|
+
if (!isRecord(raw)) {
|
|
161
|
+
onWarning('whispers.categories must be an object')
|
|
162
162
|
return undefined
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (const item of raw.slice(0, VOICE_RULES_MAX)) {
|
|
169
|
-
if (!isRecord(item)) {
|
|
170
|
-
onWarning('whisper rule must be an object')
|
|
164
|
+
const pools: Partial<Record<WhisperCategory, readonly string[]>> = {}
|
|
165
|
+
for (const key of Object.keys(raw)) {
|
|
166
|
+
if (!(WHISPER_CATEGORIES as readonly string[]).includes(key)) {
|
|
167
|
+
onWarning('unknown whisper category ' + key + ' ignored')
|
|
171
168
|
continue
|
|
172
169
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
onWarning('whisper rule dropped (needs keywords and a non-empty pool)')
|
|
170
|
+
const pool = normalizePool(raw[key], 'whisperCategory', onWarning)
|
|
171
|
+
if (pool !== undefined) pools[key as WhisperCategory] = pool
|
|
172
|
+
}
|
|
173
|
+
return Object.keys(pools).length > 0 ? pools : undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Normalize whisper outcome pools; a key replaces that outcome's built-in pool (an explicit empty pool mutes it). */
|
|
177
|
+
export function normalizeWhisperResults(
|
|
178
|
+
raw: unknown,
|
|
179
|
+
onWarning: (message: string) => void = () => {},
|
|
180
|
+
): Partial<Record<WhisperResult, readonly string[]>> | undefined {
|
|
181
|
+
if (raw === undefined) return undefined
|
|
182
|
+
if (!isRecord(raw)) {
|
|
183
|
+
onWarning('whispers.results must be an object')
|
|
184
|
+
return undefined
|
|
185
|
+
}
|
|
186
|
+
const pools: Partial<Record<WhisperResult, readonly string[]>> = {}
|
|
187
|
+
for (const key of Object.keys(raw)) {
|
|
188
|
+
if (!(WHISPER_RESULTS as readonly string[]).includes(key)) {
|
|
189
|
+
onWarning('unknown whisper result ' + key + ' ignored')
|
|
194
190
|
continue
|
|
195
191
|
}
|
|
196
|
-
|
|
192
|
+
const pool = normalizePool(raw[key], 'whisperResult', onWarning)
|
|
193
|
+
if (pool !== undefined) pools[key as WhisperResult] = pool
|
|
197
194
|
}
|
|
198
|
-
return
|
|
195
|
+
return Object.keys(pools).length > 0 ? pools : undefined
|
|
199
196
|
}
|
|
200
197
|
|
|
201
198
|
/** Normalize the panel block (labels / stats / actions; warn-and-drop). */
|
|
@@ -271,7 +268,7 @@ export function normalizePanel(
|
|
|
271
268
|
export const VOICE_PACK_KEYS = new Set(['$schema', 'voicePackVersion', 'status', 'tools', 'toolRemaining', 'whispers', 'panel'])
|
|
272
269
|
|
|
273
270
|
/** Allowed whisper-section fields (drift-locked in tests). */
|
|
274
|
-
export const WHISPER_KEYS = new Set(['
|
|
271
|
+
export const WHISPER_KEYS = new Set(['categories', 'results'])
|
|
275
272
|
|
|
276
273
|
/**
|
|
277
274
|
* Normalize one raw voice.json document into a VoicePack, or undefined when
|
|
@@ -340,14 +337,20 @@ export function normalizeVoicePack(
|
|
|
340
337
|
onWarning('whispers must be an object')
|
|
341
338
|
} else {
|
|
342
339
|
for (const key of Object.keys(whispersRaw)) {
|
|
343
|
-
if (
|
|
340
|
+
if (key === 'generic' || key === 'rules') {
|
|
341
|
+
// pet M6: keyword and ambient whisper channels are gone; legacy
|
|
342
|
+
// fields are ignored with a warning, never mapped to the new shape.
|
|
343
|
+
onWarning('whispers.' + key + ' is no longer supported and was ignored')
|
|
344
|
+
} else if (!WHISPER_KEYS.has(key)) {
|
|
345
|
+
onWarning('unknown whispers field ' + key + ' ignored')
|
|
346
|
+
}
|
|
344
347
|
}
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
+
const categories = normalizeWhisperCategories(whispersRaw.categories, onWarning)
|
|
349
|
+
const results = normalizeWhisperResults(whispersRaw.results, onWarning)
|
|
350
|
+
if (categories !== undefined || results !== undefined) {
|
|
348
351
|
overrides.whispers = {
|
|
349
|
-
...(
|
|
350
|
-
...(
|
|
352
|
+
...(categories === undefined ? {} : { categories }),
|
|
353
|
+
...(results === undefined ? {} : { results }),
|
|
351
354
|
}
|
|
352
355
|
}
|
|
353
356
|
}
|