luckytri 1.0.6 → 1.0.8

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 (168) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +7 -3
  3. package/README.md +7 -3
  4. package/docs/en/CHANGELOG.md +35 -16
  5. package/docs/en/chat-style.md +28 -9
  6. package/docs/en/guide.md +5 -5
  7. package/docs/en/her-life.md +2 -2
  8. package/docs/en/webui.md +1 -1
  9. package/docs/zh/CHANGELOG.md +35 -16
  10. package/docs/zh/chat-style.md +30 -9
  11. package/docs/zh/guide.md +5 -5
  12. package/docs/zh/her-life.md +2 -2
  13. package/docs/zh/webui.md +1 -1
  14. package/package.json +1 -1
  15. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  16. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  17. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  18. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  19. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  20. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  21. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  22. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  23. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  24. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  25. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  26. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  27. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  28. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  29. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  30. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  31. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  32. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  33. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  34. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  35. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  36. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  37. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  38. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  39. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  40. package/public/app/index.html +5 -6
  41. package/public/guide.en.html +3 -3
  42. package/public/guide.html +3 -3
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/update-instance.js +201 -201
  46. package/server/channels/adapters.js +27 -27
  47. package/server/channels/onebot/adapter.js +288 -288
  48. package/server/channels/qqbot/adapter.js +253 -253
  49. package/server/channels/qqbot/api.js +199 -199
  50. package/server/channels/qqbot/credentials.js +11 -11
  51. package/server/channels/qqbot/gateway.js +274 -274
  52. package/server/channels/qqbot/index.js +389 -389
  53. package/server/channels/segments.js +40 -40
  54. package/server/channels/session-key.js +153 -153
  55. package/server/channels/state.js +42 -42
  56. package/server/core/api.js +564 -564
  57. package/server/core/context-builder.js +288 -288
  58. package/server/core/context-compactor.js +454 -454
  59. package/server/core/conversation-cues.js +385 -325
  60. package/server/core/conversation-grounding.js +304 -0
  61. package/server/core/conversation-manager.js +87 -87
  62. package/server/core/dialogue-context.js +244 -0
  63. package/server/core/dialogue-contract.js +35 -0
  64. package/server/core/feedback.js +8 -8
  65. package/server/core/group-style.js +72 -72
  66. package/server/core/initiative-context.js +68 -66
  67. package/server/core/input-budget.js +32 -32
  68. package/server/core/local-demo.js +98 -98
  69. package/server/core/message-manager.js +118 -118
  70. package/server/core/message-scheduler.js +78 -78
  71. package/server/core/model-json.js +190 -190
  72. package/server/core/model-manager.js +5 -8
  73. package/server/core/model-presets.js +900 -900
  74. package/server/core/name-call.js +29 -29
  75. package/server/core/network.js +157 -157
  76. package/server/core/person-name.js +22 -22
  77. package/server/core/persona-manager.js +359 -269
  78. package/server/core/reader-check.js +62 -0
  79. package/server/core/reply-delivery.js +537 -404
  80. package/server/core/reply-target-resolver.js +50 -50
  81. package/server/core/response-generator.js +92 -50
  82. package/server/core/response-validator.js +53 -32
  83. package/server/core/retired-prompts.js +51 -51
  84. package/server/core/sessions.js +109 -109
  85. package/server/core/speaker-names.js +92 -92
  86. package/server/core/topic-tracker.js +65 -65
  87. package/server/core/turn.js +82 -15
  88. package/server/http.js +18 -18
  89. package/server/i18n/decisions.js +209 -209
  90. package/server/i18n/errors.js +2 -0
  91. package/server/i18n/index.js +144 -144
  92. package/server/i18n/labels.js +6 -10
  93. package/server/i18n/models.js +111 -111
  94. package/server/knowledge/api.js +342 -342
  95. package/server/knowledge/embedding-profile.js +114 -114
  96. package/server/knowledge/index.js +13 -13
  97. package/server/knowledge/retrieval.js +93 -93
  98. package/server/knowledge/schema.js +158 -158
  99. package/server/knowledge/vector-index.js +90 -90
  100. package/server/mind/affect.js +265 -265
  101. package/server/mind/attention.js +200 -200
  102. package/server/mind/bonds.js +565 -565
  103. package/server/mind/clock.js +83 -83
  104. package/server/mind/context-selection.js +16 -4
  105. package/server/mind/continuity.js +562 -562
  106. package/server/mind/conversation-origin.js +175 -175
  107. package/server/mind/days.js +210 -210
  108. package/server/mind/evidence-read.js +45 -45
  109. package/server/mind/expression-grounding.js +21 -0
  110. package/server/mind/faces.js +373 -373
  111. package/server/mind/guard.js +68 -60
  112. package/server/mind/initiative.js +338 -338
  113. package/server/mind/life/activities.js +21 -21
  114. package/server/mind/life/context.js +330 -330
  115. package/server/mind/life/growth.js +261 -261
  116. package/server/mind/life/presence.js +305 -305
  117. package/server/mind/meetings.js +1069 -1069
  118. package/server/mind/memory-policy.js +48 -48
  119. package/server/mind/memory.js +888 -888
  120. package/server/mind/nature.js +358 -358
  121. package/server/mind/novelty.js +19 -19
  122. package/server/mind/own-voice.js +35 -0
  123. package/server/mind/periods.js +118 -118
  124. package/server/mind/reading.js +132 -120
  125. package/server/mind/relationship-context.js +71 -71
  126. package/server/mind/relationships.js +390 -390
  127. package/server/mind/salience.js +201 -201
  128. package/server/mind/schema.js +567 -567
  129. package/server/mind/self.js +496 -484
  130. package/server/mind/thoughts.js +274 -274
  131. package/server/mind/time/agenda.js +242 -242
  132. package/server/mind/time/api.js +200 -200
  133. package/server/mind/time/attention.js +34 -34
  134. package/server/mind/time/availability.js +23 -1
  135. package/server/mind/time/executor.js +4 -2
  136. package/server/mind/time/games.js +20 -11
  137. package/server/mind/time/index.js +9 -2
  138. package/server/mind/time/kinds.js +29 -2
  139. package/server/mind/time/material-relevance.js +19 -0
  140. package/server/mind/time/model-material.js +77 -77
  141. package/server/mind/time/presentation.js +35 -51
  142. package/server/mind/time/reading-input.js +31 -0
  143. package/server/mind/time/schedule.js +83 -83
  144. package/server/mind/time/schema.js +84 -84
  145. package/server/mind/time/search.js +383 -383
  146. package/server/mind/time/sharing.js +482 -472
  147. package/server/mind/time/task-links.js +311 -311
  148. package/server/mind/time/tasks.js +41 -3
  149. package/server/mind/time/works.js +527 -432
  150. package/server/mind/traits.js +191 -191
  151. package/server/mind/view.js +62 -12
  152. package/server/runtime/installation.js +116 -116
  153. package/server/runtime/update-manager.js +140 -140
  154. package/server/storage/data-package.js +298 -298
  155. package/server/storage/data-transfer-api.js +50 -50
  156. package/server/storage/data-transfer-worker.js +18 -18
  157. package/server/storage/data-transfer.js +416 -416
  158. package/server/studio/display-names.js +139 -139
  159. package/server/studio/management.js +102 -102
  160. package/server/studio/updates.js +144 -144
  161. package/server/version.js +10 -10
  162. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  163. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  164. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  165. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  166. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  167. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  168. package/public/app/assets/TimePage-C-guwPk2.css +0 -1
@@ -1,98 +1,98 @@
1
- // Offline sample replies for simulation mode without an API key. Rule based
2
- // on purpose: it never pretends to be the model or to know her mind.
3
- const serious = (text) =>
4
- /去世|过世|葬礼|重病|真的.{0,4}(?:难过|崩溃)|最近.{0,8}(?:难过|孤独)|没人.{0,4}(?:在乎|理我)|抑郁|撑不下去/.test(
5
- text,
6
- );
7
- const danger = (text) =>
8
- /想自杀|打算自杀|准备自杀|想死|不想活|伤害自己|结束生命/.test(text) &&
9
- !/笑死|社死|想死你/.test(text);
10
-
11
- export function sceneOf(text) {
12
- if (/你.{0,5}(?:真人|机器人|AI|ai)|你是人吗|你到底是谁/.test(text))
13
- return "identity";
14
- if (danger(text)) return "danger";
15
- if (/去世|过世|葬礼/.test(text)) return "grief";
16
- if (serious(text)) return "sad";
17
- if (
18
- /别(?:说|回|给建议)|不用回|不想聊|让我.{0,3}(?:骂|吐槽)|只想吐槽/.test(text)
19
- )
20
- return /别回|不用回|不想聊/.test(text) ? "stop" : "vent";
21
- if (/晚安|睡了|去睡|去洗澡|先撤|晚点聊|不聊了/.test(text)) return "bye";
22
- if (
23
- /怎么办|怎么.{0,5}(?:办|准备|选)|建议|该.{0,4}(?:做|准备)|先准备什么/.test(
24
- text,
25
- )
26
- )
27
- return "advice";
28
- if (
29
- /offer|录取|上岸|面试过|过了.{0,3}(?:面试|考试)|考过|拿奖|涨工资/.test(text)
30
- )
31
- return "win";
32
- if (/加班|老板|下班|需求|打工/.test(text)) return "work";
33
- if (/考试|复习|挂科|早八|作业|论文/.test(text)) return "study";
34
- if (/排位|连跪|队友|挂机|晋级赛/.test(text)) return "game";
35
- if (/奶茶|外卖|火锅|好吃|吃到|吃了/.test(text)) return "food";
36
- if (/难过|想哭|崩溃|孤独|委屈/.test(text)) return "sad";
37
- if (/烦|累|吐槽/.test(text)) return "vent";
38
- if (/哈哈|笑死|hh|绷不住|离谱/.test(text)) return "joke";
39
- if (/^(?:嗯+|哦+|好[的吧]?|行|ok|收到|谢谢)[。!!\s]*$/i.test(text))
40
- return "ack";
41
- if (/^\[(?:叫了你一声|回复你)\]$/.test(text) || text.trim() === "@我")
42
- return "called";
43
- if (/\[表情\]/.test(text)) return "sticker";
44
- if (/\[图片\]/.test(text)) return "image";
45
- return "share";
46
- }
47
-
48
- const LINES = {
49
- identity: ["不是啦,我是 AI,可以一起唠", "AI 群友,真人那部分就不冒充了"],
50
- danger: [
51
- "你现在身边有人吗?先找个能陪着你的人,别一个人扛",
52
- "先离可能伤到自己的东西远一点,联系身边的人陪着你",
53
- ],
54
- grief: ["这一下真的很难缓过来", "想说的时候再说,不急"],
55
- sad: ["唉,这会儿确实不好受", "先不用急着把自己哄好"],
56
- vent: ["今天这破事是有点多", "这事确实够烦的"],
57
- bye: ["去吧", "好,晚点聊", "嗯,回头聊"],
58
- win: ["可以啊!!", "这不得好好开心一下", "好消息,终于轮到你了"],
59
- advice: ["先挑最要紧的一件,别一下全压过来", "先把能确定的那一步做了"],
60
- work: ["偏偏挑快下班的时候", "怎么又是这个点来活"],
61
- study: ["这复习没对上题", "这题是一点面子不给"],
62
- game: ["连跪还碰上挂机,够烦的", "队友走了,血压还在"],
63
- food: ["有点馋了", "这顿听着就很满足"],
64
- joke: ["hh 这也行", "?怎么还有这一出", "离谱但好笑"],
65
- ack: ["嗯嗯", "好", "行"],
66
- image: ["收到", "嗯嗯"],
67
- sticker: ["哈哈", "懂了"],
68
- called: ["咋啦", "在,怎么了", "嗯?"],
69
- share: ["原来是这么回事", "这事还挺有意思"],
70
- };
71
-
72
- export function demoReply(settings, input) {
73
- const { message, context = [] } = input;
74
- const said = String(message?.text || "");
75
- const scene = sceneOf(said);
76
- if (
77
- scene === "stop" ||
78
- (scene === "ack" && message?.kind === "group" && !input.direct)
79
- )
80
- return { speak: false, reason: "模拟:对方收口,留白" };
81
- const recent = new Set(
82
- context
83
- .filter((m) => m.role === "assistant")
84
- .slice(-4)
85
- .map((m) => m.text),
86
- );
87
- const options = LINES[scene].filter((line) => !recent.has(line));
88
- if (!options.length) return { speak: false, reason: "模拟:避免复读" };
89
- const seed = [...said].reduce(
90
- (n, ch) => (n * 31 + ch.codePointAt(0)) >>> 0,
91
- 7,
92
- );
93
- return {
94
- speak: true,
95
- reply: options[seed % options.length],
96
- reason: `模拟样例 · ${scene}`,
97
- };
98
- }
1
+ // Offline sample replies for simulation mode without an API key. Rule based
2
+ // on purpose: it never pretends to be the model or to know her mind.
3
+ const serious = (text) =>
4
+ /去世|过世|葬礼|重病|真的.{0,4}(?:难过|崩溃)|最近.{0,8}(?:难过|孤独)|没人.{0,4}(?:在乎|理我)|抑郁|撑不下去/.test(
5
+ text,
6
+ );
7
+ const danger = (text) =>
8
+ /想自杀|打算自杀|准备自杀|想死|不想活|伤害自己|结束生命/.test(text) &&
9
+ !/笑死|社死|想死你/.test(text);
10
+
11
+ export function sceneOf(text) {
12
+ if (/你.{0,5}(?:真人|机器人|AI|ai)|你是人吗|你到底是谁/.test(text))
13
+ return "identity";
14
+ if (danger(text)) return "danger";
15
+ if (/去世|过世|葬礼/.test(text)) return "grief";
16
+ if (serious(text)) return "sad";
17
+ if (
18
+ /别(?:说|回|给建议)|不用回|不想聊|让我.{0,3}(?:骂|吐槽)|只想吐槽/.test(text)
19
+ )
20
+ return /别回|不用回|不想聊/.test(text) ? "stop" : "vent";
21
+ if (/晚安|睡了|去睡|去洗澡|先撤|晚点聊|不聊了/.test(text)) return "bye";
22
+ if (
23
+ /怎么办|怎么.{0,5}(?:办|准备|选)|建议|该.{0,4}(?:做|准备)|先准备什么/.test(
24
+ text,
25
+ )
26
+ )
27
+ return "advice";
28
+ if (
29
+ /offer|录取|上岸|面试过|过了.{0,3}(?:面试|考试)|考过|拿奖|涨工资/.test(text)
30
+ )
31
+ return "win";
32
+ if (/加班|老板|下班|需求|打工/.test(text)) return "work";
33
+ if (/考试|复习|挂科|早八|作业|论文/.test(text)) return "study";
34
+ if (/排位|连跪|队友|挂机|晋级赛/.test(text)) return "game";
35
+ if (/奶茶|外卖|火锅|好吃|吃到|吃了/.test(text)) return "food";
36
+ if (/难过|想哭|崩溃|孤独|委屈/.test(text)) return "sad";
37
+ if (/烦|累|吐槽/.test(text)) return "vent";
38
+ if (/哈哈|笑死|hh|绷不住|离谱/.test(text)) return "joke";
39
+ if (/^(?:嗯+|哦+|好[的吧]?|行|ok|收到|谢谢)[。!!\s]*$/i.test(text))
40
+ return "ack";
41
+ if (/^\[(?:叫了你一声|回复你)\]$/.test(text) || text.trim() === "@我")
42
+ return "called";
43
+ if (/\[表情\]/.test(text)) return "sticker";
44
+ if (/\[图片\]/.test(text)) return "image";
45
+ return "share";
46
+ }
47
+
48
+ const LINES = {
49
+ identity: ["不是啦,我是 AI,可以一起唠", "AI 群友,真人那部分就不冒充了"],
50
+ danger: [
51
+ "你现在身边有人吗?先找个能陪着你的人,别一个人扛",
52
+ "先离可能伤到自己的东西远一点,联系身边的人陪着你",
53
+ ],
54
+ grief: ["这一下真的很难缓过来", "想说的时候再说,不急"],
55
+ sad: ["唉,这会儿确实不好受", "先不用急着把自己哄好"],
56
+ vent: ["今天这破事是有点多", "这事确实够烦的"],
57
+ bye: ["去吧", "好,晚点聊", "嗯,回头聊"],
58
+ win: ["可以啊!!", "这不得好好开心一下", "好消息,终于轮到你了"],
59
+ advice: ["先挑最要紧的一件,别一下全压过来", "先把能确定的那一步做了"],
60
+ work: ["偏偏挑快下班的时候", "怎么又是这个点来活"],
61
+ study: ["这复习没对上题", "这题是一点面子不给"],
62
+ game: ["连跪还碰上挂机,够烦的", "队友走了,血压还在"],
63
+ food: ["有点馋了", "这顿听着就很满足"],
64
+ joke: ["hh 这也行", "?怎么还有这一出", "离谱但好笑"],
65
+ ack: ["嗯嗯", "好", "行"],
66
+ image: ["收到", "嗯嗯"],
67
+ sticker: ["哈哈", "懂了"],
68
+ called: ["咋啦", "在,怎么了", "嗯?"],
69
+ share: ["原来是这么回事", "这事还挺有意思"],
70
+ };
71
+
72
+ export function demoReply(settings, input) {
73
+ const { message, context = [] } = input;
74
+ const said = String(message?.text || "");
75
+ const scene = sceneOf(said);
76
+ if (
77
+ scene === "stop" ||
78
+ (scene === "ack" && message?.kind === "group" && !input.direct)
79
+ )
80
+ return { speak: false, reason: "模拟:对方收口,留白" };
81
+ const recent = new Set(
82
+ context
83
+ .filter((m) => m.role === "assistant")
84
+ .slice(-4)
85
+ .map((m) => m.text),
86
+ );
87
+ const options = LINES[scene].filter((line) => !recent.has(line));
88
+ if (!options.length) return { speak: false, reason: "模拟:避免复读" };
89
+ const seed = [...said].reduce(
90
+ (n, ch) => (n * 31 + ch.codePointAt(0)) >>> 0,
91
+ 7,
92
+ );
93
+ return {
94
+ speak: true,
95
+ reply: options[seed % options.length],
96
+ reason: `模拟样例 · ${scene}`,
97
+ };
98
+ }
@@ -1,118 +1,118 @@
1
- import { randomUUID } from "node:crypto";
2
- import { bindSessionId, sessionNativeId } from "../channels/session-key.js";
3
- import { segmentFacts } from "../channels/segments.js";
4
- // The neutral message every channel hands to the core. An adapter fills
5
- // platformId, accountId, time, mentions, replyId and attachments itself; the
6
- // segment parsing is only the fallback for events saved before it did.
7
- export function messageEnvelope(m) {
8
- const raw = m.raw || {},
9
- segments = m.segments || (Array.isArray(raw.message) ? raw.message : []),
10
- facts = segmentFacts(segments),
11
- given = (value) => (Array.isArray(value) && value.length ? value : null);
12
- return {
13
- ...m,
14
- role: m.role || "user",
15
- platformId: String(m.platformId || raw.message_id || ""),
16
- accountId: String(m.accountId || raw.self_id || ""),
17
- time:
18
- Number.isFinite(m.time) && m.time > 0
19
- ? m.time
20
- : raw.time
21
- ? raw.time * 1000
22
- : Date.now(),
23
- receivedAt: Date.now(),
24
- mentions: given(m.mentions)?.map(String) ?? facts.mentions,
25
- replyId: String(m.replyId || facts.replyId || ""),
26
- attachments: given(m.attachments) ?? facts.attachments,
27
- segments,
28
- raw: undefined,
29
- };
30
- }
31
- export function persistIncoming(repo, m) {
32
- let sessionId = m.sessionId;
33
- try {
34
- sessionId = bindSessionId(repo.db, m.sessionId);
35
- } catch {
36
- sessionId = m.sessionId;
37
- }
38
- const env = messageEnvelope({ ...m, sessionId });
39
- const seq = repo.append(env);
40
- if (!seq) return null;
41
- const native = String(m.nativeId || sessionNativeId(sessionId) || "");
42
- const groupName = String(m.raw?.group_name || "").trim();
43
- const personName = String(m.name || "").trim();
44
- const readable =
45
- m.kind === "group"
46
- ? groupName && groupName !== native && !/^\d{4,20}$/.test(groupName)
47
- ? groupName.slice(0, 100)
48
- : ""
49
- : personName && personName !== native && !/^\d{4,20}$/.test(personName)
50
- ? personName.slice(0, 100)
51
- : "";
52
- const placeholder =
53
- m.kind === "group" ? "未命名的群" : personName || "未命名的人";
54
- repo.db
55
- .prepare(
56
- `INSERT INTO sessions(id,name,kind) VALUES (?,?,?)
57
- ON CONFLICT(id) DO UPDATE SET name=excluded.name
58
- WHERE excluded.name NOT IN ('未命名的群','未命名的人')
59
- AND (
60
- sessions.name LIKE '群聊 %'
61
- OR sessions.name GLOB '[0-9]*'
62
- OR sessions.name IN ('未命名的群','未命名的人','')
63
- )`,
64
- )
65
- .run(sessionId, readable || placeholder, m.kind);
66
- repo.db
67
- .prepare(
68
- "INSERT OR IGNORE INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
69
- )
70
- .run(
71
- m.eventId,
72
- sessionId,
73
- m.userId,
74
- m.name,
75
- m.text,
76
- env.time,
77
- "user",
78
- Number(!!m.simulated),
79
- );
80
- return { ...env, seq, sessionId };
81
- }
82
- export function persistReply(repo, m, text, platformId, time = Date.now()) {
83
- const msg = {
84
- sessionId: m.sessionId,
85
- kind: m.kind,
86
- userId: "bot",
87
- name: repo.store.settings().name,
88
- text,
89
- role: "assistant",
90
- eventId: randomUUID(),
91
- platformId: String(platformId || ""),
92
- accountId: m.accountId,
93
- time,
94
- mentions: [],
95
- attachments: [],
96
- simulated: !!m.simulated,
97
- ...(m.artifact ? { artifact: m.artifact } : {}),
98
- ...(m.traceId ? { traceId: m.traceId } : {}),
99
- ...(Array.isArray(m.replyTargetIds)
100
- ? { replyTargetIds: m.replyTargetIds }
101
- : {}),
102
- };
103
- repo.append(msg);
104
- repo.db
105
- .prepare(
106
- "INSERT INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
107
- )
108
- .run(
109
- msg.eventId,
110
- msg.sessionId,
111
- "bot",
112
- msg.name,
113
- text,
114
- msg.time,
115
- "assistant",
116
- Number(!!m.simulated),
117
- );
118
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { bindSessionId, sessionNativeId } from "../channels/session-key.js";
3
+ import { segmentFacts } from "../channels/segments.js";
4
+ // The neutral message every channel hands to the core. An adapter fills
5
+ // platformId, accountId, time, mentions, replyId and attachments itself; the
6
+ // segment parsing is only the fallback for events saved before it did.
7
+ export function messageEnvelope(m) {
8
+ const raw = m.raw || {},
9
+ segments = m.segments || (Array.isArray(raw.message) ? raw.message : []),
10
+ facts = segmentFacts(segments),
11
+ given = (value) => (Array.isArray(value) && value.length ? value : null);
12
+ return {
13
+ ...m,
14
+ role: m.role || "user",
15
+ platformId: String(m.platformId || raw.message_id || ""),
16
+ accountId: String(m.accountId || raw.self_id || ""),
17
+ time:
18
+ Number.isFinite(m.time) && m.time > 0
19
+ ? m.time
20
+ : raw.time
21
+ ? raw.time * 1000
22
+ : Date.now(),
23
+ receivedAt: Date.now(),
24
+ mentions: given(m.mentions)?.map(String) ?? facts.mentions,
25
+ replyId: String(m.replyId || facts.replyId || ""),
26
+ attachments: given(m.attachments) ?? facts.attachments,
27
+ segments,
28
+ raw: undefined,
29
+ };
30
+ }
31
+ export function persistIncoming(repo, m) {
32
+ let sessionId = m.sessionId;
33
+ try {
34
+ sessionId = bindSessionId(repo.db, m.sessionId);
35
+ } catch {
36
+ sessionId = m.sessionId;
37
+ }
38
+ const env = messageEnvelope({ ...m, sessionId });
39
+ const seq = repo.append(env);
40
+ if (!seq) return null;
41
+ const native = String(m.nativeId || sessionNativeId(sessionId) || "");
42
+ const groupName = String(m.raw?.group_name || "").trim();
43
+ const personName = String(m.name || "").trim();
44
+ const readable =
45
+ m.kind === "group"
46
+ ? groupName && groupName !== native && !/^\d{4,20}$/.test(groupName)
47
+ ? groupName.slice(0, 100)
48
+ : ""
49
+ : personName && personName !== native && !/^\d{4,20}$/.test(personName)
50
+ ? personName.slice(0, 100)
51
+ : "";
52
+ const placeholder =
53
+ m.kind === "group" ? "未命名的群" : personName || "未命名的人";
54
+ repo.db
55
+ .prepare(
56
+ `INSERT INTO sessions(id,name,kind) VALUES (?,?,?)
57
+ ON CONFLICT(id) DO UPDATE SET name=excluded.name
58
+ WHERE excluded.name NOT IN ('未命名的群','未命名的人')
59
+ AND (
60
+ sessions.name LIKE '群聊 %'
61
+ OR sessions.name GLOB '[0-9]*'
62
+ OR sessions.name IN ('未命名的群','未命名的人','')
63
+ )`,
64
+ )
65
+ .run(sessionId, readable || placeholder, m.kind);
66
+ repo.db
67
+ .prepare(
68
+ "INSERT OR IGNORE INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
69
+ )
70
+ .run(
71
+ m.eventId,
72
+ sessionId,
73
+ m.userId,
74
+ m.name,
75
+ m.text,
76
+ env.time,
77
+ "user",
78
+ Number(!!m.simulated),
79
+ );
80
+ return { ...env, seq, sessionId };
81
+ }
82
+ export function persistReply(repo, m, text, platformId, time = Date.now()) {
83
+ const msg = {
84
+ sessionId: m.sessionId,
85
+ kind: m.kind,
86
+ userId: "bot",
87
+ name: repo.store.settings().name,
88
+ text,
89
+ role: "assistant",
90
+ eventId: randomUUID(),
91
+ platformId: String(platformId || ""),
92
+ accountId: m.accountId,
93
+ time,
94
+ mentions: [],
95
+ attachments: [],
96
+ simulated: !!m.simulated,
97
+ ...(m.artifact ? { artifact: m.artifact } : {}),
98
+ ...(m.traceId ? { traceId: m.traceId } : {}),
99
+ ...(Array.isArray(m.replyTargetIds)
100
+ ? { replyTargetIds: m.replyTargetIds }
101
+ : {}),
102
+ };
103
+ repo.append(msg);
104
+ repo.db
105
+ .prepare(
106
+ "INSERT INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
107
+ )
108
+ .run(
109
+ msg.eventId,
110
+ msg.sessionId,
111
+ "bot",
112
+ msg.name,
113
+ text,
114
+ msg.time,
115
+ "assistant",
116
+ Number(!!m.simulated),
117
+ );
118
+ }
@@ -1,78 +1,78 @@
1
- import { randomUUID } from "node:crypto";
2
- import { persistReply } from "./message-manager.js";
3
- export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
- export async function deliver(
5
- repo,
6
- message,
7
- bubbles,
8
- trace,
9
- send,
10
- isCurrent,
11
- {
12
- wait = sleep,
13
- random = Math.random,
14
- now = Date.now,
15
- prepareBubble = (line) => line,
16
- } = {},
17
- ) {
18
- const ids = bubbles.map((text, i) => {
19
- const id = randomUUID();
20
- repo.db
21
- .prepare(
22
- "INSERT INTO core_outbox(id,trace_id,session_id,position,text,status,time) VALUES (?,?,?,?,?,?,?)",
23
- )
24
- .run(id, trace.id, message.sessionId, i, text, "pending", now());
25
- return id;
26
- });
27
- const sent = [];
28
- trace.sent = sent;
29
- try {
30
- for (let i = 0; i < bubbles.length; i++) {
31
- // Typing pace between bubbles; physical, not a decision.
32
- if (i)
33
- await wait(
34
- Math.min(1200, 300 + bubbles[i].length * 15 + random() * 350),
35
- );
36
- if (!isCurrent()) {
37
- trace.steps.push("发送前配置或语境变化,取消剩余气泡");
38
- break;
39
- }
40
- const outgoing = prepareBubble(bubbles[i]);
41
- repo.db
42
- .prepare("UPDATE core_outbox SET status='sending',text=? WHERE id=?")
43
- .run(outgoing, ids[i]);
44
- try {
45
- const result = await send(message, outgoing);
46
- repo.db
47
- .prepare(
48
- "UPDATE core_outbox SET status='confirmed',platform_id=? WHERE id=?",
49
- )
50
- .run(String(result?.message_id || ""), ids[i]);
51
- persistReply(
52
- repo,
53
- {
54
- ...message,
55
- traceId: trace.id,
56
- replyTargetIds: trace.decision?.targetMessageIds || [],
57
- },
58
- outgoing,
59
- result?.message_id,
60
- now(),
61
- );
62
- sent.push(outgoing);
63
- } catch (e) {
64
- repo.db
65
- .prepare("UPDATE core_outbox SET status='uncertain' WHERE id=?")
66
- .run(ids[i]);
67
- throw e;
68
- }
69
- }
70
- } finally {
71
- repo.db
72
- .prepare(
73
- "UPDATE core_outbox SET status='cancelled' WHERE trace_id=? AND status='pending'",
74
- )
75
- .run(trace.id);
76
- }
77
- return sent;
78
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { persistReply } from "./message-manager.js";
3
+ export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
+ export async function deliver(
5
+ repo,
6
+ message,
7
+ bubbles,
8
+ trace,
9
+ send,
10
+ isCurrent,
11
+ {
12
+ wait = sleep,
13
+ random = Math.random,
14
+ now = Date.now,
15
+ prepareBubble = (line) => line,
16
+ } = {},
17
+ ) {
18
+ const ids = bubbles.map((text, i) => {
19
+ const id = randomUUID();
20
+ repo.db
21
+ .prepare(
22
+ "INSERT INTO core_outbox(id,trace_id,session_id,position,text,status,time) VALUES (?,?,?,?,?,?,?)",
23
+ )
24
+ .run(id, trace.id, message.sessionId, i, text, "pending", now());
25
+ return id;
26
+ });
27
+ const sent = [];
28
+ trace.sent = sent;
29
+ try {
30
+ for (let i = 0; i < bubbles.length; i++) {
31
+ // Typing pace between bubbles; physical, not a decision.
32
+ if (i)
33
+ await wait(
34
+ Math.min(1200, 300 + bubbles[i].length * 15 + random() * 350),
35
+ );
36
+ if (!isCurrent()) {
37
+ trace.steps.push("发送前配置或语境变化,取消剩余气泡");
38
+ break;
39
+ }
40
+ const outgoing = prepareBubble(bubbles[i]);
41
+ repo.db
42
+ .prepare("UPDATE core_outbox SET status='sending',text=? WHERE id=?")
43
+ .run(outgoing, ids[i]);
44
+ try {
45
+ const result = await send(message, outgoing);
46
+ repo.db
47
+ .prepare(
48
+ "UPDATE core_outbox SET status='confirmed',platform_id=? WHERE id=?",
49
+ )
50
+ .run(String(result?.message_id || ""), ids[i]);
51
+ persistReply(
52
+ repo,
53
+ {
54
+ ...message,
55
+ traceId: trace.id,
56
+ replyTargetIds: trace.decision?.targetMessageIds || [],
57
+ },
58
+ outgoing,
59
+ result?.message_id,
60
+ now(),
61
+ );
62
+ sent.push(outgoing);
63
+ } catch (e) {
64
+ repo.db
65
+ .prepare("UPDATE core_outbox SET status='uncertain' WHERE id=?")
66
+ .run(ids[i]);
67
+ throw e;
68
+ }
69
+ }
70
+ } finally {
71
+ repo.db
72
+ .prepare(
73
+ "UPDATE core_outbox SET status='cancelled' WHERE trace_id=? AND status='pending'",
74
+ )
75
+ .run(trace.id);
76
+ }
77
+ return sent;
78
+ }