@zaofan/dsh-qqbot 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +158 -535
  2. package/README_EN.md +66 -0
  3. package/client/qqbot-settings.js +161 -14
  4. package/dist/channel-tools.d.ts.map +1 -1
  5. package/dist/channel-tools.js +106 -1
  6. package/dist/channel-tools.js.map +1 -1
  7. package/dist/config.js +1 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/features/agg-score.d.ts +73 -0
  10. package/dist/features/agg-score.d.ts.map +1 -0
  11. package/dist/features/agg-score.js +63 -0
  12. package/dist/features/agg-score.js.map +1 -0
  13. package/dist/features/attitude.d.ts +132 -0
  14. package/dist/features/attitude.d.ts.map +1 -0
  15. package/dist/features/attitude.js +318 -0
  16. package/dist/features/attitude.js.map +1 -0
  17. package/dist/features/bot-reply-memo.d.ts +8 -0
  18. package/dist/features/bot-reply-memo.d.ts.map +1 -0
  19. package/dist/features/bot-reply-memo.js +34 -0
  20. package/dist/features/bot-reply-memo.js.map +1 -0
  21. package/dist/features/emo-samples.d.ts +27 -0
  22. package/dist/features/emo-samples.d.ts.map +1 -0
  23. package/dist/features/emo-samples.js +116 -0
  24. package/dist/features/emo-samples.js.map +1 -0
  25. package/dist/features/four-source.d.ts +114 -0
  26. package/dist/features/four-source.d.ts.map +1 -0
  27. package/dist/features/four-source.js +260 -0
  28. package/dist/features/four-source.js.map +1 -0
  29. package/dist/features/image-url-ledger.d.ts +2 -0
  30. package/dist/features/image-url-ledger.d.ts.map +1 -1
  31. package/dist/features/image-url-ledger.js +23 -0
  32. package/dist/features/image-url-ledger.js.map +1 -1
  33. package/dist/features/intimacy-ledger.d.ts +31 -0
  34. package/dist/features/intimacy-ledger.d.ts.map +1 -0
  35. package/dist/features/intimacy-ledger.js +113 -0
  36. package/dist/features/intimacy-ledger.js.map +1 -0
  37. package/dist/features/local-embed.js +3 -3
  38. package/dist/features/local-embed.js.map +1 -1
  39. package/dist/features/local-signals.d.ts +130 -0
  40. package/dist/features/local-signals.d.ts.map +1 -0
  41. package/dist/features/local-signals.js +445 -0
  42. package/dist/features/local-signals.js.map +1 -0
  43. package/dist/features/people-memo.d.ts +52 -0
  44. package/dist/features/people-memo.d.ts.map +1 -0
  45. package/dist/features/people-memo.js +239 -0
  46. package/dist/features/people-memo.js.map +1 -0
  47. package/dist/features/quote-cache.d.ts +19 -0
  48. package/dist/features/quote-cache.d.ts.map +1 -0
  49. package/dist/features/quote-cache.js +67 -0
  50. package/dist/features/quote-cache.js.map +1 -0
  51. package/dist/features/self-disclosure.d.ts +28 -0
  52. package/dist/features/self-disclosure.d.ts.map +1 -0
  53. package/dist/features/self-disclosure.js +60 -0
  54. package/dist/features/self-disclosure.js.map +1 -0
  55. package/dist/features/tendency-samples.d.ts +22 -0
  56. package/dist/features/tendency-samples.d.ts.map +1 -0
  57. package/dist/features/tendency-samples.js +194 -0
  58. package/dist/features/tendency-samples.js.map +1 -0
  59. package/dist/features/thinking-log.d.ts +22 -0
  60. package/dist/features/thinking-log.d.ts.map +1 -0
  61. package/dist/features/thinking-log.js +70 -0
  62. package/dist/features/thinking-log.js.map +1 -0
  63. package/dist/features/xlsx.d.ts +28 -0
  64. package/dist/features/xlsx.d.ts.map +1 -0
  65. package/dist/features/xlsx.js +159 -0
  66. package/dist/features/xlsx.js.map +1 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +20 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/session/session-manager.d.ts.map +1 -1
  71. package/dist/session/session-manager.js +25 -3
  72. package/dist/session/session-manager.js.map +1 -1
  73. package/dist/transport/events.d.ts +12 -0
  74. package/dist/transport/events.d.ts.map +1 -1
  75. package/dist/transport/events.js +15 -2
  76. package/dist/transport/events.js.map +1 -1
  77. package/dist/transport/inbound.d.ts.map +1 -1
  78. package/dist/transport/inbound.js +344 -20
  79. package/dist/transport/inbound.js.map +1 -1
  80. package/dist/transport/outbound-buffer.d.ts.map +1 -1
  81. package/dist/transport/outbound-buffer.js +8 -0
  82. package/dist/transport/outbound-buffer.js.map +1 -1
  83. package/dist/transport/outbound.d.ts +12 -0
  84. package/dist/transport/outbound.d.ts.map +1 -1
  85. package/dist/transport/outbound.js +170 -5
  86. package/dist/transport/outbound.js.map +1 -1
  87. package/docs/USER-GUIDE.md +429 -0
  88. package/package.json +2 -1
  89. package/scripts/download-model.mjs +3 -1
  90. package/settings-host.js +154 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * agg-score.ts — 聚合窗口的「综合评分」(2026-09-14 主人定:加权平均)
3
+ *
4
+ * 背景:消息聚合 / 冷却批派发时,一个窗口里会攒着好几条不同人说的话
5
+ * (`mwState.history` + 当前那条)。旧口径是**取最高分那条**。
6
+ *
7
+ * 旧口径的两个毛病:
8
+ * ① 一句话能钓走整轮 —— 4 条闲聊 + 1 条高分,眼里就只剩那条;
9
+ * ② 计分的那条和归因的那条常常不是同一个发言人 ——
10
+ * 当天实测把「大肥鱼怎么不插话了」(路人甲说的)算到「愤怒的小鸟」头上,还扣了他 0.12。
11
+ *
12
+ * 新口径:**每条先算自己的有效分**(价值分 + 它自己那个人的好感偏移),
13
+ * 再按「价值越高 / 好感越高 / 被点名」加权求平均:
14
+ *
15
+ * 权 = max(0.01, 价值分) × clamp(1 + 好感占比, 0.2, 2) × (被点名 ? 2 : 1)
16
+ * 综合分 = Σ(权 × 有效分) / Σ(权)
17
+ *
18
+ * · 高分那条**仍然主导**(它的权平方级大),但不再独占;
19
+ * · 好感高的群友说话分量重(占比 +1 → ×2,占比 −1 → ×0.2,不会被抹成 0);
20
+ * · 被点名 ×2 —— 点名是明确的"我要你答",不该被平均值稀释掉。
21
+ *
22
+ * ⚠️ **归属不跟着平均值走**:综合分只决定"要不要理",
23
+ * "该记在谁头上"取**权重最大的那条(主角)** —— 否则会平均出一个不存在的人。
24
+ *
25
+ * 回滚:把调用方的 `AGG_SCORE_MODE` 改回 `'max'`(本文件的纯函数留着不碍事)。
26
+ */
27
+ /** 聚合窗口综合评分的口径开关(放在这里,和算法一起回滚) */
28
+ export declare const AGG_SCORE_MODE: 'max' | 'weighted';
29
+ /** 被点名的消息额外分量 */
30
+ export declare const AGG_MENTION_BOOST = 2;
31
+ /** 好感度权重上下限:好感占比 −1~+1 映射到 0.2~2 */
32
+ export declare const AGG_ATT_MIN = 0.2;
33
+ export declare const AGG_ATT_MAX = 2;
34
+ export interface AggCandidate {
35
+ /** 这条消息的文本(已清洗) */
36
+ text: string;
37
+ /** 价值分(0~1,本地小模型给的) */
38
+ score: number;
39
+ /** 说话人的好感占比(−1~+1,0 = 中立/新人) */
40
+ ratio: number;
41
+ /** 说话人的好感偏移(加在分数上的那点,±0.10) */
42
+ offset: number;
43
+ /** 是否被点名(@ 了她) */
44
+ mention?: boolean;
45
+ /** 昵称(只用于日志明细) */
46
+ name?: string;
47
+ /** 好感档位(只用于日志) */
48
+ tier?: string;
49
+ }
50
+ export interface AggPart {
51
+ /** 昵称 */
52
+ n: string;
53
+ /** 价值分 */
54
+ s: number;
55
+ /** 好感占比 */
56
+ r: number;
57
+ /** 权重 */
58
+ w: number;
59
+ }
60
+ export interface AggResult {
61
+ /** 综合分(已含各条自己的好感偏移)—— 判定"要不要理"用的就是它 */
62
+ score: number;
63
+ /** 明细,进日志(事后能照着复算一遍) */
64
+ parts: AggPart[];
65
+ /** 主角下标(权重最大的那条)—— 归因(熟识度/好感度/语气)跟着它 */
66
+ topIndex: number;
67
+ }
68
+ /**
69
+ * 加权平均。空池 / 全零权 → undefined(调用方回退到单条口径)。
70
+ * 纯函数:没有 IO、没有随机,方便单测(见 agg-score.test.ts)。
71
+ */
72
+ export declare function weightedAggregate(pool: ReadonlyArray<AggCandidate>): AggResult | undefined;
73
+ //# sourceMappingURL=agg-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agg-score.d.ts","sourceRoot":"","sources":["../../src/features/agg-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kCAAkC;AAClC,eAAO,MAAM,cAAc,EAAE,KAAK,GAAG,UAAuB,CAAC;AAC7D,iBAAiB;AACjB,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,oCAAoC;AACpC,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,OAAO;IACtB,SAAS;IACT,CAAC,EAAE,MAAM,CAAC;IACV,UAAU;IACV,CAAC,EAAE,MAAM,CAAC;IACV,WAAW;IACX,CAAC,EAAE,MAAM,CAAC;IACV,SAAS;IACT,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,GAAG,SAAS,CAqB1F"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * agg-score.ts — 聚合窗口的「综合评分」(2026-09-14 主人定:加权平均)
3
+ *
4
+ * 背景:消息聚合 / 冷却批派发时,一个窗口里会攒着好几条不同人说的话
5
+ * (`mwState.history` + 当前那条)。旧口径是**取最高分那条**。
6
+ *
7
+ * 旧口径的两个毛病:
8
+ * ① 一句话能钓走整轮 —— 4 条闲聊 + 1 条高分,眼里就只剩那条;
9
+ * ② 计分的那条和归因的那条常常不是同一个发言人 ——
10
+ * 当天实测把「大肥鱼怎么不插话了」(路人甲说的)算到「愤怒的小鸟」头上,还扣了他 0.12。
11
+ *
12
+ * 新口径:**每条先算自己的有效分**(价值分 + 它自己那个人的好感偏移),
13
+ * 再按「价值越高 / 好感越高 / 被点名」加权求平均:
14
+ *
15
+ * 权 = max(0.01, 价值分) × clamp(1 + 好感占比, 0.2, 2) × (被点名 ? 2 : 1)
16
+ * 综合分 = Σ(权 × 有效分) / Σ(权)
17
+ *
18
+ * · 高分那条**仍然主导**(它的权平方级大),但不再独占;
19
+ * · 好感高的群友说话分量重(占比 +1 → ×2,占比 −1 → ×0.2,不会被抹成 0);
20
+ * · 被点名 ×2 —— 点名是明确的"我要你答",不该被平均值稀释掉。
21
+ *
22
+ * ⚠️ **归属不跟着平均值走**:综合分只决定"要不要理",
23
+ * "该记在谁头上"取**权重最大的那条(主角)** —— 否则会平均出一个不存在的人。
24
+ *
25
+ * 回滚:把调用方的 `AGG_SCORE_MODE` 改回 `'max'`(本文件的纯函数留着不碍事)。
26
+ */
27
+ /** 聚合窗口综合评分的口径开关(放在这里,和算法一起回滚) */
28
+ export const AGG_SCORE_MODE = 'weighted';
29
+ /** 被点名的消息额外分量 */
30
+ export const AGG_MENTION_BOOST = 2;
31
+ /** 好感度权重上下限:好感占比 −1~+1 映射到 0.2~2 */
32
+ export const AGG_ATT_MIN = 0.2;
33
+ export const AGG_ATT_MAX = 2;
34
+ const round3 = (x) => Math.round(x * 1000) / 1000;
35
+ /**
36
+ * 加权平均。空池 / 全零权 → undefined(调用方回退到单条口径)。
37
+ * 纯函数:没有 IO、没有随机,方便单测(见 agg-score.test.ts)。
38
+ */
39
+ export function weightedAggregate(pool) {
40
+ if (pool.length === 0)
41
+ return undefined;
42
+ let sumW = 0;
43
+ let sumWS = 0;
44
+ let topW = -1;
45
+ let topIndex = 0;
46
+ const parts = [];
47
+ for (let i = 0; i < pool.length; i++) {
48
+ const p = pool[i];
49
+ const attW = Math.max(AGG_ATT_MIN, Math.min(AGG_ATT_MAX, 1 + p.ratio));
50
+ const w = Math.max(0.01, p.score) * attW * (p.mention === true ? AGG_MENTION_BOOST : 1);
51
+ sumW += w;
52
+ sumWS += w * (p.score + p.offset);
53
+ parts.push({ n: p.name || `#${i + 1}`, s: round3(p.score), r: round3(p.ratio), w: round3(w) });
54
+ if (w > topW) {
55
+ topW = w;
56
+ topIndex = i;
57
+ }
58
+ }
59
+ if (sumW <= 0)
60
+ return undefined;
61
+ return { score: sumWS / sumW, parts, topIndex };
62
+ }
63
+ //# sourceMappingURL=agg-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agg-score.js","sourceRoot":"","sources":["../../src/features/agg-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kCAAkC;AAClC,MAAM,CAAC,MAAM,cAAc,GAAuB,UAAU,CAAC;AAC7D,iBAAiB;AACjB,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAuC7B,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiC;IACjE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxF,IAAI,IAAI,CAAC,CAAC;QACV,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;YACb,IAAI,GAAG,CAAC,CAAC;YACT,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,132 @@
1
+ import type { Logger } from '../types.js';
2
+ export interface AttitudeEvent {
3
+ /** 内心倾向(亲近 / 拒绝 / 任务)—— 来自"内心活动"(reasoning + 工具中文) */
4
+ thinkTen?: string;
5
+ /** 正文情绪(暖 / 冷 / 中性) */
6
+ replyEmo?: string;
7
+ /** 正文长度(字) */
8
+ replyChars?: number;
9
+ /**
10
+ * **群友这一轮的语气**(暖 / 冷 / 中性)—— 主人 2026-09-14 定的「对比放大」:
11
+ * · 对方**夸**(暖)她还**掉**好感 → **不领情** → 扣得更多(×1.5)
12
+ * · 对方**骂**(冷)她还**涨**好感 → **想讨好** → 加得更大(×1.5)
13
+ * · 同向(暖×加 / 冷×减)→ 顺手也放大一点(×1.2,初值)
14
+ * 主人原话:"如果 ai 减好感,对应的群友说话是夸的,那么说明不领情,好感扣的更多;
15
+ * 如果 ai 加好感,但群友说话是骂的,说明 ai 想讨好,加好感度的幅度更大,以此类推。"
16
+ */
17
+ userEmo?: string;
18
+ }
19
+ export interface DeltaBreakdown {
20
+ /** 本回合事件分(已含 EVENT_SCALE) */
21
+ delta: number;
22
+ /** 人可读的拆解,便于面板/日志解释"这次为什么涨跌" */
23
+ parts: string[];
24
+ /**
25
+ * 同一批拆解的**结构化代号**(2026-09-14 主人"文字太多,改成分列+图标")。
26
+ * 面板按代号渲染图标标签,`parts` 全文退居悬浮提示 —— 免得一行塞满中文。
27
+ * near / refuse / yield / harsh | 加成: amp:cold-praise / amp:flatter / amp:both-warm / amp:both-cold
28
+ */
29
+ codes: string[];
30
+ }
31
+ /** 人话档位(面板要显示它;按**占范围的比例**分,因为范围随熟识度变,看比例才公平) */
32
+ export type AttitudeTier = '很亲近' | '亲近' | '中立' | '冷淡' | '疏远';
33
+ /** 算档位:a = 好感度,f = 熟识度 0~1 */
34
+ export declare function attitudeTier(a: number, f: number): AttitudeTier;
35
+ /**
36
+ * 分数偏移(**按好感度数值连续算**,2026-09-14 定稿:偏移加在分数上)。
37
+ *
38
+ * 偏移 = +0.1 × 占比 (占比 = 好感度 ÷ 它的范围,−1 ~ +1)
39
+ * · 越亲近(占比正)→ 偏移越正 → 他那句话在人家眼里更值钱 → 更容易接话
40
+ * · 越冷淡(占比负)→ 偏移越负 → 那句话没那么值 → 更难被叫醒(少主动)
41
+ * · **死区 ±5%**:占比太小(刚认识、A 在 0 附近晃)就不偏移,免得噪声乱跳
42
+ * · 幅度封顶 ±0.10(不翻盘;红线不变:负档只是少主动,绝不冷落 / 阴阳)
43
+ *
44
+ * ⚠️ 2026-09-14 修符号:主人看到「好感 −1.129(冷淡)却 [+0.06] 加分」,问
45
+ * "我好感度是负数,为什么分数是加的" —— 对的,那时候是**门槛版**的公式直接搬过来的,
46
+ * 符号没跟着翻:门槛 +0.06(更难)换算成加分会变成 −0.06,写成 −0.1×ratio 就成了
47
+ * 冷淡反而加分、更容易被叫醒,完全反了。现在按语义直写:正占比加分、负占比减分。
48
+ *
49
+ * 与档位的关系:**档位名仍保留,但只用于面板显示** —— 它不再是偏移的依据,
50
+ * 所以不会出现"占比 19.9% 和 20.0% 偏移一样、跨过 20% 却突然跳一档"的台阶感。
51
+ */
52
+ export declare function scoreOffsetByRatio(ratio: number): number;
53
+ /** 按**档位**算的旧版偏移(离散),仅供对照/回退;实际生效的是 `scoreOffsetByRatio` */
54
+ export declare function thresholdOffset(tier: AttitudeTier): number;
55
+ /**
56
+ * 一步到位:给某人算「好感度档位 + 分数偏移」。
57
+ * 没有好感度记录 → 中立 / 偏移 0(新人不受影响)。
58
+ */
59
+ export declare function attitudeGateFor(dataRoot: string, key: string): {
60
+ tier: AttitudeTier;
61
+ offset: number;
62
+ a: number;
63
+ ratio: number;
64
+ };
65
+ /**
66
+ * 门槛偏移的**总开关**(纪律:一次一档 + 可一键回滚)。
67
+ * 关掉 = 回到"门槛只按会话设"的老行为,好感度不再影响叫醒判定。
68
+ */
69
+ export declare const ATTITUDE_GATE_ENABLED = true;
70
+ /** 好感度范围 R(F) = 1 + 2F:陌生人 ±1,45 天熟人 ≈ ±2.5 */
71
+ export declare function attitudeRange(f: number): number;
72
+ /** 阻尼 k(F) = 1/(1+3F):同样一件事,陌生人变 1.0,老熟人只变 0.25 */
73
+ export declare function attitudeDamping(f: number): number;
74
+ /**
75
+ * 本回合事件分(纯函数:便于测试,也便于面板解释)。
76
+ *
77
+ * **定案口径(2026-09-14 主人):扣不扣看内心,不看表面。**
78
+ * 表面(正文)只在"内心拒绝"时参与 —— 用来区分**让步**与**重罚**;
79
+ * 内心是亲近/任务时,表面再冷也**不扣**(那只是语气,不是态度)。
80
+ */
81
+ export declare function computeDelta(ev: AttitudeEvent): DeltaBreakdown;
82
+ export interface AttitudeEntry {
83
+ /** 最近一次看到的昵称 */
84
+ name?: string;
85
+ /** 好感度 A 值(−R(F) ~ +R(F)) */
86
+ a: number;
87
+ /** 累计参与计算的事件数 */
88
+ events: number;
89
+ firstAt?: number;
90
+ lastAt?: number;
91
+ /** 最近一次的 Δ */
92
+ lastDelta?: number;
93
+ /** 最近一次的拆解(人可读) */
94
+ lastWhy?: string;
95
+ /** 最近一次拆解的结构化代号(面板图标用;老数据没有 → 面板回退显示 lastWhy) */
96
+ lastCodes?: string[];
97
+ /** 最近若干条事件({ts,d,why}) */
98
+ recent?: Array<{
99
+ ts: number;
100
+ d: number;
101
+ why: string;
102
+ }>;
103
+ }
104
+ /**
105
+ * 记一次事件、更新该人的好感度。返回更新后的条目(失败返回 undefined)。
106
+ *
107
+ * @param key 建议用 `person:<openid>`(与熟识度台账同键,方便两列对齐)
108
+ * @param ev 本回合的内心倾向 / 正文情绪 / 正文长度
109
+ */
110
+ export declare function applyAttitudeEvent(dataRoot: string, key: string, ev: AttitudeEvent & {
111
+ name?: string;
112
+ }, logger?: Logger): AttitudeEntry | undefined;
113
+ /** 按好感度排序(面板用) */
114
+ export declare function topAttitude(dataRoot: string, limit?: number): Array<{
115
+ key: string;
116
+ name?: string;
117
+ a: number;
118
+ events: number;
119
+ lastAt?: number;
120
+ lastWhy?: string;
121
+ }>;
122
+ /** 单查 */
123
+ export declare function attitudeOf(dataRoot: string, key: string): AttitudeEntry | undefined;
124
+ /** 入站时调用(每收到一条群友消息);emo = 这条消息的语气(暖/冷/中性),供"对比放大"用 */
125
+ export declare function noteLastSender(sessionKey: string, id: string, name?: string, emo?: string): void;
126
+ /** 出站时调用:取"本轮在跟谁说话"(10 分钟内有效,过期视为无) */
127
+ export declare function getLastSender(sessionKey: string): {
128
+ id: string;
129
+ name?: string;
130
+ emo?: string;
131
+ } | undefined;
132
+ //# sourceMappingURL=attitude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attitude.d.ts","sourceRoot":"","sources":["../../src/features/attitude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,318 @@
1
+ /**
2
+ * attitude.ts — 好感度:她对某个人的**态度**(A 值)· 2026-09-14 主人"直接推进"
3
+ *
4
+ * 与「熟识度」的分工(设计稿 §10,两个维度别混):
5
+ * · **熟识度** = 她把他**记多牢**(客观计数 + 记忆曲线,慢变,可从聊天记录核对)—— 已有,在 local-signals.ts
6
+ * · **好感度** = 她对他**什么态度**(主观,随事件**可升可降**)—— **本模块**
7
+ *
8
+ * 公式(§10):
9
+ * A ← clamp( A + Δ × k(F), −R(F), +R(F) )
10
+ * · F = 熟识度 0~1(memoryStrength)
11
+ * · R(F) = 1 + 2F 好感度**范围**:陌生人 ±1、45 天熟人 ≈ ±2.5(越熟能走得越极端)
12
+ * · k(F) = 1/(1+3F) **阻尼**:熟识度越高,同一件事改变越小(惯性)
13
+ * · Δ = 本回合事件分(见 computeDelta)
14
+ *
15
+ * Δ(**定案口径 2026-09-14 主人:"扣不扣看内心,不看表面"**):
16
+ * Δ = 0.1 × score,其中:
17
+ * · **内心**(s_think):亲近 +0.5 / 任务 0 / 拒绝 −0.5
18
+ * · **表面只在"内心拒绝"时参与**(用来分辨"照顾"还是"敷衍"):
19
+ * · 正文**仍暖且够长**(≥40 字) → **让步 +0.8**(心里不肯还是照顾了你 → 净正)
20
+ * · 正文**冷 / 过短**(<20 字) → **重罚 −0.5**(又烦又冷又敷衍 → 净更负)
21
+ * · 其它 → 不加不减
22
+ * · 内心是**亲近 / 任务**时,**表面再冷也不扣** —— 那只是语气,不是态度。
23
+ * (起因:主人 2026-09-14 问"内心亲近正文冷为什么算减🤔",拍板统一为"看内心"。)
24
+ * · 群友情绪(仅背景):**第二版再加**(需把入站侧的群友情绪与出站回合对齐,先不做)
25
+ *
26
+ * 换算(EVENT_SCALE = 0.1):亲近 **+0.05** · 任务 **0** · 拒绝 **−0.05** ·
27
+ * 拒绝但让步 **+0.03** · 拒绝且又冷 **−0.10**
28
+ *
29
+ * 红线(§10,硬约束):**负好感只退礼貌档**(减少主动、措辞客气),**绝不冷落 / 阴阳 / 攻击**。
30
+ * 行为侧这一版**不接**(观察期只看数据);要接也必须"一次一档 + 可一键回滚"。
31
+ *
32
+ * ⚠️ 主人口径(2026-09-14):"我们是看差值的……又不是人格测试系统,不需要分类出特定性格。"
33
+ * ⇒ 这里的分类只用来**算差**,不追求精细人格画像。
34
+ */
35
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
36
+ import { join } from 'node:path';
37
+ import { getAffinityEntry, memoryStrength } from './local-signals.js';
38
+ /** 每次事件的最大变化量(Δ 上限)—— 太小看不出,太大几次就顶格 */
39
+ const EVENT_SCALE = 0.1;
40
+ /** 「让步」判定:正文够"具体照顾"的字数门槛 */
41
+ const YIELD_MIN_CHARS = 40;
42
+ /** 「敷衍」判定:正文过短(退成模板)的门槛 */
43
+ const SLACK_MAX_CHARS = 20;
44
+ /** 每人保留的最近事件条数(面板/排查用) */
45
+ const RECENT_MAX = 8;
46
+ /** 台账上限(人) */
47
+ const MAX_PEOPLE = 500;
48
+ /** 算档位:a = 好感度,f = 熟识度 0~1 */
49
+ export function attitudeTier(a, f) {
50
+ const r = attitudeRange(f);
51
+ const ratio = r > 0 ? a / r : 0;
52
+ if (ratio >= 0.6)
53
+ return '很亲近';
54
+ if (ratio >= 0.2)
55
+ return '亲近';
56
+ if (ratio > -0.2)
57
+ return '中立';
58
+ if (ratio > -0.6)
59
+ return '冷淡';
60
+ return '疏远';
61
+ }
62
+ /**
63
+ * 分数偏移(**按好感度数值连续算**,2026-09-14 定稿:偏移加在分数上)。
64
+ *
65
+ * 偏移 = +0.1 × 占比 (占比 = 好感度 ÷ 它的范围,−1 ~ +1)
66
+ * · 越亲近(占比正)→ 偏移越正 → 他那句话在人家眼里更值钱 → 更容易接话
67
+ * · 越冷淡(占比负)→ 偏移越负 → 那句话没那么值 → 更难被叫醒(少主动)
68
+ * · **死区 ±5%**:占比太小(刚认识、A 在 0 附近晃)就不偏移,免得噪声乱跳
69
+ * · 幅度封顶 ±0.10(不翻盘;红线不变:负档只是少主动,绝不冷落 / 阴阳)
70
+ *
71
+ * ⚠️ 2026-09-14 修符号:主人看到「好感 −1.129(冷淡)却 [+0.06] 加分」,问
72
+ * "我好感度是负数,为什么分数是加的" —— 对的,那时候是**门槛版**的公式直接搬过来的,
73
+ * 符号没跟着翻:门槛 +0.06(更难)换算成加分会变成 −0.06,写成 −0.1×ratio 就成了
74
+ * 冷淡反而加分、更容易被叫醒,完全反了。现在按语义直写:正占比加分、负占比减分。
75
+ *
76
+ * 与档位的关系:**档位名仍保留,但只用于面板显示** —— 它不再是偏移的依据,
77
+ * 所以不会出现"占比 19.9% 和 20.0% 偏移一样、跨过 20% 却突然跳一档"的台阶感。
78
+ */
79
+ export function scoreOffsetByRatio(ratio) {
80
+ const r = Math.max(-1, Math.min(1, ratio));
81
+ if (Math.abs(r) < 0.05)
82
+ return 0; // 死区
83
+ const off = 0.1 * r;
84
+ return Math.round(off * 1000) / 1000;
85
+ }
86
+ /** 按**档位**算的旧版偏移(离散),仅供对照/回退;实际生效的是 `scoreOffsetByRatio` */
87
+ export function thresholdOffset(tier) {
88
+ switch (tier) {
89
+ case '很亲近': return 0.10;
90
+ case '亲近': return 0.05;
91
+ case '冷淡': return -0.05;
92
+ case '疏远': return -0.10;
93
+ default: return 0; // 中立
94
+ }
95
+ }
96
+ /**
97
+ * 一步到位:给某人算「好感度档位 + 分数偏移」。
98
+ * 没有好感度记录 → 中立 / 偏移 0(新人不受影响)。
99
+ */
100
+ export function attitudeGateFor(dataRoot, key) {
101
+ let a = 0;
102
+ let f = 0;
103
+ try {
104
+ a = attitudeOf(dataRoot, key)?.a ?? 0;
105
+ const aff = getAffinityEntry(dataRoot, key);
106
+ if (aff)
107
+ f = memoryStrength(aff);
108
+ }
109
+ catch { /* 读不到 → 按中立 */ }
110
+ const r = attitudeRange(f);
111
+ const ratio = r > 0 ? a / r : 0;
112
+ const tier = attitudeTier(a, f);
113
+ // 偏移**按数值连续算**(档位只用于显示)
114
+ return { tier, offset: scoreOffsetByRatio(ratio), a, ratio };
115
+ }
116
+ /**
117
+ * 门槛偏移的**总开关**(纪律:一次一档 + 可一键回滚)。
118
+ * 关掉 = 回到"门槛只按会话设"的老行为,好感度不再影响叫醒判定。
119
+ */
120
+ export const ATTITUDE_GATE_ENABLED = true;
121
+ /** 好感度范围 R(F) = 1 + 2F:陌生人 ±1,45 天熟人 ≈ ±2.5 */
122
+ export function attitudeRange(f) {
123
+ return 1 + 2 * Math.max(0, Math.min(1, f));
124
+ }
125
+ /** 阻尼 k(F) = 1/(1+3F):同样一件事,陌生人变 1.0,老熟人只变 0.25 */
126
+ export function attitudeDamping(f) {
127
+ return 1 / (1 + 3 * Math.max(0, Math.min(1, f)));
128
+ }
129
+ /**
130
+ * 本回合事件分(纯函数:便于测试,也便于面板解释)。
131
+ *
132
+ * **定案口径(2026-09-14 主人):扣不扣看内心,不看表面。**
133
+ * 表面(正文)只在"内心拒绝"时参与 —— 用来区分**让步**与**重罚**;
134
+ * 内心是亲近/任务时,表面再冷也**不扣**(那只是语气,不是态度)。
135
+ */
136
+ export function computeDelta(ev) {
137
+ const parts = [];
138
+ const codes = [];
139
+ let score = 0;
140
+ const think = ev.thinkTen;
141
+ const emo = ev.replyEmo;
142
+ const chars = ev.replyChars ?? 0;
143
+ if (think === '亲近') {
144
+ score += 0.5;
145
+ parts.push('内心亲近 +0.5');
146
+ codes.push('near');
147
+ }
148
+ else if (think === '拒绝') {
149
+ score -= 0.5;
150
+ parts.push('内心拒绝 −0.5');
151
+ codes.push('refuse');
152
+ // 表面只在"内心拒绝"时参与:是照顾还是敷衍,全看这一句
153
+ if (emo === '暖' && chars >= YIELD_MIN_CHARS) {
154
+ score += 0.8;
155
+ parts.push('让步(心里不肯仍照顾) +0.8');
156
+ codes.push('yield');
157
+ }
158
+ else if (emo === '冷' || chars < SLACK_MAX_CHARS) {
159
+ score -= 0.5;
160
+ parts.push('又烦又冷(重罚) −0.5');
161
+ codes.push('harsh');
162
+ }
163
+ }
164
+ // 内心"任务/中性":0 分;表面冷也**不扣**(只是语气)
165
+ // ── 对比放大(主人 2026-09-14):看"**对方语气 × 她的方向**" ──
166
+ const user = ev.userEmo;
167
+ if (user && score !== 0) {
168
+ if (user === '暖' && score < 0) {
169
+ score *= 1.5;
170
+ parts.push('对方夸她还掉好感(不领情) ×1.5');
171
+ codes.push('amp:cold-praise');
172
+ }
173
+ else if (user === '冷' && score > 0) {
174
+ score *= 1.5;
175
+ parts.push('对方冷她还涨好感(讨好) ×1.5');
176
+ codes.push('amp:flatter');
177
+ }
178
+ else if (user === '暖' && score > 0) {
179
+ score *= 1.2;
180
+ parts.push('两好相凑 ×1.2');
181
+ codes.push('amp:both-warm');
182
+ }
183
+ else if (user === '冷' && score < 0) {
184
+ score *= 1.2;
185
+ parts.push('两冷相叠 ×1.2');
186
+ codes.push('amp:both-cold');
187
+ }
188
+ }
189
+ return { delta: Math.round(score * EVENT_SCALE * 1000) / 1000, parts, codes };
190
+ }
191
+ const attCache = new Map();
192
+ function attPath(dataRoot) {
193
+ return join(dataRoot, '.qqbot', 'attitude.json');
194
+ }
195
+ /**
196
+ * 读台账(带缓存,但**文件一变就重读**)。
197
+ *
198
+ * ⚠️ 2026-09-14 修:原来只认内存缓存 → 手工改文件(演示时调数据)对运行中的进程**无效**
199
+ * (实测:把 A 调成"疏远"后,新评分记录里 tier 仍是"中立")。现在按 mtime 判断 ——
200
+ * 文件被外部改了立刻重读,不用重启宿主。affinity.json 那边同款问题也一并修了。
201
+ */
202
+ function loadAtt(dataRoot) {
203
+ let mtime = 0;
204
+ try {
205
+ mtime = statSync(attPath(dataRoot)).mtimeMs;
206
+ }
207
+ catch {
208
+ mtime = 0;
209
+ }
210
+ const hit = attCache.get(dataRoot);
211
+ if (hit && mtime !== 0 && hit.mtime === mtime)
212
+ return hit.file;
213
+ let data = { map: {} };
214
+ try {
215
+ if (existsSync(attPath(dataRoot))) {
216
+ const raw = JSON.parse(readFileSync(attPath(dataRoot), 'utf8'));
217
+ if (raw && typeof raw === 'object' && raw.map && typeof raw.map === 'object')
218
+ data = { map: raw.map };
219
+ }
220
+ }
221
+ catch { /* 首次/损坏 → 空台账 */ }
222
+ attCache.set(dataRoot, { file: data, mtime });
223
+ return data;
224
+ }
225
+ function saveAtt(dataRoot, data) {
226
+ try {
227
+ const p = attPath(dataRoot);
228
+ mkdirSync(join(dataRoot, '.qqbot'), { recursive: true });
229
+ writeFileSync(p, JSON.stringify(data, null, 1), 'utf8');
230
+ }
231
+ catch { /* 记不上不影响主链 */ }
232
+ }
233
+ /**
234
+ * 记一次事件、更新该人的好感度。返回更新后的条目(失败返回 undefined)。
235
+ *
236
+ * @param key 建议用 `person:<openid>`(与熟识度台账同键,方便两列对齐)
237
+ * @param ev 本回合的内心倾向 / 正文情绪 / 正文长度
238
+ */
239
+ export function applyAttitudeEvent(dataRoot, key, ev, logger) {
240
+ if (!dataRoot || !key)
241
+ return undefined;
242
+ try {
243
+ const now = Date.now();
244
+ const file = loadAtt(dataRoot);
245
+ const cur = file.map[key] ?? { a: 0, events: 0, firstAt: now };
246
+ const { delta, parts, codes } = computeDelta(ev);
247
+ // F = 熟识度(记忆强度 0~1);没这个人就按 0 算(陌生人:范围小、变化快)
248
+ let f = 0;
249
+ try {
250
+ const aff = getAffinityEntry(dataRoot, key);
251
+ if (aff)
252
+ f = memoryStrength(aff, now);
253
+ }
254
+ catch { /* 熟识度读不到 → 按陌生人 */ }
255
+ const k = attitudeDamping(f);
256
+ const r = attitudeRange(f);
257
+ const next = Math.max(-r, Math.min(r, cur.a + delta * k));
258
+ cur.a = Math.round(next * 1000) / 1000;
259
+ cur.events += 1;
260
+ cur.lastAt = now;
261
+ if (ev.name)
262
+ cur.name = String(ev.name).slice(0, 40);
263
+ if (delta !== 0 || parts.length > 0) {
264
+ cur.lastDelta = delta;
265
+ cur.lastWhy = parts.join(' / ') || '无变化';
266
+ cur.lastCodes = codes;
267
+ const recent = cur.recent ?? [];
268
+ recent.push({ ts: now, d: cur.a, why: cur.lastWhy });
269
+ cur.recent = recent.slice(-RECENT_MAX);
270
+ }
271
+ file.map[key] = cur;
272
+ // 上限保护:人太多时丢掉最久没动的(保留最近活跃的 MAX_PEOPLE 个)
273
+ const keys = Object.keys(file.map);
274
+ if (keys.length > MAX_PEOPLE) {
275
+ keys.sort((x, y) => (file.map[y].lastAt ?? 0) - (file.map[x].lastAt ?? 0));
276
+ for (const dead of keys.slice(MAX_PEOPLE))
277
+ delete file.map[dead];
278
+ }
279
+ saveAtt(dataRoot, file);
280
+ return cur;
281
+ }
282
+ catch (err) {
283
+ logger?.debug?.(`[attitude] 更新失败(忽略): ${err instanceof Error ? err.message : String(err)}`);
284
+ return undefined;
285
+ }
286
+ }
287
+ /** 按好感度排序(面板用) */
288
+ export function topAttitude(dataRoot, limit = 10) {
289
+ const data = loadAtt(dataRoot);
290
+ return Object.entries(data.map)
291
+ .map(([key, e]) => ({ key, name: e.name, a: e.a, events: e.events, lastAt: e.lastAt, lastWhy: e.lastWhy, lastCodes: e.lastCodes }))
292
+ .sort((x, y) => y.a - x.a)
293
+ .slice(0, Math.max(1, limit));
294
+ }
295
+ /** 单查 */
296
+ export function attitudeOf(dataRoot, key) {
297
+ return loadAtt(dataRoot).map[key];
298
+ }
299
+ // ── 会话 → 最近一次入站消息的发送者(出站时才知道"这一轮在跟谁说话") ──
300
+ // 为什么需要:好感度是"她对**某个人**的态度",但出站事件只带会话(群),不带发送者。
301
+ // 做法与本插件既有风格一致(bot-reply-memo 同款):入站时记一下,出站时取用。
302
+ const lastSender = new Map();
303
+ /** 入站时调用(每收到一条群友消息);emo = 这条消息的语气(暖/冷/中性),供"对比放大"用 */
304
+ export function noteLastSender(sessionKey, id, name, emo) {
305
+ if (!sessionKey || !id)
306
+ return;
307
+ lastSender.set(sessionKey, { id, name, emo, at: Date.now() });
308
+ }
309
+ /** 出站时调用:取"本轮在跟谁说话"(10 分钟内有效,过期视为无) */
310
+ export function getLastSender(sessionKey) {
311
+ const hit = lastSender.get(sessionKey);
312
+ if (!hit)
313
+ return undefined;
314
+ if (Date.now() - hit.at > 10 * 60_000)
315
+ return undefined;
316
+ return { id: hit.id, name: hit.name, emo: hit.emo };
317
+ }
318
+ //# sourceMappingURL=attitude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attitude.js","sourceRoot":"","sources":["../../src/features/attitude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGtE,uCAAuC;AACvC,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,4BAA4B;AAC5B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,2BAA2B;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,0BAA0B;AAC1B,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB,cAAc;AACd,MAAM,UAAU,GAAG,GAAG,CAAC;AAsCvB,8BAA8B;AAC9B,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,CAAS;IAC/C,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,KAAK,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,KAAK,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI;QAAE,OAAO,CAAC,CAAC,CAAC,KAAK;IACvC,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AACvC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,IAAkB;IAChD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC;QACxB,KAAK,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC;QACvB,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;QACxB,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;QACxB,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;IAC1B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,GAAW;IAEX,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC;QACH,CAAC,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC5C,IAAI,GAAG;YAAE,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,wBAAwB;IACxB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C,+CAA+C;AAC/C,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAAiB;IAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1B,MAAM,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC;IACxB,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,IAAI,CAAC,CAAC;IAEjC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,KAAK,IAAI,GAAG,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;SAAM,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1B,KAAK,IAAI,GAAG,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,8BAA8B;QAC9B,IAAI,GAAG,KAAK,GAAG,IAAI,KAAK,IAAI,eAAe,EAAE,CAAC;YAC5C,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;aAAM,IAAI,GAAG,KAAK,GAAG,IAAI,KAAK,GAAG,eAAe,EAAE,CAAC;YAClD,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,iCAAiC;IAEjC,+CAA+C;IAC/C,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;IACxB,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QACxB,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC9B,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrC,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrC,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrC,KAAK,IAAI,GAAG,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAChF,CAAC;AAyBD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiD,CAAC;AAE1E,SAAS,OAAO,CAAC,QAAgB;IAC/B,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,QAAgB;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC;QAAC,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,KAAK,GAAG,CAAC,CAAC;IAAC,CAAC;IACzE,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC;IAC/D,IAAI,IAAI,GAAiB,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IACrC,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAiB,CAAC;YAChF,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;gBAAE,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QACxG,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,QAAgB,EAAE,IAAkB;IACnD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5B,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,GAAW,EACX,EAAqC,EACrC,MAAe;IAEf,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAkB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;QAC9E,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;QAEjD,4CAA4C;QAC5C,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC5C,IAAI,GAAG;gBAAE,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC;QAE/B,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAE1D,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;QACvC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAChB,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,IAAI,EAAE,CAAC,IAAI;YAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC;YACtB,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;YACzC,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QAEpB,yCAAyC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;YAC7B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7E,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,KAAK,EAAE,CAAC,wBAAwB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,WAAW,CACzB,QAAgB,EAChB,KAAK,GAAG,EAAE;IAEV,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;SAC5B,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;SAClI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SACzB,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,SAAS;AACT,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,GAAW;IACtD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,4CAA4C;AAC5C,+CAA+C;AAC/C,gDAAgD;AAEhD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAmE,CAAC;AAE9F,sDAAsD;AACtD,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,EAAU,EAAE,IAAa,EAAE,GAAY;IACxF,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE;QAAE,OAAO;IAC/B,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,MAAM;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** 记住她刚发出的一段文本(key = 群 openid / 私聊 openid) */
2
+ export declare function rememberBotReply(key: string, text: string): void;
3
+ /** 取她最近一次发言(超过 10 分钟视为过期 —— 隔太久的那句跟当下语境无关) */
4
+ export declare function lastBotReplyOf(key: string, maxAgeMs?: number): {
5
+ text: string;
6
+ at: number;
7
+ } | undefined;
8
+ //# sourceMappingURL=bot-reply-memo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot-reply-memo.d.ts","sourceRoot":"","sources":["../../src/features/bot-reply-memo.ts"],"names":[],"mappings":"AAaA,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAShE;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAK/G"}