@linxin666/dsh-pet 0.3.6 → 0.3.10

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 (52) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -0
  3. package/README.zh.md +4 -0
  4. package/lib/client.js +187 -88
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +89 -11
  7. package/lib/live2d-vendor.js +106 -95
  8. package/lib/live2d-vendor.js.map +1 -1
  9. package/lib/types/announce.d.ts +53 -0
  10. package/lib/types/announce.d.ts.map +1 -0
  11. package/lib/types/announce.js +80 -0
  12. package/lib/types/client/PetDockEntry.d.ts +14 -5
  13. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  14. package/lib/types/client/PetDockEntry.js +6 -5
  15. package/lib/types/client/PetSettingsCard.d.ts +2 -1
  16. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  17. package/lib/types/client/PetSprite.d.ts +8 -0
  18. package/lib/types/client/PetSprite.d.ts.map +1 -1
  19. package/lib/types/client/PetSprite.js +29 -4
  20. package/lib/types/client/index.d.ts +8 -5
  21. package/lib/types/client/index.d.ts.map +1 -1
  22. package/lib/types/client/index.js +11 -5
  23. package/lib/types/client/locales.d.ts +2 -2
  24. package/lib/types/client/locales.js +2 -2
  25. package/lib/types/client/pet-store.d.ts +1 -1
  26. package/lib/types/client/pet-store.d.ts.map +1 -1
  27. package/lib/types/client/pet-store.js +1 -1
  28. package/lib/types/client/settings-form.d.ts +35 -43
  29. package/lib/types/client/settings-form.d.ts.map +1 -1
  30. package/lib/types/client/settings-form.js +60 -53
  31. package/lib/types/index.d.ts.map +1 -1
  32. package/lib/types/index.js +10 -9
  33. package/lib/types/service.d.ts +18 -0
  34. package/lib/types/service.d.ts.map +1 -1
  35. package/lib/types/service.js +22 -0
  36. package/package.json +17 -13
  37. package/src/announce.ts +103 -0
  38. package/src/client/PetDockEntry.test.tsx +1 -1
  39. package/src/client/PetDockEntry.tsx +15 -4
  40. package/src/client/PetSettingsCard.tsx +2 -1
  41. package/src/client/PetSprite.test.tsx +18 -0
  42. package/src/client/PetSprite.tsx +66 -3
  43. package/src/client/gameplay-hud.test.tsx +1 -1
  44. package/src/client/index.test.tsx +2 -2
  45. package/src/client/index.ts +17 -6
  46. package/src/client/locales.ts +2 -2
  47. package/src/client/pet-store.ts +2 -2
  48. package/src/client/pet.module.css +104 -0
  49. package/src/client/settings-form.ts +78 -92
  50. package/src/dsh-home.ts +1 -1
  51. package/src/index.ts +17 -15
  52. package/src/service.ts +28 -1
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * each plugin's client bundle; mirrors the official ui-plugin-config
8
8
  * card-store pattern.
9
9
  */
10
- import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
12
12
  export function numberField(field, constraints = {}) {
13
13
  const { integer = false, min } = constraints;
@@ -43,8 +43,8 @@ export function textField(field) {
43
43
  /**
44
44
  * A free-text field the Host treats as a secret and redacts from the read-back
45
45
  * (role('secret') in the section schema). The card still edits it like text,
46
- * but a save never compares the redacted value back and relies on the scope
47
- * reporting the write landed.
46
+ * but a save never compares the redacted value back: the staged set is judged
47
+ * by the mutation settling (see {@link FieldSpec.secret}).
48
48
  */
49
49
  export function secretField(field) {
50
50
  return { ...textField(field), secret: true };
@@ -129,7 +129,7 @@ export class CardForm {
129
129
  exposed: snapshot.status === 'ready',
130
130
  writable: snapshot.writable,
131
131
  dirty: plan.length > 0,
132
- invalid: plan.some(item => item.run === undefined),
132
+ invalid: plan.some(item => item.judge === undefined),
133
133
  saving: this.saving,
134
134
  failed: this.failed,
135
135
  ...this.failedReason === undefined ? {} : { failedReason: this.failedReason },
@@ -168,22 +168,26 @@ export class CardForm {
168
168
  };
169
169
  }
170
170
  /**
171
- * Write every staged edit, then re-seed from what the Host accepted.
171
+ * Write every staged edit in one atomic scope mutation, then re-seed from
172
+ * what the Host accepted.
172
173
  *
173
- * When the scope carries the optional batch surface (the dsh-web
174
- * bridge scope), every planned write rides one mutation so cross-field
175
- * validate hooks (baseURL+model) judge the batch as a unit instead of
176
- * deadlocking on per-field writes. Otherwise the per-field loop runs.
177
- * A field lands only when the Host reports it held the staged value; a
178
- * landed field's draft is dropped, a failed one stays staged for the user.
179
- * @returns settlement after every write and the read-back.
174
+ * The whole batch rides one mutate, so cross-field validate hooks
175
+ * (baseURL+model) judge it as a unit: the Host either applies every write
176
+ * or refuses the batch. The 0.1.2 scope contract never rejects a refused
177
+ * mutation the scope recovers with a fresh Host view and resolves — so
178
+ * resolution alone proves nothing: the outcome is judged by reading the
179
+ * settled snapshot back, one planned write at a time, and one missed write
180
+ * fails the whole save. A scope that still rejects on refusal (the dsh-web
181
+ * bridge scope) reports through the same failure path with its rejection
182
+ * message. A save that did not land keeps its drafts, so the user can
183
+ * correct them instead of retyping.
184
+ * @returns settlement after the mutation and the read-back.
180
185
  */
181
186
  async save() {
182
187
  const plan = this.plan();
183
- const valid = plan.filter(item => item.run !== undefined);
188
+ const valid = plan.filter((item) => item.judge !== undefined);
184
189
  if (plan.length === 0 || this.saving || valid.length !== plan.length)
185
190
  return;
186
- const plannedWrites = valid.map(item => item.op);
187
191
  // Snapshot the staged entries this save writes, so an edit staged while it
188
192
  // is in flight (which replaces the same key) survives: only delete the key
189
193
  // when the entry is still the one this save started from.
@@ -194,39 +198,35 @@ export class CardForm {
194
198
  this.failed = false;
195
199
  this.failedReason = undefined;
196
200
  this.publish();
197
- const landed = new Set();
198
- const batch = this.batchedScope();
199
- if (batch !== undefined) {
200
- const result = await batch.mutate(plannedWrites);
201
- if (result.ok) {
202
- for (const field of result.fields) {
203
- if (field.landed)
204
- landed.add(field.field);
205
- }
206
- }
207
- else {
208
- this.failedReason = result.message;
209
- }
201
+ // One atomic namespace mutation: the 0.1.2 scope contract takes ordered
202
+ // path operations, so the whole staged batch is validated, persisted, and
203
+ // recovered together either every write lands or none does.
204
+ const ops = valid.map(item => item.op.op === 'set'
205
+ ? { op: 'set', path: [item.field], value: item.op.value }
206
+ : { op: 'unset', path: [item.field] });
207
+ let failedReason;
208
+ try {
209
+ await this.scope.mutate(ops);
210
210
  }
211
- else {
212
- for (const item of valid) {
213
- if (await item.run())
214
- landed.add(item.field);
215
- }
211
+ catch (error) {
212
+ failedReason = error instanceof Error ? error.message : String(error);
216
213
  }
214
+ // The 0.1.2 scope resolves even a refused mutation (it recovers with a
215
+ // fresh view instead of throwing), so resolution alone proves nothing:
216
+ // judge every planned write against the settled snapshot. The mutation is
217
+ // atomic, so one missed write fails the whole save and keeps the drafts.
218
+ const landed = failedReason === undefined && valid.every(item => item.judge());
217
219
  for (const [field, before] of pending) {
218
- if (landed.has(field) && this.staged.get(field) === before)
220
+ if (landed && this.staged.get(field) === before)
219
221
  this.staged.delete(field);
220
222
  }
221
223
  this.saving = false;
222
- this.failed = landed.size !== pending.size;
224
+ this.failed = !landed;
225
+ // A read-back failure carries no server reason: the card surfaces its
226
+ // generic failure copy; a rejecting scope (the bridge) adds its message.
227
+ this.failedReason = failedReason;
223
228
  this.publish();
224
229
  }
225
- /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
226
- batchedScope() {
227
- const candidate = this.scope;
228
- return typeof candidate?.mutate === 'function' ? candidate : undefined;
229
- }
230
230
  /**
231
231
  * Every staged edit a save would write. An entry whose draft is not a value
232
232
  * its field accepts carries no write: the form is still dirty, and the save
@@ -240,35 +240,42 @@ export class CardForm {
240
240
  const spec = this.specOf(field);
241
241
  if (staged.clear) {
242
242
  if (this.stored(field))
243
- plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
243
+ plan.push({ field, op: { field, op: 'unset' }, judge: () => this.landedUnset(field) });
244
244
  continue;
245
245
  }
246
246
  if (staged.text === spec.format(this.sectionValue(field)))
247
247
  continue;
248
248
  const write = spec.parse(staged.text);
249
249
  if (write === undefined)
250
- plan.push({ field, op: { field, op: 'unset' }, run: undefined });
250
+ plan.push({ field, op: { field, op: 'unset' }, judge: undefined });
251
251
  else if (write.kind === 'clear')
252
- plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
252
+ plan.push({ field, op: { field, op: 'unset' }, judge: () => this.landedUnset(field) });
253
253
  else
254
- plan.push({ field, op: { field, op: 'set', value: write.value }, run: () => this.store(field, write.value) });
254
+ plan.push({ field, op: { field, op: 'set', value: write.value }, judge: () => this.landedSet(field, write.value) });
255
255
  }
256
256
  return plan;
257
257
  }
258
- async clear(field) {
259
- await this.scope.unset(field);
260
- return !this.stored(field);
261
- }
262
- async store(field, value) {
263
- await this.scope.set(field, value);
264
- // A redacted secret never appears in the user layer read-back; judging it
265
- // by value would misreport a successful secret save as failed. The bridge
266
- // reports secret writes through its secret-set markers (batch path); on
267
- // the per-field path the scope resolved, so the write is landed.
258
+ /**
259
+ * Read-back judgment for a planned set: the user layer must hold the
260
+ * intended value once the mutation has settled.
261
+ */
262
+ landedSet(field, value) {
263
+ // A redacted secret never appears in any wire view layer: the Host strips
264
+ // role('secret') fields and reports them through a sidecar the scope
265
+ // snapshot does not expose, so there is nothing to compare the draft
266
+ // against. Settling is the only signal the form has; the rest of the
267
+ // batch, when one exists, still carries the atomic verdict by read-back.
268
268
  if (this.specOf(field).secret)
269
269
  return true;
270
270
  return this.userLayer()?.[field] === value;
271
271
  }
272
+ /**
273
+ * Read-back judgment for a planned unset: the field must be gone from the
274
+ * user layer once the mutation has settled.
275
+ */
276
+ landedUnset(field) {
277
+ return !this.stored(field);
278
+ }
272
279
  stage(field, edit) {
273
280
  this.staged.set(field, edit);
274
281
  this.failed = false;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,OAAO,CAAC,MAAM,aAAa,CAAA;AAC3B,OAAO,EAAsC,KAAK,SAAS,EAA2B,MAAM,cAAc,CAAA;AAM1G,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,MAAM,GACP,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,KAAK,GACN,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5E,OAAO,EACL,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,cAAc,EACd,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAEpB,sEAAsE;AACtE,eAAO,MAAM,IAAI,QAAQ,CAAA;AAEzB,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAAgB,CAAA;AAEnC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM;;;;;;;;;;;;;;;;IAU1D;AAED,0EAA0E;AAC1E,eAAO,MAAM,KAAK,kBAA6C,CAAA;AAE/D,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CAgE7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,OAAO,CAAC,MAAM,aAAa,CAAA;AAC3B,OAAO,EAAsC,KAAK,SAAS,EAA2B,MAAM,cAAc,CAAA;AAM1G,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,MAAM,GACP,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,KAAK,GACN,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5E,OAAO,EACL,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,cAAc,EACd,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAEpB,sEAAsE;AACtE,eAAO,MAAM,IAAI,QAAQ,CAAA;AAEzB,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAAgB,CAAA;AAEnC;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM;;;;;;;;;;;;;;;;IAU1D;AAED,0EAA0E;AAC1E,eAAO,MAAM,KAAK,kBAA6C,CAAA;AAE/D,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CAkE7D"}
@@ -10,7 +10,6 @@
10
10
  * cordis.patch.yml inserts this plugin row.
11
11
  * @module @linxin666/dsh-pet
12
12
  */
13
- import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
14
13
  import z from 'schemastery';
15
14
  import { PetService, PET_SETTINGS_NAMESPACE } from "./service.js";
16
15
  import { makePetRoutes } from "./routes.js";
@@ -96,14 +95,16 @@ function applyImpl(ctx, config = {}) {
96
95
  disposeRoutes = undefined;
97
96
  }
98
97
  };
99
- installSettingsSection(ctx, settingsNamespace(PET_SETTINGS_NAMESPACE), makePetSettingsSchema(service.selectedPetId()), base, {
100
- setSource: (source) => { current = source; },
101
- onChange: () => {
102
- const section = current();
103
- service.applySettingsSection(section);
104
- service.setEnabled(section.enabled ?? true);
105
- syncRoutes();
106
- },
98
+ ctx.inject(['settings'], (settingsCtx) => {
99
+ settingsCtx.settings.installSection(ctx, PET_SETTINGS_NAMESPACE, makePetSettingsSchema(service.selectedPetId()), base, {
100
+ setSource: (source) => { current = source; },
101
+ onChange: () => {
102
+ const section = current();
103
+ service.applySettingsSection(section);
104
+ service.setEnabled(section.enabled ?? true);
105
+ syncRoutes();
106
+ },
107
+ });
107
108
  });
108
109
  syncRoutes();
109
110
  }
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { Context, Service } from '@deepseek-ai/cordis';
15
15
  import type { AffinityConfig, PetAffinityView, PetInteraction } from './affinity.ts';
16
+ import { type PetAnnouncement } from './announce.ts';
16
17
  import type { TreatConfig } from './treats.ts';
17
18
  import { type LedgerInteractionResult } from './ledger.ts';
18
19
  import { type PetDisplayConfig } from './persist.ts';
@@ -135,6 +136,12 @@ export interface PetStateView {
135
136
  * settles the view in memory but never writes pet.json.
136
137
  */
137
138
  gameplay?: PetGameplayStateView;
139
+ /**
140
+ * The freshest plugin-authored announcement (dsh-usage linkage), within
141
+ * its TTL; absent when none is fresh. Rendered as a dedicated styled
142
+ * bubble above the session stack.
143
+ */
144
+ announcement?: PetAnnouncement;
138
145
  }
139
146
  /** Result of `pet.interact`. */
140
147
  export type PetInteractResult = LedgerInteractionResult;
@@ -183,6 +190,8 @@ export declare class PetService extends Service {
183
190
  private enabled;
184
191
  /** Status-decoration master switch (M5, #567); mirrored from settings. */
185
192
  private decorationEnabled;
193
+ /** The freshest plugin-authored announcement (dsh-usage linkage). */
194
+ private announcement;
186
195
  private disposeActivity;
187
196
  /** Session whose most recent meaningful event currently drives the global pet. */
188
197
  private displaySession;
@@ -215,6 +224,15 @@ export declare class PetService extends Service {
215
224
  isEnabled(): boolean;
216
225
  /** RPC: current pet state snapshot. */
217
226
  state(currentSessionId?: string): Promise<PetStateView>;
227
+ /**
228
+ * RPC: one plugin-authored announcement bubble (dsh-usage linkage). The
229
+ * payload is validated into a bounded PetAnnouncement; a malformed one is
230
+ * dropped silently — a sibling plugin's bug must never surface as pet
231
+ * breakage. The announcement is in-memory only.
232
+ */
233
+ announce(input: unknown): {
234
+ ok: boolean;
235
+ };
218
236
  /** Current persisted display config (read-only view). */
219
237
  display(): PetDisplayConfig;
220
238
  /** RPC: the registry entries the browser half renders and selects from. */
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
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,EAAwC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAC1F,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;IAChC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;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,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAA;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,eAAe,CAAA;CAC/B;AAED,gCAAgC;AAChC,MAAM,MAAM,iBAAiB,GAAG,uBAAuB,CAAA;AAEvD,+FAA+F;AAC/F,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;CAC9B;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yBAAyB;IACzB,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IAC5B,mEAAmE;IACnE,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,IAAI,CAAC,EAAE,oBAAoB,CAAA;CAC5B;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,GAAG,EAAE,UAAU,CAAA;KAChB;CACF;AAgBD;;;;;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,qEAAqE;IACrE,OAAO,CAAC,YAAY,CAA6B;IACjD,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;IACtE;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;gBAEnD,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc;IAkChD;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAYlB,uEAAuE;IACvE,SAAS,IAAI,OAAO;IAIpB,uCAAuC;IACjC,KAAK,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI7D;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE;IAOzC,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;IAWlG,uEAAuE;IACvE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAMlC,OAAO,CAAC,YAAY;IA8DpB,uGAAuG;IACvG,OAAO,CAAC,aAAa;IAMrB,0EAA0E;IAC1E,OAAO,CAAC,UAAU;IAclB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAqBrB,gCAAgC;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAahE,2DAA2D;IAC3D,OAAO,CAAC,WAAW;IAInB,mEAAmE;IACnE,OAAO,CAAC,aAAa;IAWrB,+FAA+F;IAC/F,OAAO,CAAC,cAAc;IAMtB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAM3B,2DAA2D;IAC3D,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACG,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAuClE,qEAAqE;IAC/D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAcpF,yEAAyE;IACnE,gBAAgB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAexD,0EAA0E;IACpE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAiCjE,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;IAgFZ,OAAO,CAAC,KAAK;CAOd"}
@@ -12,6 +12,7 @@
12
12
  * @module @linxin666/dsh-pet/service
13
13
  */
14
14
  import { Service } from '@deepseek-ai/cordis';
15
+ import { announcementFresh, parseAnnouncement } from "./announce.js";
15
16
  import { emptyProjectionRuntime, isActivityPhase, projectOfficialEvent, } from "./event-projection.js";
16
17
  import { PetLedger } from "./ledger.js";
17
18
  import { DEFAULT_PET_NAME, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, PET_NAME_MAX_LENGTH, loadPetPersist, petHomeDir, savePetPersist, } from "./persist.js";
@@ -42,6 +43,8 @@ export class PetService extends Service {
42
43
  enabled;
43
44
  /** Status-decoration master switch (M5, #567); mirrored from settings. */
44
45
  decorationEnabled;
46
+ /** The freshest plugin-authored announcement (dsh-usage linkage). */
47
+ announcement;
45
48
  disposeActivity;
46
49
  /** Session whose most recent meaningful event currently drives the global pet. */
47
50
  displaySession;
@@ -119,6 +122,19 @@ export class PetService extends Service {
119
122
  async state(currentSessionId) {
120
123
  return this.view(currentSessionId);
121
124
  }
125
+ /**
126
+ * RPC: one plugin-authored announcement bubble (dsh-usage linkage). The
127
+ * payload is validated into a bounded PetAnnouncement; a malformed one is
128
+ * dropped silently — a sibling plugin's bug must never surface as pet
129
+ * breakage. The announcement is in-memory only.
130
+ */
131
+ announce(input) {
132
+ const parsed = parseAnnouncement(input, Date.now());
133
+ if (parsed === undefined)
134
+ return { ok: false };
135
+ this.announcement = parsed;
136
+ return { ok: true };
137
+ }
122
138
  /** Current persisted display config (read-only view). */
123
139
  display() {
124
140
  return { ...this.ledger.snapshot.display };
@@ -605,6 +621,11 @@ export class PetService extends Service {
605
621
  }
606
622
  // Read-only: the ledger settles on economic events only, never on a read,
607
623
  // so polling the state cannot trigger pet.json writes.
624
+ // An expired announcement simply stops appearing (the client's 2 s poll
625
+ // drops it); no timer owns its removal.
626
+ const announcement = this.announcement !== undefined && announcementFresh(this.announcement, Date.now())
627
+ ? this.announcement
628
+ : undefined;
608
629
  return {
609
630
  animation: snapshot.animation,
610
631
  ...(snapshot.bubble === undefined ? {} : { bubble: snapshot.bubble }),
@@ -612,6 +633,7 @@ export class PetService extends Service {
612
633
  sessionActive: snapshot.sessionActive,
613
634
  sessions,
614
635
  ...(decoration === undefined ? {} : { decoration }),
636
+ ...(announcement === undefined ? {} : { announcement }),
615
637
  affinity: this.ledger.affinityView(Date.now()),
616
638
  display: { ...this.ledger.snapshot.display },
617
639
  pet: {
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.3.6",
4
+ "version": "0.3.10",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -40,17 +40,17 @@
40
40
  "license": "Apache-2.0",
41
41
  "dsh": {
42
42
  "engines": {
43
- "dsh": ">=0.1.1-rc.1"
43
+ "dsh": ">=0.1.2-alpha.1"
44
44
  },
45
45
  "bundle": {
46
46
  "patch": "./cordis.patch.yml"
47
47
  },
48
48
  "client": {
49
49
  "inject": [
50
- "@deepseek-ai/dsh-client-runtime",
51
50
  "@deepseek-ai/dsh-client-connection",
52
51
  "@deepseek-ai/dsh-client-ui-settings",
53
- "@deepseek-ai/dsh-client-ui-conversation"
52
+ "@deepseek-ai/dsh-client-ui-conversation",
53
+ "@deepseek-ai/dsh-api-session-controller"
54
54
  ],
55
55
  "platform": "web"
56
56
  }
@@ -64,15 +64,19 @@
64
64
  "react-dom": "^18.2.0"
65
65
  },
66
66
  "devDependencies": {
67
- "@deepseek-ai/cordis": "^4.0.1",
68
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
69
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
70
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
71
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
72
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
73
- "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
74
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
75
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
67
+ "@deepseek-ai/cordis": "^4.0.2",
68
+ "@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.2",
69
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.2",
70
+ "@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.2",
71
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
72
+ "@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
73
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.2",
74
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.2",
75
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
76
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
77
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
78
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
79
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
76
80
  "@testing-library/dom": "^10.4.1",
77
81
  "@testing-library/react": "^16.3.2",
78
82
  "@types/node": "^22.20.0",
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The pet announcement-bubble contract (dsh-usage linkage): a sibling plugin
3
+ * pushes one structured announcement through `pet.announce(...)`, the host
4
+ * validates it into a bounded payload, and the browser half renders it as a
5
+ * dedicated, specially styled bubble above the session bubble stack.
6
+ *
7
+ * The validation lives in this pure module so the wire contract has exactly
8
+ * one home and stays testable without the cordis service.
9
+ * @module @linxin666/dsh-pet/announce
10
+ */
11
+
12
+ /** One plugin-authored announcement bubble. */
13
+ export interface PetAnnouncement {
14
+ /** Authoring plugin's source tag (`dsh-usage` for the usage statistics). */
15
+ source: string
16
+ /** Bubble content kind: a spend estimate, an account balance, or a plan-quota status. */
17
+ kind: 'balance' | 'cost' | 'plan'
18
+ /** Lead text (usually the provider display name). */
19
+ title: string
20
+ /** Balance or today-spend amount, formatted for display (kinds `balance` and `cost`). */
21
+ amount?: string
22
+ /** Plan usage percent 0-100 (kind `plan`). */
23
+ percent?: number
24
+ /** ISO 8601 reset instant (kind `plan`). */
25
+ resetAt?: string
26
+ /** Short trailing note (plan tier name, peak-period status, currency code, ...). */
27
+ note?: string
28
+ /** Visual tone; drives the bubble's accent color. */
29
+ tone: 'ok' | 'warn' | 'low'
30
+ /** Freshness window in ms; an expired announcement stops rendering. */
31
+ ttlMs: number
32
+ /** Epoch ms the announcement arrived. */
33
+ at: number
34
+ }
35
+
36
+ /** Default freshness window. */
37
+ export const ANNOUNCE_DEFAULT_TTL_MS = 10_000
38
+
39
+ /**
40
+ * Hard TTL ceiling. A repeating announcer (dsh-usage) declares its poll
41
+ * cadence as the TTL, so an `always`-mode bubble stays continuous across
42
+ * polls; the ceiling means a source that dies unmounts its bubble within at
43
+ * most one missed refresh cycle rather than lingering forever.
44
+ */
45
+ export const ANNOUNCE_MAX_TTL_MS = 7_200_000
46
+
47
+ /** Hard bounds: lengths and the TTL range (1 s .. 60 s). */
48
+ const TITLE_MAX = 80
49
+ const TEXT_MAX = 120
50
+ const SOURCE_MAX = 64
51
+
52
+ function boundedString(value: unknown, max: number): string | undefined {
53
+ if (typeof value !== 'string') return undefined
54
+ const trimmed = value.trim()
55
+ if (trimmed === '') return undefined
56
+ return trimmed.length <= max ? trimmed : trimmed.slice(0, max)
57
+ }
58
+
59
+ /**
60
+ * Validate one announce payload. Unknown fields are dropped, oversized text
61
+ * is truncated, and anything structurally wrong resolves to undefined — a
62
+ * malformed announcement never reaches the pet's bubble surface.
63
+ * @param input - the payload a sibling plugin passed to `pet.announce`.
64
+ * @param now - epoch ms the announcement arrived.
65
+ */
66
+ export function parseAnnouncement(input: unknown, now: number): PetAnnouncement | undefined {
67
+ if (typeof input !== 'object' || input === null) return undefined
68
+ const data = input as Record<string, unknown>
69
+ const source = boundedString(data.source, SOURCE_MAX)
70
+ const title = boundedString(data.title, TITLE_MAX)
71
+ if (source === undefined || title === undefined) return undefined
72
+ const kind = data.kind === 'balance' || data.kind === 'cost' || data.kind === 'plan' ? data.kind : undefined
73
+ if (kind === undefined) return undefined
74
+ const amount = boundedString(data.amount, TEXT_MAX)
75
+ const resetAt = boundedString(data.resetAt, TEXT_MAX)
76
+ const note = boundedString(data.note, TITLE_MAX)
77
+ const percent = typeof data.percent === 'number' && Number.isFinite(data.percent)
78
+ ? Math.max(0, Math.min(100, data.percent))
79
+ : undefined
80
+ if ((kind === 'balance' || kind === 'cost') && amount === undefined) return undefined
81
+ if (kind === 'plan' && percent === undefined) return undefined
82
+ const tone = data.tone === 'warn' || data.tone === 'low' ? data.tone : 'ok'
83
+ const ttlMs = typeof data.ttlMs === 'number' && Number.isFinite(data.ttlMs)
84
+ ? Math.max(1000, Math.min(ANNOUNCE_MAX_TTL_MS, data.ttlMs))
85
+ : ANNOUNCE_DEFAULT_TTL_MS
86
+ return {
87
+ source,
88
+ kind,
89
+ title,
90
+ ...(amount !== undefined ? { amount } : {}),
91
+ ...(percent !== undefined ? { percent } : {}),
92
+ ...(resetAt !== undefined ? { resetAt } : {}),
93
+ ...(note !== undefined ? { note } : {}),
94
+ tone,
95
+ ttlMs,
96
+ at: now,
97
+ }
98
+ }
99
+
100
+ /** Whether an announcement is still fresh at `now`. */
101
+ export function announcementFresh(announcement: PetAnnouncement, now: number): boolean {
102
+ return now - announcement.at < announcement.ttlMs
103
+ }
@@ -9,7 +9,7 @@ import { cleanup, render, screen } from '@testing-library/react'
9
9
  // The npm SDK's client half is a closure-factory bundle for the GUI's
10
10
  // __ModuleLoader__ (not importable under vitest); provide the defineStore
11
11
  // the pet store needs (same fake-store pattern as the settings-card tests).
12
- vi.mock('@deepseek-ai/dsh-client-runtime/client', () => ({
12
+ vi.mock('@deepseek-ai/dsh-client-store', () => ({
13
13
  defineStore: (spec: {
14
14
  init: () => unknown
15
15
  actions: Record<string, (draft: never, ...args: never[]) => void>
@@ -4,10 +4,11 @@
4
4
  * it must not ride a session-scoped slot — on the new-conversation screen no
5
5
  * session exists to scope a slot by, and the pet would vanish (issue #48).
6
6
  * The client half therefore mounts this entry straight onto 'document.body'
7
- * (see index.ts): while visible it renders the floating PetSprite (a
8
- * portal), while hidden it renders a fixed-position summon button. Which
9
- * sprite renders is decided by the host snapshot's pet id resolved against
10
- * the registry list — no per-pet component exists.
7
+ * (see index.ts): while visible it renders the floating PetSprite (a portal
8
+ * into the plugin root, so the root owns the whole surface), while hidden it
9
+ * renders a fixed-position summon button. Which sprite renders is decided by
10
+ * the host snapshot's pet id resolved against the registry list — no per-pet
11
+ * component exists.
11
12
  * @module @linxin666/dsh-pet/client/PetDockEntry
12
13
  */
13
14
 
@@ -52,6 +53,15 @@ export interface PetInjected {
52
53
  export type PetDockEntryProps =
53
54
  PetInjected
54
55
  & PropsLocale<typeof NS>
56
+ & {
57
+ /**
58
+ * DOM node the floating sprite chrome portals into. Defaults to
59
+ * document.body; the plugin apply passes its [data-dsh-plugin="pet"]
60
+ * root so root-keyed suppressors (the portrait mobile layer) and skins
61
+ * own the whole pet surface as one unit.
62
+ */
63
+ portalTarget?: Element
64
+ }
55
65
 
56
66
  const DEFAULT_DISPLAY: PetDisplayConfig = { visible: true, size: 160, right: 24, bottom: 20 }
57
67
 
@@ -108,6 +118,7 @@ export function PetDockEntry(props: PetDockEntryProps): ReactElement {
108
118
  onRename={props.rename}
109
119
  onOpenSession={props.openSession}
110
120
  onFeedbackDone={props.feedbackDone}
121
+ portalTarget={props.portalTarget}
111
122
  dragDisabled={snapshot.gameplay?.mode === 'work'}
112
123
  {...(gameplay === undefined || aux === null
113
124
  ? {}
@@ -9,7 +9,8 @@
9
9
 
10
10
  import type { ReactNode } from 'react'
11
11
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
12
- import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
12
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
13
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
13
14
  // Type-only: pulls the settings-surface SlotMap merge (the 'settings.section' entry).
14
15
  import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
15
16
  import { PluginSettingsCard, ValueField, BooleanField, ChoiceField } from './PluginSettingsCard.tsx'
@@ -1122,3 +1122,21 @@ describe('PetSprite status decoration (pet-center M5, #567)', () => {
1122
1122
  expect(timerSpy.mock.calls.length).toBe(schedulesBefore + 1)
1123
1123
  })
1124
1124
  })
1125
+
1126
+
1127
+ describe('PetSprite portal target', () => {
1128
+ it('portals the float into the provided target instead of document.body', () => {
1129
+ const host = document.createElement('div')
1130
+ document.body.appendChild(host)
1131
+ renderPet({ portalTarget: host })
1132
+ // The float (sprite chrome) lands inside the owning root, so a
1133
+ // root-keyed suppressor (the portrait mobile layer) hides it as one unit.
1134
+ expect(host.querySelector('[role="button"]')).not.toBeNull()
1135
+ })
1136
+
1137
+ it('portals the float into document.body when no target is given', () => {
1138
+ renderPet()
1139
+ expect(document.body.querySelector('[role="button"]')).not.toBeNull()
1140
+ })
1141
+ })
1142
+