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,111 @@
1
+ // English for the model library: provider names, tiers, thinking levels and the
2
+ // notes beside each preset. Product and model names stay as the vendors write
3
+ // them; only the Chinese around them is translated.
4
+ const STANDARD_TAIL = "输入超过 272K 整单按长上下文计价。";
5
+ const STANDARD_TAIL_EN =
6
+ "Input over 272K is billed entirely at the long-context rate.";
7
+ const FLAT_TAIL = "1M 内同一价格,没有分档。";
8
+ const FLAT_TAIL_EN = "One price within 1M, no tiers.";
9
+
10
+ export const MODEL_LABELS = {
11
+ // tiers
12
+ "标准(输入 ≤272K)": "Standard (input ≤272K)",
13
+ "百万(1.05M)": "Million (1.05M)",
14
+
15
+ // thinking levels
16
+ 关闭: "Off",
17
+ 极低: "Minimal",
18
+ 低: "Low",
19
+ 中: "Medium",
20
+ 高: "High",
21
+ 极高: "Extra high",
22
+ 最深: "Deepest",
23
+
24
+ // providers and labels
25
+ 火山方舟: "Volcengine Ark",
26
+ "火山方舟 Coding Plan": "Volcengine Ark Coding Plan",
27
+ "方舟自动路由(ark-code-latest)": "Ark auto-routing (ark-code-latest)",
28
+ "GLM 最新别名(glm-latest)": "GLM latest alias (glm-latest)",
29
+ "小米 MiMo": "Xiaomi MiMo",
30
+ "智谱 GLM": "Zhipu GLM",
31
+ 通义千问: "Qwen",
32
+ "亚马逊 Bedrock": "Amazon Bedrock",
33
+ 自定义: "Custom",
34
+ 自定义兼容接口: "Custom compatible API",
35
+ "OpenRouter · 自选模型": "OpenRouter · your own model",
36
+
37
+ // notes
38
+ "控制台管理的动态模型入口;实际使用哪一个模型由 Coding Plan 控制台选择,切换通常数分钟后生效。支持图片理解。":
39
+ "A dynamic model entry managed in the console; which model is actually used is chosen in the Coding Plan console, and a switch usually takes a few minutes. Supports image understanding.",
40
+ [`当前主力。思考默认开启(high),可关闭;支持看图。${FLAT_TAIL}`]: `The current mainstay. Thinking is on by default (high) and can be turned off; supports images. ${FLAT_TAIL_EN}`,
41
+ [`V4-Pro-0813。思考默认开启(high),可关闭;仅文本。${FLAT_TAIL}`]: `V4-Pro-0813. Thinking is on by default (high) and can be turned off; text only. ${FLAT_TAIL_EN}`,
42
+ [`旗舰。思考默认开启,可关闭;支持图片、视频、音频输入。${FLAT_TAIL}`]: `Flagship. Thinking is on by default and can be turned off; accepts image, video and audio input. ${FLAT_TAIL_EN}`,
43
+ [`高频调用。思考默认开启,可关闭;支持图片、视频、音频输入。${FLAT_TAIL}`]: `For frequent calls. Thinking is on by default and can be turned off; accepts image, video and audio input. ${FLAT_TAIL_EN}`,
44
+ [`旗舰。思考不能关闭,默认 max;仅文本。${FLAT_TAIL}`]: `Flagship. Thinking cannot be turned off and defaults to max; text only. ${FLAT_TAIL_EN}`,
45
+ [`原生多模态。思考不能关闭,默认 max;支持图片、视频。${FLAT_TAIL}`]: `Natively multimodal. Thinking cannot be turned off and defaults to max; supports images and video. ${FLAT_TAIL_EN}`,
46
+ "旗舰。思考不能关闭,默认 max;支持看图。1M 内同一价格;输出和输入共用窗口,官方默认输出 128K。":
47
+ "Flagship. Thinking cannot be turned off and defaults to max; supports images. One price within 1M; output and input share the window, with a vendor default output of 128K.",
48
+ "通用模型。思考默认开启,可关闭;支持看图。输出和输入共用 256K 窗口,官方默认输出 32K。":
49
+ "General-purpose. Thinking is on by default and can be turned off; supports images. Output and input share a 256K window, with a vendor default output of 32K.",
50
+ [`最强旗舰。思考不能关闭,官方未写默认档,预填 medium;支持看图。${STANDARD_TAIL}`]: `The strongest flagship. Thinking cannot be turned off; the vendor states no default level, so medium is prefilled; supports images. ${STANDARD_TAIL_EN}`,
51
+ [`复杂编码与智能体。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `Complex coding and agents. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
52
+ [`高频轻量。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `Lightweight for frequent calls. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
53
+ "OpenRouter 路由 · openai/gpt-6-astra。1.05M 上下文、128K 最大输出;支持图片、结构化输出和工具调用。":
54
+ "OpenRouter route · openai/gpt-6-astra. 1.05M context, 128K max output; supports images, structured output and tool calls.",
55
+ "OpenRouter 路由 · openai/gpt-6-sol。1.05M 上下文、128K 最大输出;支持图片、结构化输出和工具调用。":
56
+ "OpenRouter route · openai/gpt-6-sol. 1.05M context, 128K max output; supports images, structured output and tool calls.",
57
+ "OpenRouter 路由 · openai/gpt-6-luna。1.05M 上下文、128K 最大输出;支持图片、结构化输出和工具调用。":
58
+ "OpenRouter route · openai/gpt-6-luna. 1.05M context, 128K max output; supports images, structured output and tool calls.",
59
+ "OpenRouter 路由 · z-ai/glm-5.3-flash。1,310,720 上下文、131,072 最大输出;支持图片、结构化输出和工具调用。":
60
+ "OpenRouter route · z-ai/glm-5.3-flash. 1,310,720 context, 131,072 max output; supports images, structured output and tool calls.",
61
+ [`上一代旗舰,别名 gpt-5.6。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `Previous-generation flagship, alias gpt-5.6. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
62
+ [`上一代均衡款。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `Previous-generation balanced model. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
63
+ [`上一代高性价比。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `Previous-generation value model. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
64
+ [`旗舰,快照 qwen3.8-max-0902。思考默认开启(xhigh),可关闭;支持图片、视频。${FLAT_TAIL}`]: `Flagship, snapshot qwen3.8-max-0902. Thinking is on by default (xhigh) and can be turned off; supports images and video. ${FLAT_TAIL_EN}`,
65
+ [`轻量。思考默认开启(xhigh),可关闭;支持图片、视频。${FLAT_TAIL}`]: `Lightweight. Thinking is on by default (xhigh) and can be turned off; supports images and video. ${FLAT_TAIL_EN}`,
66
+ [`美国跨区推理。思考不能关闭,预填 medium;支持看图。runtime 不支持结构化输出,已关闭 JSON 输出。${STANDARD_TAIL}`]: `US cross-region inference. Thinking cannot be turned off and medium is prefilled; supports images. The runtime has no structured output, so JSON output is turned off. ${STANDARD_TAIL_EN}`,
67
+ [`AWS 模型卡尚未发布,模型 ID 取自 OpenAI 的 Bedrock 指南,JSON 输出先关闭。思考默认 medium,可关闭;支持看图。${STANDARD_TAIL}`]: `The AWS model card is not published yet; the model ID comes from OpenAI's Bedrock guide, and JSON output is off for now. Thinking defaults to medium and can be turned off; supports images. ${STANDARD_TAIL_EN}`,
68
+ "OpenCode Zen · GPT-6 系列旗舰 · Responses 接口。":
69
+ "OpenCode Zen · GPT-6 series flagship · Responses API.",
70
+ "OpenCode Zen · GPT-6 均衡款 · Responses 接口。":
71
+ "OpenCode Zen · GPT-6 balanced · Responses API.",
72
+ "OpenCode Zen · GPT-6 轻量款 · Responses 接口。":
73
+ "OpenCode Zen · GPT-6 lightweight · Responses API.",
74
+ "OpenCode Zen · 快速推理 · Chat Completions 接口。":
75
+ "OpenCode Zen · fast inference · Chat Completions API.",
76
+ "OpenCode Zen · DeepSeek V4 旗舰 · Chat Completions 接口。":
77
+ "OpenCode Zen · DeepSeek V4 flagship · Chat Completions API.",
78
+ "OpenCode Zen · 轻量高速 · Chat Completions 接口。":
79
+ "OpenCode Zen · lightweight and fast · Chat Completions API.",
80
+ "OpenCode Zen · 智谱轻量多模态 · Chat Completions 接口。":
81
+ "OpenCode Zen · Zhipu lightweight multimodal · Chat Completions API.",
82
+ "OpenCode Zen · 智谱旗舰 · Chat Completions 接口。":
83
+ "OpenCode Zen · Zhipu flagship · Chat Completions API.",
84
+ "OpenCode Zen · Kimi 旗舰 · Chat Completions 接口。":
85
+ "OpenCode Zen · Kimi flagship · Chat Completions API.",
86
+ "OpenCode Zen · 通义千问轻量多模态 · Anthropic Messages 接口。":
87
+ "OpenCode Zen · Qwen lightweight multimodal · Anthropic Messages API.",
88
+ "OpenCode Zen · MiniMax 主力模型 · Chat Completions 接口。":
89
+ "OpenCode Zen · MiniMax main model · Chat Completions API.",
90
+ "OpenCode Zen · 当前限时免费 · Chat Completions 接口。参考 OpenRouter Space Bunny Alpha 规格:1M 上下文、最高 524K 输出,支持图像/视频、推理、工具和 JSON;其匿名第三方提供方可能保留提示和回答。LuckyTri 默认输入 128K、输出 8K、低推理;当前图片链路可用,视频链路未支持。Zen 路由能力与数据政策可能不同。":
91
+ "OpenCode Zen · free for a limited time · Chat Completions API. Modeled on the OpenRouter Space Bunny Alpha spec: 1M context, up to 524K output, image/video, reasoning, tools and JSON; its anonymous third-party providers may keep prompts and replies. LuckyTri defaults to 128K input, 8K output and low reasoning; images work today, video is not supported. Zen routes may differ in ability and data policy.",
92
+ "OpenAI 兼容接口。上下文先按 128K 填,思考档可按供应商再改。":
93
+ "OpenAI-compatible API. Context is prefilled at 128K; the thinking level can be changed per provider.",
94
+ "已填好 OpenRouter API 地址。请自行填写模型 ID、API Key,并按所选模型设置上下文、输出和能力参数。":
95
+ "The OpenRouter API URL is filled in. Enter the model ID and API key yourself, and set the context, output and ability parameters for the chosen model.",
96
+ };
97
+
98
+ export const MODEL_PATTERNS = [
99
+ [
100
+ "OpenCode Go · {0} · {1} 接口。默认上下文和输出预算为保守起始值,可按账户实际模型上限调整。",
101
+ "OpenCode Go · {0} · {1} API. Default context and output budgets are conservative starting values; adjust them to your account's real model limits.",
102
+ ],
103
+ [
104
+ "Coding Plan 专属 Responses 接口 · 上下文 {0} · 官方最大输出 {1} · 支持图片理解。默认中等推理;Kimi K2.8 Preview 在 LuckyTri 中预留 64K 输出预算以保留长上下文。",
105
+ "Coding Plan–only Responses API · context {0} · vendor max output {1} · supports image understanding. Medium reasoning by default; Kimi K2.8 Preview reserves a 64K output budget in LuckyTri to keep the long context.",
106
+ ],
107
+ [
108
+ "Coding Plan 专属 Responses 接口 · 上下文 {0} · 官方最大输出 {1} · 文本模型。默认中等推理;Kimi K2.8 Preview 在 LuckyTri 中预留 64K 输出预算以保留长上下文。",
109
+ "Coding Plan–only Responses API · context {0} · vendor max output {1} · text model. Medium reasoning by default; Kimi K2.8 Preview reserves a 64K output budget in LuckyTri to keep the long context.",
110
+ ],
111
+ ];
@@ -0,0 +1,102 @@
1
+ import { claimServer, clearStarting } from "./startup-lock.js";
2
+ import { createStore } from "./storage/store.js";
3
+ import { ChatSystem } from "./core/orchestrator.js";
4
+ import { Life } from "./mind/life/index.js";
5
+ import { createApp } from "./app.js";
6
+ import { createChannelHub, createChannels } from "./channels/index.js";
7
+ import { createBackupScheduler } from "./storage/backup-scheduler.js";
8
+ import { createBackupCleanup } from "./storage/backup-cleanup.js";
9
+ import { PluginHost } from "./plugins/host.js";
10
+
11
+ claimServer();
12
+ process.on("exit", clearStarting);
13
+ const store = createStore();
14
+ // One channel at a time connects her to QQ; the rest of the system only ever
15
+ // talks to the hub.
16
+ const channel = createChannelHub(store, createChannels(store));
17
+ const chatSystem = new ChatSystem(store, channel.send, {
18
+ fetchQuoted: (message) => channel.fetchQuoted(message),
19
+ fetchImage: (file, sessionId) => channel.fetchImage(file, sessionId),
20
+ });
21
+ const life = new Life(chatSystem, {
22
+ online: () => channel.online(),
23
+ canReach: (session) => channel.canReach(session),
24
+ });
25
+ life.start();
26
+ const plugins = new PluginHost({ store, chat: chatSystem, channel, life });
27
+ chatSystem.mind.embody(() => plugins.aware());
28
+ plugins
29
+ .start()
30
+ .catch((error) => console.error(`插件没有启动:${error.message}`));
31
+ const backups = createBackupScheduler();
32
+ const cleanup = createBackupCleanup({ store, backup: backups });
33
+ const host = process.env.HOST || "127.0.0.1";
34
+ if (
35
+ !["127.0.0.1", "localhost", "::1"].includes(host) &&
36
+ !process.env.ADMIN_TOKEN
37
+ )
38
+ throw new Error("绑定外网地址前必须设置 ADMIN_TOKEN");
39
+
40
+ let stopping = false;
41
+ function shutdown() {
42
+ if (stopping) return;
43
+ stopping = true;
44
+ backups.stop();
45
+ plugins.close();
46
+ life.close();
47
+ chatSystem.close();
48
+ channel.close();
49
+ store.revision++;
50
+ clearInterval(maintenance);
51
+ const finish = () => {
52
+ try {
53
+ store.db.close();
54
+ } finally {
55
+ process.exit(0);
56
+ }
57
+ };
58
+ server.close(finish);
59
+ setTimeout(finish, 3000).unref();
60
+ }
61
+
62
+ const app = createApp({
63
+ store,
64
+ chatSystem,
65
+ life,
66
+ plugins,
67
+ runtime: {
68
+ connection: () => channel.status(),
69
+ syncChannel: () => channel.sync(),
70
+ backup: () => backups.status(),
71
+ cleanup,
72
+ shutdown,
73
+ },
74
+ });
75
+ const server = app.listen(Number(process.env.PORT || 3210), host, () => {
76
+ console.log(`LuckyTri 管理台 http://${host}:${process.env.PORT || 3210}`);
77
+ });
78
+ channel.attach(server, chatSystem);
79
+ function runMaintenance() {
80
+ store.maintenance();
81
+ try {
82
+ chatSystem.maintain();
83
+ } catch (error) {
84
+ console.error(`后台维护失败:${error.message}`);
85
+ }
86
+ try {
87
+ if (!cleanup.running) backups.tick();
88
+ } catch (error) {
89
+ console.error(`自动备份调度失败:${error.message}`);
90
+ }
91
+ try {
92
+ cleanup.tick();
93
+ } catch (error) {
94
+ console.error(`定时备份整理调度失败:${error.message}`);
95
+ }
96
+ channel.refreshDirectory().catch(() => {});
97
+ }
98
+ runMaintenance();
99
+ const maintenance = setInterval(runMaintenance, 60000);
100
+ maintenance.unref();
101
+ process.on("SIGTERM", shutdown);
102
+ process.on("SIGINT", shutdown);
@@ -0,0 +1,342 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { wrap } from "../http.js";
3
+ import { prompts } from "../core/persona-manager.js";
4
+ import { indexMemory } from "./schema.js";
5
+ import {
6
+ publicEmbeddingProfile,
7
+ saveEmbeddingProfile,
8
+ resolveEmbeddingProfile,
9
+ embedBatches,
10
+ } from "./embedding-profile.js";
11
+ import {
12
+ applySpeakerNames,
13
+ presentMemory,
14
+ speakerNames,
15
+ } from "../core/speaker-names.js";
16
+
17
+ export function mountKnowledge(app, system) {
18
+ const { repo } = system;
19
+ app.get(
20
+ "/api/core/memories/:id/permissions",
21
+ wrap((req, res) => res.json(system.memory.permissions(req.params.id))),
22
+ );
23
+ app.patch(
24
+ "/api/core/memories/:id/permissions",
25
+ wrap((req, res) =>
26
+ res.json(system.memory.setPermission(req.params.id, req.body)),
27
+ ),
28
+ );
29
+ app.get("/api/core/knowledge/embedding", (req, res) =>
30
+ res.json(publicEmbeddingProfile(repo)),
31
+ );
32
+ app.patch(
33
+ "/api/core/knowledge/embedding",
34
+ wrap((req, res) => res.json(saveEmbeddingProfile(repo, req.body))),
35
+ );
36
+ app.post(
37
+ "/api/core/knowledge/embedding/test",
38
+ wrap(async (req, res) => {
39
+ const { profile } = resolveEmbeddingProfile(repo, system.models);
40
+ if (!profile.embedding) throw Error("知识向量未启用");
41
+ const vectors = await embedBatches(system.models, profile, [
42
+ "星空与时间",
43
+ ]);
44
+ res.json({ ok: true, dimensions: vectors[0].length });
45
+ }),
46
+ );
47
+ app.post(
48
+ "/api/core/knowledge/documents/:id/reembed",
49
+ wrap(async (req, res) =>
50
+ res.json(await system.knowledge.reembedDocument(req.params.id)),
51
+ ),
52
+ );
53
+ app.get("/api/core/memories", (req, res) => {
54
+ const session = String(req.query.session || "");
55
+ if (!session) {
56
+ const rows = repo.db
57
+ .prepare("SELECT * FROM core_memories ORDER BY updated DESC LIMIT 500")
58
+ .all();
59
+ const names = speakerNames(
60
+ repo.db,
61
+ rows.map((row) => row.session_id),
62
+ );
63
+ return res.json(rows.map((row) => presentMemory(row, names)));
64
+ }
65
+ const scopes = system.memory.scopes(session);
66
+ const placeholders = scopes.map(() => "?").join(",");
67
+ const rows = repo.db
68
+ .prepare(
69
+ `SELECT * FROM core_memories WHERE session_id IN (${placeholders}) ORDER BY updated DESC LIMIT 500`,
70
+ )
71
+ .all(...scopes);
72
+ const names = speakerNames(repo.db, [session, ...scopes]);
73
+ res.json(rows.map((row) => presentMemory(row, names)));
74
+ });
75
+ app.post(
76
+ "/api/core/memories",
77
+ wrap((req, res) => {
78
+ const { session, subject, content } = req.body;
79
+ if (
80
+ typeof subject !== "string" ||
81
+ !/^[0-9A-Za-z_-]{1,64}$/.test(subject) ||
82
+ typeof content !== "string" ||
83
+ !content.trim() ||
84
+ content.length > 4000 ||
85
+ !repo.db.prepare("SELECT id FROM sessions WHERE id=?").get(session)
86
+ )
87
+ throw Error("请选择会话、用户 ID 并填写记忆");
88
+ const discretion = ["open", "private", "secret"].includes(
89
+ req.body.discretion,
90
+ )
91
+ ? req.body.discretion
92
+ : /private/.test(session)
93
+ ? "private"
94
+ : "open";
95
+ const id = randomUUID(),
96
+ now = Date.now();
97
+ repo.db
98
+ .prepare(
99
+ "INSERT INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,sources,created,updated,discretion) VALUES (?,?,?,?,'manual',1,1,'confirmed','[]',?,?,?)",
100
+ )
101
+ .run(id, session, subject, content, now, now, discretion);
102
+ indexMemory(repo.db, id, content);
103
+ repo.store.revision++;
104
+ res.json({ id });
105
+ }),
106
+ );
107
+ app.post(
108
+ "/api/core/memories/batch-delete",
109
+ wrap((req, res) => {
110
+ const { session, ids, all } = req.body || {};
111
+ const result = system.memory.deleteBatch(session, ids, {
112
+ all: all === true,
113
+ });
114
+ res.json({ ok: true, ...result });
115
+ }),
116
+ );
117
+ app.get(
118
+ "/api/core/memory-summary",
119
+ wrap((req, res) => {
120
+ const session = String(req.query.session || "");
121
+ if (!session) throw Error("请选择会话");
122
+ const rows = repo.db
123
+ .prepare(
124
+ "SELECT id,first_seq,last_seq,time,data FROM core_stages WHERE session_id=? ORDER BY last_seq DESC LIMIT 3",
125
+ )
126
+ .all(session);
127
+ const stages = rows
128
+ .map((row) => {
129
+ try {
130
+ const data = JSON.parse(row.data);
131
+ return {
132
+ id: row.id,
133
+ firstSeq: row.first_seq,
134
+ lastSeq: row.last_seq,
135
+ time: row.time,
136
+ summary:
137
+ typeof data.summary === "string" ? data.summary.trim() : "",
138
+ };
139
+ } catch {
140
+ return null;
141
+ }
142
+ })
143
+ .filter((row) => row && row.summary);
144
+ const names = speakerNames(repo.db, [session]);
145
+ const summary = applySpeakerNames(
146
+ stages.length
147
+ ? stages
148
+ .map((stage) => stage.summary)
149
+ .join(" ")
150
+ .slice(0, 5000)
151
+ : "这个会话还没有阶段性记忆总结。消息积累后会自动整理,也可以点击“立即整理”。",
152
+ names,
153
+ );
154
+ res.json({
155
+ session,
156
+ summary,
157
+ updated: stages[0]?.time || null,
158
+ source: stages.length ? "stage" : "empty",
159
+ stages: stages.map((stage) => ({
160
+ ...stage,
161
+ summary: applySpeakerNames(stage.summary, names),
162
+ })),
163
+ participants: [...names.entries()].map(([id, name]) => ({ id, name })),
164
+ });
165
+ }),
166
+ );
167
+ app.get("/api/core/stages", (req, res) =>
168
+ res.json(
169
+ repo.db
170
+ .prepare(
171
+ "SELECT * FROM core_stages WHERE session_id=? ORDER BY last_seq DESC LIMIT 100",
172
+ )
173
+ .all(String(req.query.session || ""))
174
+ .map((r) => ({ ...r, data: JSON.parse(r.data) })),
175
+ ),
176
+ );
177
+ app.get("/api/core/memories/:id/versions", (req, res) =>
178
+ res.json(
179
+ repo.db
180
+ .prepare(
181
+ "SELECT * FROM core_memory_versions WHERE memory_id=? ORDER BY id DESC",
182
+ )
183
+ .all(req.params.id),
184
+ ),
185
+ );
186
+ app.patch(
187
+ "/api/core/memories/:id",
188
+ wrap((req, res) => {
189
+ const allowed = {};
190
+ for (const k of [
191
+ "content",
192
+ "status",
193
+ "locked",
194
+ "confidence",
195
+ "importance",
196
+ "expires",
197
+ "discretion",
198
+ ])
199
+ if (k in req.body) allowed[k] = req.body[k];
200
+ if (
201
+ "content" in allowed &&
202
+ (typeof allowed.content !== "string" ||
203
+ !allowed.content.trim() ||
204
+ allowed.content.length > 4000)
205
+ )
206
+ throw Error("记忆内容无效");
207
+ if (
208
+ "status" in allowed &&
209
+ !["candidate", "confirmed", "disputed", "deleted", "expired"].includes(
210
+ allowed.status,
211
+ )
212
+ )
213
+ throw Error("记忆状态无效");
214
+ for (const k of ["confidence", "importance"])
215
+ if (
216
+ k in allowed &&
217
+ (!Number.isFinite(allowed[k]) || allowed[k] < 0 || allowed[k] > 1)
218
+ )
219
+ throw Error("置信度与重要性应在0–1");
220
+ if (
221
+ "expires" in allowed &&
222
+ allowed.expires !== null &&
223
+ (!Number.isSafeInteger(allowed.expires) || allowed.expires < 0)
224
+ )
225
+ throw Error("过期时间无效");
226
+ if ("locked" in allowed && typeof allowed.locked !== "boolean")
227
+ throw Error("锁定状态无效");
228
+ system.memory.update(req.params.id, allowed);
229
+ res.json({ ok: true });
230
+ }),
231
+ );
232
+ app.post(
233
+ "/api/core/memories/merge",
234
+ wrap((req, res) => {
235
+ const { ids, content } = req.body;
236
+ if (
237
+ !Array.isArray(ids) ||
238
+ ids.length !== 2 ||
239
+ ids[0] === ids[1] ||
240
+ typeof content !== "string" ||
241
+ !content.trim()
242
+ )
243
+ throw Error("选择两条记忆并输入合并内容");
244
+ const rows = ids.map((id) =>
245
+ repo.db.prepare("SELECT * FROM core_memories WHERE id=?").get(id),
246
+ );
247
+ if (
248
+ rows.some((r) => !r || r.locked) ||
249
+ rows[0].session_id !== rows[1].session_id ||
250
+ rows[0].subject !== rows[1].subject
251
+ )
252
+ throw Error("只能合并同范围同用户的未锁定记忆");
253
+ repo.db.exec("BEGIN IMMEDIATE");
254
+ try {
255
+ system.memory.update(ids[0], {
256
+ content,
257
+ sources: JSON.stringify(rows.flatMap((r) => JSON.parse(r.sources))),
258
+ });
259
+ system.memory.update(ids[1], { status: "deleted" });
260
+ repo.db.exec("COMMIT");
261
+ } catch (e) {
262
+ repo.db.exec("ROLLBACK");
263
+ throw e;
264
+ }
265
+ res.json({ ok: true });
266
+ }),
267
+ );
268
+ app.post(
269
+ "/api/core/memory/consolidate",
270
+ wrap(async (req, res) => {
271
+ const id = req.body.session;
272
+ const t = repo.trace(id, "memory");
273
+ try {
274
+ await system.memory.consolidate(
275
+ id,
276
+ system.models.profile(),
277
+ prompts(repo).memory,
278
+ t,
279
+ { force: true },
280
+ );
281
+ repo.finish(t, "complete");
282
+ res.json({ ok: true });
283
+ } catch (e) {
284
+ t.error = e.message;
285
+ repo.finish(t, "error");
286
+ throw e;
287
+ }
288
+ }),
289
+ );
290
+ app.get("/api/core/knowledge/collections", (req, res) =>
291
+ res.json(system.knowledge.collections(String(req.query.session || ""))),
292
+ );
293
+ app.post(
294
+ "/api/core/knowledge/collections",
295
+ wrap((req, res) => {
296
+ res.json(system.knowledge.createCollection(req.body || {}));
297
+ }),
298
+ );
299
+ app.get("/api/core/knowledge/documents", (req, res) =>
300
+ res.json(system.knowledge.documents(String(req.query.collection || ""))),
301
+ );
302
+ app.post(
303
+ "/api/core/knowledge/documents",
304
+ wrap(async (req, res) => {
305
+ const { collectionId, title, text, source, embed } = req.body || {};
306
+ res.json(
307
+ await system.knowledge.ingest({
308
+ collectionId,
309
+ title,
310
+ text,
311
+ source,
312
+ embed: embed !== false,
313
+ }),
314
+ );
315
+ }),
316
+ );
317
+ app.delete(
318
+ "/api/core/knowledge/documents/:id",
319
+ wrap((req, res) => {
320
+ system.knowledge.removeDocument(req.params.id);
321
+ res.json({ ok: true });
322
+ }),
323
+ );
324
+ app.post(
325
+ "/api/core/knowledge/search",
326
+ wrap(async (req, res) => {
327
+ const { session, text } = req.body || {};
328
+ if (typeof text !== "string" || !text.trim() || text.length > 4000)
329
+ throw Error("请输入 1–4000 字检索内容");
330
+ if (
331
+ session &&
332
+ !repo.db.prepare("SELECT id FROM sessions WHERE id=?").get(session)
333
+ )
334
+ throw Error("会话不存在");
335
+ res.json(
336
+ await system.knowledge.retrieveWithEmbed(session || "", [
337
+ { text: text.trim(), userId: "probe" },
338
+ ]),
339
+ );
340
+ }),
341
+ );
342
+ }
@@ -0,0 +1,114 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ const CONFIG = "embedding-profile";
4
+ export const EMBEDDING_DEFAULTS = {
5
+ enabled: false,
6
+ label: "知识向量",
7
+ baseUrl: "",
8
+ model: "",
9
+ apiKey: "",
10
+ dimensions: 0,
11
+ batchSize: 32,
12
+ timeoutMs: 90000,
13
+ };
14
+
15
+ export function embeddingSignature(profile) {
16
+ return createHash("sha256")
17
+ .update(
18
+ JSON.stringify([
19
+ profile.baseUrl || "",
20
+ profile.embeddingModel || profile.model || "",
21
+ profile.dimensions || 0,
22
+ ]),
23
+ )
24
+ .digest("hex");
25
+ }
26
+
27
+ export function publicEmbeddingProfile(repo) {
28
+ const saved = repo.config(CONFIG, null);
29
+ const { apiKey, ...profile } = { ...EMBEDDING_DEFAULTS, ...saved };
30
+ return {
31
+ ...profile,
32
+ configured: !!saved,
33
+ hasApiKey: !!(apiKey || process.env.EMBEDDING_API_KEY),
34
+ };
35
+ }
36
+
37
+ export function saveEmbeddingProfile(repo, input) {
38
+ const old = { ...EMBEDDING_DEFAULTS, ...repo.config(CONFIG, {}) };
39
+ const next = Object.fromEntries(
40
+ Object.keys(EMBEDDING_DEFAULTS).map((key) => [key, input[key] ?? old[key]]),
41
+ );
42
+ if (typeof next.enabled !== "boolean") throw Error("向量开关无效");
43
+ if (typeof next.apiKey !== "string") throw Error("密钥格式无效");
44
+ if (!next.apiKey) next.apiKey = old.apiKey;
45
+ next.label = String(next.label).trim().slice(0, 100);
46
+ next.model = String(next.model).trim().slice(0, 200);
47
+ next.baseUrl = String(next.baseUrl).trim().replace(/\/$/, "");
48
+ if (next.baseUrl) {
49
+ const url = new URL(next.baseUrl);
50
+ if (
51
+ !["http:", "https:"].includes(url.protocol) ||
52
+ url.username ||
53
+ url.password ||
54
+ url.search ||
55
+ url.hash
56
+ )
57
+ throw Error("向量 API 地址无效");
58
+ }
59
+ if (next.enabled && (!next.baseUrl || !next.model))
60
+ throw Error("启用向量需要独立地址和模型名");
61
+ for (const [key, min, max] of [
62
+ ["dimensions", 0, 65536],
63
+ ["batchSize", 1, 128],
64
+ ["timeoutMs", 1000, 300000],
65
+ ])
66
+ if (!Number.isSafeInteger(next[key]) || next[key] < min || next[key] > max)
67
+ throw Error(`${key} 超出范围`);
68
+ repo.saveConfig(CONFIG, next);
69
+ return publicEmbeddingProfile(repo);
70
+ }
71
+
72
+ export function resolveEmbeddingProfile(repo, models) {
73
+ const saved = repo.config(CONFIG, null);
74
+ if (saved) {
75
+ const profile = {
76
+ ...EMBEDDING_DEFAULTS,
77
+ ...saved,
78
+ id: "embedding",
79
+ embedding: saved.enabled,
80
+ embeddingModel: saved.model,
81
+ isolatedEmbeddingKey: true,
82
+ };
83
+ return { profile, signature: embeddingSignature(profile), legacy: false };
84
+ }
85
+ // Existing installations remain compatible until an independent profile is saved.
86
+ const profile = models.profile("default");
87
+ return { profile, signature: embeddingSignature(profile), legacy: true };
88
+ }
89
+
90
+ export async function embedBatches(models, profile, texts) {
91
+ if (!profile.embedding) return [];
92
+ const vectors = [];
93
+ const size = profile.batchSize || 32;
94
+ let dimension = profile.dimensions || 0;
95
+ for (let start = 0; start < texts.length; start += size) {
96
+ const batch = texts.slice(start, start + size);
97
+ const result = await models.embed(profile, batch);
98
+ if (!Array.isArray(result) || result.length !== batch.length)
99
+ throw Error("向量数量与输入不一致");
100
+ for (const vector of result) {
101
+ if (
102
+ !Array.isArray(vector) ||
103
+ !vector.length ||
104
+ !vector.every(Number.isFinite) ||
105
+ !vector.some((value) => value !== 0)
106
+ )
107
+ throw Error("向量内容无效");
108
+ dimension ||= vector.length;
109
+ if (vector.length !== dimension) throw Error("向量维度不一致");
110
+ vectors.push(vector);
111
+ }
112
+ }
113
+ return vectors;
114
+ }
@@ -0,0 +1,13 @@
1
+ export { KnowledgeManager } from "./manager.js";
2
+ export { indexChunk, indexMemory, migrateKnowledge } from "./schema.js";
3
+ export {
4
+ asPlainDocument,
5
+ chunkText,
6
+ cosine,
7
+ ftsMatchQuery,
8
+ ftsTokens,
9
+ lexicalTerms,
10
+ overlapScore,
11
+ packVector,
12
+ unpackVector,
13
+ } from "./retrieval.js";