@linxin666/dsh-pet 0.1.19 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +19 -12
- package/README.zh.md +19 -12
- package/assets/whale/pet.json +9 -0
- package/lib/client.js +202 -38
- package/lib/client.js.map +1 -1
- package/lib/index.js +897 -42
- package/lib/invariant.js +1 -1
- package/lib/{state-Diiq8vjk.js → state-Ch3f4luZ.js} +51 -23
- package/lib/types/affinity.d.ts +22 -18
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +32 -6
- package/lib/types/chatter.d.ts +111 -0
- package/lib/types/chatter.d.ts.map +1 -0
- package/lib/types/chatter.js +666 -0
- package/lib/types/client/PetDockEntry.d.ts +2 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -2
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +2 -2
- package/lib/types/client/PetSprite.d.ts +2 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +100 -25
- package/lib/types/client/PluginSettingsCard.d.ts +7 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +5 -3
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +31 -6
- package/lib/types/client/locales.d.ts +82 -80
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/sequences.d.ts +10 -0
- package/lib/types/client/sequences.d.ts.map +1 -0
- package/lib/types/client/sequences.js +20 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/spritesheet.d.ts +1 -1
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +3 -12
- package/lib/types/event-projection.d.ts +14 -1
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +38 -17
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +15 -13
- package/lib/types/invariant.js +2 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +18 -10
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +6 -4
- package/lib/types/registry.d.ts +7 -1
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +37 -1
- package/lib/types/remarks.d.ts +2 -0
- package/lib/types/remarks.d.ts.map +1 -1
- package/lib/types/remarks.js +5 -0
- package/lib/types/routes.js +2 -2
- package/lib/types/service.d.ts +16 -6
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -8
- package/lib/types/state.d.ts +7 -4
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +20 -20
- package/package.json +9 -9
- package/src/affinity.test.ts +4 -2
- package/src/affinity.ts +37 -6
- package/src/chatter.test.ts +154 -0
- package/src/chatter.ts +697 -0
- package/src/client/PetDockEntry.tsx +3 -0
- package/src/client/PetSprite.test.tsx +246 -4
- package/src/client/PetSprite.tsx +132 -36
- package/src/client/PluginSettingsCard.tsx +10 -0
- package/src/client/index.ts +26 -3
- package/src/client/locales.ts +2 -0
- package/src/client/pet-css.test.ts +42 -0
- package/src/client/pet.module.css +173 -22
- package/src/client/sequences.test.ts +33 -0
- package/src/client/sequences.ts +33 -0
- package/src/client/spritesheet.ts +3 -13
- package/src/event-projection.ts +49 -16
- package/src/index.ts +4 -1
- package/src/ledger.test.ts +42 -1
- package/src/ledger.ts +15 -7
- package/src/mount-once.ts +48 -0
- package/src/persist.test.ts +18 -1
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +50 -3
- package/src/registry.ts +47 -3
- package/src/remarks.ts +6 -0
- package/src/service.ts +42 -9
- package/src/state.test.ts +13 -0
- package/src/state.ts +24 -18
package/lib/types/ledger.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* economic events: completed-turn rewards (official or legacy) and feeds.
|
|
7
7
|
* @module @linxin666/dsh-pet/ledger
|
|
8
8
|
*/
|
|
9
|
-
import { applyInteraction, affinityViewOf, applyTurnReward, defaultAffinityConfig, } from
|
|
10
|
-
import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from
|
|
11
|
-
import { RemarkPicker } from
|
|
9
|
+
import { applyInteraction, affinityViewOf, applyTurnReward, defaultAffinityConfig, } from './affinity.js';
|
|
10
|
+
import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from './treats.js';
|
|
11
|
+
import { RemarkPicker } from './remarks.js';
|
|
12
12
|
/**
|
|
13
13
|
* Holds the current persistence snapshot and all economy bookkeeping. Every
|
|
14
14
|
* mutating call flags takeDirty so the facade persists exactly once per
|
|
@@ -135,12 +135,19 @@ export class PetLedger {
|
|
|
135
135
|
interact(kind, nowMs) {
|
|
136
136
|
if (kind === 'feed')
|
|
137
137
|
this.settleTreats(nowMs);
|
|
138
|
-
const
|
|
138
|
+
const before = this.current.affinity;
|
|
139
|
+
const outcome = applyInteraction(before, kind, nowMs, this.affinityConfig);
|
|
139
140
|
// Reactions come from the picker pools (custom per-pet slots override
|
|
140
141
|
// the built-in library per slot); outcome.reaction stays the fallback
|
|
141
142
|
// copy for direct applyInteraction callers.
|
|
142
143
|
if (kind === 'feed' && !outcome.accepted) {
|
|
143
|
-
|
|
144
|
+
this.current = { ...this.current, affinity: outcome.affinity };
|
|
145
|
+
this.dirty = true;
|
|
146
|
+
return {
|
|
147
|
+
reaction: this.picker.pickAt('feedCooldown', before.feedRejects),
|
|
148
|
+
delta: 0,
|
|
149
|
+
affinity: this.affinityView(nowMs),
|
|
150
|
+
};
|
|
144
151
|
}
|
|
145
152
|
if (kind === 'feed') {
|
|
146
153
|
const consume = consumeTreat(this.current.treats);
|
|
@@ -154,12 +161,13 @@ export class PetLedger {
|
|
|
154
161
|
this.current = { ...this.current, treats: consume.ledger };
|
|
155
162
|
this.dirty = true;
|
|
156
163
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
this.current = { ...this.current, affinity: outcome.affinity };
|
|
165
|
+
this.dirty = true;
|
|
166
|
+
const count = kind === 'pet'
|
|
167
|
+
? outcome.accepted ? before.pets : before.petRejects
|
|
168
|
+
: before.feeds;
|
|
161
169
|
return {
|
|
162
|
-
reaction: this.picker.
|
|
170
|
+
reaction: this.picker.pickAt(outcome.accepted ? kind : 'petCooldown', count),
|
|
163
171
|
delta: outcome.delta,
|
|
164
172
|
affinity: this.affinityView(nowMs),
|
|
165
173
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host single-instance guard shared by the plugin family. The family bundle
|
|
3
|
+
* (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
|
|
4
|
+
* the loader accepts a standalone install of the same package side by side;
|
|
5
|
+
* without this guard the second instance would still re-register the same
|
|
6
|
+
* webserver routes, tools, settings namespaces, and system-prompt sections
|
|
7
|
+
* and fail the boot. mountOnce makes the second host apply a no-op for the
|
|
8
|
+
* lifetime of the first instance (the browser half is already deduped by
|
|
9
|
+
* package name in the client module host).
|
|
10
|
+
*
|
|
11
|
+
* The registry rides a global symbol so two module instances of the same
|
|
12
|
+
* package (npm copy vs repository link) still share one verdict. cordis
|
|
13
|
+
* `ctx.effect` runs its callback immediately and treats the callback's
|
|
14
|
+
* return value as the fiber disposer, so the unmarker is returned, not run.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a cordis plugin apply so the package runs at most once per process.
|
|
18
|
+
* The first mount registers normally and unmarks when its fiber disposes;
|
|
19
|
+
* any later mount of the same package name is a no-op.
|
|
20
|
+
* @param packageName - npm package identity shared by every install source.
|
|
21
|
+
* @param fn - the original plugin apply.
|
|
22
|
+
* @returns an apply of the same shape.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mountOnce<T extends (...args: any[]) => unknown>(packageName: string, fn: T): T;
|
|
25
|
+
//# sourceMappingURL=mount-once.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-once.d.ts","sourceRoot":"","sources":["../../src/mount-once.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAaH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAW9F"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/mount-once.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Host single-instance guard shared by the plugin family. The family bundle
|
|
4
|
+
* (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
|
|
5
|
+
* the loader accepts a standalone install of the same package side by side;
|
|
6
|
+
* without this guard the second instance would still re-register the same
|
|
7
|
+
* webserver routes, tools, settings namespaces, and system-prompt sections
|
|
8
|
+
* and fail the boot. mountOnce makes the second host apply a no-op for the
|
|
9
|
+
* lifetime of the first instance (the browser half is already deduped by
|
|
10
|
+
* package name in the client module host).
|
|
11
|
+
*
|
|
12
|
+
* The registry rides a global symbol so two module instances of the same
|
|
13
|
+
* package (npm copy vs repository link) still share one verdict. cordis
|
|
14
|
+
* `ctx.effect` runs its callback immediately and treats the callback's
|
|
15
|
+
* return value as the fiber disposer, so the unmarker is returned, not run.
|
|
16
|
+
*/
|
|
17
|
+
const MOUNTED = Symbol.for('dsh-web-ui.mounted-plugins');
|
|
18
|
+
function mountedSet() {
|
|
19
|
+
const registry = globalThis;
|
|
20
|
+
return (registry[MOUNTED] ??= new Set());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a cordis plugin apply so the package runs at most once per process.
|
|
24
|
+
* The first mount registers normally and unmarks when its fiber disposes;
|
|
25
|
+
* any later mount of the same package name is a no-op.
|
|
26
|
+
* @param packageName - npm package identity shared by every install source.
|
|
27
|
+
* @param fn - the original plugin apply.
|
|
28
|
+
* @returns an apply of the same shape.
|
|
29
|
+
*/
|
|
30
|
+
export function mountOnce(packageName, fn) {
|
|
31
|
+
return ((...args) => {
|
|
32
|
+
const mounted = mountedSet();
|
|
33
|
+
if (mounted.has(packageName))
|
|
34
|
+
return;
|
|
35
|
+
mounted.add(packageName);
|
|
36
|
+
const ctx = args[0];
|
|
37
|
+
ctx?.effect?.(() => () => {
|
|
38
|
+
mounted.delete(packageName);
|
|
39
|
+
});
|
|
40
|
+
return fn(...args);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,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;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,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;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAmDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
|
package/lib/types/persist.js
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { dshHome } from
|
|
10
|
-
import { AFFINITY_MAX, emptyAffinity } from
|
|
11
|
-
import { defaultTreatConfig, emptyTreatLedger } from
|
|
9
|
+
import { dshHome } from './dsh-home.js';
|
|
10
|
+
import { AFFINITY_MAX, emptyAffinity } from './affinity.js';
|
|
11
|
+
import { defaultTreatConfig, emptyTreatLedger } from './treats.js';
|
|
12
12
|
export const defaultDisplayConfig = {
|
|
13
13
|
visible: true,
|
|
14
14
|
size: 160,
|
|
15
15
|
right: 24,
|
|
16
|
-
bottom:
|
|
16
|
+
bottom: 120,
|
|
17
17
|
};
|
|
18
18
|
/** Display value bounds (shared by load-time validation and setConfig). */
|
|
19
19
|
export const DISPLAY_SIZE_MIN = 32;
|
|
@@ -78,6 +78,8 @@ export function loadPetPersist(dir = petHomeDir()) {
|
|
|
78
78
|
lastFeedAt: clamp(finiteNum(rawAffinity.lastFeedAt, 0), Number.MAX_SAFE_INTEGER),
|
|
79
79
|
pets: clamp(finiteNum(rawAffinity.pets, 0), Number.MAX_SAFE_INTEGER),
|
|
80
80
|
feeds: clamp(finiteNum(rawAffinity.feeds, 0), Number.MAX_SAFE_INTEGER),
|
|
81
|
+
petRejects: clamp(finiteNum(rawAffinity.petRejects, 0), Number.MAX_SAFE_INTEGER),
|
|
82
|
+
feedRejects: clamp(finiteNum(rawAffinity.feedRejects, 0), Number.MAX_SAFE_INTEGER),
|
|
81
83
|
turns: clamp(finiteNum(rawAffinity.turns, 0), Number.MAX_SAFE_INTEGER),
|
|
82
84
|
};
|
|
83
85
|
const rawTreats = (parsed.treats ?? {});
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* whale-girl manifest overrides its own durations.
|
|
17
17
|
* @module @linxin666/dsh-pet/registry
|
|
18
18
|
*/
|
|
19
|
-
import type { PetAnimation } from './state.ts';
|
|
19
|
+
import type { ActivityPhase, PetAnimation } from './state.ts';
|
|
20
20
|
import { type PetRemarks, type PetRemarksManifest } from './remarks.ts';
|
|
21
21
|
/** Fixed row order of the 9-state animation contract. */
|
|
22
22
|
export declare const PET_ROW_ORDER: readonly PetAnimation[];
|
|
@@ -81,6 +81,8 @@ export interface PetManifest {
|
|
|
81
81
|
frames?: number[];
|
|
82
82
|
/** Optional per-track rhythm overrides; omitted tracks use the defaults. */
|
|
83
83
|
tracks?: Partial<Record<PetAnimation, PetTrackOverride>>;
|
|
84
|
+
/** Optional per-scene track sequences; every declared sequence has at least 5 items. */
|
|
85
|
+
sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
|
|
84
86
|
/**
|
|
85
87
|
* Optional witty-remark overrides the pet speaks on interactions
|
|
86
88
|
* (community contributions use this to give their pet its own voice).
|
|
@@ -109,8 +111,12 @@ export interface PetDefinition {
|
|
|
109
111
|
columns: number;
|
|
110
112
|
/** Per-row frame counts (length 9, row order above). */
|
|
111
113
|
rows: number[];
|
|
114
|
+
/** Total atlas rows (9 for v1, 11 for v2 look-row atlases). */
|
|
115
|
+
atlasRows: number;
|
|
112
116
|
/** Fully resolved animation tracks (frames + durations + loop/fallback). */
|
|
113
117
|
tracks: Record<PetAnimation, PetTrackDef>;
|
|
118
|
+
/** Validated per-scene track sequences; omitted scenes keep single-track playback. */
|
|
119
|
+
sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
|
|
114
120
|
/** Browser URL of the atlas (served by the host asset route). */
|
|
115
121
|
atlasUrl: string;
|
|
116
122
|
/** Browser URL of the manifest (served by the host asset route). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
|
package/lib/types/registry.js
CHANGED
|
@@ -20,7 +20,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { normalizePetRemarks } from
|
|
23
|
+
import { normalizePetRemarks } from './remarks.js';
|
|
24
24
|
/** Fixed row order of the 9-state animation contract. */
|
|
25
25
|
export const PET_ROW_ORDER = [
|
|
26
26
|
'idle',
|
|
@@ -86,6 +86,35 @@ const PET_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
|
86
86
|
/** Safe path-segment charset for atlas files. */
|
|
87
87
|
const PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
88
88
|
const PET_NAME_MAX_LENGTH = 80;
|
|
89
|
+
const PET_PHASES = ['idle', 'waiting', 'thinking', 'tool', 'review', 'done', 'failed'];
|
|
90
|
+
/** Validate optional scene sequences without rejecting an otherwise usable pet. */
|
|
91
|
+
function normalizeSequences(raw, id, warn) {
|
|
92
|
+
if (raw === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
95
|
+
warn('manifest ' + id + ': sequences must be an object keyed by activity phase');
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const sequences = {};
|
|
99
|
+
for (const [phase, value] of Object.entries(raw)) {
|
|
100
|
+
if (!PET_PHASES.includes(phase)) {
|
|
101
|
+
warn('manifest ' + id + ': unknown sequence phase ' + JSON.stringify(phase));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(value) || value.length < 5) {
|
|
105
|
+
warn('manifest ' + id + ': sequence ' + phase + ' must contain at least 5 animations');
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const unknownIndex = value.findIndex(animation => typeof animation !== 'string' || !PET_ROW_ORDER.includes(animation));
|
|
109
|
+
if (unknownIndex !== -1) {
|
|
110
|
+
const unknown = value[unknownIndex];
|
|
111
|
+
warn('manifest ' + id + ': sequence ' + phase + ' contains unknown animation ' + JSON.stringify(unknown));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
sequences[phase] = value;
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(sequences).length === 0 ? undefined : sequences;
|
|
117
|
+
}
|
|
89
118
|
/**
|
|
90
119
|
* Normalize one parsed manifest into a renderable pet entry, or undefined
|
|
91
120
|
* (with a warning recorded) when the manifest violates the contract.
|
|
@@ -126,11 +155,14 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
126
155
|
height: finiteInt(rawCell.height, DEFAULT_PET_CELL.height, 2048),
|
|
127
156
|
};
|
|
128
157
|
const columns = finiteInt(source.columns, DEFAULT_PET_COLUMNS, 32);
|
|
158
|
+
// v2 atlases (spriteVersionNumber 2) hold 11 rows: 9 animation rows + 2 look rows.
|
|
159
|
+
const atlasRowCount = source.spriteVersionNumber === 2 ? 11 : DEFAULT_PET_ROW_COUNT;
|
|
129
160
|
const rows = DEFAULT_FRAME_COUNTS.map((fallback, index) => {
|
|
130
161
|
const value = Array.isArray(source.frames) ? source.frames[index] : undefined;
|
|
131
162
|
return finiteInt(value, fallback, columns);
|
|
132
163
|
});
|
|
133
164
|
const remarks = normalizePetRemarks(source.remarks, message => warn('manifest ' + id + ': ' + message));
|
|
165
|
+
const sequences = normalizeSequences(source.sequences, id, warn);
|
|
134
166
|
const trackOverrides = (typeof source.tracks === 'object' && source.tracks !== null ? source.tracks : {});
|
|
135
167
|
const tracks = {};
|
|
136
168
|
for (const [row, animation] of PET_ROW_ORDER.entries()) {
|
|
@@ -165,7 +197,9 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
165
197
|
cell,
|
|
166
198
|
columns,
|
|
167
199
|
rows,
|
|
200
|
+
atlasRows: atlasRowCount,
|
|
168
201
|
tracks,
|
|
202
|
+
...(sequences === undefined ? {} : { sequences }),
|
|
169
203
|
atlasUrl: assetUrl(assetPrefix, id, spritesheet),
|
|
170
204
|
manifestUrl: assetUrl(assetPrefix, id, 'pet.json'),
|
|
171
205
|
dir,
|
|
@@ -264,7 +298,9 @@ export function petEntryView(entry) {
|
|
|
264
298
|
cell: entry.cell,
|
|
265
299
|
columns: entry.columns,
|
|
266
300
|
rows: entry.rows,
|
|
301
|
+
atlasRows: entry.atlasRows,
|
|
267
302
|
tracks: entry.tracks,
|
|
303
|
+
...(entry.sequences === undefined ? {} : { sequences: entry.sequences }),
|
|
268
304
|
atlasUrl: entry.atlasUrl,
|
|
269
305
|
manifestUrl: entry.manifestUrl,
|
|
270
306
|
};
|
package/lib/types/remarks.d.ts
CHANGED
|
@@ -52,5 +52,7 @@ export declare class RemarkPicker {
|
|
|
52
52
|
pool(kind: RemarkKind): readonly string[];
|
|
53
53
|
/** The next line for one slot (round-robin within its pool). */
|
|
54
54
|
pick(kind: RemarkKind): string;
|
|
55
|
+
/** Select a line from a stable external counter without changing local picker state. */
|
|
56
|
+
pickAt(kind: RemarkKind, count: number): string;
|
|
55
57
|
}
|
|
56
58
|
//# sourceMappingURL=remarks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remarks.d.ts","sourceRoot":"","sources":["../../src/remarks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAA;AAErF,4CAA4C;AAC5C,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAM7C,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAE9D,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAE/E,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6D3E,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC9C,UAAU,GAAG,SAAS,CAyBxB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;
|
|
1
|
+
{"version":3,"file":"remarks.d.ts","sourceRoot":"","sources":["../../src/remarks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAA;AAErF,4CAA4C;AAC5C,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAM7C,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAE9D,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAE/E,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6D3E,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC9C,UAAU,GAAG,SAAS,CAyBxB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,YAAY,SAAS,CAAC,EAAE,UAAU,EAMjC;IAED,qEAAqE;IACrE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAExC;IAED,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK7B;IAED,wFAAwF;IACxF,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9C;CACF"}
|
package/lib/types/remarks.js
CHANGED
|
@@ -154,4 +154,9 @@ export class RemarkPicker {
|
|
|
154
154
|
this.counters.set(kind, index + 1);
|
|
155
155
|
return pool[index];
|
|
156
156
|
}
|
|
157
|
+
/** Select a line from a stable external counter without changing local picker state. */
|
|
158
|
+
pickAt(kind, count) {
|
|
159
|
+
const pool = this.pools[kind];
|
|
160
|
+
return pool[Math.max(0, Math.floor(count)) % pool.length];
|
|
161
|
+
}
|
|
157
162
|
}
|
package/lib/types/routes.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import { petEntryView } from
|
|
14
|
+
import { petEntryView } from './registry.js';
|
|
15
15
|
/** Browser-facing base path of the pet API. */
|
|
16
16
|
export const PET_API_PREFIX = '/api/pet';
|
|
17
17
|
/** Browser-facing base path of the pet asset routes ('/pet/<id>/...'). */
|
|
@@ -273,4 +273,4 @@ export function makePetRoutes(deps) {
|
|
|
273
273
|
return [...apiRoutes, assetRoute];
|
|
274
274
|
}
|
|
275
275
|
// Re-exported for the package surface (the registry owns the definition now).
|
|
276
|
-
export { petPackageRoot } from
|
|
276
|
+
export { petPackageRoot } from './registry.js';
|
package/lib/types/service.d.ts
CHANGED
|
@@ -59,9 +59,12 @@ export interface PetSettingsSection {
|
|
|
59
59
|
/** Settings namespace of the pet capability. Spelled here rather than imported: the browser half spells the same value. */
|
|
60
60
|
export declare const PET_SETTINGS_NAMESPACE = "pet";
|
|
61
61
|
/**
|
|
62
|
-
* One active session as the pet displays it. Sessions run in
|
|
63
|
-
* each gets its own bubble while the sprite itself follows the
|
|
64
|
-
* meaningful event (the display session).
|
|
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.
|
|
65
68
|
*/
|
|
66
69
|
export interface PetSessionView {
|
|
67
70
|
/** Session identity (stringified for the wire; never exposed as a key). */
|
|
@@ -82,9 +85,10 @@ export interface PetStateView {
|
|
|
82
85
|
phase: PetStateSnapshot['phase'];
|
|
83
86
|
sessionActive: boolean;
|
|
84
87
|
/**
|
|
85
|
-
* Per-session bubbles for every concurrently active session,
|
|
86
|
-
* first; optional so older hosts without the multi-session
|
|
87
|
-
* consumable. The single 'bubble' above mirrors the display
|
|
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.
|
|
88
92
|
*/
|
|
89
93
|
sessions?: PetSessionView[];
|
|
90
94
|
/** Affinity ledger snapshot. */
|
|
@@ -109,6 +113,12 @@ export interface PetStateView {
|
|
|
109
113
|
/** Stock cap. */
|
|
110
114
|
max: number;
|
|
111
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* The display session's fresh inner whisper (碎碎念), when one is within
|
|
118
|
+
* its TTL — short inner-voice copy woken by the model's own output,
|
|
119
|
+
* rendered by the client as a distinct whisper bubble.
|
|
120
|
+
*/
|
|
121
|
+
whisper?: string;
|
|
112
122
|
}
|
|
113
123
|
/** Result of `pet.interact`. */
|
|
114
124
|
export type PetInteractResult = LedgerInteractionResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQ9C,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AACxF,OAAO,EASL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAA;AACrB,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
|
package/lib/types/service.js
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* @module @linxin666/dsh-pet/service
|
|
13
13
|
*/
|
|
14
14
|
import { Service } from '@deepseek-ai/cordis';
|
|
15
|
-
import { emptyProjectionRuntime, isActivityPhase, projectOfficialEvent, } from
|
|
16
|
-
import { PetLedger } from
|
|
17
|
-
import { DEFAULT_PET_NAME, DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN, PET_NAME_MAX_LENGTH, loadPetPersist, petHomeDir, savePetPersist, } from
|
|
18
|
-
import { loadPetRegistry, petEntryView, petPackageRoot, } from
|
|
19
|
-
import {
|
|
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 { WHISPER_TTL_MS } from './chatter.js';
|
|
20
|
+
import { defaultPetStateConfig, PetStateMachine, } from './state.js';
|
|
20
21
|
/** Settings namespace of the pet capability. Spelled here rather than imported: the browser half spells the same value. */
|
|
21
22
|
export const PET_SETTINGS_NAMESPACE = 'pet';
|
|
22
23
|
/** Hard cap on simultaneously displayed session bubbles (most recent first). */
|
|
@@ -159,7 +160,7 @@ export class PetService extends Service {
|
|
|
159
160
|
if (transition === undefined)
|
|
160
161
|
return;
|
|
161
162
|
runtime.officialEventsSeen = true;
|
|
162
|
-
this.applyActivity(session, transition.input);
|
|
163
|
+
this.applyActivity(session, transition.input, transition.whisper);
|
|
163
164
|
if (transition.completedTurn !== undefined) {
|
|
164
165
|
this.rewardTurn(String(session.id), transition.completedTurn);
|
|
165
166
|
}
|
|
@@ -206,9 +207,11 @@ export class PetService extends Service {
|
|
|
206
207
|
* the session becomes the host-global display session (most recent
|
|
207
208
|
* meaningful event wins the sprite animation).
|
|
208
209
|
*/
|
|
209
|
-
applyActivity(session, input) {
|
|
210
|
+
applyActivity(session, input, whisper) {
|
|
210
211
|
const activity = this.activityOf(session);
|
|
211
212
|
activity.lastInput = input;
|
|
213
|
+
if (whisper !== undefined)
|
|
214
|
+
activity.whisper = { text: whisper, at: Date.now() };
|
|
212
215
|
activity.machine.onActivityStatus(input);
|
|
213
216
|
activity.machine.onSessionActive();
|
|
214
217
|
// Move to the tail so map order reads most-recent-last, then trim the
|
|
@@ -317,13 +320,18 @@ export class PetService extends Service {
|
|
|
317
320
|
view() {
|
|
318
321
|
const snapshot = this.machine.render();
|
|
319
322
|
const entry = this.activeEntry();
|
|
320
|
-
// One bubble per concurrently active session, most recent
|
|
323
|
+
// One bubble per concurrently active TOP-LEVEL session, most recent
|
|
324
|
+
// first. Subagent children render no bubble of their own (their activity
|
|
325
|
+
// already shows through the spawning conversation's bubble/display, and
|
|
326
|
+
// the bubble buttons navigate to GUI sessions, which subagents are not).
|
|
321
327
|
// Sessions whose own machine has settled (no bubble copy) drop out, so a
|
|
322
328
|
// finished turn does not leave a stale bubble behind.
|
|
323
329
|
const sessions = [];
|
|
324
330
|
for (const [session, activity] of [...this.sessionActivity.entries()].reverse()) {
|
|
325
331
|
if (sessions.length >= MAX_SESSION_BUBBLES)
|
|
326
332
|
break;
|
|
333
|
+
if (session.header?.origin === 'subagent')
|
|
334
|
+
continue;
|
|
327
335
|
const perSession = activity.machine.render();
|
|
328
336
|
if (perSession.bubble === undefined)
|
|
329
337
|
continue;
|
|
@@ -334,6 +342,15 @@ export class PetService extends Service {
|
|
|
334
342
|
phase: perSession.phase,
|
|
335
343
|
});
|
|
336
344
|
}
|
|
345
|
+
// The display session's inner whisper rides the global view while fresh;
|
|
346
|
+
// an expired whisper simply stops appearing (the client's 2s poll drops it).
|
|
347
|
+
const displayActivity = this.displaySession === undefined
|
|
348
|
+
? undefined
|
|
349
|
+
: this.sessionActivity.get(this.displaySession);
|
|
350
|
+
const whisper = displayActivity?.whisper;
|
|
351
|
+
const freshWhisper = whisper !== undefined && Date.now() - whisper.at < WHISPER_TTL_MS
|
|
352
|
+
? whisper.text
|
|
353
|
+
: undefined;
|
|
337
354
|
// Read-only: the ledger settles on economic events only, never on a read,
|
|
338
355
|
// so polling the state cannot trigger pet.json writes.
|
|
339
356
|
return {
|
|
@@ -342,6 +359,7 @@ export class PetService extends Service {
|
|
|
342
359
|
phase: snapshot.phase,
|
|
343
360
|
sessionActive: snapshot.sessionActive,
|
|
344
361
|
sessions,
|
|
362
|
+
...(freshWhisper === undefined ? {} : { whisper: freshWhisper }),
|
|
345
363
|
affinity: this.ledger.affinityView(Date.now()),
|
|
346
364
|
display: { ...this.ledger.snapshot.display },
|
|
347
365
|
pet: {
|
package/lib/types/state.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface PetStateSnapshot {
|
|
|
39
39
|
export interface PetStateConfig {
|
|
40
40
|
/** Celebration window after `done` before settling to idle, ms (default 2400). */
|
|
41
41
|
celebrateMs: number;
|
|
42
|
+
/** Failure display window before settling to idle, ms (default 2400). */
|
|
43
|
+
failureMs: number;
|
|
42
44
|
}
|
|
43
45
|
export declare const defaultPetStateConfig: PetStateConfig;
|
|
44
46
|
/**
|
|
@@ -47,7 +49,7 @@ export declare const defaultPetStateConfig: PetStateConfig;
|
|
|
47
49
|
* - review → `review` while answer text is streaming.
|
|
48
50
|
* - waiting → `waiting` (expectant pose, needs user input).
|
|
49
51
|
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
50
|
-
* - failed → `failed`
|
|
52
|
+
* - failed → `failed` briefly, then back to `idle`.
|
|
51
53
|
* - idle → `idle` (calm breathing loop).
|
|
52
54
|
*/
|
|
53
55
|
export declare function animationForPhase(phase: ActivityPhase): PetAnimation;
|
|
@@ -55,17 +57,18 @@ export declare function animationForPhase(phase: ActivityPhase): PetAnimation;
|
|
|
55
57
|
export declare function rowOf(animation: PetAnimation): number;
|
|
56
58
|
/**
|
|
57
59
|
* PetStateMachine — one instance per host process. Holds only the latest
|
|
58
|
-
* input snapshot and
|
|
60
|
+
* input snapshot and terminal-state timing; no storage, no side effects.
|
|
59
61
|
*/
|
|
60
62
|
export declare class PetStateMachine {
|
|
61
|
-
private readonly config;
|
|
62
63
|
private readonly now;
|
|
63
64
|
private phase;
|
|
64
65
|
private line;
|
|
65
66
|
private phrase;
|
|
66
67
|
private sessionActive;
|
|
67
68
|
private doneAt;
|
|
68
|
-
|
|
69
|
+
private failedAt;
|
|
70
|
+
private readonly config;
|
|
71
|
+
constructor(config?: Partial<PetStateConfig>, now?: () => number);
|
|
69
72
|
/** Consume one projected activity update. */
|
|
70
73
|
onActivityStatus(input: PetStateInput): void;
|
|
71
74
|
/** A session became the active one (or a fresh session started). */
|
package/lib/types/state.d.ts.map
CHANGED
|
@@ -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;
|
|
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;IACnB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAuD,CAAA;AAE3F;;;;;;;;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;IAWxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVtB,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAEvC,YACE,MAAM,GAAE,OAAO,CAAC,cAAc,CAAyB,EACtC,GAAG,GAAE,MAAM,MAAiB,EAG9C;IAED,6CAA6C;IAC7C,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAM3C;IAED,oEAAoE;IACpE,eAAe,IAAI,IAAI,CAEtB;IAED,sDAAsD;IACtD,iBAAiB,IAAI,IAAI,CAOxB;IAED,6CAA6C;IAC7C,MAAM,IAAI,gBAAgB,CAqBzB;CACF"}
|
package/lib/types/state.js
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* function of (input, nowMs); persistence and RPC live in the service.
|
|
10
10
|
* @module @linxin666/dsh-pet/state
|
|
11
11
|
*/
|
|
12
|
-
export const defaultPetStateConfig = { celebrateMs: 2400 };
|
|
12
|
+
export const defaultPetStateConfig = { celebrateMs: 2400, failureMs: 2400 };
|
|
13
13
|
/**
|
|
14
14
|
* Map one activity phase onto the animation contract.
|
|
15
15
|
* - thinking → `running` and tool → `running-right` (focused work).
|
|
16
16
|
* - review → `review` while answer text is streaming.
|
|
17
17
|
* - waiting → `waiting` (expectant pose, needs user input).
|
|
18
18
|
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
19
|
-
* - failed → `failed`
|
|
19
|
+
* - failed → `failed` briefly, then back to `idle`.
|
|
20
20
|
* - idle → `idle` (calm breathing loop).
|
|
21
21
|
*/
|
|
22
22
|
export function animationForPhase(phase) {
|
|
@@ -47,27 +47,28 @@ export function rowOf(animation) {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* PetStateMachine — one instance per host process. Holds only the latest
|
|
50
|
-
* input snapshot and
|
|
50
|
+
* input snapshot and terminal-state timing; no storage, no side effects.
|
|
51
51
|
*/
|
|
52
52
|
export class PetStateMachine {
|
|
53
|
-
config;
|
|
54
53
|
now;
|
|
55
54
|
phase = 'idle';
|
|
56
55
|
line;
|
|
57
56
|
phrase;
|
|
58
57
|
sessionActive = false;
|
|
59
58
|
doneAt;
|
|
59
|
+
failedAt;
|
|
60
|
+
config;
|
|
60
61
|
constructor(config = defaultPetStateConfig, now = Date.now) {
|
|
61
|
-
this.config = config;
|
|
62
62
|
this.now = now;
|
|
63
|
+
this.config = { ...defaultPetStateConfig, ...config };
|
|
63
64
|
}
|
|
64
65
|
/** Consume one projected activity update. */
|
|
65
66
|
onActivityStatus(input) {
|
|
66
67
|
this.phase = input.phase;
|
|
67
68
|
this.line = input.line;
|
|
68
69
|
this.phrase = input.phrase;
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
this.doneAt = input.phase === 'done' ? this.now() : undefined;
|
|
71
|
+
this.failedAt = input.phase === 'failed' ? this.now() : undefined;
|
|
71
72
|
}
|
|
72
73
|
/** A session became the active one (or a fresh session started). */
|
|
73
74
|
onSessionActive() {
|
|
@@ -80,24 +81,23 @@ export class PetStateMachine {
|
|
|
80
81
|
this.line = undefined;
|
|
81
82
|
this.phrase = undefined;
|
|
82
83
|
this.doneAt = undefined;
|
|
84
|
+
this.failedAt = undefined;
|
|
83
85
|
}
|
|
84
86
|
/** Render the current animation decision. */
|
|
85
87
|
render() {
|
|
86
88
|
const nowMs = this.now();
|
|
87
89
|
let animation = animationForPhase(this.phase);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
const settled = this.phase === 'idle'
|
|
100
|
-
|| (this.phase === 'done' && this.doneAt !== undefined && nowMs - this.doneAt >= this.config.celebrateMs);
|
|
90
|
+
const doneSettled = this.phase === 'done'
|
|
91
|
+
&& this.doneAt !== undefined
|
|
92
|
+
&& nowMs - this.doneAt >= this.config.celebrateMs;
|
|
93
|
+
const failedSettled = this.phase === 'failed'
|
|
94
|
+
&& this.failedAt !== undefined
|
|
95
|
+
&& nowMs - this.failedAt >= this.config.failureMs;
|
|
96
|
+
if (doneSettled || failedSettled)
|
|
97
|
+
animation = 'idle';
|
|
98
|
+
// Settled sessions never bubble: idle (e.g. an aborted/stopped turn),
|
|
99
|
+
// completed celebration expiry, and failed display expiry all fall silent.
|
|
100
|
+
const settled = this.phase === 'idle' || doneSettled || failedSettled;
|
|
101
101
|
const bubble = settled ? undefined : this.phrase ?? this.line;
|
|
102
102
|
return {
|
|
103
103
|
animation,
|