@linxin666/dsh-pet 0.1.20 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +18 -11
  3. package/README.zh.md +18 -11
  4. package/assets/whale/pet.json +9 -0
  5. package/lib/client.js +394 -58
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +1007 -43
  8. package/lib/invariant.js +1 -1
  9. package/lib/{state-Diiq8vjk.js → state-DrMX22GL.js} +86 -28
  10. package/lib/types/affinity.d.ts +4 -0
  11. package/lib/types/affinity.d.ts.map +1 -1
  12. package/lib/types/affinity.js +32 -6
  13. package/lib/types/chatter.d.ts +111 -0
  14. package/lib/types/chatter.d.ts.map +1 -0
  15. package/lib/types/chatter.js +756 -0
  16. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  17. package/lib/types/client/PetDockEntry.js +1 -1
  18. package/lib/types/client/PetSprite.d.ts.map +1 -1
  19. package/lib/types/client/PetSprite.js +133 -16
  20. package/lib/types/client/PluginSettingsCard.d.ts +28 -0
  21. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  22. package/lib/types/client/PluginSettingsCard.js +147 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +1 -0
  25. package/lib/types/client/locales.d.ts +4 -0
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +4 -0
  28. package/lib/types/client/sequences.d.ts +10 -0
  29. package/lib/types/client/sequences.d.ts.map +1 -0
  30. package/lib/types/client/sequences.js +20 -0
  31. package/lib/types/event-projection.d.ts +14 -1
  32. package/lib/types/event-projection.d.ts.map +1 -1
  33. package/lib/types/event-projection.js +38 -17
  34. package/lib/types/ledger.d.ts.map +1 -1
  35. package/lib/types/ledger.js +15 -7
  36. package/lib/types/loopback.d.ts +25 -0
  37. package/lib/types/loopback.d.ts.map +1 -0
  38. package/lib/types/loopback.js +68 -0
  39. package/lib/types/persist.d.ts.map +1 -1
  40. package/lib/types/persist.js +3 -1
  41. package/lib/types/registry.d.ts +7 -1
  42. package/lib/types/registry.d.ts.map +1 -1
  43. package/lib/types/registry.js +47 -3
  44. package/lib/types/remarks.d.ts +2 -0
  45. package/lib/types/remarks.d.ts.map +1 -1
  46. package/lib/types/remarks.js +35 -0
  47. package/lib/types/routes.d.ts.map +1 -1
  48. package/lib/types/routes.js +14 -0
  49. package/lib/types/service.d.ts +6 -0
  50. package/lib/types/service.d.ts.map +1 -1
  51. package/lib/types/service.js +15 -2
  52. package/lib/types/state.d.ts +7 -4
  53. package/lib/types/state.d.ts.map +1 -1
  54. package/lib/types/state.js +20 -20
  55. package/package.json +9 -9
  56. package/src/affinity.test.ts +4 -2
  57. package/src/affinity.ts +37 -6
  58. package/src/chatter.test.ts +154 -0
  59. package/src/chatter.ts +787 -0
  60. package/src/client/PetDockEntry.test.tsx +93 -0
  61. package/src/client/PetDockEntry.tsx +1 -0
  62. package/src/client/PetSprite.test.tsx +253 -3
  63. package/src/client/PetSprite.tsx +192 -37
  64. package/src/client/PluginSettingsCard.tsx +229 -18
  65. package/src/client/index.test.tsx +95 -0
  66. package/src/client/index.ts +1 -0
  67. package/src/client/locales.ts +4 -0
  68. package/src/client/pet-css.test.ts +54 -0
  69. package/src/client/pet.module.css +215 -57
  70. package/src/client/sequences.test.ts +33 -0
  71. package/src/client/sequences.ts +33 -0
  72. package/src/client/settings-card.module.css +90 -1
  73. package/src/event-projection.ts +49 -16
  74. package/src/ledger.test.ts +42 -1
  75. package/src/ledger.ts +15 -7
  76. package/src/loopback.ts +63 -0
  77. package/src/persist.test.ts +18 -1
  78. package/src/persist.ts +3 -1
  79. package/src/registry.test.ts +79 -4
  80. package/src/registry.ts +58 -6
  81. package/src/remarks.ts +36 -0
  82. package/src/routes.ts +11 -0
  83. package/src/service.ts +27 -2
  84. package/src/state.test.ts +13 -0
  85. package/src/state.ts +24 -18
package/lib/invariant.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AFFINITY_MAX, n as animationForPhase, o as AFFINITY_RANKS, u as defaultAffinityConfig } from "./state-Diiq8vjk.js";
1
+ import { a as AFFINITY_MAX, n as animationForPhase, o as AFFINITY_RANKS, u as defaultAffinityConfig } from "./state-DrMX22GL.js";
2
2
  //#region src/invariant.ts
3
3
  /**
4
4
  * Package invariants — cheap structural checks run at import time on the
@@ -29,7 +29,13 @@ const BUILTIN_REMARKS = {
29
29
  "今天的摸头也收货成功!",
30
30
  "蹭蹭你的手心,这是回礼~",
31
31
  "多摸摸我,亲密度会涨哦!",
32
- "闭眼享受中,请勿打扰~"
32
+ "闭眼享受中,请勿打扰~",
33
+ "头再低一点,够不着了~",
34
+ "呼噜呼噜,声音都冒出来了",
35
+ "这手感,比小鱼干还上瘾",
36
+ "摸到第三下,满意",
37
+ "耳朵后面,别漏了……啊,舒服",
38
+ "被摸得尾巴都卷起来了"
33
39
  ],
34
40
  petCooldown: [
35
41
  "摸过头啦,让鲸鱼娘歇口气~",
@@ -41,7 +47,13 @@ const BUILTIN_REMARKS = {
41
47
  "我知道你喜欢我,但也要节制呀~",
42
48
  "歇一歇,摸摸的手感会更好哦~",
43
49
  "咕……等我回个蓝~",
44
- "让我先消化一下刚才的爱!"
50
+ "让我先消化一下刚才的爱!",
51
+ "痒痒的,先让我缓一下……",
52
+ "再摸就掉线了,真的",
53
+ "库存的呼噜声用完了",
54
+ "手歇会儿,我也要补个蓝",
55
+ "舒服归舒服,得缓缓呀",
56
+ "再摸下去,我就要融化了"
45
57
  ],
46
58
  feed: [
47
59
  "呜哇!小鱼干好好吃!",
@@ -53,7 +65,13 @@ const BUILTIN_REMARKS = {
53
65
  "小鱼干最好吃了,再来亿条!",
54
66
  "饱餐一顿,马上满血复活~",
55
67
  "这个味道,是幸福的味道!",
56
- "吃完了还不忘舔舔爪子~"
68
+ "吃完了还不忘舔舔爪子~",
69
+ "这小鱼干,是今天的顶配",
70
+ "一口下去,精神头全回来了",
71
+ "脆!香!就是这个味儿",
72
+ "边吃边摇尾巴,形象不要了",
73
+ "好吃到眼睛都眯起来了",
74
+ "这块鱼干我记住了,懂我的"
57
75
  ],
58
76
  feedCooldown: [
59
77
  "吃饱啦,晚点再喂~",
@@ -65,7 +83,13 @@ const BUILTIN_REMARKS = {
65
83
  "先散步一圈再吃下一顿!",
66
84
  "肚皮已经鼓鼓的啦~",
67
85
  "好吃是好吃,可也得节制呀~",
68
- "等我饿了会告诉你哦~"
86
+ "等我饿了会告诉你哦~",
87
+ "胃说它满了,脑子说还能吃",
88
+ "这条得留着慢慢品",
89
+ "先消消食,待会儿再战",
90
+ "塞不下了,真塞不下了",
91
+ "闻着香,可惜没地方放了",
92
+ "嗝……这顿值了"
69
93
  ],
70
94
  noTreats: [
71
95
  "没有小鱼干了,多陪我工作一会儿吧~",
@@ -77,7 +101,13 @@ const BUILTIN_REMARKS = {
77
101
  "粮仓见底啦,用几轮任务换一条鱼干吧~",
78
102
  "饿着肚子等你完成下一轮任务~",
79
103
  "小鱼干藏在你的工作里,去找找看!",
80
- "先工作后干饭,我们的约定哦~"
104
+ "先工作后干饭,我们的约定哦~",
105
+ "粮仓见底,全靠感情撑着了",
106
+ "饿是真饿,活也是真得干",
107
+ "画饼充饥……不对,画鱼干充饥",
108
+ "没鱼干的日子,靠意志力过",
109
+ "下一轮任务,我闻到了鱼干味",
110
+ "先记账上,欠我两条,记住啦"
81
111
  ]
82
112
  };
83
113
  /** The legacy first line of one kind (direct callers' fallback copy). */
@@ -137,6 +167,11 @@ var RemarkPicker = class {
137
167
  this.counters.set(kind, index + 1);
138
168
  return pool[index];
139
169
  }
170
+ /** Select a line from a stable external counter without changing local picker state. */
171
+ pickAt(kind, count) {
172
+ const pool = this.pools[kind];
173
+ return pool[Math.max(0, Math.floor(count)) % pool.length];
174
+ }
140
175
  };
141
176
  //#endregion
142
177
  //#region src/affinity.ts
@@ -220,6 +255,8 @@ function emptyAffinity() {
220
255
  lastFeedAt: 0,
221
256
  pets: 0,
222
257
  feeds: 0,
258
+ petRejects: 0,
259
+ feedRejects: 0,
223
260
  turns: 0
224
261
  };
225
262
  }
@@ -246,6 +283,11 @@ function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
246
283
  function clamp(points) {
247
284
  return Math.min(AFFINITY_MAX, Math.max(0, points));
248
285
  }
286
+ /** Built-in reaction selected deterministically from a persisted counter. */
287
+ function countedRemark(kind, count) {
288
+ const pool = BUILTIN_REMARKS[kind];
289
+ return pool[Math.max(0, Math.floor(count)) % pool.length] ?? builtinRemark(kind);
290
+ }
249
291
  /**
250
292
  * Apply one interaction to a copy of the state (immutable style: returns a
251
293
  * new object; the caller replaces the persisted state). Cooldowns only
@@ -255,36 +297,42 @@ function clamp(points) {
255
297
  function applyInteraction(state, kind, nowMs, config = defaultAffinityConfig) {
256
298
  const next = { ...state };
257
299
  if (kind === "pet") {
258
- if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) return {
259
- affinity: state,
260
- delta: 0,
261
- reaction: builtinRemark("petCooldown"),
262
- accepted: false
263
- };
300
+ if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) {
301
+ next.petRejects += 1;
302
+ return {
303
+ affinity: next,
304
+ delta: 0,
305
+ reaction: countedRemark("petCooldown", state.petRejects),
306
+ accepted: false
307
+ };
308
+ }
264
309
  next.lastPetAt = nowMs;
265
310
  next.pets += 1;
266
311
  next.points = clamp(state.points + config.petReward);
267
312
  return {
268
313
  affinity: next,
269
314
  delta: config.petReward,
270
- reaction: builtinRemark("pet"),
315
+ reaction: countedRemark("pet", state.pets),
271
316
  accepted: true
272
317
  };
273
318
  }
274
319
  if (kind === "feed") {
275
- if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) return {
276
- affinity: state,
277
- delta: 0,
278
- reaction: builtinRemark("feedCooldown"),
279
- accepted: false
280
- };
320
+ if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) {
321
+ next.feedRejects += 1;
322
+ return {
323
+ affinity: next,
324
+ delta: 0,
325
+ reaction: countedRemark("feedCooldown", state.feedRejects),
326
+ accepted: false
327
+ };
328
+ }
281
329
  next.lastFeedAt = nowMs;
282
330
  next.feeds += 1;
283
331
  next.points = clamp(state.points + config.feedReward);
284
332
  return {
285
333
  affinity: next,
286
334
  delta: config.feedReward,
287
- reaction: builtinRemark("feed"),
335
+ reaction: countedRemark("feed", state.feeds),
288
336
  accepted: true
289
337
  };
290
338
  }
@@ -304,14 +352,17 @@ function applyTurnReward(state, config = defaultAffinityConfig) {
304
352
  }
305
353
  //#endregion
306
354
  //#region src/state.ts
307
- const defaultPetStateConfig = { celebrateMs: 2400 };
355
+ const defaultPetStateConfig = {
356
+ celebrateMs: 2400,
357
+ failureMs: 2400
358
+ };
308
359
  /**
309
360
  * Map one activity phase onto the animation contract.
310
361
  * - thinking → `running` and tool → `running-right` (focused work).
311
362
  * - review → `review` while answer text is streaming.
312
363
  * - waiting → `waiting` (expectant pose, needs user input).
313
364
  * - done → `jumping` (celebration), then back to `idle` after the window.
314
- * - failed → `failed` until another activity event arrives.
365
+ * - failed → `failed` briefly, then back to `idle`.
315
366
  * - idle → `idle` (calm breathing loop).
316
367
  */
317
368
  function animationForPhase(phase) {
@@ -341,26 +392,31 @@ function rowOf(animation) {
341
392
  }
342
393
  /**
343
394
  * PetStateMachine — one instance per host process. Holds only the latest
344
- * input snapshot and the celebration timing; no storage, no side effects.
395
+ * input snapshot and terminal-state timing; no storage, no side effects.
345
396
  */
346
397
  var PetStateMachine = class {
347
- config;
348
398
  now;
349
399
  phase = "idle";
350
400
  line;
351
401
  phrase;
352
402
  sessionActive = false;
353
403
  doneAt;
404
+ failedAt;
405
+ config;
354
406
  constructor(config = defaultPetStateConfig, now = Date.now) {
355
- this.config = config;
356
407
  this.now = now;
408
+ this.config = {
409
+ ...defaultPetStateConfig,
410
+ ...config
411
+ };
357
412
  }
358
413
  /** Consume one projected activity update. */
359
414
  onActivityStatus(input) {
360
415
  this.phase = input.phase;
361
416
  this.line = input.line;
362
417
  this.phrase = input.phrase;
363
- if (input.phase === "done") this.doneAt = this.now();
418
+ this.doneAt = input.phase === "done" ? this.now() : void 0;
419
+ this.failedAt = input.phase === "failed" ? this.now() : void 0;
364
420
  }
365
421
  /** A session became the active one (or a fresh session started). */
366
422
  onSessionActive() {
@@ -373,14 +429,16 @@ var PetStateMachine = class {
373
429
  this.line = void 0;
374
430
  this.phrase = void 0;
375
431
  this.doneAt = void 0;
432
+ this.failedAt = void 0;
376
433
  }
377
434
  /** Render the current animation decision. */
378
435
  render() {
379
436
  const nowMs = this.now();
380
437
  let animation = animationForPhase(this.phase);
381
- if (this.phase === "done" && this.doneAt !== void 0) if (nowMs - this.doneAt < this.config.celebrateMs) animation = "jumping";
382
- else animation = "idle";
383
- const bubble = this.phase === "idle" || this.phase === "done" && this.doneAt !== void 0 && nowMs - this.doneAt >= this.config.celebrateMs ? void 0 : this.phrase ?? this.line;
438
+ const doneSettled = this.phase === "done" && this.doneAt !== void 0 && nowMs - this.doneAt >= this.config.celebrateMs;
439
+ const failedSettled = this.phase === "failed" && this.failedAt !== void 0 && nowMs - this.failedAt >= this.config.failureMs;
440
+ if (doneSettled || failedSettled) animation = "idle";
441
+ const bubble = this.phase === "idle" || doneSettled || failedSettled ? void 0 : this.phrase ?? this.line;
384
442
  return {
385
443
  animation,
386
444
  ...bubble === void 0 ? {} : { bubble },
@@ -21,6 +21,10 @@ export interface AffinityState {
21
21
  pets: number;
22
22
  /** Total feed count (lifetime). */
23
23
  feeds: number;
24
+ /** Total pet attempts rejected by cooldown (lifetime). */
25
+ petRejects: number;
26
+ /** Total feed attempts rejected by cooldown (lifetime). */
27
+ feedRejects: number;
24
28
  /** Total completed turns witnessed (lifetime). */
25
29
  turns: number;
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"affinity.d.ts","sourceRoot":"","sources":["../../src/affinity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,YAAc,CAAA;AAEvC;;;;8EAI8E;AAC9E,eAAO,MAAM,cAAc;aACvB,GAAG,EAAE,CAAC;aAAE,IAAI,EAAE,IAAI;aAAE,KAAK,EAAE,GAAG;;aAC9B,GAAG,EAAE,EAAE;aAAE,IAAI,EAAE,IAAI;aAAE,KAAK,EAAE,IAAI;;aAChC,GAAG,EAAE,EAAE;aAAE,IAAI,EAAE,IAAI;aAAE,KAAK,EAAE,KAAK;;aACjC,GAAG,EAAE,EAAE;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,MAAM;;aACpC,GAAG,EAAE,GAAG;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,OAAO;;aACtC,GAAG,EAAE,GAAG;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,QAAQ;;aACvC,GAAG,EAAE,IAAK;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,SAAS;;aAC1C,GAAG,EAAE,KAAM;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,UAAU;;aAC5C,GAAG,EAAE,MAAO;aAAE,IAAI,EAAE,MAAM;aAAE,KAAK,EAAE,WAAW;EACxC,CAAA;AAEV,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAMnC,CAAA;AAED,wBAAgB,aAAa,IAAI,aAAa,CAW7C;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,QAAQ,EAAE,aAAa,CAAA;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,8BAA8B;AAC9B,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAMtE;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAA;IACpB,kDAAkD;IAClD,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,eAAe,CAYjB;AAYD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,cAAsC,GAC7C,kBAAkB,CA2CpB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,cAAsC,GAC7C,aAAa,CAKf"}
@@ -7,7 +7,7 @@
7
7
  * built-in pool); the ledger layers per-pet custom remarks on top.
8
8
  * @module @linxin666/dsh-pet/affinity
9
9
  */
10
- import { builtinRemark } from './remarks.js';
10
+ import { BUILTIN_REMARKS, builtinRemark } from './remarks.js';
11
11
  /**
12
12
  * Affinity points cap. Historically 100; removed so long-term companions
13
13
  * keep growing — the default limit is now the full 999,999,999 range.
@@ -37,7 +37,16 @@ export const defaultAffinityConfig = {
37
37
  feedCooldownMs: 30_000,
38
38
  };
39
39
  export function emptyAffinity() {
40
- return { points: 0, lastPetAt: 0, lastFeedAt: 0, pets: 0, feeds: 0, turns: 0 };
40
+ return {
41
+ points: 0,
42
+ lastPetAt: 0,
43
+ lastFeedAt: 0,
44
+ pets: 0,
45
+ feeds: 0,
46
+ petRejects: 0,
47
+ feedRejects: 0,
48
+ turns: 0,
49
+ };
41
50
  }
42
51
  /** Rank for a point total. */
43
52
  export function rankOf(points) {
@@ -65,6 +74,11 @@ export function affinityViewOf(state, nowMs, config = defaultAffinityConfig) {
65
74
  function clamp(points) {
66
75
  return Math.min(AFFINITY_MAX, Math.max(0, points));
67
76
  }
77
+ /** Built-in reaction selected deterministically from a persisted counter. */
78
+ function countedRemark(kind, count) {
79
+ const pool = BUILTIN_REMARKS[kind];
80
+ return pool[Math.max(0, Math.floor(count)) % pool.length] ?? builtinRemark(kind);
81
+ }
68
82
  /**
69
83
  * Apply one interaction to a copy of the state (immutable style: returns a
70
84
  * new object; the caller replaces the persisted state). Cooldowns only
@@ -75,7 +89,13 @@ export function applyInteraction(state, kind, nowMs, config = defaultAffinityCon
75
89
  const next = { ...state };
76
90
  if (kind === 'pet') {
77
91
  if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) {
78
- return { affinity: state, delta: 0, reaction: builtinRemark('petCooldown'), accepted: false };
92
+ next.petRejects += 1;
93
+ return {
94
+ affinity: next,
95
+ delta: 0,
96
+ reaction: countedRemark('petCooldown', state.petRejects),
97
+ accepted: false,
98
+ };
79
99
  }
80
100
  next.lastPetAt = nowMs;
81
101
  next.pets += 1;
@@ -83,13 +103,19 @@ export function applyInteraction(state, kind, nowMs, config = defaultAffinityCon
83
103
  return {
84
104
  affinity: next,
85
105
  delta: config.petReward,
86
- reaction: builtinRemark('pet'),
106
+ reaction: countedRemark('pet', state.pets),
87
107
  accepted: true,
88
108
  };
89
109
  }
90
110
  if (kind === 'feed') {
91
111
  if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) {
92
- return { affinity: state, delta: 0, reaction: builtinRemark('feedCooldown'), accepted: false };
112
+ next.feedRejects += 1;
113
+ return {
114
+ affinity: next,
115
+ delta: 0,
116
+ reaction: countedRemark('feedCooldown', state.feedRejects),
117
+ accepted: false,
118
+ };
93
119
  }
94
120
  next.lastFeedAt = nowMs;
95
121
  next.feeds += 1;
@@ -97,7 +123,7 @@ export function applyInteraction(state, kind, nowMs, config = defaultAffinityCon
97
123
  return {
98
124
  affinity: next,
99
125
  delta: config.feedReward,
100
- reaction: builtinRemark('feed'),
126
+ reaction: countedRemark('feed', state.feeds),
101
127
  accepted: true,
102
128
  };
103
129
  }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Pet chatter — the pet's voice while sessions work. Two speakers live here:
3
+ *
4
+ * 1. The status voice (session bubbles): big per-scene copy pools instead of
5
+ * one fixed line per phase, a fine-grained tool-name → copy-family map,
6
+ * and a compact real-argument hint ('跑跑 npm test'), in the spirit of
7
+ * the working-activity plugin's status line. Lines rotate round-robin —
8
+ * while a phase persists the copy advances every few seconds, so the pet
9
+ * feels alive without flickering per streamed chunk.
10
+ * 2. The murmur engine (碎碎念): the pet's inner whispers, woken by the
11
+ * model's own output — keyword moods (errors, test greens, plans,
12
+ * self-corrections, victories...) plus an ambient pool earned by output
13
+ * volume. A cooldown keeps whispers occasional.
14
+ *
15
+ * Pure and deterministic: round-robin everywhere (no Math.random), clocks are
16
+ * injected. The first line of each status pool is the legacy fixed copy the
17
+ * plugin has always shown, so existing installs keep their wording until the
18
+ * scene cycles. No emoji anywhere (repository rule); ~ is the whale-girl's
19
+ * signature.
20
+ * @module @linxin666/dsh-pet/chatter
21
+ */
22
+ /** Status copy scenes — the situations a session bubble can report. */
23
+ export type StatusScene = 'prepare' | 'waiting' | 'thinking' | 'review' | 'toolResult' | 'done' | 'failed' | 'toolFailed' | 'maxTokens' | 'interrupted' | 'blocked';
24
+ /** While a scene persists, its copy advances on this cadence (ms). */
25
+ export declare const STATUS_ROTATE_MS = 4000;
26
+ /** Fixed-copy pools per status scene (first line = legacy wording). */
27
+ export declare const STATUS_POOLS: Readonly<Record<StatusScene, readonly string[]>>;
28
+ /** Tool families for friendlier per-tool status copy. */
29
+ export type ToolCategory = 'read' | 'write' | 'edit' | 'shell' | 'grep' | 'find' | 'ls' | 'webSearch' | 'webFetch' | 'mcp' | 'memory' | 'subagent' | 'todo' | 'browser' | 'git' | 'ask' | 'generic';
30
+ /** Map a raw tool name onto its copy family (working-activity style regexes). */
31
+ export declare function toolCategory(toolName: string): ToolCategory;
32
+ /**
33
+ * Per-family tool status pools. '{tool}' interpolates the compact tool name,
34
+ * '{hint}' the compact real-argument hint (both optional per line); the first
35
+ * entry of every pool is the legacy '正在使用 {tool}' wording.
36
+ */
37
+ export declare const TOOL_POOLS: Readonly<Record<ToolCategory, readonly string[]>>;
38
+ /** Pools for the parallel-tools line; '{n}' interpolates the running count. */
39
+ export declare const TOOL_REMAINING_POOL: readonly string[];
40
+ /**
41
+ * A compact, human-readable hint of what a tool call actually touches —
42
+ * the command, the path, the pattern, the query. Best-effort parse of the
43
+ * raw arguments JSON; unknown shapes stay hintless. Capped short so the
44
+ * bubble stays compact.
45
+ */
46
+ export declare function toolArgHint(toolName: string, argumentsJson: string): string | undefined;
47
+ /**
48
+ * Round-robin voice for status copy. Scene-keyed picks stay STABLE while the
49
+ * same scene repeats (streaming chunks re-emit the same phase many times per
50
+ * second, and rotating per chunk would make the bubble flicker), but advance
51
+ * once the scene has persisted past the rotation cadence, so a long thinking
52
+ * stretch keeps changing its wording.
53
+ */
54
+ export declare class StatusVoice {
55
+ private readonly rotateMs;
56
+ private readonly counters;
57
+ private lastScene;
58
+ private lastLine;
59
+ private lastLineAt;
60
+ constructor(rotateMs?: number);
61
+ /** Draw the next line of one pool, advancing its round-robin cursor. */
62
+ private draw;
63
+ /** Reuse the stable line or advance when the cadence elapsed. */
64
+ private voice;
65
+ /** Status line for a phase scene. */
66
+ scene(scene: StatusScene, nowMs: number): string;
67
+ /** Status line for a tool call, with the real-argument hint when known. */
68
+ tool(toolName: string, displayName: string, hint: string | undefined, nowMs: number): string;
69
+ /** Status line while sibling tools still run (always reflects the count). */
70
+ toolRemaining(count: number, nowMs: number): string;
71
+ }
72
+ /** One murmur trigger: keywords in the model output wake a themed pool. */
73
+ export interface WhisperRule {
74
+ /** Lowercase substrings that wake this pool (matched against chunk text). */
75
+ keywords: readonly string[];
76
+ /** Themed inner-whisper lines. */
77
+ pool: readonly string[];
78
+ }
79
+ /** Murmur pacing: cooldown between whispers and output volume that earns one. */
80
+ export declare const WHISPER_COOLDOWN_MS = 9000;
81
+ export declare const WHISPER_CHAR_BUDGET = 420;
82
+ /** How long a whisper stays on screen (host-side expiry). */
83
+ export declare const WHISPER_TTL_MS = 8000;
84
+ /** Ambient inner-whisper pool (no keyword needed; earned by output volume). */
85
+ export declare const WHISPER_GENERIC_POOL: readonly string[];
86
+ /** Keyword-triggered whisper rules, most specific moods first. */
87
+ export declare const WHISPER_RULES: readonly WhisperRule[];
88
+ /**
89
+ * The murmur engine (碎碎念): watches the model's own output and lets the pet
90
+ * whisper its inner voice. Two ways to earn a whisper:
91
+ * - a keyword rule matches the fresh chunk text (themed whisper);
92
+ * - enough output volume flowed by without one (ambient whisper).
93
+ * A cooldown keeps whispers occasional; all picks are round-robin so tests
94
+ * reproduce exact lines.
95
+ */
96
+ export declare class WhisperEngine {
97
+ private readonly cooldownMs;
98
+ private readonly charBudget;
99
+ private readonly counters;
100
+ private genericCursor;
101
+ private lastWhisperAt;
102
+ private charsSinceWhisper;
103
+ constructor(cooldownMs?: number, charBudget?: number);
104
+ /**
105
+ * Feed one model-output chunk (reasoning or text). Returns the whisper to
106
+ * show, or undefined when the moment stays quiet.
107
+ */
108
+ feed(text: string, nowMs: number): string | undefined;
109
+ private speak;
110
+ }
111
+ //# sourceMappingURL=chatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatter.d.ts","sourceRoot":"","sources":["../../src/chatter.ts"],"names":[],"mappings":"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"}