@linxin666/dsh-pet 0.2.8 → 0.3.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.
@@ -258,6 +258,14 @@ export declare function resolvePetManifest(raw: unknown, dir: string, options?:
258
258
  * discipline can apply (review-spd follow-up, pet-center M4/M5).
259
259
  */
260
260
  export declare const PET_SCAN_JSON_CAP: number;
261
+ /**
262
+ * Scan-time read ceiling for a live2d model3.json, matching the asset
263
+ * route's model cap (PET_ASSET_CAPS.model). Model descriptors are far
264
+ * larger than the other scanned JSON, but a pathological file — huge, or a
265
+ * FIFO/device — must still be skipped with a warning instead of stalling
266
+ * or OOM-ing the host at plugin startup (same review-spd follow-up).
267
+ */
268
+ export declare const PET_SCAN_LIVE2D_MODEL_CAP: number;
261
269
  /** Decoration asset URL prefix (served by the decoration route, M5). */
262
270
  export declare const DECORATION_ASSET_PREFIX = "/api/pet/decoration";
263
271
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
@@ -302,9 +302,20 @@ function resolveLive2dEntry(manifest, dir, options) {
302
302
  record('error', 'pet ' + manifest.id + ': renderer live2d requires a live2d block');
303
303
  return undefined;
304
304
  }
305
+ const modelFile = join(dir, block.model);
305
306
  let model3;
306
307
  try {
307
- model3 = JSON.parse(readFileSync(join(dir, block.model), 'utf8'));
308
+ // Stat guard before the read: a pathological model file — huge, or a
309
+ // FIFO/device — is skipped with a warning instead of stalling the host
310
+ // at scan time, mirroring the voice/decoration descriptor discipline.
311
+ // The guard stays silent on stat errors, so a missing or unreadable
312
+ // path is re-stat'ed here to fall through to the original fail-closed
313
+ // 'not readable' diagnostic below.
314
+ if (guardedScannedJsonStat(modelFile, options, 'live2d model ' + block.model, PET_SCAN_LIVE2D_MODEL_CAP) === undefined) {
315
+ statSync(modelFile);
316
+ return undefined;
317
+ }
318
+ model3 = JSON.parse(readFileSync(modelFile, 'utf8'));
308
319
  }
309
320
  catch (error) {
310
321
  record('error', 'pet ' + manifest.id + ': live2d model ' + block.model + ' is not readable: '
@@ -373,7 +384,7 @@ function scanPetDir(dir, options) {
373
384
  const manifestFile = join(dir, name, 'pet.json');
374
385
  if (!existsSync(manifestFile))
375
386
  continue;
376
- const parsed = readPetJson(manifestFile, options.warnings);
387
+ const parsed = readPetJson(manifestFile, options);
377
388
  if (parsed === undefined)
378
389
  continue;
379
390
  const entryDir = join(dir, name);
@@ -406,13 +417,20 @@ function scanPetDir(dir, options) {
406
417
  }
407
418
  return entries;
408
419
  }
409
- /** Read and parse one manifest file; undefined (warning recorded) on failure. */
410
- function readPetJson(file, warnings) {
420
+ /**
421
+ * Read and parse one pet.json manifest; undefined (warning recorded) on
422
+ * failure. The descriptor stat guard applies first: a pathological file —
423
+ * huge, or a FIFO/device — is skipped with a warning instead of stalling
424
+ * or OOM-ing the host at scan time (same discipline as voice/decoration).
425
+ */
426
+ function readPetJson(file, options) {
427
+ if (guardedScannedJsonStat(file, options, 'pet manifest') === undefined)
428
+ return undefined;
411
429
  try {
412
430
  return JSON.parse(readFileSync(file, 'utf8'));
413
431
  }
414
432
  catch (error) {
415
- warnings?.push('skipping ' + file + ': ' + (error instanceof Error ? error.message : String(error)));
433
+ options.warnings?.push('skipping ' + file + ': ' + (error instanceof Error ? error.message : String(error)));
416
434
  return undefined;
417
435
  }
418
436
  }
@@ -424,13 +442,23 @@ function readPetJson(file, warnings) {
424
442
  * discipline can apply (review-spd follow-up, pet-center M4/M5).
425
443
  */
426
444
  export const PET_SCAN_JSON_CAP = 64 * 1024;
445
+ /**
446
+ * Scan-time read ceiling for a live2d model3.json, matching the asset
447
+ * route's model cap (PET_ASSET_CAPS.model). Model descriptors are far
448
+ * larger than the other scanned JSON, but a pathological file — huge, or a
449
+ * FIFO/device — must still be skipped with a warning instead of stalling
450
+ * or OOM-ing the host at plugin startup (same review-spd follow-up).
451
+ */
452
+ export const PET_SCAN_LIVE2D_MODEL_CAP = 32 * 1024 * 1024;
427
453
  /**
428
454
  * Stat one scanned JSON descriptor with a regular-file + size guard, so a
429
455
  * pathological user file is skipped with a warning instead of stalling or
430
456
  * OOM-ing the host at startup. Returns the Stats, or undefined when the
431
- * caller must skip the file (a warning was recorded).
457
+ * caller must skip the file (a warning was recorded). 'cap' defaults to
458
+ * the descriptor ceiling (PET_SCAN_JSON_CAP); model descriptors pass the
459
+ * larger live2d ceiling.
432
460
  */
433
- function guardedScannedJsonStat(file, options, what) {
461
+ function guardedScannedJsonStat(file, options, what, cap = PET_SCAN_JSON_CAP) {
434
462
  let st;
435
463
  try {
436
464
  st = statSync(file);
@@ -446,8 +474,8 @@ function guardedScannedJsonStat(file, options, what) {
446
474
  warn(what + ' is not a regular file; ignored');
447
475
  return undefined;
448
476
  }
449
- if (st.size > PET_SCAN_JSON_CAP) {
450
- warn(what + ' exceeds the ' + PET_SCAN_JSON_CAP + '-byte scan ceiling; ignored');
477
+ if (st.size > cap) {
478
+ warn(what + ' exceeds the ' + cap + '-byte scan ceiling; ignored');
451
479
  return undefined;
452
480
  }
453
481
  return st;
@@ -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;AAM9C,+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;AAWD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUrF;AAsPD,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;AAwMD,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,CAwDjI;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;AAWD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUrF;AAwND,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;AAwMD,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,CAwDjI;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
@@ -17,6 +17,7 @@ import { join, sep } from 'node:path';
17
17
  import { DECORATION_ASSET_PREFIX, petEntryView, petPackageRoot } from "./registry.js";
18
18
  import { isPetAllowed } from "./access.js";
19
19
  import { dshHome } from "./dsh-home.js";
20
+ import { readJsonBody, writeJson } from "./http.js";
20
21
  /** Browser-facing base path of the pet API. */
21
22
  export const PET_API_PREFIX = '/api/pet';
22
23
  /** Browser-facing base path of the pet asset routes ('/pet/<id>/...'). */
@@ -76,52 +77,18 @@ function mimeFor(file) {
76
77
  return 'application/octet-stream';
77
78
  return MIME_BY_EXT[file.slice(dot).toLowerCase()] ?? 'application/octet-stream';
78
79
  }
79
- /** Write one JSON response. */
80
- function json(res, status, body) {
81
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
82
- res.end(JSON.stringify(body));
83
- }
84
80
  /** Require the method or answer 405. */
85
81
  function requireMethod(req, res, method) {
86
82
  if (req.method === method)
87
83
  return true;
88
- json(res, 405, { ok: false, error: 'method-not-allowed' });
84
+ writeJson(res, 405, { ok: false, error: 'method-not-allowed' });
89
85
  return false;
90
86
  }
91
- /** Read a JSON request body (bounded). */
92
- function readJsonBody(req) {
93
- return new Promise((resolve, reject) => {
94
- let size = 0;
95
- const chunks = [];
96
- req.on('data', (chunk) => {
97
- size += chunk.length;
98
- if (size > 64 * 1024) {
99
- reject(new Error('body-too-large'));
100
- queueMicrotask(() => req.destroy());
101
- return;
102
- }
103
- chunks.push(chunk);
104
- });
105
- req.on('end', () => {
106
- if (chunks.length === 0) {
107
- resolve({});
108
- return;
109
- }
110
- try {
111
- resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
112
- }
113
- catch {
114
- reject(new Error('invalid-json'));
115
- }
116
- });
117
- req.on('error', reject);
118
- });
119
- }
120
87
  /** Shared route fence: loopback always passes; a live paired-device cookie is an extra allow path. */
121
88
  function guard(ctx, req, res) {
122
89
  if (isPetAllowed(ctx, req))
123
90
  return true;
124
- json(res, 403, { ok: false, error: 'forbidden: loopback-only' });
91
+ writeJson(res, 403, { ok: false, error: 'forbidden: loopback-only' });
125
92
  return false;
126
93
  }
127
94
  /** Wrap one async service call as a GET JSON route. */
@@ -134,8 +101,8 @@ function getRoute(ctx, path, run) {
134
101
  return;
135
102
  if (!requireMethod(req, res, 'GET'))
136
103
  return;
137
- run().then((value) => json(res, 200, value), (error) => {
138
- json(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
104
+ run().then((value) => writeJson(res, 200, value), (error) => {
105
+ writeJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
139
106
  });
140
107
  },
141
108
  };
@@ -150,13 +117,18 @@ function postRoute(ctx, path, run) {
150
117
  return Promise.resolve();
151
118
  if (!requireMethod(req, res, 'POST'))
152
119
  return Promise.resolve();
153
- return readJsonBody(req).then((body) => {
154
- const record = (typeof body === 'object' && body !== null) ? body : {};
155
- return run(record).then((value) => json(res, 200, value), (error) => {
156
- json(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
120
+ // Shared lenient reader (64 KiB cap): an empty body yields null and is
121
+ // restored to {} at the call site (legacy empty-body semantics); invalid
122
+ // JSON and over-limit bodies also yield null, so the endpoint validators
123
+ // below keep answering 400 with the same { ok: false, error } envelope.
124
+ return readJsonBody(req, { maxBytes: 64 * 1024 }).then((parsed) => {
125
+ const payload = parsed ?? {};
126
+ const record = (typeof payload === 'object' && payload !== null) ? payload : {};
127
+ return run(record).then((value) => writeJson(res, 200, value), (error) => {
128
+ writeJson(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
157
129
  });
158
130
  }, (error) => {
159
- json(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
131
+ writeJson(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
160
132
  });
161
133
  },
162
134
  };
@@ -369,7 +341,7 @@ function runtimeHandler(ctx, roots) {
369
341
  const base = spec.root === 'runtimeDir' ? roots.runtimeDir : roots.vendorDir;
370
342
  const file = join(base, name);
371
343
  if (!existsSync(file)) {
372
- json(res, 404, { ok: false, error: 'runtime-file-missing', file: name });
344
+ writeJson(res, 404, { ok: false, error: 'runtime-file-missing', file: name });
373
345
  return;
374
346
  }
375
347
  const resolved = containedRealpath(base, file);
@@ -172,6 +172,12 @@ export declare class PetService extends Service {
172
172
  * disposed sessions are removed by the 'session/disposed' listener.
173
173
  */
174
174
  private readonly sessionActivity;
175
+ /**
176
+ * Sessions whose reward source is the official event stream. This metadata
177
+ * outlives transient visual resets so a derived legacy `done` cannot reward
178
+ * the same turn again after the pet is disabled and re-enabled.
179
+ */
180
+ private readonly officialEventSessions;
175
181
  constructor(ctx: Context, config?: PetConfig);
176
182
  /**
177
183
  * The draw-time voice-pool provider handed to every projection runtime.
@@ -215,6 +221,8 @@ export declare class PetService extends Service {
215
221
  /** Start or stop the session-activity listeners that drive the pet. */
216
222
  setEnabled(enabled: boolean): void;
217
223
  private syncActivity;
224
+ /** Drop transient activity because terminal events missed while disabled cannot be replayed safely. */
225
+ private resetActivity;
218
226
  /** Return the per-session activity record, creating it on first sight. */
219
227
  private activityOf;
220
228
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQ9C,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AACxF,OAAO,EASL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAA;AAGtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAA;AAEnB,4BAA4B;AAC5B,MAAM,WAAW,SAAS;IACxB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,4BAA4B;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAC/B,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,oEAAoE;IACpE,IAAI,CAAC,EAAE,SAAS,WAAW,EAAE,CAAA;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,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;IACd,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,2HAA2H;AAC3H,eAAO,MAAM,sBAAsB,QAAQ,CAAA;AAE3C;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACxC,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,yCAAyC;IACzC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;CACjC;AAED,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAChC,aAAa,EAAE,OAAO,CAAA;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,EAAE,eAAe,CAAA;IACzB,6BAA6B;IAC7B,OAAO,EAAE,gBAAgB,CAAA;IACzB,4CAA4C;IAC5C,GAAG,EAAE;QACH,mBAAmB;QACnB,EAAE,EAAE,MAAM,CAAA;QACV,iDAAiD;QACjD,WAAW,EAAE,MAAM,CAAA;QACnB,4BAA4B;QAC5B,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,MAAM,EAAE;QACN,0BAA0B;QAC1B,OAAO,EAAE,MAAM,CAAA;QACf,iBAAiB;QACjB,GAAG,EAAE,MAAM,CAAA;KACZ,CAAA;IACD;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,iBAAiB,GAAG,uBAAuB,CAAA;AAEvD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,GAAG,EAAE,UAAU,CAAA;KAChB;CACF;AAiBD;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAK;IAE5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,0EAA0E;IAC1E,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAA0B;IACjD,kFAAkF;IAClF,OAAO,CAAC,cAAc,CAAqB;IAC3C;;;;OAIG;IACH,OAAO,CAAC,UAAU,CAA8D;IAChF;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsC;gBAE1D,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc;IAgChD;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAYlB,uEAAuE;IACvE,SAAS,IAAI,OAAO;IAIpB,uCAAuC;IACjC,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC;IAIpC,yDAAyD;IACzD,OAAO,IAAI,gBAAgB;IAI3B,2EAA2E;IACrE,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAItC,gEAAgE;IAChE,gBAAgB,IAAI,WAAW;IAI/B,wEAAwE;IAClE,WAAW,IAAI,OAAO,CAAC;QAAE,WAAW,EAAE,qBAAqB,EAAE,CAAA;KAAE,CAAC;IAItE;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAMxB,yCAAyC;IACzC,WAAW,IAAI,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAI1D,6CAA6C;IAC7C,aAAa,IAAI,MAAM;IAIvB,yEAAyE;IACzE,OAAO,CAAC,KAAK,GAAE,MAA6B,GAAG,MAAM;IAMrD,4EAA4E;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAUlG,uEAAuE;IACvE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKlC,OAAO,CAAC,YAAY;IA4DpB,0EAA0E;IAC1E,OAAO,CAAC,UAAU;IAYlB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAqBrB,gCAAgC;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOhE,iCAAiC;IAC3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAOpF,wFAAwF;IAClF,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAWnG,2EAA2E;IACrE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAS/F;;;;;OAKG;IACH,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAoBvD,oFAAoF;IACpF,OAAO,CAAC,mBAAmB;IAe3B,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAIlB,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,IAAI;IAyDZ,OAAO,CAAC,KAAK;CAOd"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
@@ -54,6 +54,12 @@ export class PetService extends Service {
54
54
  * disposed sessions are removed by the 'session/disposed' listener.
55
55
  */
56
56
  sessionActivity = new Map();
57
+ /**
58
+ * Sessions whose reward source is the official event stream. This metadata
59
+ * outlives transient visual resets so a derived legacy `done` cannot reward
60
+ * the same turn again after the pet is disabled and re-enabled.
61
+ */
62
+ officialEventSessions = new WeakSet();
57
63
  constructor(ctx, config = {}) {
58
64
  super(ctx, 'pet');
59
65
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -164,6 +170,8 @@ export class PetService extends Service {
164
170
  setEnabled(enabled) {
165
171
  this.enabled = enabled;
166
172
  this.syncActivity();
173
+ if (!enabled)
174
+ this.resetActivity();
167
175
  }
168
176
  syncActivity() {
169
177
  if (this.disposeActivity !== undefined) {
@@ -200,6 +208,7 @@ export class PetService extends Service {
200
208
  if (transition === undefined)
201
209
  return;
202
210
  runtime.officialEventsSeen = true;
211
+ this.officialEventSessions.add(session);
203
212
  this.applyActivity(session, transition.input, transition.whisper);
204
213
  if (transition.completedTurn !== undefined) {
205
214
  this.rewardTurn(String(session.id), transition.completedTurn);
@@ -207,6 +216,7 @@ export class PetService extends Service {
207
216
  }),
208
217
  this.ctx.on('session/disposed', (session) => {
209
218
  this.ledger.forgetSession(String(session.id));
219
+ this.officialEventSessions.delete(session);
210
220
  this.sessionActivity.delete(session);
211
221
  if (session !== this.displaySession)
212
222
  return;
@@ -230,12 +240,20 @@ export class PetService extends Service {
230
240
  dispose(); };
231
241
  })();
232
242
  }
243
+ /** Drop transient activity because terminal events missed while disabled cannot be replayed safely. */
244
+ resetActivity() {
245
+ this.displaySession = undefined;
246
+ this.sessionActivity.clear();
247
+ this.machine.onSessionDisposed();
248
+ }
233
249
  /** Return the per-session activity record, creating it on first sight. */
234
250
  activityOf(session) {
235
251
  let activity = this.sessionActivity.get(session);
236
252
  if (activity === undefined) {
253
+ const runtime = emptyProjectionRuntime(this.voicePools());
254
+ runtime.officialEventsSeen = this.officialEventSessions.has(session);
237
255
  activity = {
238
- runtime: emptyProjectionRuntime(this.voicePools()),
256
+ runtime,
239
257
  machine: new PetStateMachine(this.stateConfig),
240
258
  };
241
259
  this.sessionActivity.set(session, activity);
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.2.8",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -103,6 +103,9 @@ export class PetSettingsCardController {
103
103
  private diagnostics: PetDiagnosticView[] = []
104
104
  private loaded = false
105
105
  private attempts = 0
106
+ private disposed = false
107
+ /** Pending deferred-load or retry timer; cancelled by dispose(). */
108
+ private pendingTimer: number | undefined
106
109
 
107
110
  /** @param scope - the bound settings scope for the 'pet' namespace. */
108
111
  constructor(scope: SettingsScope<PetSettings>) {
@@ -120,7 +123,9 @@ export class PetSettingsCardController {
120
123
  // the first registry request until that pass completes so transport
121
124
  // plugins (notably remote-web-ui on a paired non-loopback origin) can
122
125
  // install their fetch channel before /api/pet/pets is issued.
123
- window.setTimeout(() => {
126
+ this.pendingTimer = window.setTimeout(() => {
127
+ this.pendingTimer = undefined
128
+ if (this.disposed) return
124
129
  void this.loadPets()
125
130
  void this.loadDiagnostics()
126
131
  }, 0)
@@ -130,6 +135,7 @@ export class PetSettingsCardController {
130
135
  private async loadDiagnostics(): Promise<void> {
131
136
  try {
132
137
  this.diagnostics = await fetchPetDiagnostics()
138
+ if (this.disposed) return
133
139
  this.store.set(this.projection())
134
140
  } catch {
135
141
  this.diagnostics = []
@@ -138,17 +144,23 @@ export class PetSettingsCardController {
138
144
 
139
145
  /** Resolve the registry choices once (retried a few times on failure). */
140
146
  private async loadPets(): Promise<void> {
141
- if (this.loaded) return
147
+ if (this.loaded || this.disposed) return
142
148
  try {
143
149
  const list = await fetchPetChoices()
150
+ if (this.disposed) return
144
151
  this.petChoices.splice(0, this.petChoices.length, ...list.map(choice => choice.id))
145
152
  for (const choice of list) this.petLabels.set(choice.id, choice.displayName)
146
153
  this.loaded = true
147
154
  this.store.set(this.projection())
148
155
  } catch {
156
+ if (this.disposed) return
149
157
  this.attempts += 1
150
158
  if (this.attempts < 3) {
151
- window.setTimeout(() => { void this.loadPets() }, 3000)
159
+ this.pendingTimer = window.setTimeout(() => {
160
+ this.pendingTimer = undefined
161
+ if (this.disposed) return
162
+ void this.loadPets()
163
+ }, 3000)
152
164
  }
153
165
  }
154
166
  }
@@ -177,10 +189,16 @@ export class PetSettingsCardController {
177
189
  }
178
190
 
179
191
  /**
180
- * Release the card's scope subscription and bound stores; the slot
181
- * disposer calls this on teardown.
192
+ * Release the card's scope subscription, bound stores and pending load
193
+ * timers; the slot disposer calls this on teardown.
182
194
  */
183
195
  dispose(): void {
196
+ if (this.disposed) return
197
+ this.disposed = true
198
+ if (this.pendingTimer !== undefined) {
199
+ window.clearTimeout(this.pendingTimer)
200
+ this.pendingTimer = undefined
201
+ }
184
202
  this.form.dispose()
185
203
  }
186
204
  }
@@ -159,6 +159,22 @@ describe('PetSprite custom visual (pet-center M3)', () => {
159
159
  })
160
160
  })
161
161
 
162
+ describe('PetSprite always-visible close control', () => {
163
+ it('renders a corner close button and hides without petting', () => {
164
+ const onHide = vi.fn()
165
+ const onPet = vi.fn()
166
+ renderPet({ onHide, onPet })
167
+
168
+ const close = screen.getByTestId('pet-close')
169
+ expect(close.getAttribute('aria-label')).toBe('隐藏')
170
+ expect(close.getAttribute('title')).toBe('隐藏')
171
+ fireEvent.click(close)
172
+
173
+ expect(onHide).toHaveBeenCalledTimes(1)
174
+ expect(onPet).not.toHaveBeenCalled()
175
+ })
176
+ })
177
+
162
178
  describe('PetSprite rename input', () => {
163
179
  it('submits the draft on Enter outside composition', () => {
164
180
  const { onRename } = renderPet()
@@ -466,34 +466,58 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
466
466
  }}
467
467
  >
468
468
  <div
469
- ref={spriteRef}
470
- className={styles.sprite}
471
- style={{
472
- width: spriteWidth,
473
- height: spriteHeight,
474
- ...(props.visual === undefined
475
- ? {
476
- backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
477
- backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * (definition.atlasRows ?? rows.length) * spriteScale) + 'px',
478
- backgroundRepeat: 'no-repeat',
479
- backgroundPosition: '0 0',
480
- }
481
- : {}),
482
- cursor: dragRef.current === null ? 'grab' : 'grabbing',
483
- }}
484
- onPointerDown={onPointerDown}
485
- onPointerMove={onPointerMove}
486
- onPointerUp={onPointerUp}
487
- onClick={() => {
488
- // A pointer sequence that moved (dragged) still fires a trailing
489
- // click; skip the pet when that happened.
490
- if (draggedRef.current) return
491
- props.onPet()
492
- }}
493
- role="button"
494
- aria-label={definition.displayName}
469
+ className={styles.spriteWrap}
470
+ style={{ width: spriteWidth, height: spriteHeight }}
495
471
  >
496
- {props.visual}
472
+ <div
473
+ ref={spriteRef}
474
+ className={styles.sprite}
475
+ style={{
476
+ width: spriteWidth,
477
+ height: spriteHeight,
478
+ ...(props.visual === undefined
479
+ ? {
480
+ backgroundImage: imageReady ? 'url(' + definition.atlasUrl + ')' : undefined,
481
+ backgroundSize: (cell.width * columns * spriteScale) + 'px ' + (cell.height * (definition.atlasRows ?? rows.length) * spriteScale) + 'px',
482
+ backgroundRepeat: 'no-repeat',
483
+ backgroundPosition: '0 0',
484
+ }
485
+ : {}),
486
+ cursor: dragRef.current === null ? 'grab' : 'grabbing',
487
+ }}
488
+ onPointerDown={onPointerDown}
489
+ onPointerMove={onPointerMove}
490
+ onPointerUp={onPointerUp}
491
+ onClick={() => {
492
+ // A pointer sequence that moved (dragged) still fires a trailing
493
+ // click; skip the pet when that happened.
494
+ if (draggedRef.current) return
495
+ props.onPet()
496
+ }}
497
+ role="button"
498
+ aria-label={definition.displayName}
499
+ >
500
+ {props.visual}
501
+ </div>
502
+ <button
503
+ type="button"
504
+ className={styles.closeButton}
505
+ aria-label={panelLabel('hide', props.t('pet.hide'))}
506
+ title={panelLabel('hide', props.t('pet.hide'))}
507
+ data-testid="pet-close"
508
+ onPointerDown={(e) => {
509
+ // Keep the close control from starting a drag on the sprite.
510
+ e.stopPropagation()
511
+ }}
512
+ onClick={(e) => {
513
+ // The close control sits beside the pet button; do not pet as a
514
+ // side effect of closing the overlay.
515
+ e.stopPropagation()
516
+ props.onHide()
517
+ }}
518
+ >
519
+ ×
520
+ </button>
497
521
  </div>
498
522
  {feedback !== null && (
499
523
  <div key={feedback.at} ref={bubbleRef} className={clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet)}>
@@ -121,8 +121,13 @@ export function apply(ctx: ClientContext): void {
121
121
 
122
122
  // First-level settings section: one staged form over the 'pet' settings
123
123
  // namespace, registered as a top-level settings page. The controller loads
124
- // the petId choices from the registry endpoint itself.
124
+ // the petId choices from the registry endpoint itself — the registry lists
125
+ // the available pets (built-in assets plus user dirs), so the section only
126
+ // ever shows installed pets. Installing new pets happens in the Workshop
127
+ // store.
125
128
  const petSettings = new PetSettingsCardController(settingsScope)
129
+ // The section entry owns the controller: unregistering it (fiber disposal,
130
+ // hot reload) releases the scope subscription through petSettings.dispose.
126
131
  ctx.slots.inject('settings.section', () => {
127
132
  const unregister = ctx.slots.register({
128
133
  name: 'settings.section',
@@ -133,8 +138,8 @@ export function apply(ctx: ClientContext): void {
133
138
  inject: () => petSettings.inject(),
134
139
  }, PetSettingsSection)
135
140
  return () => {
136
- petSettings.dispose()
137
141
  unregister()
142
+ petSettings.dispose()
138
143
  }
139
144
  })
140
145
 
@@ -9,11 +9,42 @@
9
9
  }
10
10
 
11
11
  .sprite {
12
- position: relative;
13
12
  image-rendering: auto;
14
13
  touch-action: none;
15
14
  }
16
15
 
16
+ .spriteWrap {
17
+ position: relative;
18
+ flex: 0 0 auto;
19
+ }
20
+
21
+ .closeButton {
22
+ position: absolute;
23
+ top: 4px;
24
+ right: 4px;
25
+ z-index: 2;
26
+ width: 24px;
27
+ height: 24px;
28
+ padding: 0;
29
+ border: 1px solid rgba(226, 232, 255, 0.72);
30
+ border-radius: 999px;
31
+ color: #f8fafc;
32
+ background: rgba(7, 11, 26, 0.78);
33
+ font: 600 18px/20px sans-serif;
34
+ cursor: pointer;
35
+ touch-action: manipulation;
36
+ transition: background 120ms ease, box-shadow 120ms ease;
37
+ }
38
+
39
+ .closeButton:hover {
40
+ background: rgba(44, 62, 126, 0.95);
41
+ }
42
+
43
+ .closeButton:focus-visible {
44
+ outline: none;
45
+ box-shadow: 0 0 0 2px rgba(126, 152, 255, 0.95);
46
+ }
47
+
17
48
  .bubble {
18
49
  position: absolute;
19
50
  bottom: 100%;
@@ -402,7 +433,8 @@
402
433
 
403
434
  .action,
404
435
  .summon,
405
- .bubbleMore {
436
+ .bubbleMore,
437
+ .closeButton {
406
438
  transition: none;
407
439
  }
408
440
  }
@@ -11,18 +11,18 @@
11
11
  border: 1px dashed var(--dsw-alias-border-l2);
12
12
  border-radius: 8px;
13
13
  font-size: 12px;
14
- color: var(--dsw-alias-label-dimmed);
14
+ color: var(--dsw-alias-label-tertiary);
15
15
  }
16
16
  .diagnosticsTitle {
17
17
  display: block;
18
18
  font-weight: 600;
19
19
  margin-bottom: 4px;
20
- color: var(--dsw-alias-label);
20
+ color: var(--dsw-alias-label-primary);
21
21
  }
22
22
  .diagnostics ul {
23
23
  margin: 0;
24
24
  padding-left: 16px;
25
25
  }
26
26
  .diagnostics li[data-level="error"] {
27
- color: var(--dsw-alias-error, #c04848);
27
+ color: var(--dsw-alias-label-error, #c04848);
28
28
  }