@linxin666/dsh-pet 0.1.13 → 0.1.15
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 +11 -7
- package/README.zh.md +11 -7
- package/lib/client.js +38 -17
- package/lib/client.js.map +1 -1
- package/lib/index.js +393 -150
- package/lib/invariant.js +1 -1
- package/lib/{state-C9EyycwI.js → state-CFyJv0sQ.js} +22 -7
- package/lib/types/affinity.d.ts +15 -0
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +14 -0
- package/lib/types/client/PluginSettingsCard.d.ts +26 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +27 -7
- package/lib/types/client/WhalePet.d.ts.map +1 -1
- package/lib/types/client/WhalePet.js +2 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.js +3 -3
- package/lib/types/client/settings-form.d.ts +14 -5
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +38 -10
- package/lib/types/dsh-home.d.ts +17 -0
- package/lib/types/dsh-home.d.ts.map +1 -0
- package/lib/types/dsh-home.js +34 -0
- package/lib/types/event-projection.d.ts +36 -0
- package/lib/types/event-projection.d.ts.map +1 -0
- package/lib/types/event-projection.js +98 -0
- package/lib/types/ledger.d.ts +77 -0
- package/lib/types/ledger.d.ts.map +1 -0
- package/lib/types/ledger.js +139 -0
- package/lib/types/persist.d.ts +5 -1
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +7 -3
- package/lib/types/service.d.ts +24 -44
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +98 -131
- package/lib/types/state.d.ts +10 -12
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +14 -10
- package/package.json +1 -1
- package/src/affinity.ts +33 -0
- package/src/client/PluginSettingsCard.tsx +83 -17
- package/src/client/WhalePet.test.tsx +25 -1
- package/src/client/WhalePet.tsx +6 -0
- package/src/client/index.ts +3 -3
- package/src/client/pet.module.css +9 -0
- package/src/client/settings-card.module.css +6 -0
- package/src/client/settings-form.ts +41 -9
- package/src/dsh-home.test.ts +35 -0
- package/src/dsh-home.ts +36 -0
- package/src/event-projection.ts +128 -0
- package/src/ledger.test.ts +67 -0
- package/src/ledger.ts +183 -0
- package/src/persist.ts +7 -3
- package/src/service.ts +110 -171
- package/src/state.test.ts +4 -1
- package/src/state.ts +17 -13
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official session event projection — pure. Maps the durable DSH session
|
|
3
|
+
* vocabulary onto the pet's visual phases and carries an optional completed-
|
|
4
|
+
* turn reward for the ledger. Holds no state of its own; callers keep a
|
|
5
|
+
* {@link ProjectionRuntime} per session and feed events in arrival order.
|
|
6
|
+
* @module @linxin666/dsh-pet/event-projection
|
|
7
|
+
*/
|
|
8
|
+
/** Fresh projection runtime for a newly seen session. */
|
|
9
|
+
export function emptyProjectionRuntime() {
|
|
10
|
+
return { activeTools: new Set(), officialEventsSeen: false, stepHadFailure: false };
|
|
11
|
+
}
|
|
12
|
+
/** Keep tool names readable inside the compact status bubble. */
|
|
13
|
+
function displayToolName(name) {
|
|
14
|
+
const compact = name.replace(/\s+/g, ' ').trim() || '工具';
|
|
15
|
+
return compact.length <= 24 ? compact : `${compact.slice(0, 21)}...`;
|
|
16
|
+
}
|
|
17
|
+
/** Whether a legacy phase is part of the pet's supported vocabulary. */
|
|
18
|
+
export function isActivityPhase(phase) {
|
|
19
|
+
return ['idle', 'waiting', 'thinking', 'tool', 'review', 'done', 'failed'].includes(phase);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Project the durable DSH session vocabulary into the pet's visual phases.
|
|
23
|
+
* Unknown and log-only events do not disturb the last meaningful activity.
|
|
24
|
+
*/
|
|
25
|
+
export function projectOfficialEvent(event, runtime) {
|
|
26
|
+
switch (event.type) {
|
|
27
|
+
case 'turn/start':
|
|
28
|
+
runtime.activeTools.clear();
|
|
29
|
+
runtime.stepHadFailure = false;
|
|
30
|
+
return { input: { phase: 'waiting', line: '准备开始' } };
|
|
31
|
+
case 'step/start':
|
|
32
|
+
runtime.activeTools.clear();
|
|
33
|
+
runtime.stepHadFailure = false;
|
|
34
|
+
return { input: { phase: 'waiting', line: '等待模型响应' } };
|
|
35
|
+
case 'assistant/chunk': {
|
|
36
|
+
const { chunk } = event.data;
|
|
37
|
+
if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
|
|
38
|
+
return { input: { phase: 'thinking', line: '正在思考' } };
|
|
39
|
+
}
|
|
40
|
+
if (chunk.type === 'text-delta' && chunk.text.length > 0) {
|
|
41
|
+
return { input: { phase: 'review', line: '整理回复中' } };
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
case 'assistant/message':
|
|
46
|
+
return { input: { phase: 'review', line: '整理回复中' } };
|
|
47
|
+
case 'tool/call':
|
|
48
|
+
runtime.activeTools.add(String(event.data.callId));
|
|
49
|
+
return {
|
|
50
|
+
input: {
|
|
51
|
+
phase: 'tool',
|
|
52
|
+
line: `正在使用 ${displayToolName(event.data.name)}`,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
case 'tool/result': {
|
|
56
|
+
const block = event.data.message.content[0];
|
|
57
|
+
runtime.activeTools.delete(String(event.data.message.source.callId));
|
|
58
|
+
runtime.stepHadFailure ||= event.data.error !== undefined || block.isError === true;
|
|
59
|
+
if (runtime.activeTools.size > 0) {
|
|
60
|
+
return {
|
|
61
|
+
input: {
|
|
62
|
+
phase: 'tool',
|
|
63
|
+
line: `还有 ${runtime.activeTools.size} 个工具运行中`,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return runtime.stepHadFailure
|
|
68
|
+
? { input: { phase: 'failed', line: '工具执行失败' } }
|
|
69
|
+
: { input: { phase: 'thinking', line: '处理工具结果' } };
|
|
70
|
+
}
|
|
71
|
+
case 'turn/end': {
|
|
72
|
+
runtime.activeTools.clear();
|
|
73
|
+
switch (event.data.reason.kind) {
|
|
74
|
+
case 'completed':
|
|
75
|
+
return {
|
|
76
|
+
input: { phase: 'done', line: '完成啦' },
|
|
77
|
+
completedTurn: event.data.turn,
|
|
78
|
+
};
|
|
79
|
+
case 'error':
|
|
80
|
+
return { input: { phase: 'failed', line: '执行失败' } };
|
|
81
|
+
case 'max-tokens':
|
|
82
|
+
return { input: { phase: 'failed', line: '达到输出上限' } };
|
|
83
|
+
case 'interrupted':
|
|
84
|
+
return { input: { phase: 'failed', line: '执行意外中断' } };
|
|
85
|
+
case 'blocked':
|
|
86
|
+
return { input: { phase: 'waiting', line: '等待继续' } };
|
|
87
|
+
case 'aborted':
|
|
88
|
+
return { input: { phase: 'idle', line: '已停止' } };
|
|
89
|
+
default:
|
|
90
|
+
// TurnEndReasonMap is merge-extensible; a newer ending must not
|
|
91
|
+
// leave the pet showing stale in-progress work.
|
|
92
|
+
return { input: { phase: 'idle' } };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
default:
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pet affinity economy (ledger) — composes the pure affinity and treats
|
|
3
|
+
* modules with the cooldown/dedup bookkeeping and emits updated persistence
|
|
4
|
+
* snapshots, marking dirty so the owning facade decides when to flush. Read
|
|
5
|
+
* paths (view) no longer settle the economy; settlements happen on explicit
|
|
6
|
+
* economic events: completed-turn rewards (official or legacy) and feeds.
|
|
7
|
+
* @module @linxin666/dsh-pet/ledger
|
|
8
|
+
*/
|
|
9
|
+
import { type AffinityConfig, type PetAffinityView, type PetInteraction } from './affinity.ts';
|
|
10
|
+
import { type TreatConfig } from './treats.ts';
|
|
11
|
+
import type { PetDisplayConfig, PetPersist } from './persist.ts';
|
|
12
|
+
/** Tuning overrides for the affinity economy. */
|
|
13
|
+
export interface LedgerConfig {
|
|
14
|
+
affinity?: Partial<AffinityConfig>;
|
|
15
|
+
treats?: Partial<TreatConfig>;
|
|
16
|
+
}
|
|
17
|
+
/** Result of one ledger interaction (the shape the pet RPC returns). */
|
|
18
|
+
export interface LedgerInteractionResult {
|
|
19
|
+
/** Reaction copy bubble. */
|
|
20
|
+
reaction: string;
|
|
21
|
+
/** Points gained (0 when inside the cooldown). */
|
|
22
|
+
delta: number;
|
|
23
|
+
/** Full affinity snapshot (same shape as the state view). */
|
|
24
|
+
affinity: PetAffinityView;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Holds the current persistence snapshot and all economy bookkeeping. Every
|
|
28
|
+
* mutating call flags takeDirty so the facade persists exactly once per
|
|
29
|
+
* batch of changes; read methods (snapshot, affinityView) never write.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PetLedger {
|
|
32
|
+
private readonly affinityConfig;
|
|
33
|
+
private readonly treatConfig;
|
|
34
|
+
private current;
|
|
35
|
+
/** Completed turns already rewarded, per session (turn numbers are per-session). */
|
|
36
|
+
private rewardedTurns;
|
|
37
|
+
private lastLegacyTurnRewardAt;
|
|
38
|
+
private dirty;
|
|
39
|
+
constructor(persist: PetPersist, config?: LedgerConfig);
|
|
40
|
+
/** Affinity cooldown/rank tuning (read-only). */
|
|
41
|
+
get affinity(): AffinityConfig;
|
|
42
|
+
/** The current persistence snapshot (trade a copy when mutating). */
|
|
43
|
+
get snapshot(): PetPersist;
|
|
44
|
+
/** Stock cap reported to clients. */
|
|
45
|
+
get treatMax(): number;
|
|
46
|
+
/** Consume the pending-write flag if any mutation occurred. */
|
|
47
|
+
takeDirty(): boolean;
|
|
48
|
+
/** Replace the display block (clamping stays a caller concern). */
|
|
49
|
+
setDisplay(display: PetDisplayConfig): void;
|
|
50
|
+
/** Replace the pet display name (validation stays a caller concern). */
|
|
51
|
+
setName(name: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Settle the treat economy (work + time output since the last settlement).
|
|
54
|
+
* A zero-gain first settlement still starts the time clock (anchor write),
|
|
55
|
+
* which is how the 30-minute time output can ever accrue. Returns true when
|
|
56
|
+
* the in-memory ledger changed and should be persisted.
|
|
57
|
+
*/
|
|
58
|
+
settleTreats(nowMs: number): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Award the completed-turn reward once per session+turn (idempotent) and
|
|
61
|
+
* run the treat settlement that work output feeds. Returns true when the
|
|
62
|
+
* snapshot changed.
|
|
63
|
+
*/
|
|
64
|
+
rewardTurn(sessionId: string, turn: number, nowMs: number): boolean;
|
|
65
|
+
/** Preserve turn rewards for installations that only emit legacy activity. */
|
|
66
|
+
rewardLegacyTurn(nowMs: number): boolean;
|
|
67
|
+
private applyTurnReward;
|
|
68
|
+
/**
|
|
69
|
+
* Pet or feed the pet. Feeding settles first, then gates on the feed
|
|
70
|
+
* cooldown before spending stock — a feed inside the cooldown must not burn
|
|
71
|
+
* a treat for nothing.
|
|
72
|
+
*/
|
|
73
|
+
interact(kind: PetInteraction, nowMs: number): LedgerInteractionResult;
|
|
74
|
+
/** Current affinity view for the RPC snapshot. */
|
|
75
|
+
affinityView(nowMs: number): PetAffinityView;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEhE,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAC9B;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;gBAET,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB;IAM1D,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO;IAMpB,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAK3C,wEAAwE;IACxE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAapC;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IASnE,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxC,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAyBtE,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;CAG7C"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pet affinity economy (ledger) — composes the pure affinity and treats
|
|
3
|
+
* modules with the cooldown/dedup bookkeeping and emits updated persistence
|
|
4
|
+
* snapshots, marking dirty so the owning facade decides when to flush. Read
|
|
5
|
+
* paths (view) no longer settle the economy; settlements happen on explicit
|
|
6
|
+
* economic events: completed-turn rewards (official or legacy) and feeds.
|
|
7
|
+
* @module @linxin666/dsh-pet/ledger
|
|
8
|
+
*/
|
|
9
|
+
import { applyInteraction, affinityViewOf, applyTurnReward, defaultAffinityConfig, } from "./affinity.js";
|
|
10
|
+
import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from "./treats.js";
|
|
11
|
+
/**
|
|
12
|
+
* Holds the current persistence snapshot and all economy bookkeeping. Every
|
|
13
|
+
* mutating call flags takeDirty so the facade persists exactly once per
|
|
14
|
+
* batch of changes; read methods (snapshot, affinityView) never write.
|
|
15
|
+
*/
|
|
16
|
+
export class PetLedger {
|
|
17
|
+
affinityConfig;
|
|
18
|
+
treatConfig;
|
|
19
|
+
current;
|
|
20
|
+
/** Completed turns already rewarded, per session (turn numbers are per-session). */
|
|
21
|
+
rewardedTurns = new Map();
|
|
22
|
+
lastLegacyTurnRewardAt = 0;
|
|
23
|
+
dirty = false;
|
|
24
|
+
constructor(persist, config = {}) {
|
|
25
|
+
this.affinityConfig = { ...defaultAffinityConfig, ...(config.affinity ?? {}) };
|
|
26
|
+
this.treatConfig = { ...defaultTreatConfig, ...(config.treats ?? {}) };
|
|
27
|
+
this.current = persist;
|
|
28
|
+
}
|
|
29
|
+
/** Affinity cooldown/rank tuning (read-only). */
|
|
30
|
+
get affinity() {
|
|
31
|
+
return this.affinityConfig;
|
|
32
|
+
}
|
|
33
|
+
/** The current persistence snapshot (trade a copy when mutating). */
|
|
34
|
+
get snapshot() {
|
|
35
|
+
return this.current;
|
|
36
|
+
}
|
|
37
|
+
/** Stock cap reported to clients. */
|
|
38
|
+
get treatMax() {
|
|
39
|
+
return this.treatConfig.maxTreats;
|
|
40
|
+
}
|
|
41
|
+
/** Consume the pending-write flag if any mutation occurred. */
|
|
42
|
+
takeDirty() {
|
|
43
|
+
const was = this.dirty;
|
|
44
|
+
this.dirty = false;
|
|
45
|
+
return was;
|
|
46
|
+
}
|
|
47
|
+
/** Replace the display block (clamping stays a caller concern). */
|
|
48
|
+
setDisplay(display) {
|
|
49
|
+
this.current = { ...this.current, display };
|
|
50
|
+
this.dirty = true;
|
|
51
|
+
}
|
|
52
|
+
/** Replace the pet display name (validation stays a caller concern). */
|
|
53
|
+
setName(name) {
|
|
54
|
+
this.current = { ...this.current, name };
|
|
55
|
+
this.dirty = true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Settle the treat economy (work + time output since the last settlement).
|
|
59
|
+
* A zero-gain first settlement still starts the time clock (anchor write),
|
|
60
|
+
* which is how the 30-minute time output can ever accrue. Returns true when
|
|
61
|
+
* the in-memory ledger changed and should be persisted.
|
|
62
|
+
*/
|
|
63
|
+
settleTreats(nowMs) {
|
|
64
|
+
const settlement = settleTreatGrants(this.current.treats, this.current.affinity.turns, nowMs, this.treatConfig);
|
|
65
|
+
if (settlement.ledger === this.current.treats)
|
|
66
|
+
return false;
|
|
67
|
+
this.current = { ...this.current, treats: settlement.ledger };
|
|
68
|
+
this.dirty = true;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Award the completed-turn reward once per session+turn (idempotent) and
|
|
73
|
+
* run the treat settlement that work output feeds. Returns true when the
|
|
74
|
+
* snapshot changed.
|
|
75
|
+
*/
|
|
76
|
+
rewardTurn(sessionId, turn, nowMs) {
|
|
77
|
+
const last = this.rewardedTurns.get(sessionId) ?? 0;
|
|
78
|
+
if (turn <= last)
|
|
79
|
+
return false;
|
|
80
|
+
this.rewardedTurns.set(sessionId, turn);
|
|
81
|
+
let changed = this.applyTurnReward();
|
|
82
|
+
if (this.settleTreats(nowMs))
|
|
83
|
+
changed = true;
|
|
84
|
+
return changed;
|
|
85
|
+
}
|
|
86
|
+
/** Preserve turn rewards for installations that only emit legacy activity. */
|
|
87
|
+
rewardLegacyTurn(nowMs) {
|
|
88
|
+
// A legacy done snapshot may repeat during the celebration window.
|
|
89
|
+
if (nowMs - this.lastLegacyTurnRewardAt < 5_000)
|
|
90
|
+
return false;
|
|
91
|
+
this.lastLegacyTurnRewardAt = nowMs;
|
|
92
|
+
let changed = this.applyTurnReward();
|
|
93
|
+
if (this.settleTreats(nowMs))
|
|
94
|
+
changed = true;
|
|
95
|
+
return changed;
|
|
96
|
+
}
|
|
97
|
+
applyTurnReward() {
|
|
98
|
+
this.current = {
|
|
99
|
+
...this.current,
|
|
100
|
+
affinity: applyTurnReward(this.current.affinity, this.affinityConfig),
|
|
101
|
+
};
|
|
102
|
+
this.dirty = true;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Pet or feed the pet. Feeding settles first, then gates on the feed
|
|
107
|
+
* cooldown before spending stock — a feed inside the cooldown must not burn
|
|
108
|
+
* a treat for nothing.
|
|
109
|
+
*/
|
|
110
|
+
interact(kind, nowMs) {
|
|
111
|
+
if (kind === 'feed')
|
|
112
|
+
this.settleTreats(nowMs);
|
|
113
|
+
const outcome = applyInteraction(this.current.affinity, kind, nowMs, this.affinityConfig);
|
|
114
|
+
if (kind === 'feed' && !outcome.accepted) {
|
|
115
|
+
return { reaction: outcome.reaction, delta: 0, affinity: this.affinityView(nowMs) };
|
|
116
|
+
}
|
|
117
|
+
if (kind === 'feed') {
|
|
118
|
+
const consume = consumeTreat(this.current.treats);
|
|
119
|
+
if (!consume.ok) {
|
|
120
|
+
return {
|
|
121
|
+
reaction: '没有小鱼干了,多陪鲸鱼娘工作一会儿吧~',
|
|
122
|
+
delta: 0,
|
|
123
|
+
affinity: this.affinityView(nowMs),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
this.current = { ...this.current, treats: consume.ledger };
|
|
127
|
+
this.dirty = true;
|
|
128
|
+
}
|
|
129
|
+
if (outcome.accepted) {
|
|
130
|
+
this.current = { ...this.current, affinity: outcome.affinity };
|
|
131
|
+
this.dirty = true;
|
|
132
|
+
}
|
|
133
|
+
return { reaction: outcome.reaction, delta: outcome.delta, affinity: this.affinityView(nowMs) };
|
|
134
|
+
}
|
|
135
|
+
/** Current affinity view for the RPC snapshot. */
|
|
136
|
+
affinityView(nowMs) {
|
|
137
|
+
return affinityViewOf(this.current.affinity, nowMs, this.affinityConfig);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/lib/types/persist.d.ts
CHANGED
|
@@ -36,7 +36,11 @@ export declare const DEFAULT_PET_NAME = "\u9CB8\u9C7C\u5A18";
|
|
|
36
36
|
/** Name constraints. */
|
|
37
37
|
export declare const PET_NAME_MAX_LENGTH = 20;
|
|
38
38
|
export declare function emptyPersist(): PetPersist;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the persistence directory ($DSH_HOME or ~/.dsh). Delegates to the
|
|
41
|
+
* shared {@link dshHome} resolution so the plugin family keeps one DSH_HOME
|
|
42
|
+
* definition (env override, ~ expansion, cwd-joined relative values).
|
|
43
|
+
*/
|
|
40
44
|
export declare function petHomeDir(): string;
|
|
41
45
|
/** Load persisted state; missing or corrupt files fall back to defaults. */
|
|
42
46
|
export declare function loadPetPersist(dir?: string): PetPersist;
|
|
@@ -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,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAOzC;AAED
|
|
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,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAYD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAwCrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
|
package/lib/types/persist.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import {
|
|
9
|
+
import { dshHome } from "./dsh-home.js";
|
|
10
10
|
import { AFFINITY_MAX, emptyAffinity } from "./affinity.js";
|
|
11
11
|
import { defaultTreatConfig, emptyTreatLedger } from "./treats.js";
|
|
12
12
|
export const defaultDisplayConfig = {
|
|
@@ -31,9 +31,13 @@ export function emptyPersist() {
|
|
|
31
31
|
display: { ...defaultDisplayConfig },
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the persistence directory ($DSH_HOME or ~/.dsh). Delegates to the
|
|
36
|
+
* shared {@link dshHome} resolution so the plugin family keeps one DSH_HOME
|
|
37
|
+
* definition (env override, ~ expansion, cwd-joined relative values).
|
|
38
|
+
*/
|
|
35
39
|
export function petHomeDir() {
|
|
36
|
-
return
|
|
40
|
+
return dshHome();
|
|
37
41
|
}
|
|
38
42
|
/** Numeric field guard: finite numbers only, else the fallback. */
|
|
39
43
|
function finiteNum(value, fallback) {
|
package/lib/types/service.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pet host service — the `pet.*` RPC domain.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `pet.state` / `pet.interact` / `pet.setVisible` / `pet.setConfig`
|
|
7
|
-
*
|
|
2
|
+
* Pet host service — the `pet.*` RPC domain. A composition facade: it wires
|
|
3
|
+
* the pure event projection (`event-projection`) onto the state machine,
|
|
4
|
+
* delegates the affinity economy to the ledger (`ledger`), and routes
|
|
5
|
+
* persistence through `persist`. The API gateway maps these methods onto
|
|
6
|
+
* `pet.state` / `pet.interact` / `pet.setVisible` / `pet.setConfig` for
|
|
7
|
+
* browser consumers.
|
|
8
8
|
* @module @linxin666/dsh-pet/service
|
|
9
9
|
*/
|
|
10
10
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
11
|
-
import {
|
|
11
|
+
import type { AffinityConfig, PetAffinityView, PetInteraction } from './affinity.ts';
|
|
12
|
+
import type { TreatConfig } from './treats.ts';
|
|
13
|
+
import { type LedgerInteractionResult } from './ledger.ts';
|
|
12
14
|
import { type PetDisplayConfig } from './persist.ts';
|
|
13
|
-
import { type TreatConfig } from './treats.ts';
|
|
14
15
|
import { type PetStateConfig, type PetStateSnapshot } from './state.ts';
|
|
15
16
|
/** Plugin configuration. */
|
|
16
17
|
export interface PetConfig {
|
|
@@ -53,18 +54,7 @@ export interface PetStateView {
|
|
|
53
54
|
phase: PetStateSnapshot['phase'];
|
|
54
55
|
sessionActive: boolean;
|
|
55
56
|
/** Affinity ledger snapshot. */
|
|
56
|
-
affinity:
|
|
57
|
-
points: number;
|
|
58
|
-
rank: string;
|
|
59
|
-
rankEmoji: string;
|
|
60
|
-
pets: number;
|
|
61
|
-
feeds: number;
|
|
62
|
-
turns: number;
|
|
63
|
-
/** True while the pet interaction is inside its cooldown. */
|
|
64
|
-
petCooldown: boolean;
|
|
65
|
-
/** True while the feed is inside its cooldown. */
|
|
66
|
-
feedCooldown: boolean;
|
|
67
|
-
};
|
|
57
|
+
affinity: PetAffinityView;
|
|
68
58
|
/** Display configuration. */
|
|
69
59
|
display: PetDisplayConfig;
|
|
70
60
|
/** User-customizable pet display name. */
|
|
@@ -78,14 +68,7 @@ export interface PetStateView {
|
|
|
78
68
|
};
|
|
79
69
|
}
|
|
80
70
|
/** Result of `pet.interact`. */
|
|
81
|
-
export
|
|
82
|
-
/** Reaction copy bubble. */
|
|
83
|
-
reaction: string;
|
|
84
|
-
/** Points gained (0 when inside the cooldown). */
|
|
85
|
-
delta: number;
|
|
86
|
-
/** Full affinity snapshot (same shape as state view). */
|
|
87
|
-
affinity: PetStateView['affinity'];
|
|
88
|
-
}
|
|
71
|
+
export type PetInteractResult = LedgerInteractionResult;
|
|
89
72
|
declare module '@deepseek-ai/cordis' {
|
|
90
73
|
interface Context {
|
|
91
74
|
pet: PetService;
|
|
@@ -93,21 +76,20 @@ declare module '@deepseek-ai/cordis' {
|
|
|
93
76
|
}
|
|
94
77
|
/**
|
|
95
78
|
* Cordis service exposing the pet RPC domain. Lazy: nothing is scanned or
|
|
96
|
-
* written until
|
|
97
|
-
* in-memory state, and persistence happens on
|
|
98
|
-
*
|
|
79
|
+
* written until an economic event or interaction arrives; event listeners
|
|
80
|
+
* update only in-memory state, and persistence happens on economic changes
|
|
81
|
+
* (turn rewards, feeds, config/name changes) — never on a read.
|
|
99
82
|
*/
|
|
100
83
|
export declare class PetService extends Service {
|
|
101
84
|
static inject: string[];
|
|
102
85
|
private readonly machine;
|
|
103
|
-
private readonly
|
|
104
|
-
private readonly treatConfig;
|
|
86
|
+
private readonly ledger;
|
|
105
87
|
private readonly persistDir;
|
|
106
|
-
private persist;
|
|
107
|
-
/** Completed turns already rewarded, per session (turn numbers are per-session). */
|
|
108
|
-
private rewardedTurns;
|
|
109
88
|
private enabled;
|
|
110
89
|
private disposeActivity;
|
|
90
|
+
/** Session whose most recent meaningful event currently drives the global pet. */
|
|
91
|
+
private displaySession;
|
|
92
|
+
private readonly sessionActivity;
|
|
111
93
|
constructor(ctx: Context, config?: PetConfig);
|
|
112
94
|
/** Whether the pet service consumes session activity while enabled. */
|
|
113
95
|
isEnabled(): boolean;
|
|
@@ -120,6 +102,10 @@ export declare class PetService extends Service {
|
|
|
120
102
|
/** Start or stop the session-activity listeners that drive the pet. */
|
|
121
103
|
setEnabled(enabled: boolean): void;
|
|
122
104
|
private syncActivity;
|
|
105
|
+
/** Return the projection state associated with one live session. */
|
|
106
|
+
private activityRuntime;
|
|
107
|
+
/** Commit one activity as the host-global pet's most recent display state. */
|
|
108
|
+
private applyActivity;
|
|
123
109
|
/** RPC: pet or feed the pet. */
|
|
124
110
|
interact(kind: PetInteraction): Promise<PetInteractResult>;
|
|
125
111
|
/** RPC: show or hide the pet. */
|
|
@@ -151,15 +137,9 @@ export declare class PetService extends Service {
|
|
|
151
137
|
private syncSettingsFromPet;
|
|
152
138
|
/** Award the turn reward once per completed turn (idempotent per session + turn). */
|
|
153
139
|
private rewardTurn;
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
* and persist whenever the ledger changed. A zero-gain first settlement
|
|
157
|
-
* still starts the time clock (anchor write), which is what lets the
|
|
158
|
-
* 30-minute time output ever accrue.
|
|
159
|
-
*/
|
|
160
|
-
private settleTreats;
|
|
140
|
+
/** Preserve turn rewards for installations that only emit legacy activity. */
|
|
141
|
+
private rewardLegacyTurn;
|
|
161
142
|
private view;
|
|
162
|
-
private affinityView;
|
|
163
143
|
private flush;
|
|
164
144
|
}
|
|
165
145
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
|