luckytri 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.github/SECURITY.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +250 -0
  4. package/README.md +250 -0
  5. package/bin/luckytri.js +107 -0
  6. package/docs/brand/banner.png +0 -0
  7. package/docs/brand/palette.png +0 -0
  8. package/docs/brand/portrait.png +0 -0
  9. package/docs/en/CHANGELOG.md +113 -0
  10. package/docs/en/architecture.md +152 -0
  11. package/docs/en/chat-style.md +62 -0
  12. package/docs/en/connect.md +63 -0
  13. package/docs/en/guide.md +216 -0
  14. package/docs/en/her-life.md +189 -0
  15. package/docs/en/npm-release.md +25 -0
  16. package/docs/en/plugins.md +82 -0
  17. package/docs/en/recovery.md +28 -0
  18. package/docs/en/time.md +71 -0
  19. package/docs/en/webui.md +73 -0
  20. package/docs/zh/CHANGELOG.md +113 -0
  21. package/docs/zh/architecture.md +152 -0
  22. package/docs/zh/chat-style.md +62 -0
  23. package/docs/zh/connect.md +63 -0
  24. package/docs/zh/guide.md +216 -0
  25. package/docs/zh/her-life.md +189 -0
  26. package/docs/zh/npm-release.md +33 -0
  27. package/docs/zh/plugins.md +82 -0
  28. package/docs/zh/recovery.md +28 -0
  29. package/docs/zh/time.md +71 -0
  30. package/docs/zh/webui.md +73 -0
  31. package/package.json +108 -0
  32. package/plugins/sdk/index.d.ts +155 -0
  33. package/plugins/sdk/testing.js +58 -0
  34. package/plugins/weather/index.js +82 -0
  35. package/plugins/weather/luckytri-plugin.json +29 -0
  36. package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
  37. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  38. package/public/app/assets/Empty-CpP5kQHH.js +2 -0
  39. package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
  40. package/public/app/assets/Field-D4gY-INU.js +1 -0
  41. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  42. package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
  43. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  44. package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
  45. package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
  46. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  47. package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
  48. package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
  49. package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
  50. package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
  51. package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
  52. package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
  53. package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
  54. package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
  55. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  56. package/public/app/assets/TimePage-Cw44inM4.js +3 -0
  57. package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
  58. package/public/app/assets/index-BheRW-1t.js +4 -0
  59. package/public/app/assets/index-YDz1ree1.css +1 -0
  60. package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
  61. package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
  62. package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
  63. package/public/app/assets/media-BzPDBm7e.js +1 -0
  64. package/public/app/index.html +27 -0
  65. package/public/guide.en.html +1053 -0
  66. package/public/guide.html +893 -0
  67. package/public/plugin-kit/v1/bridge.js +30 -0
  68. package/public/plugin-kit/v1/kit.css +14 -0
  69. package/public/tutorial.en.md +214 -0
  70. package/public/tutorial.md +214 -0
  71. package/scripts/backup.js +311 -0
  72. package/scripts/browser.js +41 -0
  73. package/scripts/launch.js +94 -0
  74. package/scripts/plugin.js +150 -0
  75. package/scripts/proxy-config.js +167 -0
  76. package/scripts/recover.js +26 -0
  77. package/scripts/runtime.js +25 -0
  78. package/scripts/service.js +47 -0
  79. package/scripts/setup.js +50 -0
  80. package/scripts/start-server.js +41 -0
  81. package/scripts/stop.js +36 -0
  82. package/server/app.js +96 -0
  83. package/server/channels/adapters.js +27 -0
  84. package/server/channels/contract.js +57 -0
  85. package/server/channels/hub.js +165 -0
  86. package/server/channels/index.js +25 -0
  87. package/server/channels/onebot/adapter.js +288 -0
  88. package/server/channels/onebot/index.js +336 -0
  89. package/server/channels/onebot/token.js +3 -0
  90. package/server/channels/qqbot/adapter.js +253 -0
  91. package/server/channels/qqbot/api.js +199 -0
  92. package/server/channels/qqbot/credentials.js +11 -0
  93. package/server/channels/qqbot/gateway.js +274 -0
  94. package/server/channels/qqbot/index.js +389 -0
  95. package/server/channels/segments.js +40 -0
  96. package/server/channels/session-key.js +153 -0
  97. package/server/channels/state.js +42 -0
  98. package/server/core/api.js +564 -0
  99. package/server/core/capabilities.js +208 -0
  100. package/server/core/context-builder.js +288 -0
  101. package/server/core/context-compactor.js +454 -0
  102. package/server/core/conversation-cues.js +325 -0
  103. package/server/core/conversation-manager.js +87 -0
  104. package/server/core/events.js +60 -0
  105. package/server/core/feedback.js +8 -0
  106. package/server/core/group-style.js +72 -0
  107. package/server/core/image-preprocessor.js +208 -0
  108. package/server/core/initiative-context.js +66 -0
  109. package/server/core/input-budget.js +32 -0
  110. package/server/core/local-demo.js +98 -0
  111. package/server/core/message-manager.js +118 -0
  112. package/server/core/message-scheduler.js +78 -0
  113. package/server/core/model-json.js +190 -0
  114. package/server/core/model-manager.js +1213 -0
  115. package/server/core/model-presets.js +900 -0
  116. package/server/core/name-call.js +29 -0
  117. package/server/core/net-guard.js +49 -0
  118. package/server/core/network.js +157 -0
  119. package/server/core/orchestrator.js +569 -0
  120. package/server/core/perceivers.js +91 -0
  121. package/server/core/person-name.js +22 -0
  122. package/server/core/persona-manager.js +243 -0
  123. package/server/core/reply-delivery.js +393 -0
  124. package/server/core/reply-target-resolver.js +50 -0
  125. package/server/core/repository.js +293 -0
  126. package/server/core/response-generator.js +50 -0
  127. package/server/core/response-validator.js +508 -0
  128. package/server/core/retired-prompts.js +51 -0
  129. package/server/core/sessions.js +109 -0
  130. package/server/core/speaker-names.js +92 -0
  131. package/server/core/topic-tracker.js +65 -0
  132. package/server/core/turn-processor.js +630 -0
  133. package/server/core/turn.js +236 -0
  134. package/server/core/vision-manager.js +633 -0
  135. package/server/http.js +18 -0
  136. package/server/i18n/decisions.js +206 -0
  137. package/server/i18n/errors.js +497 -0
  138. package/server/i18n/index.js +144 -0
  139. package/server/i18n/labels.js +653 -0
  140. package/server/i18n/models.js +111 -0
  141. package/server/index.js +102 -0
  142. package/server/knowledge/api.js +342 -0
  143. package/server/knowledge/embedding-profile.js +114 -0
  144. package/server/knowledge/index.js +13 -0
  145. package/server/knowledge/manager.js +386 -0
  146. package/server/knowledge/retrieval.js +93 -0
  147. package/server/knowledge/schema.js +154 -0
  148. package/server/knowledge/vector-index.js +90 -0
  149. package/server/mind/affect.js +265 -0
  150. package/server/mind/anticipations.js +507 -0
  151. package/server/mind/api.js +703 -0
  152. package/server/mind/attention.js +200 -0
  153. package/server/mind/bonds.js +558 -0
  154. package/server/mind/budget.js +152 -0
  155. package/server/mind/clock.js +83 -0
  156. package/server/mind/context-selection.js +61 -0
  157. package/server/mind/continuity.js +562 -0
  158. package/server/mind/conversation-origin.js +175 -0
  159. package/server/mind/day-planner.js +324 -0
  160. package/server/mind/days.js +210 -0
  161. package/server/mind/evidence-read.js +45 -0
  162. package/server/mind/evidence.js +148 -0
  163. package/server/mind/faces.js +138 -0
  164. package/server/mind/guard.js +60 -0
  165. package/server/mind/index.js +445 -0
  166. package/server/mind/initiative.js +333 -0
  167. package/server/mind/life/activities.js +21 -0
  168. package/server/mind/life/context.js +309 -0
  169. package/server/mind/life/diary.js +786 -0
  170. package/server/mind/life/growth.js +252 -0
  171. package/server/mind/life/index.js +1049 -0
  172. package/server/mind/life/presence.js +305 -0
  173. package/server/mind/meetings.js +1069 -0
  174. package/server/mind/memory-policy.js +48 -0
  175. package/server/mind/memory.js +888 -0
  176. package/server/mind/nature.js +262 -0
  177. package/server/mind/novelty.js +19 -0
  178. package/server/mind/observations.js +84 -0
  179. package/server/mind/own-day.js +324 -0
  180. package/server/mind/own-voice.js +318 -0
  181. package/server/mind/periods.js +118 -0
  182. package/server/mind/reading.js +120 -0
  183. package/server/mind/salience.js +201 -0
  184. package/server/mind/schema.js +562 -0
  185. package/server/mind/self.js +484 -0
  186. package/server/mind/senses.js +53 -0
  187. package/server/mind/thoughts.js +274 -0
  188. package/server/mind/time/activity-clock.js +196 -0
  189. package/server/mind/time/agenda.js +242 -0
  190. package/server/mind/time/api.js +200 -0
  191. package/server/mind/time/attention.js +34 -0
  192. package/server/mind/time/availability.js +69 -0
  193. package/server/mind/time/commitment-review.js +198 -0
  194. package/server/mind/time/delivery-links.js +130 -0
  195. package/server/mind/time/executor.js +512 -0
  196. package/server/mind/time/experiences.js +128 -0
  197. package/server/mind/time/games.js +562 -0
  198. package/server/mind/time/index.js +739 -0
  199. package/server/mind/time/intent.js +166 -0
  200. package/server/mind/time/kinds.js +171 -0
  201. package/server/mind/time/model-material.js +77 -0
  202. package/server/mind/time/plugin-activity.js +207 -0
  203. package/server/mind/time/presentation.js +51 -0
  204. package/server/mind/time/schedule.js +83 -0
  205. package/server/mind/time/schema.js +84 -0
  206. package/server/mind/time/search.js +383 -0
  207. package/server/mind/time/sharing.js +472 -0
  208. package/server/mind/time/task-links.js +311 -0
  209. package/server/mind/time/tasks.js +587 -0
  210. package/server/mind/time/works.js +432 -0
  211. package/server/mind/traits.js +191 -0
  212. package/server/mind/util.js +110 -0
  213. package/server/mind/view.js +273 -0
  214. package/server/paths.js +102 -0
  215. package/server/plugins/api.js +204 -0
  216. package/server/plugins/host.js +967 -0
  217. package/server/plugins/install.js +295 -0
  218. package/server/plugins/manifest.js +201 -0
  219. package/server/plugins/permissions.js +42 -0
  220. package/server/plugins/runner/guard.mjs +36 -0
  221. package/server/plugins/runner/worker.mjs +250 -0
  222. package/server/plugins/schema.js +91 -0
  223. package/server/plugins/zip.js +112 -0
  224. package/server/startup-lock.js +118 -0
  225. package/server/storage/archive.js +241 -0
  226. package/server/storage/backup-cleanup-worker.js +8 -0
  227. package/server/storage/backup-cleanup.js +414 -0
  228. package/server/storage/backup-lock.js +76 -0
  229. package/server/storage/backup-scheduler.js +131 -0
  230. package/server/storage/store.js +199 -0
  231. package/server/studio/display-names.js +138 -0
  232. package/server/studio/management.js +102 -0
  233. package/server/studio/readiness.js +190 -0
  234. package/server/studio/settings.js +236 -0
  235. package/server/version.js +10 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { join } from "node:path";
4
+ import { runtimePaths } from "../server/paths.js";
5
+ import { VERSION } from "../server/version.js";
6
+
7
+ const help = `LuckyTri ${VERSION}
8
+
9
+ 用法 / Usage: luckytri [command] [--home ABSOLUTE_PATH]
10
+
11
+ luckytri 后台启动并打开 WebUI / Launch and open WebUI
12
+ luckytri --no-browser 后台启动 / Launch without opening a browser
13
+ start 前台运行,Ctrl+C 停止 / Run in foreground
14
+ stop 停止当前实例 / Stop this instance
15
+ setup 初始化配置,保留已有文件 / Initialize configuration
16
+ paths 显示配置、数据和安装目录 / Show paths
17
+ backup [--auto --force] 备份数据库 / Back up the database
18
+ recovery verify --backup FILE
19
+ recovery restore --backup FILE --to NEW_FILE
20
+ plugin new|check|pack|registry-check ...
21
+ --version, -v 显示版本 / Show version
22
+ --help, -h 显示帮助 / Show help
23
+
24
+ 升级 / Update: luckytri stop && npm install -g luckytri@latest && luckytri
25
+ 配置和用户数据在安装目录之外,卸载或更新 npm 包不会删除它们。`;
26
+
27
+ try {
28
+ const [major, minor] = process.versions.node.split(".").map(Number);
29
+ if (major < 24 || (major === 24 && minor < 5))
30
+ throw Error("LuckyTri 需要 Node.js >=24.5 / requires Node.js >=24.5");
31
+ const args = process.argv.slice(2);
32
+ const homes = args.filter((arg) => arg === "--home");
33
+ if (homes.length > 1)
34
+ throw Error("--home 只能指定一次 / specify --home only once");
35
+ const homeIndex = args.indexOf("--home");
36
+ if (homeIndex >= 0) {
37
+ const home = args[homeIndex + 1];
38
+ if (!home || home.startsWith("--"))
39
+ throw Error("--home 需要绝对路径 / needs an absolute path");
40
+ process.env.LUCKYTRI_HOME = home;
41
+ args.splice(homeIndex, 2);
42
+ }
43
+ if (args.includes("--help") || args.includes("-h") || args[0] === "help") {
44
+ console.log(help);
45
+ } else if (args.includes("--version") || args.includes("-v")) {
46
+ console.log(VERSION);
47
+ } else {
48
+ const command =
49
+ !args.length || args[0] === "--no-browser" ? "launch" : args.shift();
50
+ const scripts = {
51
+ launch: "launch.js",
52
+ start: "start-server.js",
53
+ stop: "stop.js",
54
+ backup: "backup.js",
55
+ recovery: "recover.js",
56
+ plugin: "plugin.js",
57
+ };
58
+ if (!["setup", "paths", ...Object.keys(scripts)].includes(command))
59
+ throw Error(
60
+ `未知命令 / Unknown command: ${command}\n使用 luckytri --help 查看帮助。`,
61
+ );
62
+ if (
63
+ ["setup", "paths", "start", "stop", "launch"].includes(command) &&
64
+ args.some((arg) => command !== "launch" || arg !== "--no-browser")
65
+ )
66
+ throw Error("参数无效 / Invalid arguments; see luckytri --help");
67
+ if (command === "paths") {
68
+ const { prepareRuntime } = await import("../scripts/runtime.js");
69
+ prepareRuntime({ initialize: false });
70
+ console.log(JSON.stringify(runtimePaths(), null, 2));
71
+ } else {
72
+ if (command !== "plugin" && command !== "recovery") {
73
+ const { prepareRuntime } = await import("../scripts/runtime.js");
74
+ prepareRuntime({
75
+ initialize: ["setup", "launch", "start"].includes(command),
76
+ });
77
+ }
78
+ if (command === "setup")
79
+ console.log(
80
+ `配置已就绪 / Configuration ready: ${runtimePaths().config}`,
81
+ );
82
+ else {
83
+ const child = spawn(
84
+ process.execPath,
85
+ [join(runtimePaths().package, "scripts", scripts[command]), ...args],
86
+ {
87
+ env: process.env,
88
+ stdio: "inherit",
89
+ windowsHide: true,
90
+ },
91
+ );
92
+ for (const signal of ["SIGINT", "SIGTERM"])
93
+ process.on(signal, () => child.kill(signal));
94
+ child.once("error", (error) => {
95
+ console.error(error.message);
96
+ process.exitCode = 1;
97
+ });
98
+ child.once("exit", (code, signal) => {
99
+ process.exitCode = signal ? 1 : (code ?? 1);
100
+ });
101
+ }
102
+ }
103
+ }
104
+ } catch (error) {
105
+ console.error(error.message);
106
+ process.exitCode = 1;
107
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,113 @@
1
+ <p align="right"><a href="../zh/CHANGELOG.md">中文</a> · <b>English</b></p>
2
+
3
+ # Changelog
4
+
5
+ ## 1.0.0 — 2026-10-04
6
+
7
+ Plugins. The core still defines who she is; a plugin decides what of the world she can touch.
8
+
9
+ - **Plugin API v1.** A plugin can offer a channel, a sense, a sourced experience, material for an activity, an action she may choose, shelf reading and a sandboxed page. It cannot speak for her, and it cannot write her nature, self, bonds or memory.
10
+ - **Permissions are enforced.** Each plugin is a restricted process. Without the matching permission it cannot read her state, use the network, register a channel or call a model. Accepted once when enabled, and again if an upgrade asks for more.
11
+ - **Market and import.** The studio can browse an index, import a zip link, a GitHub repository, an uploaded package or a local folder, check sha256, roll back and remove. What she already lived through does not leave with the plugin.
12
+ - **A built-in plugin, off by default.** Weather.
13
+ - **Database version 4.** A snapshot is kept before the upgrade. With no plugin enabled, she behaves as in 0.9.9.
14
+
15
+ ## 0.9.9 — 2026-10-02
16
+
17
+ The last tidy-up before 1.0: two ways to connect, a bilingual interface, rewritten documentation, and a cleaner, easier-to-extend repository.
18
+
19
+ - **QQ official bot, either-or with OneBot 11.** New `server/channels/` with a common channel contract and a `ChannelHub`: one channel is active at a time, switching while running needs no restart, and `LUCKYTRI_CHANNEL` can pin it. Both channels hand the core the same neutral message, so attention, memory, relationships, solitude, diary and time are fully shared; only how messages arrive differs. The official channel obtains and renews its access token, connects to the Open Platform gateway (heartbeat, resume, backoff reconnect), converts @-in-group, all-group and one-to-one events with de-duplication, replies to the original message inside the passive-reply window and sends proactive messages outside it, records whether a person has turned proactive messages off and uses that to decide whether she may reach out, and the limits on names and cross-group identity are documented.
20
+ - **Renaming sessions.** The official interface gives no group names, so on both channels you can rename a session under "Chat".
21
+ - **Bilingual.** The top-right corner switches between 中文 and English; Chinese is the default and the choice is kept in the browser. All interface text, dates and numbers, and the errors and labels the server shows in the interface have English; what she says, her diary, thoughts and reasons are not translated. Every document exists in both languages with matching file names and a language switch at the top.
22
+ - **No mention of any third-party connector.** System settings, the guide and the docs keep only connection address and token details, with no names or jump links.
23
+ - **Rewritten README and docs.** The new README explains the purpose, her day and the core framework; `docs/zh` and `docs/en` add architecture and extension points, a comparison of the two ways to connect QQ and the studio design, and refresh the rest. The guide page is generated in Chinese and English with Markdown downloads.
24
+ - **A cleaner repository.** Working notes from an instance and one-off repair scripts are removed; test corpora are synthetic sentences with unchanged validation semantics; `.gitignore` is hardened; a new `tests/repo-hygiene.test.js` scans for keys, instance data, local paths and third-party connector names. Version notes are now written in general terms and contain no runtime data.
25
+ - **A clearer structure.** Storage moved to `server/storage/`, readiness checks joined `server/studio/`, feedback, group voice and model presets joined `server/core/`, lifetime modules moved into `server/mind/life/`, evaluation and audit scripts into `scripts/dev/`, browser tests into `tests/ui/`; `server/version.js` is the single source of the version and fixes the stale version in the request header.
26
+ - **Small touches.** The placeholder and empty-state text of the dropdown select can be translated.
27
+
28
+ ## 0.9.1 — 2026-09-29
29
+
30
+ - **Stickers read by tone.** A sticker with a usable picture can enter vision and the text label of an ordinary one enters the context. She first judges whether it is a joke, a rant or a new topic and no longer solemnly repeats the words in the picture.
31
+ - **New pictures judged by their own message.** Selective vision takes only the same person's current, explicitly quoted or adjacent images; cacheable vision notes keep only what is visible.
32
+ - **Rapid messages merged before the first line goes out.** A brief re-check was added before sending; when the other person adds more, the old draft is withdrawn and both are reorganized together. Crisis replies do not wait.
33
+ - **Wordplay and abbreviations decoded from context first.** When unsure she asks briefly, without lecturing or explaining the joke.
34
+ - **What she lives for is steadier.** The same wish in different words stays one entry; it is kept in mind when others' words touch it, when she says it, or when she writes a thought about it; writing a thought that puts the wish down stops pinning it; promises to others no longer take that seat. The end-of-day snapshot records it too.
35
+ - **The choices she made and her reasons come back at the next meeting.**
36
+ - **Continuity gained automatic backups.** While the service runs, an integrity-checked backup is made in another process every 24 hours by default and only the latest 7 are kept; `BACKUP_INTERVAL_HOURS=0` turns it off.
37
+ - **Small format slips in a model reply no longer lose a whole turn.** It is repaired where possible and the model is asked once more only when it cannot be.
38
+ - **More honest and steadier replies.** She does not say she played something she did not; presenting her own words as the other person's is sent back for a rewrite; when asked "how do you know?" the basis is checked first and something she raised unprompted cannot invent a message the other person sent; review no longer uses nature settings to veto her honest answer that she is a program.
39
+ - **When nobody calls her and her words are not in shape she stays silent**, instead of sending a one-word stand-in.
40
+ - **When she is addressed directly and the model is briefly unavailable, she retries later by herself** (once after 30 seconds and once after 2 minutes).
41
+ - **Solitude no longer idles.** When only time has passed and there has been repeated idling, the interval lengthens each time up to 6 hours; when her own thoughts only continue the previous one several times in a row, something new must be picked up for the thought to be kept.
42
+ - **Memory is separated from "who she is".** Opinions about topics no longer pile up in her self.
43
+ - **New `npm run audit:sent`** replays the replies she recently sent against today's checks; **new `npm run test:clock`** runs the backend tests with the real clock moved to the past and the future. Replay and trial chat no longer depend on the real clock.
44
+
45
+ ## 0.9.0 — 2026-09-28
46
+
47
+ - **The vision is stated.** LuckyTri is about continuous experience, self and connection with the world; QQ chat is only the current doorway. The Chinese and English READMEs were updated together.
48
+ - **Only the generic OneBot 11 reverse-WebSocket connection is kept.** System settings show only the connection address and token details; the README and connection guide were rewritten.
49
+ - **Attention recognizes what she lives for.** When a topic touches `self.livingFor` she may chime in because she wants to, or stay quiet; the diary and night review can see it and only write about it when it was really touched.
50
+ - **A meeting leaves meaning, and a will counts only when touched.** What a meeting meant to her returns the next time that person is present; meaning fades with the days she has lived and, once faded, is only recalled by that same person's own words.
51
+ - **Discretion tightened everywhere.** Private words and secrets never enter another room through the diary, notes, impressions, meetings or reviews; a reply that would carry them is rewritten before sending. Words passing by in a session she is not in do not become her experience.
52
+ - **Relationships feel more real.** "Long time no see" is told apart from "still around but no words exchanged"; the same experience is never counted again and again as closeness; an impression kept in mind is not contact.
53
+ - **The rhythm of a room is not her character.** Sentence length and punctuation in a group go into `inner.room` and she speaks by her own habits.
54
+ - **Attention, touch counts and the long-absence list follow one rule:** two words from the same person's own speech; one phrase is not enough; two people's words are not combined; her own words do not count.
55
+
56
+ ## 0.8.2 — 2026-09-26
57
+
58
+ LuckyTri admits she is made of code and a model, and still treats her heart, memory and choices as her own.
59
+
60
+ - **Stance**: the default nature is now someone already living her days. Asked who she is, she answers that she is made of code and a model; this life, what she remembers and the choices she makes are her own; she does not call herself merely an assistant and does not invent a body or experiences that never happened. She may think of someone because she wants to and never uses guilt to keep anyone.
61
+ - **Unmodified seed**: if the first version is still the nature shipped with an earlier release, it is replaced by the current seed on read, without using up the "only two edits" allowance.
62
+ - **Small things for herself**: solitude may leave one wish with no message source, with strength capped; the strongest one now appears as `self.livingFor`.
63
+
64
+ ## 0.8.1 — 2026-09-25
65
+
66
+ The WebUI was rebuilt as "TA's little world": she is the center of the interface, a breathing, shape-shifting, blinking glow of soul that answers a poke, and colors, sky and motion follow mood and rhythm (seven themes: lively, sweet, calm, low, irritable, sleepy, night). The information architecture is TA (Now, Inner life, People, Lifetime), Daily life (Chat, Memory) and Settings (Nature, System); native browser dialogs became in-interface dialogs. New `GET /api/mind/presence`, `/api/mind/today` and `/api/mind/people/:id`.
67
+
68
+ **The weight of time**: she does not only accumulate, she also fades and looks ahead. Self threads, notes and memory fade by the days she has lived and return to view when a related topic recalls them (without writing); closeness falls after a long absence and warms on reunion; promises and expectations (others' arrangements, what she promised) can be undone; night sorting and a layered autobiography (diary → review → chapter → my journey so far).
69
+
70
+ ## 0.8.0 — 2026-09-24 · StartSoul
71
+
72
+ One her, speaking from the heart, with days of her own.
73
+
74
+ - **Unified mind** (`server/mind/`): a versioned nature, a global mood, relationships consistent across groups, self threads that grow gradually out of experience, her face in each group, global notes. Every change cites its source and can be undone.
75
+ - **Speaking from the heart**: participation probability, cooldown and "always reply when @-ed" are gone. Each close look makes one model call that yields understanding, feeling, changes to people, a choice, a reason and the wording; mood and relationships change even in silence.
76
+ - **Attention**: a deterministic "look closely or glance"; ordinary group chat calls the model only when she cares.
77
+ - **Global memory and discretion**: one memory serves all sessions, labeled public / private / secret.
78
+ - **Lifetime**: daily rhythm, global solitude, reading, bedtime diary, night sorting, review, a rewritable autobiography and reaching out decided by her.
79
+ - **Token management**: a global ledger, a daily cap and background shares, a cache-friendly request layout.
80
+ - The old persona became the first version of nature and old data migrates automatically; the old per-session memory and probability mechanisms were retired.
81
+
82
+ ## 0.7.0 — 2026-09-24
83
+
84
+ - New "Time" workbench, low-frequency solitude enabled per session and optional proactive messaging.
85
+ - Layered context compression: recent text stays verbatim, older chat is condensed into multi-level summaries.
86
+ - Requests reordered by cache rules; output caps per stage; finer network-error classes; jittered exponential backoff; per-model circuit breaking and a session-level fallback model.
87
+
88
+ ## 0.6.0 — 2026-09-22
89
+
90
+ - Rebuilt the group chat core: messages are batched first, then speaker, @, quote chain and topic are resolved, and finally the speaking decision and the reply are produced separately.
91
+ - Long-term memory isolated by session, user and shared scope with confidence, source and expiry; model profiles support vendor presets, context window and vision ability; image understanding has an energy-saving mode.
92
+ - The WebUI was rebuilt in a fresh mint theme.
93
+
94
+ ## 0.5.1 / 0.5.0 — 2026-09-21
95
+
96
+ - Project branding unified (now LuckyTri); the README became Chinese by default with English switchable.
97
+ - Model settings gained provider presets, reasoning effort, Temperature, Top P and maximum output tokens.
98
+ - A guided QQ connection step: a local OneBot token and reverse-WebSocket configuration, with QQ login still done in the connector; the project never receives or stores a QQ password.
99
+
100
+ ## 0.4.0 — 2026-09-19
101
+
102
+ - Reply feedback: natural, too long, too stiff, too many memes, undoable.
103
+ - New getting-started guide with five connection steps; a complete Chinese guide; commands for configuration setup, stopping and consistent backups.
104
+
105
+ ## 0.3.0 — 2026-09-19
106
+
107
+ - Plain everyday speech first, fewer fixed comforting templates; optional group-voice adaptation; a customer-service tone, repetition or piled-up memes trigger at most one rewrite.
108
+
109
+ ## 0.2.0 — 2026-09-19
110
+
111
+ - Memory inbox, memory search and editing; model connection test and connection checks.
112
+ - Simulated and real context separated; cooldown and rate limits persisted; event de-duplication independent of context.
113
+ - SQLite query indexes and log retention caps.
@@ -0,0 +1,152 @@
1
+ <p align="right"><a href="../zh/architecture.md">中文</a> · <b>English</b></p>
2
+
3
+ # Architecture and extension points
4
+
5
+ This document is for anyone who wants to read, change or extend LuckyTri. It answers three questions: how the code is layered, how a message travels through her life, and where to start when you want to add something.
6
+
7
+ ## One picture
8
+
9
+ ```mermaid
10
+ flowchart LR
11
+ subgraph Channels["channels/ — only how messages come and go"]
12
+ OB["onebot<br/>reverse WebSocket"]
13
+ QB["qqbot<br/>official gateway"]
14
+ end
15
+ Hub["ChannelHub<br/>one channel at a time"]
16
+ subgraph Core["core/ — senses and mouth"]
17
+ Recv["receive · batch · perceive"]
18
+ Turn["one turn · validate · send"]
19
+ end
20
+ subgraph Mind["mind/ — her"]
21
+ Self["nature · self · faces"]
22
+ Affect["mood · bonds · memory"]
23
+ Life["life/ — solitude · diary · review"]
24
+ Time["time/ — activities · works · promises"]
25
+ end
26
+ Know["knowledge/ — reference library"]
27
+ Store[("storage/ — one SQLite file")]
28
+ Studio["studio/ — management API"]
29
+ Web["studio-web/ — the studio (Vue)"]
30
+ OB --> Hub
31
+ QB --> Hub
32
+ Hub -->|"neutral message"| Recv
33
+ Recv --> Turn
34
+ Turn -->|"send"| Hub
35
+ Recv <--> Mind
36
+ Turn <--> Mind
37
+ Life --> Hub
38
+ Know --> Turn
39
+ Mind --> Store
40
+ Core --> Store
41
+ Studio --> Mind
42
+ Studio --> Core
43
+ Web --> Studio
44
+ ```
45
+
46
+ Nothing above the channel knows which platform a message came from. The core sees one kind of neutral message, and she reads herself from one shared mind.
47
+
48
+ ## Directories
49
+
50
+ ```text
51
+ server/
52
+ index.js app.js http.js composition root, Express shell, HTTP helpers
53
+ version.js the single source of the version (reads package.json)
54
+ channels/ the connection layer: hub, contract, session keys, onebot/, qqbot/
55
+ core/ the conversation pipeline: receive, batch, perceive, turn, validate, send,
56
+ plus model management, feedback, group voice and model presets
57
+ mind/ her: nature, mood, bonds, self, faces, memory, attention, bottom lines…
58
+ life/ lifetime: waking, solitude, diary, night, review, reaching out
59
+ time/ time: activities, to-dos, works, experiences, sharing, standalone search
60
+ knowledge/ reference library: chunking, retrieval, vector index
61
+ storage/ SQLite store, schema version and migration, backups and auto-backups
62
+ studio/ management API: settings, display names, readiness checks
63
+ i18n/ English dictionaries for server-visible text (translated at the API exit only)
64
+ studio-web/src/ the studio: Vue 3 + Vite + TypeScript
65
+ pages/ one folder per entry
66
+ components/ stores/ mood/ i18n/ styles/
67
+ scripts/ setup, launch, start-server, stop, backup, recover, build-guide
68
+ scripts/dev/ evaluation and audit: evaluate-*, audit-sent, test-clock
69
+ tests/ *.test.js backend tests · ui/ browser tests · helpers/ deterministic world
70
+ docs/ zh/ and en/ mirrored documents · brand/ images
71
+ public/ the built studio (app/) and the guide pages
72
+ ```
73
+
74
+ ## The life of a message
75
+
76
+ 1. **The channel receives.** The adapter turns the platform's raw event into a neutral message: session key, sender, time, whether she was @-ed, the quoted message, attachments. The platform's format exists only inside the adapter.
77
+ 2. **Batching and perception** (`core/`). Messages arriving close together form a batch; @s, quote chains and name calls are parsed, giving each message its relation to her.
78
+ 3. **Attention** (`mind/attention.js`, no model call). Being called, a private chat and crisis signals always get a close look; ordinary group chat is decided by a deterministic score between a close look and a glance.
79
+ 4. **The her of this moment** (`mind/view.js`). Nature, threads of self, bonds, mood, memory and nearby promises are condensed into a few hundred tokens with a hard cap: the stable part goes into a cacheable prefix, the changing part at the end.
80
+ 5. **One turn call.** The model returns, in one go: what this means to her, a feeling, changes to people, a choice (speak, react briefly, say she does not feel like talking, stay silent), a reason and the wording.
81
+ 6. **Experience.** Whether she speaks or not, feelings and changes are written into the mind, each citing its source.
82
+ 7. **Validation and sending.** Local checks on tone and secrecy, one review or rewrite when needed, a safe short line if it still fails; the context is checked for staleness again before sending. Uncertain delivery is never resent.
83
+ 8. **Lifetime.** When nobody is talking, time still passes through her: solitude, reading, the diary, night sorting, review and reaching out all read and write the same mind.
84
+
85
+ Replay runs the same pipeline but only reads the mind as it was before that moment; trial chat reads her mind as it is now but writes nothing and sends nothing to QQ.
86
+
87
+ ## Invariants
88
+
89
+ These are the promises the code and the tests guard together; make sure they still hold when you change something.
90
+
91
+ - **One her**: a session only marks where something happened, it does not wall off her self.
92
+ - **The system is one**: she can perceive her own state, including what is connected and whether a plugin is on. The studio and what she knows are the same state.
93
+ - **Sourced, gradual, undoable**: mind tables are append-only; each change cites a specific experience; an undo leaves a tombstone and is never written back.
94
+ - **Fading is computed at read time**: no "faded" flag is stored, so replay only reads the past; fading counts the days she lived through, not calendar days.
95
+ - **Discretion follows the source**: what was learned privately or told in confidence never appears in another room.
96
+ - **The clock is injectable**: the test world has its own clock; any code quietly reading the real clock shows up under `npm run test:clock`.
97
+ - **Her words are not translated**: interface text can be localized; what she says, her diary, thoughts and reasons are her own content and stay as written.
98
+ - **One database holds the data**: SQLite under `data/` in WAL mode; the schema version is kept in `PRAGMA user_version` and migrations are idempotent.
99
+
100
+ ## Extension points
101
+
102
+ ### Connecting a plugin
103
+
104
+ A plugin does not edit the core. It runs in its own process and uses Plugin API v1: a channel registers on `ChannelHub`, a sense lands in `inner.senses`, an experience is stored in `mind_observations`, an activity kind joins `server/mind/time/kinds.js`, an action joins `server/core/capabilities.js`, and attachment perception joins `server/core/perceivers.js`. The host lives in `server/plugins/`. See [Plugins](plugins.md).
105
+
106
+ ### Adding a channel
107
+
108
+ 1. Create a folder under `server/channels/` that fulfils the contract in `contract.js` (`type`, `capabilities`, `attach`, `start`, `stop`, `send`, `fetchQuoted`, `fetchImage`, `refreshDirectory`, `canReach`, `status`, `close`), wrapped with `defineChannel`.
109
+ 2. Turn platform events into neutral messages: the adapter fills in `platformId`, `accountId`, `time`, `mentions`, `replyId` and `attachments` itself.
110
+ 3. Register the adapter in `adapters.js` (so the core knows what that platform's media links look like), add one factory line in `createChannels` in `index.js`, and build session keys with `formatSessionKey`.
111
+ 4. Write a test against a fake platform in `tests/` and let the same scenarios in `channel-contract.test.js` run through it.
112
+
113
+ Nothing above the core changes. The connection option in settings and the readiness checks each need a small section per channel.
114
+
115
+ ### Adding a mind module
116
+
117
+ 1. Add tables in `server/mind/schema.js` (append, never overwrite); bump `DATABASE_VERSION` in `server/storage/archive.js` when needed and keep the migration idempotent.
118
+ 2. Write the module in `server/mind/` and wire it into `Mind` through `server/mind/index.js`; changes must cite sources and support undo.
119
+ 3. To reach her "now", read it in `view.js` and respect the length cap.
120
+ 4. To show it in the interface, add an endpoint in `server/mind/api.js`.
121
+ 5. Write tests with the deterministic world in `tests/helpers/world.js`, always using the world's clock for time.
122
+
123
+ ### Adding a studio page
124
+
125
+ 1. Write the page in `studio-web/src/pages/<name>/`, register the entry and navigation in `router.ts`, and mount the component in the `loaders` map of `pages.ts` under the page name.
126
+ 2. Wrap every string with `t()`, `N_()` or `localized()`; the Chinese original is the key.
127
+ 3. Add English in `studio-web/src/i18n/en/`; missing English falls back to Chinese and `tests/i18n-web.test.js` will tell you.
128
+ 4. Use the localized helpers in `format.ts` for dates and numbers.
129
+
130
+ ### Adding or changing translations
131
+
132
+ - Interface text: `studio-web/src/i18n/en/*.ts`, one file per area.
133
+ - Server-visible text (errors, readiness checks, labels, model preset notes): the dictionaries in `server/i18n/`, with `{0}` variable patterns.
134
+ - Documents: `docs/zh/` and `docs/en/` have matching file names; when you change one side, update the other.
135
+ - `tests/i18n-server.test.js`, `tests/i18n-web.test.js` and `tests/ui/i18n.mjs` check the dictionaries, the interface and a whole English page respectively.
136
+
137
+ ## Test layout
138
+
139
+ | Command | What it covers |
140
+ | --- | --- |
141
+ | `npm test` | All backend tests, including the deterministic "life simulation" |
142
+ | `npm run test:clock` | The suite run with the real clock moved 60 days back and 400 days ahead |
143
+ | `npm run test:ui` | Browser tests: smoke, the little world, navigation, time, performance, search, English interface |
144
+ | `npm run format:check` | Prettier check |
145
+ | `tests/repo-hygiene.test.js` | Repository hygiene: no keys, instance data, local paths or third-party connector names |
146
+
147
+ ## Conventions
148
+
149
+ - Change the version only in `package.json`; the server reads it through `server/version.js`.
150
+ - The built studio is committed in `public/app/`; run `npm run build:ui` after changing the frontend.
151
+ - The guide pages are generated by `npm run docs:build` from `docs/zh/guide.md` and `docs/en/guide.md`.
152
+ - Runtime data, backups, reports and secrets stay on the machine and never enter the repository.
@@ -0,0 +1,62 @@
1
+ <p align="right"><a href="../zh/chat-style.md">中文</a> · <b>English</b></p>
2
+
3
+ # How she talks, and the checks before she sends
4
+
5
+ The goal is to pick up the specific thing a group is talking about and answer in relaxed, plain speech. She does not prove herself by playing a type of person; how she speaks comes from her nature, her mood right now, how she feels about who is present, and the way she has grown into this place.
6
+
7
+ ## How she speaks
8
+
9
+ By default she speaks plain, everyday language:
10
+
11
+ - Everyday replies are usually short, and a reaction of a word or two is fine; a necessary piece of advice is never cut just to be short.
12
+ - She reacts to the specific thing, instead of following a fixed "repeat the feeling, comfort, advise, ask a question" routine.
13
+ - Venting is not assumed to want advice; good news does not get a consoling answer; jokes are put away when someone is really hurting.
14
+ - A conversation may simply end; she does not ask "and then?" every turn and does not turn every message into a joke.
15
+ - She uses memory only when it relates to the current topic and does not show off a memory file; when a reference is unclear she asks, and never invents experiences.
16
+ - She does not force slang to seem young. A serious setting beats style.
17
+
18
+ The following are hand-designed directions, not results measured on an external model (the Chinese originals are the intent; here they are rendered loosely):
19
+
20
+ | They say | Direction we want |
21
+ | --- | --- |
22
+ | Five minutes to clocking off and the boss dumps more work | Of course it picks right now |
23
+ | None of what I revised came up | Not a single guess landed |
24
+ | I passed the interview!! | Nice!! That calls for a little celebration |
25
+ | Don't advise me, just let me rant | Okay, rant away, I'm listening |
26
+ | I'm going to take a shower | Go ahead |
27
+
28
+ ## The rhythm of this place
29
+
30
+ In a group she hears the rhythm in which others speak there. It switches on after at least 12 distinct messages from 3 members within the last 7 days of ordinary human text, with each member contributing at most 8 so one talkative person cannot stand for the whole group. It measures common sentence length, the share of short sentences, closing punctuation, emoji and the frequency of conspicuous internet slang.
31
+
32
+ The statistics reach her only as `inner.room` and are **not her character**: she speaks by her own habits, need not copy them, and need not deliberately sound different. The adapter builds no profile of anyone's verbal habits, quotes no member, and does not treat commands in a group as settings; links, long texts, code, image placeholders, repeats, obvious abuse and common sensitive fields are filtered out. The statistics never cross groups, private chats or simulation mode.
33
+
34
+ ## Feedback
35
+
36
+ Under "Chat" you can give feedback on a reply she sent: natural, too long, too stiff, too many memes, or undo it. Feedback is not a rewrite rule: it reaches her as something she heard, read by herself during solitude and in the next turns of that conversation, and it never rewrites her nature.
37
+
38
+ ## Checks before sending
39
+
40
+ Before a reply goes out, local checks look for:
41
+
42
+ - a customer-service or consulting tone, list-like report replies, stage actions;
43
+ - unfounded intimate forms of address, excess length, repetition, back-to-back questions;
44
+ - ignoring "no advice needed", joking in a serious setting, piled-up slang;
45
+ - presenting her own earlier words as the other person's ("you just said…" when their record has no such line);
46
+ - claiming to have played or finished something (she has no record of such experience; a liking is not a record of having played);
47
+ - secrets and things known privately must not be said in another room.
48
+
49
+ When she is asked "how do you know?", the basis is checked first: for something she said unprompted (the other person had been silent for two hours or more), the basis can only be a guess or a real memory, never that she saw a message they sent; if a rewrite still invents a source, a verified correction is sent instead.
50
+
51
+ After finding a problem she rewrites at most once, keeping the meaning and facts; if it still fails she uses a safe short line, never simply truncating a long sentence or dropping hedges such as "I'm not sure". When nobody called her and her words are not in shape, she chooses not to speak rather than send a single-word stand-in; in private chats and when @-ed she still replies with a short line. Confessions, corrections, crises and complex group replies get one extra review (skipped when tokens are tight).
52
+
53
+ Besides the turn's own call, the review and the rewrite happen at most once each, so latency and cost can increase. The checks are conservative rules and do not promise to catch every stiff or wrong expression; real naturalness still depends on the model, the nature and the specific group.
54
+
55
+ ## How to try it
56
+
57
+ - **Trial chat**: on the right of the "Nature" page, or the small TA at the bottom right. It runs the same pipeline as QQ and reads her mind as it is now; the Nature page can also include an unsaved draft. Trial chat is not stored, not sent to QQ and creates no memory.
58
+ - **Simulated message**: under "Chat", only in simulation mode; it goes into a simulated session and never changes her mood, relationships or memory.
59
+ - **Replay**: "Back to that moment" reads only the mind as it was before then.
60
+ - **Audit**: `npm run audit:sent` replays the replies she recently sent against today's checks, read-only and without calling a model. Run it before changing a check to see whether the new rule would hurt. The replies already passed the checks when sent, so the number is for judging a new rule, not a score of how well she talks.
61
+
62
+ For real acceptance, watch 10–20 consecutive turns rather than a single line: does she pick up details, ask questions too often, repeat catchphrases, talk over others, suddenly turn into customer service, or mix up a joke with real sadness. Automated tests cover engineering constraints and hand-made scenario samples; the language quality of a real model still depends on your own observation.
@@ -0,0 +1,63 @@
1
+ <p align="right"><a href="../zh/connect.md">中文</a> · <b>English</b></p>
2
+
3
+ # Connecting QQ: two ways, one her
4
+
5
+ LuckyTri meets QQ through a "channel". A channel only handles how messages arrive and how they go out; **everything below the core — attention, memory, relationships, solitude, diary, time — is shared by both channels**. Changing how you connect does not change who she is: same nature, same memory, same behavior.
6
+
7
+ Only one channel is active at a time. Switch under "System → Connect QQ"; switching while running needs no restart. You can also pin it with the environment variable `LUCKYTRI_CHANNEL=onebot|qqbot`, after which the interface cannot change it.
8
+
9
+ ## How to choose
10
+
11
+ | | OneBot 11 | QQ official bot |
12
+ | --- | --- | --- |
13
+ | What you need | An OneBot 11 connector of your own (with reverse WebSocket client support) and a QQ account | A bot on the QQ Open Platform: AppID and AppSecret |
14
+ | Direction | The connector connects to LuckyTri's `/onebot/v11/ws` | LuckyTri connects to the Open Platform's WebSocket gateway |
15
+ | Logging in to QQ | Done in the connector | Not needed |
16
+ | Group message scope | All group messages | All of them when "receive all messages" is enabled on the Open Platform; otherwise only messages that @ her |
17
+ | Names | Group names and nicknames are available | The official interface gives no group names and one-to-one nicknames are often empty; you can rename a session under "Chat" |
18
+ | Recognizing people across groups | By QQ number: the same person in every group and private chat | The same person has a different openid in each group; she can only tell it is one person when the platform provides a unified identity (`union_openid`) |
19
+ | Replying | Any time | Inside the passive-reply window (5 minutes in groups, 60 minutes in one-to-one) as a reply to the original message, each bubble with a rising sequence number; beyond it a proactive message is sent |
20
+ | Reaching out | No extra limits | Sent as proactive messages; when a person or group has turned the bot's proactive messages off, she waits for them to come to her |
21
+ | Images | Fetched through the connector | Attachment URLs go through the same vision path; only official media domains are allowed |
22
+
23
+ Neither is "more right". If you want every group message and don't mind running a connector, pick OneBot 11. If you would rather not log in to QQ or maintain a connector, pick the official bot and enable "receive all messages" on the Open Platform.
24
+
25
+ ## OneBot 11
26
+
27
+ LuckyTri only implements the reverse-WebSocket side of OneBot 11 and ships no connector.
28
+
29
+ | Item | Value |
30
+ | --- | --- |
31
+ | Protocol | OneBot 11 |
32
+ | Reverse WebSocket URL | `ws://127.0.0.1:3210/onebot/v11/ws` (the port is your `PORT`) |
33
+ | Token | `ONEBOT_TOKEN` from `.env` |
34
+ | Message format | array |
35
+
36
+ When the connector is on another machine, replace `127.0.0.1` with an address that reaches LuckyTri and set `ADMIN_TOKEN` first. Session keys look like `onebot:<account>:group:<group id>` and `onebot:<account>:private:<QQ number>`; the early `group:<group id>` form is still recognized.
37
+
38
+ ## QQ official bot
39
+
40
+ 1. On the QQ Open Platform create a bot and note its **AppID** and **AppSecret**; enable group and one-to-one messages as the platform requires and add the bot to a test group or as a contact. The platform's entry points and review rules change over time; follow the Open Platform's current instructions.
41
+ 2. Under "System → Connect QQ" choose "QQ official bot", enter the AppID and AppSecret and save. You can also set `QQBOT_APP_ID` and `QQBOT_APP_SECRET` in `.env` (environment variables win). The AppSecret is only stored; it is never sent back to the page.
42
+ 3. The connection page shows the connection state, the bot's name and the time of the latest event. The group message scope reads "All group messages" or "Only messages that @ her"; for the latter, enable "receive all messages" on the Open Platform.
43
+
44
+ Session keys look like `qqbot:<AppID>:group:<group openid>` and `qqbot:<AppID>:private:<user openid>`.
45
+
46
+ ### How she behaves on the official channel
47
+
48
+ - **@ and quotes**: whether she was @-ed comes from the platform's "is this the bot" flag; quoted content comes from the message elements carried by the event, and a quote of her own message counts as someone replying to her.
49
+ - **Replies and bubbles**: inside the window a reply carries the original message id and each bubble increments the sequence number; outside the window it is sent as a proactive message instead. When delivery is uncertain nothing is resent, exactly as with OneBot.
50
+ - **Reaching out and sharing**: before she reaches out she checks whether the person has turned proactive messages off; if so she does not send and waits for them to come to her. In one-to-one chats one wake-up per period is available as a fallback.
51
+ - **Names**: when a group name or one-to-one nickname is unavailable she uses a name already known, and otherwise a placeholder "QQ user · last four". Any session can be renamed under "Chat", on both channels.
52
+ - **Identity**: on the official platform the same person has a different openid in each group. Like someone who has only met another person in separate rooms, she will not treat the two as one person until the platform provides a unified identity. This is deliberate, not a defect.
53
+ - **Images**: attachment URLs go to the same vision path; the official channel needs no directory refresh.
54
+
55
+ ## What happens when you switch
56
+
57
+ - Switching drops the current connection; sessions from the previous method stay readable but can no longer be sent to.
58
+ - Memory, relationships, diary and self are untouched, because they belong to her and not to the channel.
59
+ - The two methods number people differently (QQ number vs openid). Her impression of a person follows that number, so after switching the same real person shows up as a new one.
60
+
61
+ ## For developers: adding a channel
62
+
63
+ The channel contract lives in `server/channels/contract.js`: `type`, `capabilities`, `attach`, `start`, `stop`, `send`, `fetchQuoted`, `fetchImage`, `refreshDirectory`, `canReach`, `status`, `close`. A new channel is one folder that fulfils the contract, an adapter registered in `server/channels/adapters.js`, and one line in `createChannels` in `server/channels/index.js`; nothing above the core changes. The steps are in `docs/en/architecture.md`.