@linxin666/dsh-pet 0.1.18 → 0.1.19
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.
- package/README.i18n.yaml +2 -2
- package/README.md +12 -5
- package/README.zh.md +12 -5
- package/lib/client.js +48 -11
- package/lib/client.js.map +1 -1
- package/lib/index.js +100 -30
- package/lib/invariant.js +2 -2
- package/lib/state-Diiq8vjk.js +394 -0
- package/lib/types/affinity.d.ts +31 -3
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +21 -7
- package/lib/types/client/PetSettingsCard.d.ts +5 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +7 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +8 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +14 -8
- package/lib/types/client/settings-form.d.ts +8 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +15 -1
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +3 -1
- package/lib/types/index.d.ts +4 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -1
- package/lib/types/ledger.d.ts +16 -1
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +29 -4
- package/lib/types/registry.d.ts +10 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +3 -0
- package/lib/types/remarks.d.ts +56 -0
- package/lib/types/remarks.d.ts.map +1 -0
- package/lib/types/remarks.js +157 -0
- package/lib/types/service.d.ts +40 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +90 -18
- package/lib/types/state.js +5 -5
- package/lib/types/treats.d.ts +3 -0
- package/lib/types/treats.d.ts.map +1 -1
- package/lib/types/treats.js +5 -2
- package/package.json +1 -1
- package/src/affinity.test.ts +19 -0
- package/src/affinity.ts +22 -7
- package/src/client/PetSettingsCard.tsx +8 -0
- package/src/client/PetSprite.test.tsx +33 -0
- package/src/client/PetSprite.tsx +16 -1
- package/src/client/index.ts +14 -8
- package/src/client/pet.module.css +19 -0
- package/src/client/settings-form.ts +15 -1
- package/src/event-projection.ts +3 -1
- package/src/index.ts +12 -1
- package/src/ledger.test.ts +40 -9
- package/src/ledger.ts +33 -4
- package/src/persist.test.ts +1 -1
- package/src/registry.test.ts +27 -0
- package/src/registry.ts +12 -0
- package/src/remarks.test.ts +89 -0
- package/src/remarks.ts +177 -0
- package/src/service.ts +116 -18
- package/src/state.test.ts +9 -0
- package/src/state.ts +5 -5
- package/src/treats.test.ts +13 -11
- package/src/treats.ts +5 -2
- package/lib/state-CFyJv0sQ.js +0 -213
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
|
package/lib/types/service.js
CHANGED
|
@@ -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 { Service } from '@deepseek-ai/cordis';
|
|
@@ -15,6 +19,8 @@ import { loadPetRegistry, petEntryView, petPackageRoot, } from "./registry.js";
|
|
|
15
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
|
-
|
|
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
|
|
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.
|
|
55
|
-
|
|
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.
|
|
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.
|
|
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
|
|
163
|
-
|
|
164
|
-
let
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
202
|
+
return activity;
|
|
170
203
|
}
|
|
171
|
-
/**
|
|
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
|
-
|
|
222
|
-
|
|
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,23 @@ 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 session, most recent first.
|
|
321
|
+
// Sessions whose own machine has settled (no bubble copy) drop out, so a
|
|
322
|
+
// finished turn does not leave a stale bubble behind.
|
|
323
|
+
const sessions = [];
|
|
324
|
+
for (const [session, activity] of [...this.sessionActivity.entries()].reverse()) {
|
|
325
|
+
if (sessions.length >= MAX_SESSION_BUBBLES)
|
|
326
|
+
break;
|
|
327
|
+
const perSession = activity.machine.render();
|
|
328
|
+
if (perSession.bubble === undefined)
|
|
329
|
+
continue;
|
|
330
|
+
sessions.push({
|
|
331
|
+
sessionId: String(session.id),
|
|
332
|
+
animation: perSession.animation,
|
|
333
|
+
bubble: perSession.bubble,
|
|
334
|
+
phase: perSession.phase,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
266
337
|
// Read-only: the ledger settles on economic events only, never on a read,
|
|
267
338
|
// so polling the state cannot trigger pet.json writes.
|
|
268
339
|
return {
|
|
@@ -270,6 +341,7 @@ export class PetService extends Service {
|
|
|
270
341
|
...(snapshot.bubble === undefined ? {} : { bubble: snapshot.bubble }),
|
|
271
342
|
phase: snapshot.phase,
|
|
272
343
|
sessionActive: snapshot.sessionActive,
|
|
344
|
+
sessions,
|
|
273
345
|
affinity: this.ledger.affinityView(Date.now()),
|
|
274
346
|
display: { ...this.ledger.snapshot.display },
|
|
275
347
|
pet: {
|
package/lib/types/state.js
CHANGED
|
@@ -94,11 +94,11 @@ export class PetStateMachine {
|
|
|
94
94
|
animation = 'idle';
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 }),
|
package/lib/types/treats.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/lib/types/treats.js
CHANGED
|
@@ -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:
|
|
13
|
-
timeTreatMs:
|
|
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.
|
|
4
|
+
"version": "0.1.19",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
package/src/affinity.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
32
|
-
*
|
|
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: '
|
|
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: '
|
|
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
|
}
|
|
@@ -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. */
|
|
@@ -194,6 +194,39 @@ describe('PetSprite status bubble', () => {
|
|
|
194
194
|
expect(screen.queryByText('摸摸成功')).not.toBeNull()
|
|
195
195
|
expect(screen.queryByText('正在思考')).toBeNull()
|
|
196
196
|
})
|
|
197
|
+
|
|
198
|
+
it('renders one bubble per concurrent session without duplicating the global bubble', () => {
|
|
199
|
+
renderPet({
|
|
200
|
+
snapshot: {
|
|
201
|
+
...workingSnapshot,
|
|
202
|
+
bubble: '正在思考',
|
|
203
|
+
sessions: [
|
|
204
|
+
{ sessionId: 's-a', animation: 'running', phase: 'thinking', bubble: '正在思考' },
|
|
205
|
+
{ sessionId: 's-b', animation: 'running-right', phase: 'tool', bubble: '正在使用 grep' },
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
// The display session appears in the stack exactly once: the legacy
|
|
210
|
+
// single bubble is not rendered on top of the session list.
|
|
211
|
+
expect(screen.getAllByText('正在思考')).toHaveLength(1)
|
|
212
|
+
expect(screen.queryByText('正在使用 grep')).not.toBeNull()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('lets feedback replace the whole session bubble stack', () => {
|
|
216
|
+
renderPet({
|
|
217
|
+
snapshot: {
|
|
218
|
+
...workingSnapshot,
|
|
219
|
+
sessions: [
|
|
220
|
+
{ sessionId: 's-a', animation: 'running', phase: 'thinking', bubble: '正在思考' },
|
|
221
|
+
{ sessionId: 's-b', animation: 'running-right', phase: 'tool', bubble: '正在使用 grep' },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
feedback: { text: '摸摸成功', kind: 'pet', at: 1 },
|
|
225
|
+
})
|
|
226
|
+
expect(screen.queryByText('摸摸成功')).not.toBeNull()
|
|
227
|
+
expect(screen.queryByText('正在思考')).toBeNull()
|
|
228
|
+
expect(screen.queryByText('正在使用 grep')).toBeNull()
|
|
229
|
+
})
|
|
197
230
|
})
|
|
198
231
|
|
|
199
232
|
describe('PetSprite definition-driven render', () => {
|
package/src/client/PetSprite.tsx
CHANGED
|
@@ -209,7 +209,13 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
|
|
|
209
209
|
const pos = dragPos ?? { right: display.right, bottom: display.bottom }
|
|
210
210
|
const spriteWidth = Math.round(cell.width * spriteScale)
|
|
211
211
|
const spriteHeight = Math.round(cell.height * spriteScale)
|
|
212
|
-
|
|
212
|
+
// Concurrent sessions each render their own bubble (stacked above the
|
|
213
|
+
// sprite); the legacy single 'bubble' is the fallback when the host serves
|
|
214
|
+
// no per-session list.
|
|
215
|
+
const sessionBubbles = snapshot?.sessions ?? []
|
|
216
|
+
const statusBubble = feedback === null && !hovered && sessionBubbles.length === 0
|
|
217
|
+
? snapshot?.bubble
|
|
218
|
+
: undefined
|
|
213
219
|
const displayName = snapshot?.name ?? definition.displayName
|
|
214
220
|
|
|
215
221
|
const float = (
|
|
@@ -264,6 +270,15 @@ export function PetSprite(props: PetSpriteProps): ReactPortal {
|
|
|
264
270
|
{feedback.text}
|
|
265
271
|
</div>
|
|
266
272
|
)}
|
|
273
|
+
{feedback === null && !hovered && sessionBubbles.length > 0 && (
|
|
274
|
+
<div className={styles.bubbleStack} role="status" aria-live="polite">
|
|
275
|
+
{sessionBubbles.map(session => (
|
|
276
|
+
<div key={session.sessionId} className={clsx(styles.bubble, styles.bubbleStatus)}>
|
|
277
|
+
{session.bubble}
|
|
278
|
+
</div>
|
|
279
|
+
))}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
267
282
|
{statusBubble !== undefined && (
|
|
268
283
|
<div className={clsx(styles.bubble, styles.bubbleStatus)} role="status" aria-live="polite">
|
|
269
284
|
{statusBubble}
|
package/src/client/index.ts
CHANGED
|
@@ -116,14 +116,20 @@ export function apply(ctx: ClientContext): void {
|
|
|
116
116
|
// namespace, registered as a top-level settings page. The controller loads
|
|
117
117
|
// the petId choices from the registry endpoint itself.
|
|
118
118
|
const petSettings = new PetSettingsCardController(settingsScope)
|
|
119
|
-
ctx.slots.inject('settings.section', () =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
ctx.slots.inject('settings.section', () => {
|
|
120
|
+
const unregister = ctx.slots.register({
|
|
121
|
+
name: 'settings.section',
|
|
122
|
+
id: 'pet',
|
|
123
|
+
order: 130,
|
|
124
|
+
label: () => ctx.locale.bind('pet')('settings.title'),
|
|
125
|
+
locale: 'pet',
|
|
126
|
+
inject: () => petSettings.inject(),
|
|
127
|
+
}, PetSettingsSection)
|
|
128
|
+
return () => {
|
|
129
|
+
petSettings.dispose()
|
|
130
|
+
unregister()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
127
133
|
|
|
128
134
|
// The global pet entry, its store, and the poll loop live while the plugin
|
|
129
135
|
// is enabled; toggling the setting off hides the pet and stops polling.
|
|
@@ -46,6 +46,25 @@
|
|
|
46
46
|
animation: none;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/* 多会话气泡栈:并行会话各自一个气泡,自精灵头顶向上堆叠,
|
|
50
|
+
最近更新的会话在最上。栈内气泡改流入列布局,不再绝对定位
|
|
51
|
+
互相覆盖。 */
|
|
52
|
+
.bubbleStack {
|
|
53
|
+
position: absolute;
|
|
54
|
+
bottom: 100%;
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 4px;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.bubbleStack .bubble {
|
|
63
|
+
position: relative;
|
|
64
|
+
bottom: auto;
|
|
65
|
+
margin-bottom: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
@keyframes pet-bubble-pop {
|
|
50
69
|
0% {
|
|
51
70
|
opacity: 0;
|
|
@@ -234,6 +234,9 @@ export class CardForm<T> {
|
|
|
234
234
|
private readonly specs: Map<string, FieldSpec>
|
|
235
235
|
private readonly staged = new Map<string, StagedEdit>()
|
|
236
236
|
private readonly listeners = new Set<() => void>()
|
|
237
|
+
/** The scope subscription installed in the constructor; released by dispose(). */
|
|
238
|
+
private readonly disposeScope: () => void
|
|
239
|
+
private disposed = false
|
|
237
240
|
private saving = false
|
|
238
241
|
private failed = false
|
|
239
242
|
private failedReason: string | undefined
|
|
@@ -244,7 +247,18 @@ export class CardForm<T> {
|
|
|
244
247
|
specs: FieldSpec[],
|
|
245
248
|
) {
|
|
246
249
|
this.specs = new Map(specs.map(spec => [spec.field, spec]))
|
|
247
|
-
scope.subscribe(() => { this.publish() })
|
|
250
|
+
this.disposeScope = scope.subscribe(() => { this.publish() })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Release the scope subscription and every bound store listener. The card
|
|
255
|
+
* must call this on teardown; later calls are no-ops.
|
|
256
|
+
*/
|
|
257
|
+
dispose(): void {
|
|
258
|
+
if (this.disposed) return
|
|
259
|
+
this.disposed = true
|
|
260
|
+
this.disposeScope()
|
|
261
|
+
this.listeners.clear()
|
|
248
262
|
}
|
|
249
263
|
|
|
250
264
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
package/src/event-projection.ts
CHANGED
|
@@ -115,7 +115,9 @@ export function projectOfficialEvent(
|
|
|
115
115
|
case 'blocked':
|
|
116
116
|
return { input: { phase: 'waiting', line: '等待继续' } }
|
|
117
117
|
case 'aborted':
|
|
118
|
-
|
|
118
|
+
// A stopped session settles to idle without a bubble: the pet
|
|
119
|
+
// visibly calms down and the session drops out of the bubble stack.
|
|
120
|
+
return { input: { phase: 'idle' } }
|
|
119
121
|
default:
|
|
120
122
|
// TurnEndReasonMap is merge-extensible; a newer ending must not
|
|
121
123
|
// leave the pet showing stale in-progress work.
|
package/src/index.ts
CHANGED
|
@@ -20,11 +20,12 @@ import { makePetRoutes } from './routes.ts'
|
|
|
20
20
|
import { loadPetRegistry, petPackageRoot } from './registry.ts'
|
|
21
21
|
import { DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN } from './persist.ts'
|
|
22
22
|
|
|
23
|
-
export { PetService } from './service.ts'
|
|
23
|
+
export { PetService, MAX_SESSION_BUBBLES } from './service.ts'
|
|
24
24
|
export type {
|
|
25
25
|
PetConfig,
|
|
26
26
|
PetInteractResult,
|
|
27
27
|
PetSettingsSection,
|
|
28
|
+
PetSessionView,
|
|
28
29
|
PetStateView,
|
|
29
30
|
} from './service.ts'
|
|
30
31
|
export {
|
|
@@ -60,6 +61,16 @@ export {
|
|
|
60
61
|
settleTreatGrants,
|
|
61
62
|
} from './treats.ts'
|
|
62
63
|
export type { TreatConfig, TreatLedger, TreatSettlement } from './treats.ts'
|
|
64
|
+
export {
|
|
65
|
+
BUILTIN_REMARKS,
|
|
66
|
+
REMARK_KINDS,
|
|
67
|
+
REMARK_LINE_MAX,
|
|
68
|
+
REMARK_LINES_MAX,
|
|
69
|
+
RemarkPicker,
|
|
70
|
+
builtinRemark,
|
|
71
|
+
normalizePetRemarks,
|
|
72
|
+
} from './remarks.ts'
|
|
73
|
+
export type { PetRemarks, PetRemarksManifest, RemarkKind } from './remarks.ts'
|
|
63
74
|
export {
|
|
64
75
|
DEFAULT_PET_ID,
|
|
65
76
|
DEFAULT_PET_NAME,
|