@linxin666/dsh-pet 0.1.18 → 0.1.20

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 (90) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +12 -5
  3. package/README.zh.md +12 -5
  4. package/lib/client.js +95 -26
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +144 -31
  7. package/lib/invariant.js +2 -2
  8. package/lib/state-Diiq8vjk.js +394 -0
  9. package/lib/types/affinity.d.ts +39 -11
  10. package/lib/types/affinity.d.ts.map +1 -1
  11. package/lib/types/affinity.js +21 -7
  12. package/lib/types/client/PetDockEntry.d.ts +2 -0
  13. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  14. package/lib/types/client/PetDockEntry.js +2 -2
  15. package/lib/types/client/PetSettingsCard.d.ts +5 -0
  16. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  17. package/lib/types/client/PetSettingsCard.js +9 -2
  18. package/lib/types/client/PetSprite.d.ts +2 -0
  19. package/lib/types/client/PetSprite.d.ts.map +1 -1
  20. package/lib/types/client/PetSprite.js +22 -15
  21. package/lib/types/client/PluginSettingsCard.d.ts +1 -1
  22. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  23. package/lib/types/client/index.d.ts +1 -1
  24. package/lib/types/client/index.d.ts.map +1 -1
  25. package/lib/types/client/index.js +45 -14
  26. package/lib/types/client/locales.d.ts +82 -80
  27. package/lib/types/client/locales.d.ts.map +1 -1
  28. package/lib/types/client/locales.js +2 -0
  29. package/lib/types/client/settings-form.d.ts +8 -0
  30. package/lib/types/client/settings-form.d.ts.map +1 -1
  31. package/lib/types/client/settings-form.js +15 -1
  32. package/lib/types/client/spritesheet.d.ts +1 -1
  33. package/lib/types/client/spritesheet.d.ts.map +1 -1
  34. package/lib/types/client/spritesheet.js +3 -12
  35. package/lib/types/event-projection.d.ts.map +1 -1
  36. package/lib/types/event-projection.js +3 -1
  37. package/lib/types/index.d.ts +6 -3
  38. package/lib/types/index.d.ts.map +1 -1
  39. package/lib/types/index.js +15 -12
  40. package/lib/types/invariant.js +2 -2
  41. package/lib/types/ledger.d.ts +16 -1
  42. package/lib/types/ledger.d.ts.map +1 -1
  43. package/lib/types/ledger.js +31 -6
  44. package/lib/types/mount-once.d.ts +25 -0
  45. package/lib/types/mount-once.d.ts.map +1 -0
  46. package/lib/types/mount-once.js +42 -0
  47. package/lib/types/persist.js +3 -3
  48. package/lib/types/registry.d.ts +10 -0
  49. package/lib/types/registry.d.ts.map +1 -1
  50. package/lib/types/registry.js +3 -0
  51. package/lib/types/remarks.d.ts +56 -0
  52. package/lib/types/remarks.d.ts.map +1 -0
  53. package/lib/types/remarks.js +157 -0
  54. package/lib/types/routes.js +2 -2
  55. package/lib/types/service.d.ts +44 -3
  56. package/lib/types/service.d.ts.map +1 -1
  57. package/lib/types/service.js +100 -23
  58. package/lib/types/state.d.ts.map +1 -1
  59. package/lib/types/state.js +5 -5
  60. package/lib/types/treats.d.ts +3 -0
  61. package/lib/types/treats.d.ts.map +1 -1
  62. package/lib/types/treats.js +5 -2
  63. package/package.json +1 -1
  64. package/src/affinity.test.ts +19 -0
  65. package/src/affinity.ts +22 -7
  66. package/src/client/PetDockEntry.tsx +3 -0
  67. package/src/client/PetSettingsCard.tsx +8 -0
  68. package/src/client/PetSprite.test.tsx +89 -3
  69. package/src/client/PetSprite.tsx +36 -12
  70. package/src/client/index.ts +40 -11
  71. package/src/client/locales.ts +2 -0
  72. package/src/client/pet.module.css +52 -10
  73. package/src/client/settings-form.ts +15 -1
  74. package/src/client/spritesheet.ts +3 -13
  75. package/src/event-projection.ts +3 -1
  76. package/src/index.ts +16 -2
  77. package/src/ledger.test.ts +40 -9
  78. package/src/ledger.ts +33 -4
  79. package/src/mount-once.ts +48 -0
  80. package/src/persist.test.ts +1 -1
  81. package/src/registry.test.ts +27 -0
  82. package/src/registry.ts +12 -0
  83. package/src/remarks.test.ts +89 -0
  84. package/src/remarks.ts +177 -0
  85. package/src/service.ts +124 -18
  86. package/src/state.test.ts +9 -0
  87. package/src/state.ts +5 -5
  88. package/src/treats.test.ts +13 -11
  89. package/src/treats.ts +5 -2
  90. package/lib/state-CFyJv0sQ.js +0 -213
@@ -5,6 +5,10 @@
5
5
  * persistence through `persist`. The API gateway maps these methods onto
6
6
  * `pet.state` / `pet.pets` / `pet.interact` / `pet.setVisible` /
7
7
  * `pet.setConfig` / `pet.setName` / `pet.setPet` for browser consumers.
8
+ *
9
+ * Concurrent sessions each keep their own machine: the sprite animation
10
+ * follows the most recent meaningful event (the display session) while the
11
+ * state view carries one bubble per active session.
8
12
  * @module @linxin666/dsh-pet/service
9
13
  */
10
14
  import { Context, Service } from '@deepseek-ai/cordis';
@@ -54,12 +58,39 @@ export interface PetSettingsSection {
54
58
  }
55
59
  /** Settings namespace of the pet capability. Spelled here rather than imported: the browser half spells the same value. */
56
60
  export declare const PET_SETTINGS_NAMESPACE = "pet";
61
+ /**
62
+ * One active TOP-LEVEL session as the pet displays it. Sessions run in
63
+ * parallel, so each gets its own bubble while the sprite itself follows the
64
+ * most recent meaningful event (the display session). Subagent children
65
+ * report no bubble of their own: their work is already reflected by the
66
+ * bubble of the conversation that spawned them, and the bubble buttons
67
+ * navigate to GUI sessions, which subagents are not.
68
+ */
69
+ export interface PetSessionView {
70
+ /** Session identity (stringified for the wire; never exposed as a key). */
71
+ sessionId: string;
72
+ /** The animation this session's activity maps onto. */
73
+ animation: PetStateSnapshot['animation'];
74
+ /** This session's status bubble copy. */
75
+ bubble: string;
76
+ /** This session's raw activity phase. */
77
+ phase: PetStateSnapshot['phase'];
78
+ }
79
+ /** Hard cap on simultaneously displayed session bubbles (most recent first). */
80
+ export declare const MAX_SESSION_BUBBLES = 12;
57
81
  /** Snapshot returned by `pet.state`. */
58
82
  export interface PetStateView {
59
83
  animation: PetStateSnapshot['animation'];
60
84
  bubble?: string;
61
85
  phase: PetStateSnapshot['phase'];
62
86
  sessionActive: boolean;
87
+ /**
88
+ * Per-session bubbles for every concurrently active TOP-LEVEL session,
89
+ * most recent first; optional so older hosts without the multi-session
90
+ * view stay consumable. The single 'bubble' above mirrors the display
91
+ * session.
92
+ */
93
+ sessions?: PetSessionView[];
63
94
  /** Affinity ledger snapshot. */
64
95
  affinity: PetAffinityView;
65
96
  /** Display configuration. */
@@ -99,6 +130,7 @@ declare module '@deepseek-ai/cordis' {
99
130
  export declare class PetService extends Service {
100
131
  static inject: string[];
101
132
  private readonly machine;
133
+ private readonly stateConfig;
102
134
  private readonly ledger;
103
135
  private readonly registry;
104
136
  private readonly persistDir;
@@ -106,6 +138,11 @@ export declare class PetService extends Service {
106
138
  private disposeActivity;
107
139
  /** Session whose most recent meaningful event currently drives the global pet. */
108
140
  private displaySession;
141
+ /**
142
+ * Per-session activity, most recent last (Map insertion order). Bounded by
143
+ * MAX_SESSION_BUBBLES so a burst of sessions cannot grow it without bound;
144
+ * disposed sessions are removed by the 'session/disposed' listener.
145
+ */
109
146
  private readonly sessionActivity;
110
147
  constructor(ctx: Context, config?: PetConfig);
111
148
  /** Whether the pet service consumes session activity while enabled. */
@@ -135,9 +172,13 @@ export declare class PetService extends Service {
135
172
  /** Start or stop the session-activity listeners that drive the pet. */
136
173
  setEnabled(enabled: boolean): void;
137
174
  private syncActivity;
138
- /** Return the projection state associated with one live session. */
139
- private activityRuntime;
140
- /** Commit one activity as the host-global pet's most recent display state. */
175
+ /** Return the per-session activity record, creating it on first sight. */
176
+ private activityOf;
177
+ /**
178
+ * Commit one activity: the session's own machine renders its bubble, and
179
+ * the session becomes the host-global display session (most recent
180
+ * meaningful event wins the sprite animation).
181
+ */
141
182
  private applyActivity;
142
183
  /** RPC: pet or feed the pet. */
143
184
  interact(kind: PetInteraction): Promise<PetInteractResult>;
@@ -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,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQ9C,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AACxF,OAAO,EASL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAA;AACrB,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AACtB,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,+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;CAClB;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;CACF;AAED,gCAAgC;AAChC,MAAM,MAAM,iBAAiB,GAAG,uBAAuB,CAAA;AAEvD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,GAAG,EAAE,UAAU,CAAA;KAChB;CACF;AAUD;;;;;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,OAAO,CAAC,eAAe,CAA0B;IACjD,kFAAkF;IAClF,OAAO,CAAC,cAAc,CAAqB;IAC3C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsC;IAEtE,YAAY,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,EA6B/C;IAED,uEAAuE;IACvE,SAAS,IAAI,OAAO,CAEnB;IAED,uCAAuC;IACjC,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,CAEnC;IAED,yDAAyD;IACzD,OAAO,IAAI,gBAAgB,CAE1B;IAED,2EAA2E;IACrE,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAErC;IAED,gEAAgE;IAChE,gBAAgB,IAAI,WAAW,CAE9B;IAED,yCAAyC;IACzC,WAAW,IAAI,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAEzD;IAED,6CAA6C;IAC7C,aAAa,IAAI,MAAM,CAEtB;IAED,yEAAyE;IACzE,OAAO,CAAC,KAAK,GAAE,MAA6B,GAAG,MAAM,CAIpD;IAED,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,CAQjG;IAED,uEAAuE;IACvE,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGjC;IAED,OAAO,CAAC,YAAY;IA4DpB,0EAA0E;IAC1E,OAAO,CAAC,UAAU;IAYlB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAoBrB,gCAAgC;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAK/D;IAED,iCAAiC;IAC3B,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAKnF;IAED,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,CASlG;IAED,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,CAO9F;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAiBtD;IAED,oFAAoF;IACpF,OAAO,CAAC,mBAAmB;IAe3B,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAIlB,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,IAAI;IA6CZ,OAAO,CAAC,KAAK;CAOd"}
@@ -5,16 +5,22 @@
5
5
  * persistence through `persist`. The API gateway maps these methods onto
6
6
  * `pet.state` / `pet.pets` / `pet.interact` / `pet.setVisible` /
7
7
  * `pet.setConfig` / `pet.setName` / `pet.setPet` for browser consumers.
8
+ *
9
+ * Concurrent sessions each keep their own machine: the sprite animation
10
+ * follows the most recent meaningful event (the display session) while the
11
+ * state view carries one bubble per active session.
8
12
  * @module @linxin666/dsh-pet/service
9
13
  */
10
14
  import { Service } from '@deepseek-ai/cordis';
11
- import { emptyProjectionRuntime, isActivityPhase, projectOfficialEvent, } from "./event-projection.js";
12
- import { PetLedger } from "./ledger.js";
13
- import { DEFAULT_PET_NAME, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, PET_NAME_MAX_LENGTH, loadPetPersist, petHomeDir, savePetPersist, } from "./persist.js";
14
- import { loadPetRegistry, petEntryView, petPackageRoot, } from "./registry.js";
15
- import { defaultPetStateConfig, PetStateMachine, } from "./state.js";
15
+ import { emptyProjectionRuntime, isActivityPhase, projectOfficialEvent, } from './event-projection.js';
16
+ import { PetLedger } from './ledger.js';
17
+ import { DEFAULT_PET_NAME, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, PET_NAME_MAX_LENGTH, loadPetPersist, petHomeDir, savePetPersist, } from './persist.js';
18
+ import { loadPetRegistry, petEntryView, petPackageRoot, } from './registry.js';
19
+ import { defaultPetStateConfig, PetStateMachine, } from './state.js';
16
20
  /** Settings namespace of the pet capability. Spelled here rather than imported: the browser half spells the same value. */
17
21
  export const PET_SETTINGS_NAMESPACE = 'pet';
22
+ /** Hard cap on simultaneously displayed session bubbles (most recent first). */
23
+ export const MAX_SESSION_BUBBLES = 12;
18
24
  /**
19
25
  * Cordis service exposing the pet RPC domain. Lazy: nothing is scanned or
20
26
  * written until an economic event or interaction arrives; event listeners
@@ -24,6 +30,7 @@ export const PET_SETTINGS_NAMESPACE = 'pet';
24
30
  export class PetService extends Service {
25
31
  static inject = [];
26
32
  machine;
33
+ stateConfig;
27
34
  ledger;
28
35
  registry;
29
36
  persistDir;
@@ -31,7 +38,12 @@ export class PetService extends Service {
31
38
  disposeActivity;
32
39
  /** Session whose most recent meaningful event currently drives the global pet. */
33
40
  displaySession;
34
- sessionActivity = new WeakMap();
41
+ /**
42
+ * Per-session activity, most recent last (Map insertion order). Bounded by
43
+ * MAX_SESSION_BUBBLES so a burst of sessions cannot grow it without bound;
44
+ * disposed sessions are removed by the 'session/disposed' listener.
45
+ */
46
+ sessionActivity = new Map();
35
47
  constructor(ctx, config = {}) {
36
48
  super(ctx, 'pet');
37
49
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -49,12 +61,15 @@ export class PetService extends Service {
49
61
  // copied pet.json): fall back to the registry default.
50
62
  persist = { ...persist, petId: this.registry.defaultEntry().id };
51
63
  }
52
- const ledgerConfig = { affinity: config.affinity, treats: config.treats };
64
+ const selected = this.registry.byId(persist.petId) ?? this.registry.defaultEntry();
65
+ const ledgerConfig = {
66
+ affinity: config.affinity,
67
+ treats: config.treats,
68
+ remarks: selected.remarks,
69
+ };
53
70
  this.ledger = new PetLedger(persist, ledgerConfig);
54
- this.machine = new PetStateMachine({
55
- ...defaultPetStateConfig,
56
- ...(config.state ?? {}),
57
- });
71
+ this.stateConfig = { ...defaultPetStateConfig, ...(config.state ?? {}) };
72
+ this.machine = new PetStateMachine(this.stateConfig);
58
73
  this.enabled = config.enabled ?? true;
59
74
  this.syncActivity();
60
75
  }
@@ -99,6 +114,7 @@ export class PetService extends Service {
99
114
  if (entry === undefined)
100
115
  return { ok: false, error: 'unknown-pet' };
101
116
  this.ledger.setPetId(entry.id);
117
+ this.ledger.setRemarks(entry.remarks);
102
118
  this.flush();
103
119
  this.syncSettingsFromPet();
104
120
  return { ok: true, petId: entry.id };
@@ -118,7 +134,7 @@ export class PetService extends Service {
118
134
  this.disposeActivity = (() => {
119
135
  const disposers = [
120
136
  this.ctx.on('session/event', (session, event) => {
121
- const runtime = this.activityRuntime(session);
137
+ const runtime = this.activityOf(session).runtime;
122
138
  // `activity/status` is an optional compatibility input. It is not
123
139
  // declared as a durable event type by this package because current
124
140
  // Harness installations publish the official session vocabulary.
@@ -149,27 +165,63 @@ export class PetService extends Service {
149
165
  }
150
166
  }),
151
167
  this.ctx.on('session/disposed', (session) => {
168
+ this.ledger.forgetSession(String(session.id));
169
+ this.sessionActivity.delete(session);
152
170
  if (session !== this.displaySession)
153
171
  return;
172
+ // The display session is gone: fall back to the most recent
173
+ // remaining session's last input, or settle to idle when none.
154
174
  this.displaySession = undefined;
155
- this.machine.onSessionDisposed();
175
+ const remaining = [...this.sessionActivity.entries()].at(-1);
176
+ if (remaining !== undefined) {
177
+ const [nextSession, activity] = remaining;
178
+ this.displaySession = nextSession;
179
+ if (activity.lastInput !== undefined)
180
+ this.machine.onActivityStatus(activity.lastInput);
181
+ this.machine.onSessionActive();
182
+ }
183
+ else {
184
+ this.machine.onSessionDisposed();
185
+ }
156
186
  }),
157
187
  ];
158
188
  return () => { for (const dispose of disposers)
159
189
  dispose(); };
160
190
  })();
161
191
  }
162
- /** Return the projection state associated with one live session. */
163
- activityRuntime(session) {
164
- let runtime = this.sessionActivity.get(session);
165
- if (runtime === undefined) {
166
- runtime = emptyProjectionRuntime();
167
- this.sessionActivity.set(session, runtime);
192
+ /** Return the per-session activity record, creating it on first sight. */
193
+ activityOf(session) {
194
+ let activity = this.sessionActivity.get(session);
195
+ if (activity === undefined) {
196
+ activity = {
197
+ runtime: emptyProjectionRuntime(),
198
+ machine: new PetStateMachine(this.stateConfig),
199
+ };
200
+ this.sessionActivity.set(session, activity);
168
201
  }
169
- return runtime;
202
+ return activity;
170
203
  }
171
- /** Commit one activity as the host-global pet's most recent display state. */
204
+ /**
205
+ * Commit one activity: the session's own machine renders its bubble, and
206
+ * the session becomes the host-global display session (most recent
207
+ * meaningful event wins the sprite animation).
208
+ */
172
209
  applyActivity(session, input) {
210
+ const activity = this.activityOf(session);
211
+ activity.lastInput = input;
212
+ activity.machine.onActivityStatus(input);
213
+ activity.machine.onSessionActive();
214
+ // Move to the tail so map order reads most-recent-last, then trim the
215
+ // oldest session states beyond the bubble cap. The display session is
216
+ // reassigned below, so trimming its stale predecessor is safe.
217
+ this.sessionActivity.delete(session);
218
+ this.sessionActivity.set(session, activity);
219
+ while (this.sessionActivity.size > MAX_SESSION_BUBBLES) {
220
+ const oldest = this.sessionActivity.keys().next().value;
221
+ if (oldest === undefined)
222
+ break;
223
+ this.sessionActivity.delete(oldest);
224
+ }
173
225
  this.displaySession = session;
174
226
  this.machine.onActivityStatus(input);
175
227
  this.machine.onSessionActive();
@@ -218,8 +270,10 @@ export class PetService extends Service {
218
270
  * @param section - the resolved settings section.
219
271
  */
220
272
  applySettingsSection(section) {
221
- if (typeof section.petId === 'string' && this.registry.byId(section.petId) !== undefined) {
222
- this.ledger.setPetId(section.petId);
273
+ const selected = typeof section.petId === 'string' ? this.registry.byId(section.petId) : undefined;
274
+ if (selected !== undefined) {
275
+ this.ledger.setPetId(selected.id);
276
+ this.ledger.setRemarks(selected.remarks);
223
277
  }
224
278
  else if (section.petId !== undefined) {
225
279
  // The stored selection names a pet the registry no longer has: keep the
@@ -263,6 +317,28 @@ export class PetService extends Service {
263
317
  view() {
264
318
  const snapshot = this.machine.render();
265
319
  const entry = this.activeEntry();
320
+ // One bubble per concurrently active TOP-LEVEL session, most recent
321
+ // first. Subagent children render no bubble of their own (their activity
322
+ // already shows through the spawning conversation's bubble/display, and
323
+ // the bubble buttons navigate to GUI sessions, which subagents are not).
324
+ // Sessions whose own machine has settled (no bubble copy) drop out, so a
325
+ // finished turn does not leave a stale bubble behind.
326
+ const sessions = [];
327
+ for (const [session, activity] of [...this.sessionActivity.entries()].reverse()) {
328
+ if (sessions.length >= MAX_SESSION_BUBBLES)
329
+ break;
330
+ if (session.header?.origin === 'subagent')
331
+ continue;
332
+ const perSession = activity.machine.render();
333
+ if (perSession.bubble === undefined)
334
+ continue;
335
+ sessions.push({
336
+ sessionId: String(session.id),
337
+ animation: perSession.animation,
338
+ bubble: perSession.bubble,
339
+ phase: perSession.phase,
340
+ });
341
+ }
266
342
  // Read-only: the ledger settles on economic events only, never on a read,
267
343
  // so polling the state cannot trigger pet.json writes.
268
344
  return {
@@ -270,6 +346,7 @@ export class PetService extends Service {
270
346
  ...(snapshot.bubble === undefined ? {} : { bubble: snapshot.bubble }),
271
347
  phase: snapshot.phase,
272
348
  sessionActive: snapshot.sessionActive,
349
+ sessions,
273
350
  affinity: this.ledger.affinityView(Date.now()),
274
351
  display: { ...this.ledger.snapshot.display },
275
352
  pet: {
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEnG,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,eAAe,GACf,cAAc,GACd,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAA;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,SAAS,EAAE,YAAY,CAAA;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oEAAoE;IACpE,KAAK,EAAE,aAAa,CAAA;IACpB,0DAA0D;IAC1D,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAsC,CAAA;AAE1E;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAUpE;AAED,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAarD;AAED;;;GAGG;AACH,qBAAa,eAAe;IAQxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,MAAM,CAAoB;gBAGf,MAAM,GAAE,cAAsC,EAC9C,GAAG,GAAE,MAAM,MAAiB;IAG/C,6CAA6C;IAC7C,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAO5C,oEAAoE;IACpE,eAAe,IAAI,IAAI;IAIvB,sDAAsD;IACtD,iBAAiB,IAAI,IAAI;IAQzB,6CAA6C;IAC7C,MAAM,IAAI,gBAAgB;CAwB3B"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEnG,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,eAAe,GACf,cAAc,GACd,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAA;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,SAAS,EAAE,YAAY,CAAA;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oEAAoE;IACpE,KAAK,EAAE,aAAa,CAAA;IACpB,0DAA0D;IAC1D,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAsC,CAAA;AAE1E;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAUpE;AAED,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAarD;AAED;;;GAGG;AACH,qBAAa,eAAe;IAQxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,MAAM,CAAoB;IAElC,YACmB,MAAM,GAAE,cAAsC,EAC9C,GAAG,GAAE,MAAM,MAAiB,EAC3C;IAEJ,6CAA6C;IAC7C,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAK3C;IAED,oEAAoE;IACpE,eAAe,IAAI,IAAI,CAEtB;IAED,sDAAsD;IACtD,iBAAiB,IAAI,IAAI,CAMxB;IAED,6CAA6C;IAC7C,MAAM,IAAI,gBAAgB,CAuBzB;CACF"}
@@ -94,11 +94,11 @@ export class PetStateMachine {
94
94
  animation = 'idle';
95
95
  }
96
96
  }
97
- const bubble = this.phase === 'done'
98
- && this.doneAt !== undefined
99
- && nowMs - this.doneAt >= this.config.celebrateMs
100
- ? undefined
101
- : this.phrase ?? this.line;
97
+ // Settled sessions never bubble: idle (e.g. an aborted/stopped turn)
98
+ // and done past its celebration window both fall silent.
99
+ const settled = this.phase === 'idle'
100
+ || (this.phase === 'done' && this.doneAt !== undefined && nowMs - this.doneAt >= this.config.celebrateMs);
101
+ const bubble = settled ? undefined : this.phrase ?? this.line;
102
102
  return {
103
103
  animation,
104
104
  ...(bubble === undefined ? {} : { bubble }),
@@ -3,6 +3,9 @@
3
3
  * two sources, both tied to companionship:
4
4
  * - work output: every N completed turns grant one treat;
5
5
  * - time output: every T minutes of wall-clock time grant one treat.
6
+ * Difficulty tuned deliberately high (10x the original cadence): treats are
7
+ * a rare delicacy the pet earns through sustained companionship, not a
8
+ * routine drop.
6
9
  * Feeding consumes one treat. Settlement is lazy: it runs whenever the host
7
10
  * serves a state snapshot or an interaction, so there is no timer and no
8
11
  * drift — elapsed periods are computed from the persisted last-grant marks.
@@ -1 +1 @@
1
- {"version":3,"file":"treats.d.ts","sourceRoot":"","sources":["../../src/treats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAA;IACrB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,kBAAkB,EAAE,WAIhC,CAAA;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,gBAAgB,EAAE,MAAM,CAAA;IACxB,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,MAAM,EAAE,WAAW,CAAA;IACnB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;CACf;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,WAAgC,GACvC,eAAe,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,WAAW,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAGnD"}
1
+ {"version":3,"file":"treats.d.ts","sourceRoot":"","sources":["../../src/treats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAA;IACrB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,kBAAkB,EAAE,WAIhC,CAAA;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,gBAAgB,EAAE,MAAM,CAAA;IACxB,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,MAAM,EAAE,WAAW,CAAA;IACnB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;CACf;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,WAAgC,GACvC,eAAe,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,WAAW,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAGnD"}
@@ -3,14 +3,17 @@
3
3
  * two sources, both tied to companionship:
4
4
  * - work output: every N completed turns grant one treat;
5
5
  * - time output: every T minutes of wall-clock time grant one treat.
6
+ * Difficulty tuned deliberately high (10x the original cadence): treats are
7
+ * a rare delicacy the pet earns through sustained companionship, not a
8
+ * routine drop.
6
9
  * Feeding consumes one treat. Settlement is lazy: it runs whenever the host
7
10
  * serves a state snapshot or an interaction, so there is no timer and no
8
11
  * drift — elapsed periods are computed from the persisted last-grant marks.
9
12
  * @module @linxin666/dsh-pet/treats
10
13
  */
11
14
  export const defaultTreatConfig = {
12
- turnsPerTreat: 3,
13
- timeTreatMs: 30 * 60_000,
15
+ turnsPerTreat: 30,
16
+ timeTreatMs: 300 * 60_000,
14
17
  maxTreats: 20,
15
18
  };
16
19
  export function emptyTreatLedger() {
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.1.18",
4
+ "version": "0.1.20",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -52,6 +52,14 @@ describe('applyInteraction', () => {
52
52
  const state = { ...emptyAffinity(), points: AFFINITY_MAX - 1 }
53
53
  const outcome = applyInteraction(state, 'pet', 1_000_000)
54
54
  expect(outcome.affinity.points).toBe(AFFINITY_MAX)
55
+ expect(outcome.affinity.points).toBe(999_999_999)
56
+ })
57
+
58
+ it('keeps the legacy first lines as the default reactions', () => {
59
+ const outcome = applyInteraction(emptyAffinity(), 'pet', 1_000_000)
60
+ expect(outcome.reaction).toBe('咕噜咕噜~被摸摸好舒服!')
61
+ const feed = applyInteraction(emptyAffinity(), 'feed', 1_000_000)
62
+ expect(feed.reaction).toBe('呜哇!小鱼干好好吃!')
55
63
  })
56
64
  })
57
65
 
@@ -71,4 +79,15 @@ describe('rankOf', () => {
71
79
  expect(rankOf(AFFINITY_MAX).name).toBe(AFFINITY_RANKS[AFFINITY_RANKS.length - 1]!.name)
72
80
  expect(rankOf(-1).name).toBe(AFFINITY_RANKS[0]!.name)
73
81
  })
82
+
83
+ it('extends past the removed 100 cap with higher tiers', () => {
84
+ // The original four tiers keep their thresholds; the ladder now reaches
85
+ // into the 999,999,999 range instead of freezing at 80 points.
86
+ expect(rankOf(80).name).toBe('深海羁绊')
87
+ expect(rankOf(200).name).toBe('心有灵犀')
88
+ expect(rankOf(100_000).name).toBe('鲸生共渡')
89
+ expect(rankOf(1_000_000).name).toBe('鲸生共渡')
90
+ expect(AFFINITY_RANKS[AFFINITY_RANKS.length - 1]!.min).toBe(100_000)
91
+ expect(AFFINITY_MAX).toBe(999_999_999)
92
+ })
74
93
  })
package/src/affinity.ts CHANGED
@@ -3,9 +3,13 @@
3
3
  * work together and care for it: every completed turn earns a small reward,
4
4
  * petting earns a tiny one (cooldown-gated), feeding earns the most.
5
5
  * Persistence lives in the service; this module only computes transitions.
6
+ * Reaction copy resolves from the shared remark library (first line of each
7
+ * built-in pool); the ledger layers per-pet custom remarks on top.
6
8
  * @module @linxin666/dsh-pet/affinity
7
9
  */
8
10
 
11
+ import { builtinRemark } from './remarks.ts'
12
+
9
13
  /** One interaction the user can perform on the pet. */
10
14
  export type PetInteraction = 'pet' | 'feed'
11
15
 
@@ -25,16 +29,27 @@ export interface AffinityState {
25
29
  turns: number
26
30
  }
27
31
 
28
- export const AFFINITY_MAX = 100
32
+ /**
33
+ * Affinity points cap. Historically 100; removed so long-term companions
34
+ * keep growing — the default limit is now the full 999,999,999 range.
35
+ */
36
+ export const AFFINITY_MAX = 999_999_999
29
37
 
30
38
  /** Affinity ranks by points; the pet visibly grows with its rank.
31
- * Marker glyphs are plain ASCII (the repo bans all emoji characters);
32
- * they read as a growing star trail alongside the rank name. */
39
+ * The original four tiers (0/25/50/80) are unchanged; higher tiers reach
40
+ * into the extended cap so the ladder stays meaningful for veteran
41
+ * companions. Marker glyphs are plain ASCII (the repo bans all emoji
42
+ * characters); they read as a growing star trail alongside the rank name. */
33
43
  export const AFFINITY_RANKS = [
34
44
  { min: 0, name: '幼鲸', emoji: '*' },
35
45
  { min: 25, name: '伙伴', emoji: '**' },
36
46
  { min: 50, name: '挚友', emoji: '***' },
37
47
  { min: 80, name: '深海羁绊', emoji: '****' },
48
+ { min: 200, name: '心有灵犀', emoji: '*****' },
49
+ { min: 500, name: '传说羁绊', emoji: '******' },
50
+ { min: 2_000, name: '神话羁绊', emoji: '*******' },
51
+ { min: 10_000, name: '永恒之契', emoji: '********' },
52
+ { min: 100_000, name: '鲸生共渡', emoji: '*********' },
38
53
  ] as const
39
54
 
40
55
  /** Interaction tuning (all in points / ms). */
@@ -136,7 +151,7 @@ export function applyInteraction(
136
151
  const next = { ...state }
137
152
  if (kind === 'pet') {
138
153
  if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) {
139
- return { affinity: state, delta: 0, reaction: '摸过头啦,让鲸鱼娘歇口气~', accepted: false }
154
+ return { affinity: state, delta: 0, reaction: builtinRemark('petCooldown'), accepted: false }
140
155
  }
141
156
  next.lastPetAt = nowMs
142
157
  next.pets += 1
@@ -144,13 +159,13 @@ export function applyInteraction(
144
159
  return {
145
160
  affinity: next,
146
161
  delta: config.petReward,
147
- reaction: '咕噜咕噜~被摸摸好舒服!',
162
+ reaction: builtinRemark('pet'),
148
163
  accepted: true,
149
164
  }
150
165
  }
151
166
  if (kind === 'feed') {
152
167
  if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) {
153
- return { affinity: state, delta: 0, reaction: '吃饱啦,晚点再喂~', accepted: false }
168
+ return { affinity: state, delta: 0, reaction: builtinRemark('feedCooldown'), accepted: false }
154
169
  }
155
170
  next.lastFeedAt = nowMs
156
171
  next.feeds += 1
@@ -158,7 +173,7 @@ export function applyInteraction(
158
173
  return {
159
174
  affinity: next,
160
175
  delta: config.feedReward,
161
- reaction: '呜哇!小鱼干好好吃!',
176
+ reaction: builtinRemark('feed'),
162
177
  accepted: true,
163
178
  }
164
179
  }
@@ -37,6 +37,8 @@ export interface PetInjected {
37
37
  dragEnd: (right: number, bottom: number) => void
38
38
  /** Rename the selected pet (persisted by the host). */
39
39
  rename: (name: string) => void
40
+ /** Navigate the GUI to the session a bubble reports on. */
41
+ openSession: (sessionId: string) => void
40
42
  /** Clear the reaction bubble. */
41
43
  feedbackDone: () => void
42
44
  }
@@ -83,6 +85,7 @@ export function PetDockEntry(props: PetDockEntryProps): ReactElement {
83
85
  onHide={props.hide}
84
86
  onDragEnd={props.dragEnd}
85
87
  onRename={props.rename}
88
+ onOpenSession={props.openSession}
86
89
  onFeedbackDone={props.feedbackDone}
87
90
  t={props.t}
88
91
  />
@@ -134,6 +134,14 @@ export class PetSettingsCardController {
134
134
  inject(): PetSettingsCardFace {
135
135
  return { hooks: { petSettingsCard: this.store }, ...this.form.actions() }
136
136
  }
137
+
138
+ /**
139
+ * Release the card's scope subscription and bound stores; the slot
140
+ * disposer calls this on teardown.
141
+ */
142
+ dispose(): void {
143
+ this.form.dispose()
144
+ }
137
145
  }
138
146
 
139
147
  /** Props the renderer binds for the pet settings card. */