@linxin666/dsh-pet 0.3.19 → 0.3.21

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 (57) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +1 -1
  3. package/README.zh.md +1 -1
  4. package/lib/client.js +191 -53
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +174 -61
  7. package/lib/types/client/gameplay-hud.d.ts +15 -0
  8. package/lib/types/client/gameplay-hud.d.ts.map +1 -1
  9. package/lib/types/client/gameplay-hud.js +48 -6
  10. package/lib/types/client/index.d.ts.map +1 -1
  11. package/lib/types/client/index.js +21 -13
  12. package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -1
  13. package/lib/types/client/renderers/Frames2dVisualMount.js +5 -0
  14. package/lib/types/client/renderers/frames2d.d.ts +5 -4
  15. package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
  16. package/lib/types/client/renderers/frames2d.js +96 -39
  17. package/lib/types/client/work-tick-gate.d.ts +40 -0
  18. package/lib/types/client/work-tick-gate.d.ts.map +1 -0
  19. package/lib/types/client/work-tick-gate.js +49 -0
  20. package/lib/types/event-projection.d.ts +14 -0
  21. package/lib/types/event-projection.d.ts.map +1 -1
  22. package/lib/types/event-projection.js +32 -18
  23. package/lib/types/gameplay.d.ts +4 -0
  24. package/lib/types/gameplay.d.ts.map +1 -1
  25. package/lib/types/gameplay.js +11 -2
  26. package/lib/types/ledger.d.ts +8 -0
  27. package/lib/types/ledger.d.ts.map +1 -1
  28. package/lib/types/ledger.js +25 -0
  29. package/lib/types/persist.d.ts +6 -0
  30. package/lib/types/persist.d.ts.map +1 -1
  31. package/lib/types/persist.js +25 -1
  32. package/lib/types/routes.d.ts.map +1 -1
  33. package/lib/types/routes.js +6 -0
  34. package/lib/types/service.d.ts +24 -0
  35. package/lib/types/service.d.ts.map +1 -1
  36. package/lib/types/service.js +43 -1
  37. package/package.json +14 -14
  38. package/src/client/PetDockEntry.test.tsx +1 -0
  39. package/src/client/gameplay-hud.test.tsx +165 -2
  40. package/src/client/gameplay-hud.tsx +62 -6
  41. package/src/client/index.ts +23 -13
  42. package/src/client/pet.module.css +1 -1
  43. package/src/client/renderers/Frames2dVisualMount.test.tsx +73 -0
  44. package/src/client/renderers/Frames2dVisualMount.tsx +4 -0
  45. package/src/client/renderers/frames2d.test.ts +129 -7
  46. package/src/client/renderers/frames2d.ts +94 -36
  47. package/src/client/work-tick-gate.test.ts +53 -0
  48. package/src/client/work-tick-gate.ts +63 -0
  49. package/src/event-projection.ts +37 -18
  50. package/src/gameplay.test.ts +36 -0
  51. package/src/gameplay.ts +14 -2
  52. package/src/ledger.test.ts +19 -0
  53. package/src/ledger.ts +24 -0
  54. package/src/persist.test.ts +13 -0
  55. package/src/persist.ts +29 -1
  56. package/src/routes.ts +5 -0
  57. package/src/service.ts +49 -0
@@ -12,10 +12,11 @@
12
12
  * Frame presentation has two modes picked once at mount by capability
13
13
  * probing:
14
14
  * - Canvas bitmap buffer (default where createImageBitmap/fetch/2D context
15
- * exist): every frame is decoded exactly once into an ImageBitmap during
16
- * the warm pass and drawn onto one <canvas> - steady-state playback issues
17
- * zero DOM mutations and zero re-decodes (measured hotspot: swapping
18
- * <img>.src per frame drove image decode + invalidation every tick).
15
+ * exist): frames are decoded once into an ImageBitmap on demand, with a
16
+ * bounded look-ahead window over the playing track, and drawn onto one
17
+ * <canvas> - steady-state playback issues zero DOM mutations and zero
18
+ * re-decodes (measured hotspot: swapping <img>.src per frame drove image
19
+ * decode + invalidation every tick).
19
20
  * - Classic <img> fallback (jsdom/tests or missing APIs): identical to the
20
21
  * historical behavior - cache-warm Image elements plus guarded src swaps,
21
22
  * so environments without modern decoding keep working unchanged.
@@ -159,39 +160,98 @@ export const frames2dRenderer = {
159
160
  // the last painted frame instead of breaking playback).
160
161
  const decoding = new Map();
161
162
  const decodedAll = [];
162
- const loadFrame = (url) => {
163
+ // All frame fetches funnel through a small pool. Full-library warm passes
164
+ // on large pets fire 1100+ requests at once, which trips the browser's
165
+ // in-flight request limit (net::ERR_INSUFFICIENT_RESOURCES) and fails
166
+ // whole batches of frames while starving the rest of the page. Playback
167
+ // demand jumps ahead of the warm backlog.
168
+ const FRAME_POOL_LIMIT = 8;
169
+ const frameQueue = [];
170
+ let activeFrames = 0;
171
+ const decodeFrame = async (url) => {
172
+ try {
173
+ const response = await fetch(url);
174
+ if (!response.ok)
175
+ throw new Error('http ' + response.status);
176
+ const bitmap = await createImageBitmap(await response.blob());
177
+ return { source: bitmap, width: bitmap.width, height: bitmap.height };
178
+ }
179
+ catch {
180
+ // Fail-open: classic Image decode keeps non-modern runtimes alive.
181
+ return await new Promise((resolve) => {
182
+ try {
183
+ const pre = new Image();
184
+ pre.onload = () => {
185
+ resolve(pre.naturalWidth > 0 ? { source: pre, width: pre.naturalWidth, height: pre.naturalHeight } : undefined);
186
+ };
187
+ pre.onerror = () => resolve(undefined);
188
+ pre.src = url;
189
+ }
190
+ catch {
191
+ resolve(undefined);
192
+ }
193
+ });
194
+ }
195
+ };
196
+ const pumpFrames = () => {
197
+ while (activeFrames < FRAME_POOL_LIMIT && frameQueue.length > 0) {
198
+ const queued = frameQueue.shift();
199
+ activeFrames += 1;
200
+ queued.release();
201
+ }
202
+ };
203
+ const loadFrame = (url, jump = false) => {
204
+ // Jump first: warm-enqueued frames already carry their memo, so a
205
+ // playback demand must reorder the unstarted entry before the cache
206
+ // lookup short-circuits.
207
+ if (jump) {
208
+ const index = frameQueue.findIndex((queued) => queued.url === url);
209
+ if (index > 0)
210
+ frameQueue.unshift(frameQueue.splice(index, 1)[0]);
211
+ }
163
212
  const cached = decoding.get(url);
164
213
  if (cached !== undefined)
165
214
  return cached;
166
- const job = (async () => {
167
- try {
168
- const response = await fetch(url);
169
- if (!response.ok)
170
- throw new Error('http ' + response.status);
171
- const bitmap = await createImageBitmap(await response.blob());
172
- return { source: bitmap, width: bitmap.width, height: bitmap.height };
173
- }
174
- catch {
175
- // Fail-open: classic Image decode keeps non-modern runtimes alive.
176
- return await new Promise((resolve) => {
177
- try {
178
- const pre = new Image();
179
- pre.onload = () => {
180
- resolve(pre.naturalWidth > 0 ? { source: pre, width: pre.naturalWidth, height: pre.naturalHeight } : undefined);
181
- };
182
- pre.onerror = () => resolve(undefined);
183
- pre.src = url;
184
- }
185
- catch {
186
- resolve(undefined);
187
- }
188
- });
189
- }
190
- })();
215
+ let release;
216
+ const gate = new Promise((resolve) => { release = resolve; });
217
+ const job = gate.then(() => (disposed ? undefined : decodeFrame(url)));
218
+ job.then((frame) => { if (frame === undefined)
219
+ decoding.delete(url); }, () => decoding.delete(url));
220
+ void job.finally(() => {
221
+ activeFrames -= 1;
222
+ pumpFrames();
223
+ });
191
224
  decoding.set(url, job);
192
225
  decodedAll.push(job.then(() => undefined, () => undefined));
226
+ const entry = { url, release };
227
+ if (jump)
228
+ frameQueue.unshift(entry);
229
+ else
230
+ frameQueue.push(entry);
231
+ pumpFrames();
193
232
  return job;
194
233
  };
234
+ /**
235
+ * Bounded look-ahead window: decode only the frames playback is about to
236
+ * need. The historical warm pass decoded every frame of every track up
237
+ * front - a shipped pet carries ~1.1k 512x683 frames, so that pass pulls
238
+ * tens of megabytes and retains every decoded bitmap for the life of the
239
+ * page. Prefetching the playing track's next frames keeps loops and phase
240
+ * switches warm while unplayed tracks (and every unselected skin's
241
+ * frames) stay on demand, where playback jumps the queue anyway.
242
+ */
243
+ const PREFETCH_AHEAD = 12;
244
+ const prefetchAhead = (trackId, index) => {
245
+ const def = config.tracks[trackId];
246
+ if (def === undefined)
247
+ return;
248
+ const end = Math.min(def.frames.length, index + 1 + PREFETCH_AHEAD);
249
+ for (let ahead = index + 1; ahead < end; ahead += 1) {
250
+ const url = def.frames[ahead];
251
+ if (url !== undefined)
252
+ void loadFrame(url);
253
+ }
254
+ };
195
255
  let disposed = false;
196
256
  let timer;
197
257
  let watchdog;
@@ -217,7 +277,7 @@ export const frames2dRenderer = {
217
277
  if (context2d === null || canvas === null)
218
278
  return;
219
279
  const myToken = ++drawToken;
220
- void loadFrame(url).then((frame) => {
280
+ void loadFrame(url, true).then((frame) => {
221
281
  if (disposed || frame === undefined || myToken !== drawToken)
222
282
  return;
223
283
  if (lastDrawnUrl === url)
@@ -239,6 +299,7 @@ export const frames2dRenderer = {
239
299
  const url = def?.frames[index];
240
300
  if (url === undefined)
241
301
  return;
302
+ prefetchAhead(trackId, index);
242
303
  if (img !== null) {
243
304
  if (img.getAttribute('src') !== url)
244
305
  img.src = url;
@@ -320,13 +381,6 @@ export const frames2dRenderer = {
320
381
  tick();
321
382
  }, WATCHDOG_MS);
322
383
  }
323
- // Warm pass: decode every frame up front (tiny same-origin webp files)
324
- // so loops and phase switches never wait on a first decode - same intent
325
- // as the historical Image-cache warm loop, now feeding the decode cache.
326
- for (const warmTrack of Object.values(config.tracks)) {
327
- for (const warmUrl of warmTrack.frames)
328
- void loadFrame(warmUrl);
329
- }
330
384
  play(track);
331
385
  let disposedOnce = false;
332
386
  const dispose = () => {
@@ -340,7 +394,10 @@ export const frames2dRenderer = {
340
394
  if (watchdog !== undefined)
341
395
  clearInterval(watchdog);
342
396
  // Release decoded bitmaps after pending decodes settle; close() is
343
- // browser-only, so guard it for exotic hosts.
397
+ // browser-only, so guard it for exotic hosts. Queued-but-unstarted
398
+ // frames release immediately as no-ops so the settle barrier drains.
399
+ for (const queued of frameQueue.splice(0))
400
+ queued.release();
344
401
  void Promise.allSettled(decodedAll).then(() => {
345
402
  for (const job of decoding.values()) {
346
403
  void job.then((frame) => {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Page-wide work-tick gate for the pet's work mode. Hot reloads can leave
3
+ * several GameplayHud instances alive, each running its own work interval;
4
+ * without a shared gate every interval would call workTick and each stale call
5
+ * re-rolls, re-grants treats and re-plays the success/fail track, so the
6
+ * outcome appears to play several times per window. The gate accepts the first
7
+ * adjudication of a window and silently suppresses the duplicates that follow.
8
+ *
9
+ * The window is the active pet's own `gameplay.work.tickMs`, not a constant: a
10
+ * pet configured with a shorter cadence must actually adjudicate that often,
11
+ * while a fixed window would downgrade it without saying so (#1494).
12
+ * @module @linxin666/dsh-pet/client/work-tick-gate
13
+ */
14
+ /** Window used when the active pet declares no work cadence. */
15
+ export declare const DEFAULT_WORK_TICK_MS = 10000;
16
+ /**
17
+ * The gate window for one pet: its configured cadence, clamped to the manifest's
18
+ * own bounds so a malformed registry entry cannot disable or flood the gate.
19
+ * @param tickMs - the active definition's `gameplay.work.tickMs`, when it has one.
20
+ * @returns the window in milliseconds.
21
+ */
22
+ export declare function workTickWindowMs(tickMs: number | undefined): number;
23
+ /** The shared work-tick gate. */
24
+ export interface WorkTickGate {
25
+ /**
26
+ * Admit one adjudication for the current window.
27
+ * @param tickMs - the active pet's configured work cadence.
28
+ * @returns true when this call may adjudicate, false for a duplicate.
29
+ */
30
+ allow: (tickMs: number | undefined) => boolean;
31
+ /** Forget the last adjudication (used when work mode is entered). */
32
+ reset: () => void;
33
+ }
34
+ /**
35
+ * Create a gate.
36
+ * @param now - the clock, injectable so tests control the window.
37
+ * @returns the gate over that clock.
38
+ */
39
+ export declare function createWorkTickGate(now?: () => number): WorkTickGate;
40
+ //# sourceMappingURL=work-tick-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-tick-gate.d.ts","sourceRoot":"","sources":["../../../src/client/work-tick-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,QAAS,CAAA;AAM1C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE;AAED,iCAAiC;AACjC,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAA;IAC9C,qEAAqE;IACrE,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,MAAiB,GAAG,YAAY,CAa7E"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Page-wide work-tick gate for the pet's work mode. Hot reloads can leave
3
+ * several GameplayHud instances alive, each running its own work interval;
4
+ * without a shared gate every interval would call workTick and each stale call
5
+ * re-rolls, re-grants treats and re-plays the success/fail track, so the
6
+ * outcome appears to play several times per window. The gate accepts the first
7
+ * adjudication of a window and silently suppresses the duplicates that follow.
8
+ *
9
+ * The window is the active pet's own `gameplay.work.tickMs`, not a constant: a
10
+ * pet configured with a shorter cadence must actually adjudicate that often,
11
+ * while a fixed window would downgrade it without saying so (#1494).
12
+ * @module @linxin666/dsh-pet/client/work-tick-gate
13
+ */
14
+ /** Window used when the active pet declares no work cadence. */
15
+ export const DEFAULT_WORK_TICK_MS = 10_000;
16
+ /** Manifest bounds for `gameplay.work.tickMs` (src/gameplay.ts). */
17
+ const MIN_WORK_TICK_MS = 1_000;
18
+ const MAX_WORK_TICK_MS = 60_000;
19
+ /**
20
+ * The gate window for one pet: its configured cadence, clamped to the manifest's
21
+ * own bounds so a malformed registry entry cannot disable or flood the gate.
22
+ * @param tickMs - the active definition's `gameplay.work.tickMs`, when it has one.
23
+ * @returns the window in milliseconds.
24
+ */
25
+ export function workTickWindowMs(tickMs) {
26
+ if (typeof tickMs !== 'number' || !Number.isFinite(tickMs))
27
+ return DEFAULT_WORK_TICK_MS;
28
+ return Math.min(MAX_WORK_TICK_MS, Math.max(MIN_WORK_TICK_MS, tickMs));
29
+ }
30
+ /**
31
+ * Create a gate.
32
+ * @param now - the clock, injectable so tests control the window.
33
+ * @returns the gate over that clock.
34
+ */
35
+ export function createWorkTickGate(now = Date.now) {
36
+ let lastAdjudicatedAt = 0;
37
+ return {
38
+ allow: (tickMs) => {
39
+ const at = now();
40
+ if (at - lastAdjudicatedAt < workTickWindowMs(tickMs))
41
+ return false;
42
+ lastAdjudicatedAt = at;
43
+ return true;
44
+ },
45
+ reset: () => {
46
+ lastAdjudicatedAt = 0;
47
+ },
48
+ };
49
+ }
@@ -11,8 +11,15 @@
11
11
  * tool/result and turn/end — so the pet's inner voice always roughly knows
12
12
  * what is going on and never mis-fires on output text. The wall clock is
13
13
  * injected by the caller, keeping every projection reproducible.
14
+ *
15
+ * Since the 0.1.5-alpha.2 cohort the stream itself is no longer durable
16
+ * vocabulary: per-chunk phase input arrives through the process-local
17
+ * `agent/assistant-stream` publication ({@link projectAssistantStreamFrame}),
18
+ * while the durable log settles one `assistant/message` (or `assistant/attempt`)
19
+ * per attempt.
14
20
  * @module @linxin666/dsh-pet/event-projection
15
21
  */
22
+ import type { AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
16
23
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
17
24
  import type { PetStateInput } from './state.ts';
18
25
  import { StatusVoice, WhisperEngine, type VoicePoolsProvider } from './chatter.ts';
@@ -56,4 +63,11 @@ export declare function isActivityPhase(phase: string): phase is PetStateInput['
56
63
  * @param nowMs - injected wall clock for copy rotation and whisper pacing.
57
64
  */
58
65
  export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
66
+ /**
67
+ * Project one live `agent/assistant-stream` publication into the pet's visual
68
+ * phases. Chunk frames are the alpha.2 replacement for the retired durable
69
+ * `assistant/chunk` event: a reasoning delta keeps the pet thinking, a text
70
+ * delta moves it to review; start, end, and non-delta chunks change nothing.
71
+ */
72
+ export declare function projectAssistantStreamFrame(frame: AssistantStreamFrame, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
59
73
  //# sourceMappingURL=event-projection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,WAAW,EAGX,aAAa,EAGb,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,+EAA+E;IAC/E,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CASpF;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAmHnC"}
1
+ {"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,WAAW,EAGX,aAAa,EAGb,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,+EAA+E;IAC/E,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CASpF;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAiGnC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAkBnC"}
@@ -11,6 +11,12 @@
11
11
  * tool/result and turn/end — so the pet's inner voice always roughly knows
12
12
  * what is going on and never mis-fires on output text. The wall clock is
13
13
  * injected by the caller, keeping every projection reproducible.
14
+ *
15
+ * Since the 0.1.5-alpha.2 cohort the stream itself is no longer durable
16
+ * vocabulary: per-chunk phase input arrives through the process-local
17
+ * `agent/assistant-stream` publication ({@link projectAssistantStreamFrame}),
18
+ * while the durable log settles one `assistant/message` (or `assistant/attempt`)
19
+ * per attempt.
14
20
  * @module @linxin666/dsh-pet/event-projection
15
21
  */
16
22
  import { StatusVoice, toolArgHint, toolCategory, WhisperEngine, whisperCategoryOf, looksLikeTestTool, } from "./chatter.js";
@@ -55,24 +61,6 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
55
61
  runtime.activeTools.clear();
56
62
  runtime.stepHadFailure = false;
57
63
  return { input: { phase: 'waiting', line: runtime.voice.scene('waiting', nowMs) } };
58
- case 'assistant/chunk': {
59
- const { chunk } = event.data;
60
- if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
61
- const whisper = runtime.whispers.feed('thinking', nowMs);
62
- return {
63
- input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
64
- ...(whisper === undefined ? {} : { whisper }),
65
- };
66
- }
67
- if (chunk.type === 'text-delta' && chunk.text.length > 0) {
68
- const whisper = runtime.whispers.feed('writing', nowMs);
69
- return {
70
- input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
71
- ...(whisper === undefined ? {} : { whisper }),
72
- };
73
- }
74
- return undefined;
75
- }
76
64
  case 'assistant/message':
77
65
  return { input: { phase: 'review', line: runtime.voice.scene('review', nowMs) } };
78
66
  case 'tool/call': {
@@ -156,3 +144,29 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
156
144
  return undefined;
157
145
  }
158
146
  }
147
+ /**
148
+ * Project one live `agent/assistant-stream` publication into the pet's visual
149
+ * phases. Chunk frames are the alpha.2 replacement for the retired durable
150
+ * `assistant/chunk` event: a reasoning delta keeps the pet thinking, a text
151
+ * delta moves it to review; start, end, and non-delta chunks change nothing.
152
+ */
153
+ export function projectAssistantStreamFrame(frame, runtime, nowMs = Date.now()) {
154
+ if (frame.type !== 'chunk')
155
+ return undefined;
156
+ const { chunk } = frame;
157
+ if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
158
+ const whisper = runtime.whispers.feed('thinking', nowMs);
159
+ return {
160
+ input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
161
+ ...(whisper === undefined ? {} : { whisper }),
162
+ };
163
+ }
164
+ if (chunk.type === 'text-delta' && chunk.text.length > 0) {
165
+ const whisper = runtime.whispers.feed('writing', nowMs);
166
+ return {
167
+ input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
168
+ ...(whisper === undefined ? {} : { whisper }),
169
+ };
170
+ }
171
+ return undefined;
172
+ }
@@ -150,6 +150,10 @@ export interface PetGameplayState {
150
150
  mode: 'work' | 'sleep' | null;
151
151
  /** Epoch ms of the last lazy settle. */
152
152
  settledAt: number;
153
+ /** Accumulated remainder ms towards the next passive income tick. */
154
+ incomeCarryMs?: number;
155
+ /** Accumulated remainder ms towards the next sleep restore tick. */
156
+ restoreCarryMs?: number;
153
157
  }
154
158
  /** Fresh state for one pet: stats at their initial (default max), no currency. */
155
159
  export declare function initialGameplayState(manifest: PetGameplayManifest, now: number): PetGameplayState;
@@ -1 +1 @@
1
- {"version":3,"file":"gameplay.d.ts","sourceRoot":"","sources":["../../src/gameplay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gameplay.d.ts","sourceRoot":"","sources":["../../src/gameplay.ts"],"names":[],"mappings":"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"}
@@ -555,7 +555,10 @@ export function settleGameplay(state, manifest, now, options) {
555
555
  }
556
556
  }
557
557
  if (manifest.passiveIncome !== undefined) {
558
- const ticks = Math.floor(elapsedMs / manifest.passiveIncome.intervalMs);
558
+ const incomeElapsed = elapsedMs + (state.incomeCarryMs ?? 0);
559
+ const interval = manifest.passiveIncome.intervalMs;
560
+ const ticks = Math.floor(incomeElapsed / interval);
561
+ state.incomeCarryMs = incomeElapsed % interval;
559
562
  if (ticks > 0) {
560
563
  const currency = manifest.passiveIncome.currency;
561
564
  state.currencies[currency] = (state.currencies[currency] ?? 0) + ticks * manifest.passiveIncome.amount;
@@ -563,13 +566,19 @@ export function settleGameplay(state, manifest, now, options) {
563
566
  }
564
567
  }
565
568
  if (state.mode === 'sleep' && manifest.sleep !== undefined) {
566
- const ticks = Math.floor(elapsedMs / manifest.sleep.restore.intervalMs);
569
+ const restoreElapsed = elapsedMs + (state.restoreCarryMs ?? 0);
570
+ const interval = manifest.sleep.restore.intervalMs;
571
+ const ticks = Math.floor(restoreElapsed / interval);
572
+ state.restoreCarryMs = restoreElapsed % interval;
567
573
  if (ticks > 0) {
568
574
  const stat = manifest.sleep.restore.stat;
569
575
  state.stats[stat] = (state.stats[stat] ?? 0) + ticks * manifest.sleep.restore.amount;
570
576
  changed = true;
571
577
  }
572
578
  }
579
+ else {
580
+ state.restoreCarryMs = 0;
581
+ }
573
582
  state.settledAt = now;
574
583
  clampGameplay(state, manifest);
575
584
  return changed;
@@ -66,6 +66,14 @@ export declare class PetLedger {
66
66
  setGameplay(petId: string, gameplay: PetGameplayState): void;
67
67
  /** Replace one pet's display name (validation stays a caller concern). */
68
68
  setPetName(petId: string, name: string): void;
69
+ /**
70
+ * Select one pet's frames2d skin; `undefined` clears the choice back to the
71
+ * pet's default look. Manifest validation stays a caller concern, exactly
72
+ * like setPetName's length check.
73
+ */
74
+ setPetSkin(petId: string, skinId: string | undefined): void;
75
+ /** The persisted skin id for one pet (undefined = the pet's default look). */
76
+ petSkin(petId: string): string | undefined;
69
77
  /**
70
78
  * Swap the reaction pools to another pet's custom remarks (called on pet
71
79
  * selection). Slots the pet does not declare fall back to voice packs or built-ins.
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7B,mFAAmF;IACnF,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,mEAAmE;IACnE,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;gBAET,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB;IAO1D,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO;IAMpB;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAItC,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAK3C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM7B,qFAAqF;IACrF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAK5D,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK7C;;;;OAIG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAc3D,8EAA8E;IAC9E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI1C;;;OAGG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,UAAU,GAAG,IAAI;IAIjE;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAapC;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IASpC,8EAA8E;IAC9E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE;IAQ5C;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IASnE,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxC,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAwCtE,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;CAG7C"}
@@ -77,6 +77,31 @@ export class PetLedger {
77
77
  this.current = { ...this.current, names: { ...this.current.names, [petId]: name } };
78
78
  this.dirty = true;
79
79
  }
80
+ /**
81
+ * Select one pet's frames2d skin; `undefined` clears the choice back to the
82
+ * pet's default look. Manifest validation stays a caller concern, exactly
83
+ * like setPetName's length check.
84
+ */
85
+ setPetSkin(petId, skinId) {
86
+ const skins = this.current.skins;
87
+ if (skinId === undefined) {
88
+ if (skins[petId] === undefined)
89
+ return;
90
+ const next = { ...skins };
91
+ delete next[petId];
92
+ this.current = { ...this.current, skins: next };
93
+ }
94
+ else {
95
+ if (skins[petId] === skinId)
96
+ return;
97
+ this.current = { ...this.current, skins: { ...skins, [petId]: skinId } };
98
+ }
99
+ this.dirty = true;
100
+ }
101
+ /** The persisted skin id for one pet (undefined = the pet's default look). */
102
+ petSkin(petId) {
103
+ return this.current.skins[petId];
104
+ }
80
105
  /**
81
106
  * Swap the reaction pools to another pet's custom remarks (called on pet
82
107
  * selection). Slots the pet does not declare fall back to voice packs or built-ins.
@@ -33,6 +33,12 @@ export interface PetPersist {
33
33
  * to its manifest displayName, so only user renames are stored here.
34
34
  */
35
35
  names: Record<string, string>;
36
+ /**
37
+ * Per-pet selected frames2d skin id (keyed by pet id). Skin ids are manifest
38
+ * data, so a stale entry (skin renamed or removed, pet swapped) is ignored
39
+ * when the state view is built instead of pinning an unresolvable track.
40
+ */
41
+ skins: Record<string, string>;
36
42
  affinity: AffinityState;
37
43
  /** Treat (小鱼干) stock ledger. */
38
44
  treats: TreatLedger;
@@ -1 +1 @@
1
- {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CASzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA+DD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAoDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAUzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAmFD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAqDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
@@ -27,6 +27,7 @@ export function emptyPersist() {
27
27
  return {
28
28
  petId: DEFAULT_PET_ID,
29
29
  names: {},
30
+ skins: {},
30
31
  affinity: emptyAffinity(),
31
32
  treats: emptyTreatLedger(),
32
33
  display: { ...defaultDisplayConfig },
@@ -60,6 +61,21 @@ function loadPetNames(parsed) {
60
61
  }
61
62
  return names;
62
63
  }
64
+ /** Sanitize the per-pet skin selection map (string keys, non-empty trimmed values). */
65
+ function loadPetSkins(parsed) {
66
+ const skins = {};
67
+ if (typeof parsed.skins !== 'object' || parsed.skins === null)
68
+ return skins;
69
+ for (const [id, value] of Object.entries(parsed.skins)) {
70
+ if (id === '' || typeof value !== 'string')
71
+ continue;
72
+ const skin = value.trim();
73
+ if (skin === '')
74
+ continue;
75
+ skins[id] = skin;
76
+ }
77
+ return skins;
78
+ }
63
79
  /** Clamp one count/score into [0, max]. */
64
80
  function clamp(value, max) {
65
81
  return Math.min(max, Math.max(0, value));
@@ -92,12 +108,19 @@ function loadGameplay(parsed) {
92
108
  currencies[key] = Math.min(GAMEPLAY_LOAD_CURRENCY_CAP, Math.max(0, Math.floor(value)));
93
109
  }
94
110
  }
95
- result[petId] = {
111
+ const item = {
96
112
  stats,
97
113
  currencies,
98
114
  mode: record.mode === 'work' || record.mode === 'sleep' ? record.mode : null,
99
115
  settledAt: clamp(finiteNum(record.settledAt, 0), Number.MAX_SAFE_INTEGER),
100
116
  };
117
+ if (typeof record.incomeCarryMs === 'number' && Number.isFinite(record.incomeCarryMs)) {
118
+ item.incomeCarryMs = Math.max(0, record.incomeCarryMs);
119
+ }
120
+ if (typeof record.restoreCarryMs === 'number' && Number.isFinite(record.restoreCarryMs)) {
121
+ item.restoreCarryMs = Math.max(0, record.restoreCarryMs);
122
+ }
123
+ result[petId] = item;
101
124
  }
102
125
  return result;
103
126
  }
@@ -146,6 +169,7 @@ export function loadPetPersist(dir = petHomeDir()) {
146
169
  return {
147
170
  petId,
148
171
  names,
172
+ skins: loadPetSkins(parsed),
149
173
  affinity,
150
174
  treats,
151
175
  display,
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAO9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAMtC;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,yBAAyB;;IAEzB,iDAAiD;;IAEjD,8EAA8E;;CAEtE,CAAA;AAEV,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcrF;AAqPD,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,QAA8B,CAAA;AAe7D,4EAA4E;AAC5E,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4MD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CAAE,GAAG,eAAe,GAAG,QAAQ,EAAE,CAgFjI;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAO9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAMtC;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,yBAAyB;;IAEzB,iDAAiD;;IAEjD,8EAA8E;;CAEtE,CAAA;AAEV,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcrF;AAqPD,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,QAA8B,CAAA;AAe7D,4EAA4E;AAC5E,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4MD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CAAE,GAAG,eAAe,GAAG,QAAQ,EAAE,CAqFjI;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
@@ -586,6 +586,12 @@ export function makePetRoutes(deps) {
586
586
  return Promise.reject(new Error('invalid-name'));
587
587
  return service.setName(name);
588
588
  }),
589
+ postRoute(ctx, PET_API_PREFIX + '/set-skin', (body) => {
590
+ const skin = body.skin;
591
+ if (skin !== undefined && typeof skin !== 'string')
592
+ return Promise.reject(new Error('invalid-skin'));
593
+ return service.setSkin(skin === undefined || skin === '' ? undefined : skin);
594
+ }),
589
595
  postRoute(ctx, PET_API_PREFIX + '/set-pet', (body) => {
590
596
  const petId = body.petId;
591
597
  if (typeof petId !== 'string')