@linxin666/dsh-pet 0.3.5 → 0.3.9

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 (88) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -0
  3. package/README.zh.md +4 -0
  4. package/contracts/voice-pack-v1.schema.json +30 -0
  5. package/cordis.patch.yml +1 -1
  6. package/lib/client.js +330 -103
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +145 -28
  9. package/lib/invariant.js +1 -1
  10. package/lib/live2d-vendor.js +106 -95
  11. package/lib/live2d-vendor.js.map +1 -1
  12. package/lib/{state-DrMX22GL.js → state-NvDEoCln.js} +5 -2
  13. package/lib/types/access.d.ts +5 -4
  14. package/lib/types/access.d.ts.map +1 -1
  15. package/lib/types/access.js +2 -12
  16. package/lib/types/affinity.d.ts +2 -0
  17. package/lib/types/affinity.d.ts.map +1 -1
  18. package/lib/types/affinity.js +12 -0
  19. package/lib/types/announce.d.ts +53 -0
  20. package/lib/types/announce.d.ts.map +1 -0
  21. package/lib/types/announce.js +80 -0
  22. package/lib/types/client/PetDockEntry.d.ts +14 -5
  23. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  24. package/lib/types/client/PetDockEntry.js +6 -5
  25. package/lib/types/client/PetSettingsCard.d.ts +2 -1
  26. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  27. package/lib/types/client/PetSprite.d.ts +8 -0
  28. package/lib/types/client/PetSprite.d.ts.map +1 -1
  29. package/lib/types/client/PetSprite.js +36 -5
  30. package/lib/types/client/index.d.ts +8 -5
  31. package/lib/types/client/index.d.ts.map +1 -1
  32. package/lib/types/client/index.js +11 -5
  33. package/lib/types/client/locales.d.ts +20 -2
  34. package/lib/types/client/locales.d.ts.map +1 -1
  35. package/lib/types/client/locales.js +20 -2
  36. package/lib/types/client/pet-store.d.ts +1 -1
  37. package/lib/types/client/pet-store.d.ts.map +1 -1
  38. package/lib/types/client/pet-store.js +1 -1
  39. package/lib/types/client/renderers/frames2d.d.ts +12 -0
  40. package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
  41. package/lib/types/client/renderers/frames2d.js +143 -18
  42. package/lib/types/client/settings-form.d.ts +35 -43
  43. package/lib/types/client/settings-form.d.ts.map +1 -1
  44. package/lib/types/client/settings-form.js +60 -53
  45. package/lib/types/index.d.ts.map +1 -1
  46. package/lib/types/index.js +10 -9
  47. package/lib/types/ledger.d.ts +4 -2
  48. package/lib/types/ledger.d.ts.map +1 -1
  49. package/lib/types/ledger.js +4 -4
  50. package/lib/types/pair-access.d.ts +35 -0
  51. package/lib/types/pair-access.d.ts.map +1 -0
  52. package/lib/types/pair-access.js +19 -0
  53. package/lib/types/remarks.d.ts +1 -1
  54. package/lib/types/remarks.d.ts.map +1 -1
  55. package/lib/types/remarks.js +11 -2
  56. package/lib/types/service.d.ts +18 -0
  57. package/lib/types/service.d.ts.map +1 -1
  58. package/lib/types/service.js +26 -1
  59. package/lib/types/voice-pack.d.ts +5 -0
  60. package/lib/types/voice-pack.d.ts.map +1 -1
  61. package/lib/types/voice-pack.js +36 -2
  62. package/package.json +17 -13
  63. package/src/access.ts +7 -27
  64. package/src/affinity.ts +13 -0
  65. package/src/announce.ts +103 -0
  66. package/src/client/PetDockEntry.test.tsx +1 -1
  67. package/src/client/PetDockEntry.tsx +15 -4
  68. package/src/client/PetSettingsCard.tsx +2 -1
  69. package/src/client/PetSprite.test.tsx +36 -0
  70. package/src/client/PetSprite.tsx +75 -4
  71. package/src/client/gameplay-hud.test.tsx +1 -1
  72. package/src/client/index.test.tsx +2 -2
  73. package/src/client/index.ts +17 -6
  74. package/src/client/locales.ts +20 -2
  75. package/src/client/pet-store.ts +2 -2
  76. package/src/client/pet.module.css +104 -0
  77. package/src/client/renderers/frames2d.test.ts +101 -0
  78. package/src/client/renderers/frames2d.ts +138 -18
  79. package/src/client/settings-form.ts +78 -92
  80. package/src/dsh-home.ts +1 -1
  81. package/src/index.ts +17 -15
  82. package/src/ledger.ts +6 -4
  83. package/src/pair-access.ts +49 -0
  84. package/src/remarks.test.ts +10 -0
  85. package/src/remarks.ts +9 -2
  86. package/src/service.ts +32 -2
  87. package/src/voice-pack.test.ts +39 -0
  88. package/src/voice-pack.ts +37 -2
@@ -149,11 +149,14 @@ function normalizePetRemarks(raw, onWarning = () => {}) {
149
149
  var RemarkPicker = class {
150
150
  counters = /* @__PURE__ */ new Map();
151
151
  pools;
152
- constructor(overrides) {
152
+ constructor(overrides, voiceOverrides) {
153
153
  this.pools = {};
154
154
  for (const kind of REMARK_KINDS) {
155
155
  const custom = overrides?.[kind];
156
- this.pools[kind] = custom !== void 0 && custom.length > 0 ? custom : BUILTIN_REMARKS[kind];
156
+ const voice = voiceOverrides?.[kind];
157
+ if (custom !== void 0 && custom.length > 0) this.pools[kind] = custom;
158
+ else if (voice !== void 0 && voice.length > 0) this.pools[kind] = voice;
159
+ else this.pools[kind] = BUILTIN_REMARKS[kind];
157
160
  }
158
161
  }
159
162
  /** The effective pool for one slot (custom override or built-in). */
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Pet trust fence: loopback (the desktop) always passes; a live paired-device
3
- * cookie is an additional allow path when remote-web-ui is loaded. The pet
4
- * never depends on that plugin — without the service the fence stays
5
- * loopback-only (same pattern as skill-explorer / aionui-panel).
2
+ * Pet trust fence: loopback (the desktop) always passes; a live
3
+ * paired-device cookie is an additional allow path when remote-web-ui is
4
+ * loaded. The pet never depends on that plugin — without the service the
5
+ * fence stays loopback-only. The decision logic lives in the generated
6
+ * pair-access.ts copy (shared by git-graph / pet / skill-explorer).
6
7
  */
7
8
  import type { IncomingMessage } from 'node:http';
8
9
  import type { Context } from '@deepseek-ai/cordis';
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/access.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAclD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAM5E"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE5E"}
@@ -1,4 +1,4 @@
1
- import { isLoopbackRequest } from "./loopback.js";
1
+ import { isPairedOrLoopbackAllowed } from "./pair-access.js";
2
2
  /**
3
3
  * Whether this request may enter any /api/pet or /pet asset route.
4
4
  * @param ctx - host context; may expose remoteWebUiPairing.
@@ -6,15 +6,5 @@ import { isLoopbackRequest } from "./loopback.js";
6
6
  * @returns true for loopback, or a live paired-device cookie.
7
7
  */
8
8
  export function isPetAllowed(ctx, request) {
9
- if (isLoopbackRequest(request))
10
- return true;
11
- const bag = ctx;
12
- const fromGet = typeof bag.get === 'function' ? bag.get('remoteWebUiPairing', false) : undefined;
13
- const pairing = (isPairingAccess(fromGet) ? fromGet : bag.remoteWebUiPairing);
14
- return pairing?.isPairedDevice(request) === true;
15
- }
16
- function isPairingAccess(value) {
17
- return value !== undefined
18
- && value !== null
19
- && typeof value.isPairedDevice === 'function';
9
+ return isPairedOrLoopbackAllowed(ctx, request);
20
10
  }
@@ -75,6 +75,8 @@ export declare const AFFINITY_RANKS: readonly [{
75
75
  readonly name: "鲸生共渡";
76
76
  readonly emoji: "*********";
77
77
  }];
78
+ /** English translations for the 9 built-in affinity rank names (#1226). */
79
+ export declare const AFFINITY_RANKS_EN: Readonly<Record<string, string>>;
78
80
  /** Interaction tuning (all in points / ms). */
79
81
  export interface AffinityConfig {
80
82
  /** Points per completed turn. */
@@ -1 +1 @@
1
- {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,YAAc,CAAA;AAEvC;;;;8EAI8E;AAC9E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUjB,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAW7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,eAAe,CAYjB;AAYD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA2CpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
1
+ {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,YAAc,CAAA;AAEvC;;;;8EAI8E;AAC9E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUjB,CAAA;AAEV,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAU9D,CAAA;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAW7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,eAAe,CAYjB;AAYD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA2CpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
@@ -29,6 +29,18 @@ export const AFFINITY_RANKS = [
29
29
  { min: 10_000, name: '永恒之契', emoji: '********' },
30
30
  { min: 100_000, name: '鲸生共渡', emoji: '*********' },
31
31
  ];
32
+ /** English translations for the 9 built-in affinity rank names (#1226). */
33
+ export const AFFINITY_RANKS_EN = {
34
+ '幼鲸': 'Baby Whale',
35
+ '伙伴': 'Companion',
36
+ '挚友': 'Close Friend',
37
+ '深海羁绊': 'Deep Sea Bond',
38
+ '心有灵犀': 'Kindred Spirit',
39
+ '传说羁绊': 'Legendary Bond',
40
+ '神话羁绊': 'Mythic Bond',
41
+ '永恒之契': 'Eternal Covenant',
42
+ '鲸生共渡': 'Lifelong Companion',
43
+ };
32
44
  export const defaultAffinityConfig = {
33
45
  turnReward: 1,
34
46
  petReward: 1,
@@ -0,0 +1,53 @@
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
+ /** One plugin-authored announcement bubble. */
12
+ export interface PetAnnouncement {
13
+ /** Authoring plugin's source tag (`dsh-usage` for the usage statistics). */
14
+ source: string;
15
+ /** Bubble content kind: a spend estimate, an account balance, or a plan-quota status. */
16
+ kind: 'balance' | 'cost' | 'plan';
17
+ /** Lead text (usually the provider display name). */
18
+ title: string;
19
+ /** Balance or today-spend amount, formatted for display (kinds `balance` and `cost`). */
20
+ amount?: string;
21
+ /** Plan usage percent 0-100 (kind `plan`). */
22
+ percent?: number;
23
+ /** ISO 8601 reset instant (kind `plan`). */
24
+ resetAt?: string;
25
+ /** Short trailing note (plan tier name, peak-period status, currency code, ...). */
26
+ note?: string;
27
+ /** Visual tone; drives the bubble's accent color. */
28
+ tone: 'ok' | 'warn' | 'low';
29
+ /** Freshness window in ms; an expired announcement stops rendering. */
30
+ ttlMs: number;
31
+ /** Epoch ms the announcement arrived. */
32
+ at: number;
33
+ }
34
+ /** Default freshness window. */
35
+ export declare const ANNOUNCE_DEFAULT_TTL_MS = 10000;
36
+ /**
37
+ * Hard TTL ceiling. A repeating announcer (dsh-usage) declares its poll
38
+ * cadence as the TTL, so an `always`-mode bubble stays continuous across
39
+ * polls; the ceiling means a source that dies unmounts its bubble within at
40
+ * most one missed refresh cycle rather than lingering forever.
41
+ */
42
+ export declare const ANNOUNCE_MAX_TTL_MS = 7200000;
43
+ /**
44
+ * Validate one announce payload. Unknown fields are dropped, oversized text
45
+ * is truncated, and anything structurally wrong resolves to undefined — a
46
+ * malformed announcement never reaches the pet's bubble surface.
47
+ * @param input - the payload a sibling plugin passed to `pet.announce`.
48
+ * @param now - epoch ms the announcement arrived.
49
+ */
50
+ export declare function parseAnnouncement(input: unknown, now: number): PetAnnouncement | undefined;
51
+ /** Whether an announcement is still fresh at `now`. */
52
+ export declare function announcementFresh(announcement: PetAnnouncement, now: number): boolean;
53
+ //# sourceMappingURL=announce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../../src/announce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,yFAAyF;IACzF,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,KAAK,CAAA;IAC3B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAA;CACX;AAED,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,UAAY,CAAA;AAc5C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAgC1F;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAErF"}
@@ -0,0 +1,80 @@
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
+ /** Default freshness window. */
12
+ export const ANNOUNCE_DEFAULT_TTL_MS = 10_000;
13
+ /**
14
+ * Hard TTL ceiling. A repeating announcer (dsh-usage) declares its poll
15
+ * cadence as the TTL, so an `always`-mode bubble stays continuous across
16
+ * polls; the ceiling means a source that dies unmounts its bubble within at
17
+ * most one missed refresh cycle rather than lingering forever.
18
+ */
19
+ export const ANNOUNCE_MAX_TTL_MS = 7_200_000;
20
+ /** Hard bounds: lengths and the TTL range (1 s .. 60 s). */
21
+ const TITLE_MAX = 80;
22
+ const TEXT_MAX = 120;
23
+ const SOURCE_MAX = 64;
24
+ function boundedString(value, max) {
25
+ if (typeof value !== 'string')
26
+ return undefined;
27
+ const trimmed = value.trim();
28
+ if (trimmed === '')
29
+ return undefined;
30
+ return trimmed.length <= max ? trimmed : trimmed.slice(0, max);
31
+ }
32
+ /**
33
+ * Validate one announce payload. Unknown fields are dropped, oversized text
34
+ * is truncated, and anything structurally wrong resolves to undefined — a
35
+ * malformed announcement never reaches the pet's bubble surface.
36
+ * @param input - the payload a sibling plugin passed to `pet.announce`.
37
+ * @param now - epoch ms the announcement arrived.
38
+ */
39
+ export function parseAnnouncement(input, now) {
40
+ if (typeof input !== 'object' || input === null)
41
+ return undefined;
42
+ const data = input;
43
+ const source = boundedString(data.source, SOURCE_MAX);
44
+ const title = boundedString(data.title, TITLE_MAX);
45
+ if (source === undefined || title === undefined)
46
+ return undefined;
47
+ const kind = data.kind === 'balance' || data.kind === 'cost' || data.kind === 'plan' ? data.kind : undefined;
48
+ if (kind === undefined)
49
+ return undefined;
50
+ const amount = boundedString(data.amount, TEXT_MAX);
51
+ const resetAt = boundedString(data.resetAt, TEXT_MAX);
52
+ const note = boundedString(data.note, TITLE_MAX);
53
+ const percent = typeof data.percent === 'number' && Number.isFinite(data.percent)
54
+ ? Math.max(0, Math.min(100, data.percent))
55
+ : undefined;
56
+ if ((kind === 'balance' || kind === 'cost') && amount === undefined)
57
+ return undefined;
58
+ if (kind === 'plan' && percent === undefined)
59
+ return undefined;
60
+ const tone = data.tone === 'warn' || data.tone === 'low' ? data.tone : 'ok';
61
+ const ttlMs = typeof data.ttlMs === 'number' && Number.isFinite(data.ttlMs)
62
+ ? Math.max(1000, Math.min(ANNOUNCE_MAX_TTL_MS, data.ttlMs))
63
+ : ANNOUNCE_DEFAULT_TTL_MS;
64
+ return {
65
+ source,
66
+ kind,
67
+ title,
68
+ ...(amount !== undefined ? { amount } : {}),
69
+ ...(percent !== undefined ? { percent } : {}),
70
+ ...(resetAt !== undefined ? { resetAt } : {}),
71
+ ...(note !== undefined ? { note } : {}),
72
+ tone,
73
+ ttlMs,
74
+ at: now,
75
+ };
76
+ }
77
+ /** Whether an announcement is still fresh at `now`. */
78
+ export function announcementFresh(announcement, now) {
79
+ return now - announcement.at < announcement.ttlMs;
80
+ }
@@ -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
  import { type ReactElement } from 'react';
@@ -41,7 +42,15 @@ export interface PetInjected {
41
42
  gameplay: GameplayApi;
42
43
  }
43
44
  /** Composed props of the global pet entry (locale + injected; no slot runtime share). */
44
- export type PetDockEntryProps = PetInjected & PropsLocale<typeof NS>;
45
+ export type PetDockEntryProps = PetInjected & PropsLocale<typeof NS> & {
46
+ /**
47
+ * DOM node the floating sprite chrome portals into. Defaults to
48
+ * document.body; the plugin apply passes its [data-dsh-plugin="pet"]
49
+ * root so root-keyed suppressors (the portrait mobile layer) and skins
50
+ * own the whole pet surface as one unit.
51
+ */
52
+ portalTarget?: Element;
53
+ };
45
54
  /**
46
55
  * Dock entry: while the pet is visible, mount the floating PetSprite (it
47
56
  * portals itself onto document.body); while hidden, render the summon
@@ -1 +1 @@
1
- {"version":3,"file":"PetDockEntry.d.ts","sourceRoot":"","sources":["../../../src/client/PetDockEntry.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAItD,OAAO,EAAe,KAAK,WAAW,EAAoB,MAAM,oBAAoB,CAAA;AACpF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,gBAAgB,CAAA;IACvB,kFAAkF;IAClF,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAA;IACf,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAChD,uDAAuD;IACvD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,2DAA2D;IAC3D,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,iCAAiC;IACjC,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,uGAAuG;IACvG,QAAQ,EAAE,WAAW,CAAA;CACtB;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAC3B,WAAW,GACT,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;AAI1B;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAwFnE"}
1
+ {"version":3,"file":"PetDockEntry.d.ts","sourceRoot":"","sources":["../../../src/client/PetDockEntry.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAItD,OAAO,EAAe,KAAK,WAAW,EAAoB,MAAM,oBAAoB,CAAA;AACpF,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,gBAAgB,CAAA;IACvB,kFAAkF;IAClF,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAA;IACf,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAChD,uDAAuD;IACvD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,2DAA2D;IAC3D,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,iCAAiC;IACjC,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,uGAAuG;IACvG,QAAQ,EAAE,WAAW,CAAA;CACtB;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAC3B,WAAW,GACT,WAAW,CAAC,OAAO,EAAE,CAAC,GACtB;IACA;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAIH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAyFnE"}
@@ -5,10 +5,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  * it must not ride a session-scoped slot — on the new-conversation screen no
6
6
  * session exists to scope a slot by, and the pet would vanish (issue #48).
7
7
  * The client half therefore mounts this entry straight onto 'document.body'
8
- * (see index.ts): while visible it renders the floating PetSprite (a
9
- * portal), while hidden it renders a fixed-position summon button. Which
10
- * sprite renders is decided by the host snapshot's pet id resolved against
11
- * the registry list — no per-pet component exists.
8
+ * (see index.ts): while visible it renders the floating PetSprite (a portal
9
+ * into the plugin root, so the root owns the whole surface), while hidden it
10
+ * renders a fixed-position summon button. Which sprite renders is decided by
11
+ * the host snapshot's pet id resolved against the registry list — no per-pet
12
+ * component exists.
12
13
  * @module @linxin666/dsh-pet/client/PetDockEntry
13
14
  */
14
15
  import { useEffect, useRef, useSyncExternalStore } from 'react';
@@ -46,7 +47,7 @@ export function PetDockEntry(props) {
46
47
  if (visible) {
47
48
  return (_jsx("span", { "data-pet-dock": true, "data-testid": "pet-dock", children: snapshot === null || definition === null
48
49
  ? null
49
- : (_jsx(PetRendererSwitch, { definition: definition, phase: snapshot?.phase ?? 'idle', onPet: props.pet, ...(aux === null ? {} : { drag: aux.drag, bus: aux.bus }), t: props.t, children: _jsx(PetSprite, { snapshot: snapshot, definition: definition, display: snapshot.display, feedback: feedback, onPet: props.pet, onFeed: props.feed, onHide: props.hide, onDragEnd: props.dragEnd, onRename: props.rename, onOpenSession: props.openSession, onFeedbackDone: props.feedbackDone, dragDisabled: snapshot.gameplay?.mode === 'work', ...(gameplay === undefined || aux === null
50
+ : (_jsx(PetRendererSwitch, { definition: definition, phase: snapshot?.phase ?? 'idle', onPet: props.pet, ...(aux === null ? {} : { drag: aux.drag, bus: aux.bus }), t: props.t, children: _jsx(PetSprite, { snapshot: snapshot, definition: definition, display: snapshot.display, feedback: feedback, onPet: props.pet, onFeed: props.feed, onHide: props.hide, onDragEnd: props.dragEnd, onRename: props.rename, onOpenSession: props.openSession, onFeedbackDone: props.feedbackDone, portalTarget: props.portalTarget, dragDisabled: snapshot.gameplay?.mode === 'work', ...(gameplay === undefined || aux === null
50
51
  ? {}
51
52
  : {
52
53
  onGameplayTap: (fx, fy) => aux.bus.tap?.(fx, fy),
@@ -8,7 +8,8 @@
8
8
  */
9
9
  import type { ReactNode } from 'react';
10
10
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
11
- import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
11
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
12
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
12
13
  import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
13
14
  /** The pet's settings fields this card edits (the namespace's full schema). */
14
15
  export interface PetSettings {
@@ -1 +1 @@
1
- {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
@@ -68,6 +68,14 @@ export interface PetSpriteProps {
68
68
  onGameplayMenu?: () => void;
69
69
  /** Disable the drag gesture (gameplay work mode blocks dragging). */
70
70
  dragDisabled?: boolean;
71
+ /**
72
+ * DOM node the floating chrome portals into. Defaults to document.body
73
+ * (the legacy behavior); the plugin apply passes its owning root (the
74
+ * [data-dsh-plugin="pet"] container) so the root owns the whole surface
75
+ * and a root-keyed suppressor (the portrait mobile layer) hides the pet
76
+ * as one unit instead of missing the portaled sprite.
77
+ */
78
+ portalTarget?: Element;
71
79
  /** Locale translate seat (namespace-bound). */
72
80
  t: TranslateNS<typeof NS>;
73
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,SAAS,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAgGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAqiB5D"}
1
+ {"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,SAAS,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AA8ID;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAqjB5D"}
@@ -12,6 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
12
12
  import { useEffect, useLayoutEffect, useRef, useState } from 'react';
13
13
  import { createPortal } from 'react-dom';
14
14
  import clsx from 'clsx';
15
+ import { announcementFresh } from "../announce.js";
15
16
  import { framePosition, rowOfTrack, trimTrack } from "./spritesheet.js";
16
17
  import { sequenceFrameAt } from "./sequences.js";
17
18
  import { animationForPhase } from "../state.js";
@@ -108,6 +109,23 @@ function StatusOrnament(props) {
108
109
  backgroundPosition: '0px 0px',
109
110
  } }));
110
111
  }
112
+ /**
113
+ * The announcement bubble (dsh-usage linkage): a dedicated, specially
114
+ * designed surface for sibling-plugin facts — a balance or today-spend pill,
115
+ * or a plan-quota card with a tone-tinted accent, a mini meter for percent
116
+ * windows, and the reset instant. It rides the top of the session bubble
117
+ * stack (column-reverse puts the DOM-last child farthest from the sprite)
118
+ * and persists for its TTL instead of the short feedback pop.
119
+ */
120
+ function UsageAnnouncementBubble(props) {
121
+ const { announcement } = props;
122
+ const tone = announcement.tone === 'low'
123
+ ? styles.bubbleUsageLow
124
+ : announcement.tone === 'warn'
125
+ ? styles.bubbleUsageWarn
126
+ : styles.bubbleUsageOk;
127
+ return (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleUsage, tone), role: "status", "aria-live": "polite", "data-dsh-pet-announcement": announcement.source, children: [_jsxs("span", { className: styles.bubbleUsageHead, children: [_jsx("span", { className: styles.bubbleUsageTitle, children: announcement.title }), (announcement.kind === 'balance' || announcement.kind === 'cost') && announcement.amount !== undefined && (_jsx("span", { className: styles.bubbleUsageValue, children: announcement.amount })), announcement.kind === 'plan' && announcement.percent !== undefined && (_jsx("span", { className: styles.bubbleUsageValue, children: Math.round(announcement.percent) + '%' }))] }), announcement.kind === 'plan' && announcement.percent !== undefined && (_jsx("span", { className: styles.bubbleUsageMeter, children: _jsx("span", { className: styles.bubbleUsageMeterFill, style: { width: Math.min(100, Math.max(0, announcement.percent)) + '%' } }) })), announcement.note !== undefined && (_jsx("span", { className: styles.bubbleUsageNote, children: announcement.note }))] }));
128
+ }
111
129
  /**
112
130
  * The floating pet. The spritesheet frame advances on requestAnimationFrame
113
131
  * with per-frame durations from the definition's tracks; the atlas image is
@@ -360,6 +378,13 @@ export function PetSprite(props) {
360
378
  const statusBubble = feedback === null && sessionBubbles.length === 0
361
379
  ? snapshot?.bubble
362
380
  : undefined;
381
+ // The freshest plugin-authored announcement (dsh-usage linkage): a
382
+ // dedicated, specially styled bubble above the session stack. The host
383
+ // already TTL-filters; this client-side check covers the last poll tick.
384
+ const announcement = snapshot?.announcement;
385
+ const usageAnnouncement = feedback === null && announcement !== undefined && announcementFresh(announcement, Date.now())
386
+ ? announcement
387
+ : undefined;
363
388
  // Each session's inner whisper (碎碎念) rides its own bubble — short
364
389
  // inner-voice copy woken by that session's activity, never the model's or
365
390
  // another session's. Instead of a second bubble of its own, a fresh
@@ -367,7 +392,7 @@ export function PetSprite(props) {
367
392
  // never wears two voices at once. Interaction feedback takes over the
368
393
  // whole bubble area while it plays, so whispers yield to it like status
369
394
  // copy.
370
- const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined;
395
+ const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined || usageAnnouncement !== undefined;
371
396
  const displayName = snapshot?.name ?? definition.displayName;
372
397
  // The host-served status decoration (M5, #567); absent = text-only bubbles.
373
398
  const decoration = snapshot?.decoration;
@@ -447,7 +472,7 @@ export function PetSprite(props) {
447
472
  }
448
473
  }
449
474
  props.onPet();
450
- }, role: "button", "aria-label": definition.displayName, children: props.visual }) }), props.hud, feedback !== null && (_jsx("div", { ref: bubbleRef, className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), feedback === null && (sessionBubbles.length > 0 || statusBubble !== undefined) && (_jsxs("div", { ref: bubbleRef, className: styles.bubbleStack, onPointerEnter: () => setStackPeek(true), onPointerLeave: () => setStackPeek(false), children: [visibleSessions.map((session, index) => {
475
+ }, role: "button", "aria-label": definition.displayName, children: props.visual }) }), props.hud, feedback !== null && (_jsx("div", { ref: bubbleRef, className: clsx(styles.bubble, feedback.kind === 'feed' ? styles.bubbleFeed : styles.bubblePet), children: feedback.text }, feedback.at)), feedback === null && (sessionBubbles.length > 0 || statusBubble !== undefined || usageAnnouncement !== undefined) && (_jsxs("div", { ref: bubbleRef, className: styles.bubbleStack, onPointerEnter: () => setStackPeek(true), onPointerLeave: () => setStackPeek(false), children: [visibleSessions.map((session, index) => {
451
476
  // A session's whisper rides ITS OWN bubble (the stack lead when
452
477
  // the current session has one, any bubble when the stack is
453
478
  // expanded). The key swap restarts the entrance animation so the
@@ -466,7 +491,7 @@ export function PetSprite(props) {
466
491
  e.stopPropagation();
467
492
  setStackPinned(open => !open);
468
493
  }, children: stackOpen ? '×' : '+' + String(sessionBubbles.length - 1) })] }, "primary"));
469
- }), sessionBubbles.length === 0 && statusBubble !== undefined && (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleStatus), role: "status", "aria-live": "polite", children: [decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), statusBubble] }, "status"))] })), hovered && dragRef.current === null && (_jsx("div", { ref: panelRef, className: clsx(styles.panel, panelAbove && styles.panelAbove), "data-placement": panelAbove ? 'above' : 'below', style: panelAbove && panelLift > 0
494
+ }), sessionBubbles.length === 0 && statusBubble !== undefined && (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleStatus), role: "status", "aria-live": "polite", children: [decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), statusBubble] }, "status")), usageAnnouncement !== undefined && _jsx(UsageAnnouncementBubble, { announcement: usageAnnouncement })] })), hovered && dragRef.current === null && (_jsx("div", { ref: panelRef, className: clsx(styles.panel, panelAbove && styles.panelAbove), "data-placement": panelAbove ? 'above' : 'below', style: panelAbove && panelLift > 0
470
495
  ? { marginBottom: panelLift }
471
496
  : undefined, onPointerEnter: () => {
472
497
  // Reaching the panel (or its bridge) must cancel any hide timer
@@ -498,12 +523,18 @@ export function PetSprite(props) {
498
523
  props.onRename(trimmed);
499
524
  setRenaming(false);
500
525
  }
501
- }, children: panelLabel('confirm', props.t('pet.confirm')) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children: panelStat('rank', 'pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.statTreats, children: panelStat('treats', 'pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { className: styles.statPoints, children: panelStat('points', 'pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [panelShows('feed') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: panelLabel('feed', props.t('pet.feed')) })), panelShows('rename') && (_jsx("button", { type: "button", className: styles.action, onClick: () => {
526
+ }, children: panelLabel('confirm', props.t('pet.confirm')) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children: (() => {
527
+ const rawRank = snapshot?.affinity.rank ?? '?';
528
+ const rankKey = `pet.rank.name.${rawRank}`;
529
+ const localized = props.t(rankKey);
530
+ const rankName = localized !== rankKey ? localized : rawRank;
531
+ return panelStat('rank', 'pet.rank', { rank: rankName });
532
+ })() })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.statTreats, children: panelStat('treats', 'pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { className: styles.statPoints, children: panelStat('points', 'pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [panelShows('feed') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: panelLabel('feed', props.t('pet.feed')) })), panelShows('rename') && (_jsx("button", { type: "button", className: styles.action, onClick: () => {
502
533
  // Cancel any pending hide so the rename box cannot
503
534
  // unmount right as the user starts typing (#303).
504
535
  clearHideTimer();
505
536
  setNameDraft(displayName);
506
537
  setRenaming(true);
507
538
  }, children: panelLabel('rename', props.t('pet.rename')) })), panelShows('hide') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: panelLabel('hide', props.t('pet.hide')) })), props.onGameplayMenu !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: props.onGameplayMenu, children: props.t('pet.gameplay.menu') }))] })] })) }))] }));
508
- return createPortal(float, document.body);
539
+ return createPortal(float, props.portalTarget ?? document.body);
509
540
  }
@@ -4,13 +4,16 @@
4
4
  * endpoints: fetch the registry list once, poll the host snapshot (~2 s),
5
5
  * forward interactions, persist drag positions. The pet is host-global (no
6
6
  * session dimension), so it mounts directly onto 'document.body' via a
7
- * single React root rather than a session-scoped sloton the
8
- * new-conversation screen no session exists, and a dock-mounted pet would
9
- * vanish there (issue #48). When the pet is hidden the entry becomes a
10
- * fixed-position summon button.
7
+ * single React root the [data-dsh-plugin="pet"] containerrather than a
8
+ * session-scoped slot: on the new-conversation screen no session exists, and
9
+ * a dock-mounted pet would vanish there (issue #48). The sprite chrome
10
+ * portals into that same root, so the root owns the whole surface and a
11
+ * root-keyed suppressor can hide it as one unit. When the pet is hidden the
12
+ * entry becomes a fixed-position summon button.
11
13
  * @module @linxin666/dsh-pet/client
12
14
  */
13
- import type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client';
15
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
16
+ import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
14
17
  /** Required services (sessions powers bubble-to-session navigation). */
15
18
  export declare const inject: string[];
16
19
  /** Re-exported for consumers that type against the injected face. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA4EnI,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAgT9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AA8ElG,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoT9C"}
@@ -4,10 +4,12 @@
4
4
  * endpoints: fetch the registry list once, poll the host snapshot (~2 s),
5
5
  * forward interactions, persist drag positions. The pet is host-global (no
6
6
  * session dimension), so it mounts directly onto 'document.body' via a
7
- * single React root rather than a session-scoped sloton the
8
- * new-conversation screen no session exists, and a dock-mounted pet would
9
- * vanish there (issue #48). When the pet is hidden the entry becomes a
10
- * fixed-position summon button.
7
+ * single React root the [data-dsh-plugin="pet"] containerrather than a
8
+ * session-scoped slot: on the new-conversation screen no session exists, and
9
+ * a dock-mounted pet would vanish there (issue #48). The sprite chrome
10
+ * portals into that same root, so the root owns the whole surface and a
11
+ * root-keyed suppressor can hide it as one unit. When the pet is hidden the
12
+ * entry becomes a fixed-position summon button.
11
13
  * @module @linxin666/dsh-pet/client
12
14
  */
13
15
  import { createElement } from 'react';
@@ -325,7 +327,11 @@ export function apply(ctx) {
325
327
  container.dataset.dshPlugin = 'pet';
326
328
  document.body.appendChild(container);
327
329
  const petRoot = createRoot(container);
328
- petRoot.render(createElement(PetDockEntry, { ...injected(), t }));
330
+ // The sprite chrome portals into THIS root (not document.body): the
331
+ // root then owns the whole surface, so a root-keyed suppressor (the
332
+ // portrait mobile layer, which hides [data-dsh-plugin="pet"]) really
333
+ // hides the sprite instead of missing the portaled float.
334
+ petRoot.render(createElement(PetDockEntry, { ...injected(), t, portalTarget: container }));
329
335
  let uiGone = false;
330
336
  disposeUi = () => {
331
337
  if (uiGone)
@@ -13,6 +13,15 @@ export declare const zh: {
13
13
  readonly 'pet.namePlaceholder': "输入新名字";
14
14
  readonly 'pet.summon': "召唤{name}";
15
15
  readonly 'pet.rank': "亲密度 {rank}";
16
+ readonly 'pet.rank.name.\u5E7C\u9CB8': "幼鲸";
17
+ readonly 'pet.rank.name.\u4F19\u4F34': "伙伴";
18
+ readonly 'pet.rank.name.\u631A\u53CB': "挚友";
19
+ readonly 'pet.rank.name.\u6DF1\u6D77\u7F81\u7ECA': "深海羁绊";
20
+ readonly 'pet.rank.name.\u5FC3\u6709\u7075\u7280': "心有灵犀";
21
+ readonly 'pet.rank.name.\u4F20\u8BF4\u7F81\u7ECA': "传说羁绊";
22
+ readonly 'pet.rank.name.\u795E\u8BDD\u7F81\u7ECA': "神话羁绊";
23
+ readonly 'pet.rank.name.\u6C38\u6052\u4E4B\u5951': "永恒之契";
24
+ readonly 'pet.rank.name.\u9CB8\u751F\u5171\u6E21': "鲸生共渡";
16
25
  readonly 'pet.points': "{points} 点";
17
26
  readonly 'pet.treats': "小鱼干 ×{n}";
18
27
  readonly 'pet.state.loading': "宠物正在赶来…";
@@ -63,7 +72,7 @@ export declare const zh: {
63
72
  readonly 'settings.off': "关";
64
73
  readonly 'settings.overridden': "已覆盖";
65
74
  readonly 'settings.reset': "恢复默认";
66
- readonly 'settings.notExposed': "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。";
75
+ readonly 'settings.notExposed': "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 $DSH_HOME/settings.yaml 直接配置,或确认提供该命名空间的插件已挂载其设置域并重启。";
67
76
  readonly 'settings.readOnly': "当前部署的设置只读。";
68
77
  readonly 'settings.expand': "展开设置";
69
78
  readonly 'settings.collapse': "收起设置";
@@ -83,6 +92,15 @@ export declare const en: {
83
92
  readonly 'pet.namePlaceholder': "Enter a new name";
84
93
  readonly 'pet.summon': "Summon {name}";
85
94
  readonly 'pet.rank': "Affinity {rank}";
95
+ readonly 'pet.rank.name.\u5E7C\u9CB8': "Baby Whale";
96
+ readonly 'pet.rank.name.\u4F19\u4F34': "Companion";
97
+ readonly 'pet.rank.name.\u631A\u53CB': "Close Friend";
98
+ readonly 'pet.rank.name.\u6DF1\u6D77\u7F81\u7ECA': "Deep Sea Bond";
99
+ readonly 'pet.rank.name.\u5FC3\u6709\u7075\u7280': "Kindred Spirit";
100
+ readonly 'pet.rank.name.\u4F20\u8BF4\u7F81\u7ECA': "Legendary Bond";
101
+ readonly 'pet.rank.name.\u795E\u8BDD\u7F81\u7ECA': "Mythic Bond";
102
+ readonly 'pet.rank.name.\u6C38\u6052\u4E4B\u5951': "Eternal Covenant";
103
+ readonly 'pet.rank.name.\u9CB8\u751F\u5171\u6E21': "Lifelong Companion";
86
104
  readonly 'pet.points': "{points} pts";
87
105
  readonly 'pet.treats': "Treats ×{n}";
88
106
  readonly 'pet.state.loading': "The pet is on its way…";
@@ -133,7 +151,7 @@ export declare const en: {
133
151
  readonly 'settings.off': "Off";
134
152
  readonly 'settings.overridden': "Overridden";
135
153
  readonly 'settings.reset': "Reset to default";
136
- readonly 'settings.notExposed': "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy's WEB_SETTINGS_NAMESPACES allowlist and restart.";
154
+ readonly 'settings.notExposed': "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit $DSH_HOME/settings.yaml directly, or confirm that the plugin owning the namespace is mounted with its settings domain and restart.";
137
155
  readonly 'settings.readOnly': "This deployment stores settings read-only.";
138
156
  readonly 'settings.expand': "Show settings";
139
157
  readonly 'settings.collapse': "Hide settings";
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsEL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsEL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+EL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+EL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}