luckytri 1.0.4 → 1.0.6

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 (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +2 -0
  3. package/README.md +2 -0
  4. package/docs/en/CHANGELOG.md +20 -13
  5. package/docs/en/guide.md +7 -5
  6. package/docs/en/her-life.md +2 -2
  7. package/docs/en/webui.md +1 -1
  8. package/docs/zh/CHANGELOG.md +20 -13
  9. package/docs/zh/guide.md +7 -5
  10. package/docs/zh/her-life.md +2 -2
  11. package/docs/zh/webui.md +1 -1
  12. package/package.json +2 -1
  13. package/public/app/assets/ChatsPage-CFMVozoG.css +1 -0
  14. package/public/app/assets/{ChatsPage-CshdtTzV.js → ChatsPage-DCYMkJ91.js} +2 -2
  15. package/public/app/assets/{Empty-C7yzOBzy.js → Empty-CLyZkxR5.js} +1 -1
  16. package/public/app/assets/{Empty-DmUB2-Bk.css → Empty-DIobYG3x.css} +1 -1
  17. package/public/app/assets/{Field-BjFv4Rfn.js → Field-BpaRwLI_.js} +1 -1
  18. package/public/app/assets/{HeartPage-eqo6ru16.js → HeartPage-BIOMTiwr.js} +1 -1
  19. package/public/app/assets/HeartPage-C-2jvgzF.css +1 -0
  20. package/public/app/assets/{LifePage-CN8GE5NK.js → LifePage-BCgM4JkR.js} +1 -1
  21. package/public/app/assets/LifePage-CG1OHbRK.css +1 -0
  22. package/public/app/assets/MemoryPage-C_Bu4rfb.css +1 -0
  23. package/public/app/assets/{MemoryPage-CCpMN4Wg.js → MemoryPage-vShvRqMt.js} +1 -1
  24. package/public/app/assets/NaturePage-CPZlImT0.css +1 -0
  25. package/public/app/assets/{NaturePage-C0JqdBTR.js → NaturePage-Cy9ptr4G.js} +2 -2
  26. package/public/app/assets/PeoplePage-3RRUITDz.css +1 -0
  27. package/public/app/assets/{PeoplePage-BXGBbO4u.js → PeoplePage-CUZekB2E.js} +1 -1
  28. package/public/app/assets/{PluginsPage-Dksp5AV5.js → PluginsPage-1RM4rmJP.js} +1 -1
  29. package/public/app/assets/{SystemPage-BenIyTvk.css → SystemPage-BI2CB5Ig.css} +1 -1
  30. package/public/app/assets/{SystemPage-Cd7aa8eE.js → SystemPage-BeT5TmIV.js} +1 -1
  31. package/public/app/assets/TimePage-C-guwPk2.css +1 -0
  32. package/public/app/assets/{TimePage-Bs_zImb4.js → TimePage-taTmPMM8.js} +2 -2
  33. package/public/app/assets/{index-BHZcHUMh.css → index-DY5AM0Hz.css} +1 -1
  34. package/public/app/assets/{index-CyRtYxP3.js → index-DdYwC5ZU.js} +3 -3
  35. package/public/app/assets/{knowledge-BJOIyuJo.js → knowledge-DJmfu2KN.js} +1 -1
  36. package/public/app/assets/{media-Bwtx8blz.js → media-BEtnE0Gv.js} +1 -1
  37. package/public/app/assets/{mind-DtTVrW6Z.js → mind-Dtiw0-QK.js} +1 -1
  38. package/public/app/index.html +6 -5
  39. package/public/guide.en.html +11 -3
  40. package/public/guide.html +6 -3
  41. package/public/tutorial.en.md +2 -0
  42. package/public/tutorial.md +2 -0
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/service-supervisor.js +50 -0
  46. package/scripts/start-server.js +71 -16
  47. package/scripts/update-instance.js +201 -201
  48. package/server/channels/adapters.js +27 -27
  49. package/server/channels/onebot/adapter.js +288 -288
  50. package/server/channels/qqbot/adapter.js +253 -253
  51. package/server/channels/qqbot/api.js +199 -199
  52. package/server/channels/qqbot/credentials.js +11 -11
  53. package/server/channels/qqbot/gateway.js +274 -274
  54. package/server/channels/qqbot/index.js +389 -389
  55. package/server/channels/segments.js +40 -40
  56. package/server/channels/session-key.js +153 -153
  57. package/server/channels/state.js +42 -42
  58. package/server/core/api.js +564 -564
  59. package/server/core/context-builder.js +288 -288
  60. package/server/core/context-compactor.js +454 -454
  61. package/server/core/conversation-cues.js +325 -325
  62. package/server/core/conversation-manager.js +87 -87
  63. package/server/core/feedback.js +8 -8
  64. package/server/core/group-style.js +72 -72
  65. package/server/core/initiative-context.js +66 -66
  66. package/server/core/input-budget.js +32 -32
  67. package/server/core/local-demo.js +98 -98
  68. package/server/core/message-manager.js +118 -118
  69. package/server/core/message-scheduler.js +78 -78
  70. package/server/core/model-json.js +190 -190
  71. package/server/core/model-presets.js +900 -900
  72. package/server/core/name-call.js +29 -29
  73. package/server/core/network.js +157 -157
  74. package/server/core/person-name.js +22 -22
  75. package/server/core/persona-manager.js +2 -1
  76. package/server/core/reply-delivery.js +404 -403
  77. package/server/core/reply-target-resolver.js +50 -50
  78. package/server/core/response-generator.js +50 -50
  79. package/server/core/response-validator.js +14 -2
  80. package/server/core/retired-prompts.js +51 -51
  81. package/server/core/sessions.js +109 -109
  82. package/server/core/speaker-names.js +92 -92
  83. package/server/core/topic-tracker.js +65 -65
  84. package/server/http.js +18 -18
  85. package/server/i18n/decisions.js +209 -206
  86. package/server/i18n/errors.js +4 -0
  87. package/server/i18n/index.js +144 -144
  88. package/server/i18n/models.js +111 -111
  89. package/server/knowledge/api.js +342 -342
  90. package/server/knowledge/embedding-profile.js +114 -114
  91. package/server/knowledge/index.js +13 -13
  92. package/server/knowledge/retrieval.js +93 -93
  93. package/server/knowledge/schema.js +158 -158
  94. package/server/knowledge/vector-index.js +90 -90
  95. package/server/mind/affect.js +265 -265
  96. package/server/mind/attention.js +200 -200
  97. package/server/mind/bonds.js +565 -565
  98. package/server/mind/clock.js +83 -83
  99. package/server/mind/continuity.js +562 -562
  100. package/server/mind/conversation-origin.js +175 -175
  101. package/server/mind/days.js +210 -210
  102. package/server/mind/evidence-read.js +45 -45
  103. package/server/mind/faces.js +373 -373
  104. package/server/mind/guard.js +60 -60
  105. package/server/mind/initiative.js +338 -338
  106. package/server/mind/life/activities.js +21 -21
  107. package/server/mind/life/context.js +330 -323
  108. package/server/mind/life/diary.js +20 -0
  109. package/server/mind/life/growth.js +261 -254
  110. package/server/mind/life/index.js +10 -2
  111. package/server/mind/life/presence.js +305 -305
  112. package/server/mind/meetings.js +1069 -1069
  113. package/server/mind/memory-policy.js +48 -48
  114. package/server/mind/memory.js +888 -888
  115. package/server/mind/nature.js +358 -358
  116. package/server/mind/novelty.js +19 -19
  117. package/server/mind/periods.js +118 -118
  118. package/server/mind/reading.js +120 -120
  119. package/server/mind/relationship-context.js +71 -0
  120. package/server/mind/relationships.js +390 -279
  121. package/server/mind/salience.js +201 -201
  122. package/server/mind/schema.js +567 -567
  123. package/server/mind/self.js +484 -484
  124. package/server/mind/thoughts.js +274 -274
  125. package/server/mind/time/agenda.js +242 -242
  126. package/server/mind/time/api.js +200 -200
  127. package/server/mind/time/attention.js +34 -34
  128. package/server/mind/time/delivery-links.js +130 -130
  129. package/server/mind/time/model-material.js +77 -77
  130. package/server/mind/time/presentation.js +51 -51
  131. package/server/mind/time/schedule.js +83 -83
  132. package/server/mind/time/schema.js +84 -84
  133. package/server/mind/time/search.js +383 -383
  134. package/server/mind/time/sharing.js +472 -472
  135. package/server/mind/time/task-links.js +311 -311
  136. package/server/mind/time/works.js +432 -432
  137. package/server/mind/traits.js +191 -191
  138. package/server/mind/view.js +9 -1
  139. package/server/runtime/installation.js +116 -116
  140. package/server/runtime/update-execution.js +2 -1
  141. package/server/runtime/update-manager.js +140 -140
  142. package/server/storage/data-package.js +298 -298
  143. package/server/storage/data-transfer-api.js +50 -50
  144. package/server/storage/data-transfer-worker.js +18 -18
  145. package/server/storage/data-transfer.js +416 -416
  146. package/server/studio/display-names.js +139 -139
  147. package/server/studio/management.js +102 -102
  148. package/server/studio/updates.js +144 -144
  149. package/server/version.js +10 -10
  150. package/public/app/assets/ChatsPage-DfyPCS9N.css +0 -1
  151. package/public/app/assets/HeartPage-Bft9wZp5.css +0 -1
  152. package/public/app/assets/LifePage-BUi5euBF.css +0 -1
  153. package/public/app/assets/MemoryPage-cZvXfkXb.css +0 -1
  154. package/public/app/assets/NaturePage-CHkpb5NK.css +0 -1
  155. package/public/app/assets/PeoplePage-BTU0H1pM.css +0 -1
  156. package/public/app/assets/TimePage-BOms_n4I.css +0 -1
@@ -3,6 +3,10 @@ import { join } from "node:path";
3
3
  import { prepareRuntime } from "./runtime.js";
4
4
  import { VERSION } from "../server/version.js";
5
5
  import { resolveInstallation } from "../server/runtime/installation.js";
6
+ import {
7
+ updatedServerAfterExit,
8
+ waitForProcessExit,
9
+ } from "./service-supervisor.js";
6
10
  import {
7
11
  resolveProxyEnvironment,
8
12
  supportsNodeEnvironmentProxy,
@@ -16,11 +20,6 @@ if (proxy.enabled && !supportsNodeEnvironmentProxy())
16
20
  "系统代理转发需要 Node.js 24.5 或更高版本。请升级 Node.js 后重启 LuckyTri。",
17
21
  );
18
22
 
19
- const args = [
20
- ...(proxy.enabled ? ["--use-env-proxy"] : []),
21
- join(selected.package, "server", "index.js"),
22
- ];
23
- const env = { ...process.env, ...proxy.env };
24
23
  if (proxy.enabled) console.log("已启用系统代理,外部 API 请求将经代理转发。");
25
24
  else if (proxy.source === "windows-pac-unsupported")
26
25
  console.log(
@@ -28,17 +27,73 @@ else if (proxy.source === "windows-pac-unsupported")
28
27
  );
29
28
  else console.log("未检测到系统代理,外部 API 请求将直连。");
30
29
 
31
- const child = spawn(process.execPath, args, {
32
- cwd: paths.home,
33
- env,
34
- stdio: "inherit",
35
- });
30
+ let currentPid;
36
31
  for (const signal of ["SIGINT", "SIGTERM"])
37
- process.on(signal, () => child.kill(signal));
38
- child.once("error", (error) => {
32
+ process.on(signal, () => {
33
+ if (currentPid) process.kill(currentPid, signal);
34
+ });
35
+
36
+ async function start(root) {
37
+ const child = spawn(
38
+ process.execPath,
39
+ [
40
+ ...(proxy.enabled ? ["--use-env-proxy"] : []),
41
+ join(root, "server", "index.js"),
42
+ ],
43
+ {
44
+ cwd: paths.home,
45
+ env: { ...process.env, ...proxy.env },
46
+ stdio: "inherit",
47
+ },
48
+ );
49
+ currentPid = child.pid;
50
+ const exited = new Promise((resolve) =>
51
+ child.once("exit", (code, signal) => resolve({ code, signal })),
52
+ );
53
+ await new Promise((resolve, reject) => {
54
+ child.once("spawn", resolve);
55
+ child.once("error", reject);
56
+ });
57
+ return { pid: child.pid, exited };
58
+ }
59
+
60
+ try {
61
+ let root = selected.package,
62
+ tracked = await start(root),
63
+ direct = true;
64
+ while (true) {
65
+ if (direct) {
66
+ const { code, signal } = await tracked.exited;
67
+ if (signal || code !== 0) {
68
+ process.exitCode = signal ? 1 : (code ?? 1);
69
+ break;
70
+ }
71
+ } else {
72
+ await waitForProcessExit(tracked.pid);
73
+ }
74
+
75
+ const update = await updatedServerAfterExit(paths.home, tracked.pid);
76
+ if (!update.matched) break;
77
+
78
+ if (update.pid) {
79
+ // The update worker starts the verified release (or the previous release
80
+ // after rollback). Keep this foreground process alive so systemd does not
81
+ // kill that process or any still-running update work in the same cgroup.
82
+ tracked = { pid: update.pid };
83
+ direct = false;
84
+ continue;
85
+ }
86
+
87
+ // If the worker died after stopping the server, bring back the currently
88
+ // selected release. The matching update record prevents normal `stop`
89
+ // requests from being mistaken for a failed update.
90
+ root = resolveInstallation(paths.home, paths.package, VERSION).package;
91
+ tracked = await start(root);
92
+ direct = true;
93
+ }
94
+ } catch (error) {
39
95
  console.error(`LuckyTri 启动失败:${error.message}`);
40
96
  process.exitCode = 1;
41
- });
42
- child.once("exit", (code, signal) => {
43
- process.exitCode = signal ? 1 : (code ?? 1);
44
- });
97
+ } finally {
98
+ currentPid = undefined;
99
+ }
@@ -1,201 +1,201 @@
1
- import { spawn } from "node:child_process";
2
- import {
3
- openSync,
4
- closeSync,
5
- writeFileSync,
6
- readFileSync,
7
- realpathSync,
8
- } from "node:fs";
9
- import { dirname, join, delimiter } from "node:path";
10
- import { pathToFileURL } from "node:url";
11
- import { prepareRuntime } from "./runtime.js";
12
- import {
13
- running,
14
- serviceUrl,
15
- serviceHeaders,
16
- localServiceRequest,
17
- } from "./service.js";
18
- import { claimLaunch, clearStarting } from "../server/startup-lock.js";
19
- import { performUpdate } from "../server/runtime/update-execution.js";
20
- import {
21
- processAlive,
22
- readUpdateJob,
23
- writeUpdateJob,
24
- } from "../server/runtime/update-manager.js";
25
- import { updateRoot } from "../server/runtime/installation.js";
26
-
27
- export function installEnvironment(env) {
28
- const kept = {};
29
- for (const [key, value] of Object.entries(env))
30
- if (
31
- /^(?:PATH|PATHEXT|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|HOME|APPDATA|LOCALAPPDATA|TEMP|TMP|TMPDIR|HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY|NODE_USE_ENV_PROXY)$/i.test(
32
- key,
33
- )
34
- )
35
- kept[key] = value;
36
- return kept;
37
- }
38
- export function findNpmCli(env = process.env) {
39
- const candidates = [
40
- env.npm_execpath,
41
- join(dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
42
- join(dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js"),
43
- ...String(env.PATH || env.Path || "")
44
- .split(delimiter)
45
- .flatMap((dir) => [
46
- join(dir, "npm"),
47
- join(dir, "node_modules/npm/bin/npm-cli.js"),
48
- ]),
49
- ];
50
- for (const candidate of candidates.filter(Boolean)) {
51
- try {
52
- const cli = realpathSync(candidate);
53
- if (
54
- JSON.parse(
55
- readFileSync(join(dirname(cli), "..", "package.json"), "utf8"),
56
- ).name === "npm"
57
- )
58
- return cli;
59
- } catch {
60
- /* Try the next native Node/npm installation location. */
61
- }
62
- }
63
- throw Error("未找到 npm");
64
- }
65
- function command(args, { cwd, env = process.env, timeout = 600000, log } = {}) {
66
- return new Promise((resolve, reject) => {
67
- const fd = log ? openSync(log, "a", 0o600) : null;
68
- const child = spawn(process.execPath, args, {
69
- cwd,
70
- env,
71
- windowsHide: true,
72
- stdio: fd === null ? "ignore" : ["ignore", fd, fd],
73
- });
74
- if (fd !== null) closeSync(fd);
75
- const timer = setTimeout(() => {
76
- child.kill();
77
- reject(Error("更新步骤超时"));
78
- }, timeout);
79
- child.once("error", (e) => {
80
- clearTimeout(timer);
81
- reject(e);
82
- });
83
- child.once("exit", (code) => {
84
- clearTimeout(timer);
85
- code === 0 ? resolve() : reject(Error("更新程序校验失败"));
86
- });
87
- });
88
- }
89
- export function updateExecution(paths) {
90
- const base = serviceUrl(),
91
- spawned = new Set();
92
- const pause = () => new Promise((r) => setTimeout(r, 250));
93
- return {
94
- async install(slot, version) {
95
- const cli = findNpmCli();
96
- const userconfig = join(updateRoot(paths.home), "install.npmrc");
97
- writeFileSync(userconfig, "", { mode: 0o600 });
98
- await command(
99
- [
100
- cli,
101
- "install",
102
- "--prefix",
103
- slot,
104
- "--registry=https://registry.npmjs.org/",
105
- "--userconfig",
106
- userconfig,
107
- "--no-audit",
108
- "--no-fund",
109
- "--omit=dev",
110
- "--engine-strict",
111
- `luckytri@${version}`,
112
- ],
113
- { cwd: slot, env: installEnvironment(process.env), log: paths.log },
114
- );
115
- },
116
- async check(root, version) {
117
- const script = `import fs from 'node:fs';import {createRequire} from 'node:module';const p=JSON.parse(fs.readFileSync(${JSON.stringify(join(root, "package.json"))},'utf8'));if(p.version!==${JSON.stringify(version)})process.exit(1);await import(${JSON.stringify(pathToFileURL(join(root, "server/app.js")).href)});const sharp=createRequire(${JSON.stringify(pathToFileURL(join(root, "package.json")).href)})('sharp');await sharp({create:{width:1,height:1,channels:3,background:'#fff'}}).png().toBuffer();`;
118
- await command(["--input-type=module", "-e", script], {
119
- cwd: root,
120
- env: installEnvironment(process.env),
121
- timeout: 30000,
122
- log: paths.log,
123
- });
124
- },
125
- async stop(pid) {
126
- if (!processAlive(pid)) return;
127
- const info = await running({ base, lenient: true, local: true });
128
- if (info?.pid === pid) {
129
- const response = await localServiceRequest(base + "/api/service/stop", {
130
- method: "POST",
131
- headers: serviceHeaders(),
132
- body: "{}",
133
- timeout: 5000,
134
- });
135
- if (!response.ok) throw Error("服务拒绝停止请求");
136
- await response.json();
137
- } else if (spawned.has(pid)) process.kill(pid, "SIGTERM");
138
- else throw Error("运行实例发生变化");
139
- for (let n = 0; n < 160 && processAlive(pid); n++) await pause();
140
- if (processAlive(pid)) throw Error("旧实例未停止");
141
- },
142
- claim: claimLaunch,
143
- release: clearStarting,
144
- async start(root) {
145
- const log = openSync(paths.log, "a", 0o600);
146
- try {
147
- const child = spawn(
148
- process.execPath,
149
- [
150
- ...(process.execArgv.includes("--use-env-proxy")
151
- ? ["--use-env-proxy"]
152
- : []),
153
- join(root, "server", "index.js"),
154
- ],
155
- {
156
- cwd: paths.home,
157
- env: { ...process.env, LUCKYTRI_LAUNCH_PID: String(process.pid) },
158
- detached: true,
159
- windowsHide: true,
160
- stdio: ["ignore", log, log],
161
- },
162
- );
163
- await new Promise((resolve, reject) => {
164
- child.once("spawn", resolve);
165
- child.once("error", reject);
166
- });
167
- child.unref();
168
- spawned.add(child.pid);
169
- return child.pid;
170
- } finally {
171
- closeSync(log);
172
- }
173
- },
174
- async health(pid, version) {
175
- for (let n = 0; n < 2400; n++) {
176
- if (!processAlive(pid)) throw Error("新实例已退出");
177
- const info = await running({ base, lenient: true, local: true });
178
- if (info?.pid === pid && info.version === version) return;
179
- await pause();
180
- }
181
- throw Error("新实例启动超时");
182
- },
183
- };
184
- }
185
- async function main() {
186
- const paths = prepareRuntime({ initialize: false }),
187
- id = process.argv[2],
188
- job = readUpdateJob(paths.home, id);
189
- if (job.parent !== Number(process.argv[3]) || !processAlive(job.parent))
190
- throw Error("更新启动信息无效");
191
- writeUpdateJob(paths.home, { ...job, workerPid: process.pid });
192
- const result = await performUpdate(paths, id, updateExecution(paths));
193
- console.log(
194
- result.status === "done" ? "版本更新与实例重启完成。" : result.error,
195
- );
196
- }
197
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
198
- main().catch((e) => {
199
- console.error("版本更新未完成:", e.code || "请查看管理台更新状态");
200
- process.exitCode = 1;
201
- });
1
+ import { spawn } from "node:child_process";
2
+ import {
3
+ openSync,
4
+ closeSync,
5
+ writeFileSync,
6
+ readFileSync,
7
+ realpathSync,
8
+ } from "node:fs";
9
+ import { dirname, join, delimiter } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+ import { prepareRuntime } from "./runtime.js";
12
+ import {
13
+ running,
14
+ serviceUrl,
15
+ serviceHeaders,
16
+ localServiceRequest,
17
+ } from "./service.js";
18
+ import { claimLaunch, clearStarting } from "../server/startup-lock.js";
19
+ import { performUpdate } from "../server/runtime/update-execution.js";
20
+ import {
21
+ processAlive,
22
+ readUpdateJob,
23
+ writeUpdateJob,
24
+ } from "../server/runtime/update-manager.js";
25
+ import { updateRoot } from "../server/runtime/installation.js";
26
+
27
+ export function installEnvironment(env) {
28
+ const kept = {};
29
+ for (const [key, value] of Object.entries(env))
30
+ if (
31
+ /^(?:PATH|PATHEXT|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|HOME|APPDATA|LOCALAPPDATA|TEMP|TMP|TMPDIR|HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY|NODE_USE_ENV_PROXY)$/i.test(
32
+ key,
33
+ )
34
+ )
35
+ kept[key] = value;
36
+ return kept;
37
+ }
38
+ export function findNpmCli(env = process.env) {
39
+ const candidates = [
40
+ env.npm_execpath,
41
+ join(dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
42
+ join(dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js"),
43
+ ...String(env.PATH || env.Path || "")
44
+ .split(delimiter)
45
+ .flatMap((dir) => [
46
+ join(dir, "npm"),
47
+ join(dir, "node_modules/npm/bin/npm-cli.js"),
48
+ ]),
49
+ ];
50
+ for (const candidate of candidates.filter(Boolean)) {
51
+ try {
52
+ const cli = realpathSync(candidate);
53
+ if (
54
+ JSON.parse(
55
+ readFileSync(join(dirname(cli), "..", "package.json"), "utf8"),
56
+ ).name === "npm"
57
+ )
58
+ return cli;
59
+ } catch {
60
+ /* Try the next native Node/npm installation location. */
61
+ }
62
+ }
63
+ throw Error("未找到 npm");
64
+ }
65
+ function command(args, { cwd, env = process.env, timeout = 600000, log } = {}) {
66
+ return new Promise((resolve, reject) => {
67
+ const fd = log ? openSync(log, "a", 0o600) : null;
68
+ const child = spawn(process.execPath, args, {
69
+ cwd,
70
+ env,
71
+ windowsHide: true,
72
+ stdio: fd === null ? "ignore" : ["ignore", fd, fd],
73
+ });
74
+ if (fd !== null) closeSync(fd);
75
+ const timer = setTimeout(() => {
76
+ child.kill();
77
+ reject(Error("更新步骤超时"));
78
+ }, timeout);
79
+ child.once("error", (e) => {
80
+ clearTimeout(timer);
81
+ reject(e);
82
+ });
83
+ child.once("exit", (code) => {
84
+ clearTimeout(timer);
85
+ code === 0 ? resolve() : reject(Error("更新程序校验失败"));
86
+ });
87
+ });
88
+ }
89
+ export function updateExecution(paths) {
90
+ const base = serviceUrl(),
91
+ spawned = new Set();
92
+ const pause = () => new Promise((r) => setTimeout(r, 250));
93
+ return {
94
+ async install(slot, version) {
95
+ const cli = findNpmCli();
96
+ const userconfig = join(updateRoot(paths.home), "install.npmrc");
97
+ writeFileSync(userconfig, "", { mode: 0o600 });
98
+ await command(
99
+ [
100
+ cli,
101
+ "install",
102
+ "--prefix",
103
+ slot,
104
+ "--registry=https://registry.npmjs.org/",
105
+ "--userconfig",
106
+ userconfig,
107
+ "--no-audit",
108
+ "--no-fund",
109
+ "--omit=dev",
110
+ "--engine-strict",
111
+ `luckytri@${version}`,
112
+ ],
113
+ { cwd: slot, env: installEnvironment(process.env), log: paths.log },
114
+ );
115
+ },
116
+ async check(root, version) {
117
+ const script = `import fs from 'node:fs';import {createRequire} from 'node:module';const p=JSON.parse(fs.readFileSync(${JSON.stringify(join(root, "package.json"))},'utf8'));if(p.version!==${JSON.stringify(version)})process.exit(1);await import(${JSON.stringify(pathToFileURL(join(root, "server/app.js")).href)});const sharp=createRequire(${JSON.stringify(pathToFileURL(join(root, "package.json")).href)})('sharp');await sharp({create:{width:1,height:1,channels:3,background:'#fff'}}).png().toBuffer();`;
118
+ await command(["--input-type=module", "-e", script], {
119
+ cwd: root,
120
+ env: installEnvironment(process.env),
121
+ timeout: 30000,
122
+ log: paths.log,
123
+ });
124
+ },
125
+ async stop(pid) {
126
+ if (!processAlive(pid)) return;
127
+ const info = await running({ base, lenient: true, local: true });
128
+ if (info?.pid === pid) {
129
+ const response = await localServiceRequest(base + "/api/service/stop", {
130
+ method: "POST",
131
+ headers: serviceHeaders(),
132
+ body: "{}",
133
+ timeout: 5000,
134
+ });
135
+ if (!response.ok) throw Error("服务拒绝停止请求");
136
+ await response.json();
137
+ } else if (spawned.has(pid)) process.kill(pid, "SIGTERM");
138
+ else throw Error("运行实例发生变化");
139
+ for (let n = 0; n < 160 && processAlive(pid); n++) await pause();
140
+ if (processAlive(pid)) throw Error("旧实例未停止");
141
+ },
142
+ claim: claimLaunch,
143
+ release: clearStarting,
144
+ async start(root) {
145
+ const log = openSync(paths.log, "a", 0o600);
146
+ try {
147
+ const child = spawn(
148
+ process.execPath,
149
+ [
150
+ ...(process.execArgv.includes("--use-env-proxy")
151
+ ? ["--use-env-proxy"]
152
+ : []),
153
+ join(root, "server", "index.js"),
154
+ ],
155
+ {
156
+ cwd: paths.home,
157
+ env: { ...process.env, LUCKYTRI_LAUNCH_PID: String(process.pid) },
158
+ detached: true,
159
+ windowsHide: true,
160
+ stdio: ["ignore", log, log],
161
+ },
162
+ );
163
+ await new Promise((resolve, reject) => {
164
+ child.once("spawn", resolve);
165
+ child.once("error", reject);
166
+ });
167
+ child.unref();
168
+ spawned.add(child.pid);
169
+ return child.pid;
170
+ } finally {
171
+ closeSync(log);
172
+ }
173
+ },
174
+ async health(pid, version) {
175
+ for (let n = 0; n < 2400; n++) {
176
+ if (!processAlive(pid)) throw Error("新实例已退出");
177
+ const info = await running({ base, lenient: true, local: true });
178
+ if (info?.pid === pid && info.version === version) return;
179
+ await pause();
180
+ }
181
+ throw Error("新实例启动超时");
182
+ },
183
+ };
184
+ }
185
+ async function main() {
186
+ const paths = prepareRuntime({ initialize: false }),
187
+ id = process.argv[2],
188
+ job = readUpdateJob(paths.home, id);
189
+ if (job.parent !== Number(process.argv[3]) || !processAlive(job.parent))
190
+ throw Error("更新启动信息无效");
191
+ writeUpdateJob(paths.home, { ...job, workerPid: process.pid });
192
+ const result = await performUpdate(paths, id, updateExecution(paths));
193
+ console.log(
194
+ result.status === "done" ? "版本更新与实例重启完成。" : result.error,
195
+ );
196
+ }
197
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
198
+ main().catch((e) => {
199
+ console.error("版本更新未完成:", e.code || "请查看管理台更新状态");
200
+ process.exitCode = 1;
201
+ });
@@ -1,27 +1,27 @@
1
- import { onebot } from "./onebot/adapter.js";
2
- import { qqbot } from "./qqbot/adapter.js";
3
- import { parseSessionKey } from "./session-key.js";
4
-
5
- // What the core may ask a platform without being connected to it, such as
6
- // which media links are safe to download. One entry per channel type.
7
- const adapters = new Map([
8
- [onebot.type, onebot],
9
- [qqbot.type, qqbot],
10
- ]);
11
-
12
- export function registerChannel(adapter) {
13
- adapters.set(adapter.type, adapter);
14
- }
15
-
16
- export function adapterFor(sessionOrType = "onebot") {
17
- try {
18
- const type = String(sessionOrType).includes(":")
19
- ? parseSessionKey(sessionOrType).channel
20
- : sessionOrType;
21
- return adapters.get(type) || onebot;
22
- } catch {
23
- return onebot;
24
- }
25
- }
26
-
27
- export { onebot, qqbot };
1
+ import { onebot } from "./onebot/adapter.js";
2
+ import { qqbot } from "./qqbot/adapter.js";
3
+ import { parseSessionKey } from "./session-key.js";
4
+
5
+ // What the core may ask a platform without being connected to it, such as
6
+ // which media links are safe to download. One entry per channel type.
7
+ const adapters = new Map([
8
+ [onebot.type, onebot],
9
+ [qqbot.type, qqbot],
10
+ ]);
11
+
12
+ export function registerChannel(adapter) {
13
+ adapters.set(adapter.type, adapter);
14
+ }
15
+
16
+ export function adapterFor(sessionOrType = "onebot") {
17
+ try {
18
+ const type = String(sessionOrType).includes(":")
19
+ ? parseSessionKey(sessionOrType).channel
20
+ : sessionOrType;
21
+ return adapters.get(type) || onebot;
22
+ } catch {
23
+ return onebot;
24
+ }
25
+ }
26
+
27
+ export { onebot, qqbot };