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
@@ -1,128 +1,128 @@
1
- import { spawn } from "node:child_process";
2
- import { openSync, closeSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { prepareRuntime } from "./runtime.js";
5
- import { claimLaunch, clearStarting } from "../server/startup-lock.js";
6
- import { applyDataImport } from "../server/storage/data-package.js";
7
- import {
8
- transferRoot,
9
- readTransferJob,
10
- writeTransferJob,
11
- IMPORT_FAILURE,
12
- IMPORT_START_FAILURE,
13
- IMPORT_RESTART_FAILURE,
14
- } from "../server/storage/data-transfer.js";
15
-
16
- const id = process.argv[2];
17
- const parent = Number(process.argv[3]);
18
- let job,
19
- paths,
20
- claimed = false;
21
- const alive = (pid) => {
22
- try {
23
- process.kill(pid, 0);
24
- return true;
25
- } catch (error) {
26
- return error.code !== "ESRCH";
27
- }
28
- };
29
- try {
30
- paths = prepareRuntime({ initialize: false });
31
- if (!Number.isInteger(parent) || parent <= 0) throw Error("导入启动信息无效");
32
- job = readTransferJob(transferRoot(), id);
33
- if (job.kind !== "import" || job.status !== "queued")
34
- throw Error("导入任务已经处理或不可用");
35
- for (let attempt = 0; attempt < 160 && alive(parent); attempt++)
36
- await new Promise((resolve) => setTimeout(resolve, 250));
37
- if (alive(parent)) throw Error("旧实例仍在运行,未替换数据");
38
- if (!claimLaunch()) throw Error("另一次启动正在进行,未替换数据");
39
- claimed = true;
40
- try {
41
- const result = applyDataImport(
42
- join(transferRoot(), id, "data.db"),
43
- paths.database,
44
- paths.backups,
45
- {
46
- id,
47
- beforeSwap(backup) {
48
- job = { ...job, backup };
49
- writeTransferJob(transferRoot(), job);
50
- },
51
- },
52
- );
53
- job = {
54
- ...job,
55
- status: "done",
56
- applied: Date.now(),
57
- ...result,
58
- };
59
- try {
60
- writeTransferJob(transferRoot(), job);
61
- } catch {
62
- console.error("导入结果记录未写入,重启后会核对已提交的数据。");
63
- }
64
- console.log("数据导入完成,当前数据快照已保留。");
65
- } catch (error) {
66
- writeTransferJob(transferRoot(), {
67
- ...job,
68
- status: "error",
69
- error: IMPORT_FAILURE,
70
- });
71
- console.error("数据导入未完成:", error.code || "校验或替换失败");
72
- }
73
- // Hand the launch claim directly to the new server so no other launch can
74
- // open the database halfway through the swap.
75
- const log = openSync(paths.log, "a", 0o600);
76
- const child = spawn(
77
- process.execPath,
78
- [
79
- ...(process.execArgv.includes("--use-env-proxy")
80
- ? ["--use-env-proxy"]
81
- : []),
82
- join(paths.package, "server", "index.js"),
83
- ],
84
- {
85
- cwd: paths.home,
86
- env: { ...process.env, LUCKYTRI_LAUNCH_PID: String(process.pid) },
87
- detached: true,
88
- windowsHide: true,
89
- stdio: ["ignore", log, log],
90
- },
91
- );
92
- closeSync(log);
93
- await new Promise((resolve, reject) => {
94
- child.once("spawn", resolve);
95
- child.once("error", reject);
96
- });
97
- child.unref();
98
- for (let attempt = 0; attempt < 120; attempt++) {
99
- try {
100
- const info = JSON.parse(
101
- (await import("node:fs")).readFileSync(
102
- join(paths.data, "startup.json"),
103
- "utf8",
104
- ),
105
- );
106
- if (info.pid === child.pid && info.role === "server") {
107
- claimed = false;
108
- break;
109
- }
110
- } catch {}
111
- await new Promise((resolve) => setTimeout(resolve, 250));
112
- }
113
- } catch (error) {
114
- if (job) {
115
- try {
116
- writeTransferJob(transferRoot(), {
117
- ...job,
118
- status: "error",
119
- error:
120
- job.status === "done" ? IMPORT_RESTART_FAILURE : IMPORT_START_FAILURE,
121
- });
122
- } catch {}
123
- }
124
- console.error("导入启动失败:", error.code || "请查看数据任务状态");
125
- process.exitCode = 1;
126
- } finally {
127
- if (claimed) clearStarting();
128
- }
1
+ import { spawn } from "node:child_process";
2
+ import { openSync, closeSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { prepareRuntime } from "./runtime.js";
5
+ import { claimLaunch, clearStarting } from "../server/startup-lock.js";
6
+ import { applyDataImport } from "../server/storage/data-package.js";
7
+ import {
8
+ transferRoot,
9
+ readTransferJob,
10
+ writeTransferJob,
11
+ IMPORT_FAILURE,
12
+ IMPORT_START_FAILURE,
13
+ IMPORT_RESTART_FAILURE,
14
+ } from "../server/storage/data-transfer.js";
15
+
16
+ const id = process.argv[2];
17
+ const parent = Number(process.argv[3]);
18
+ let job,
19
+ paths,
20
+ claimed = false;
21
+ const alive = (pid) => {
22
+ try {
23
+ process.kill(pid, 0);
24
+ return true;
25
+ } catch (error) {
26
+ return error.code !== "ESRCH";
27
+ }
28
+ };
29
+ try {
30
+ paths = prepareRuntime({ initialize: false });
31
+ if (!Number.isInteger(parent) || parent <= 0) throw Error("导入启动信息无效");
32
+ job = readTransferJob(transferRoot(), id);
33
+ if (job.kind !== "import" || job.status !== "queued")
34
+ throw Error("导入任务已经处理或不可用");
35
+ for (let attempt = 0; attempt < 160 && alive(parent); attempt++)
36
+ await new Promise((resolve) => setTimeout(resolve, 250));
37
+ if (alive(parent)) throw Error("旧实例仍在运行,未替换数据");
38
+ if (!claimLaunch()) throw Error("另一次启动正在进行,未替换数据");
39
+ claimed = true;
40
+ try {
41
+ const result = applyDataImport(
42
+ join(transferRoot(), id, "data.db"),
43
+ paths.database,
44
+ paths.backups,
45
+ {
46
+ id,
47
+ beforeSwap(backup) {
48
+ job = { ...job, backup };
49
+ writeTransferJob(transferRoot(), job);
50
+ },
51
+ },
52
+ );
53
+ job = {
54
+ ...job,
55
+ status: "done",
56
+ applied: Date.now(),
57
+ ...result,
58
+ };
59
+ try {
60
+ writeTransferJob(transferRoot(), job);
61
+ } catch {
62
+ console.error("导入结果记录未写入,重启后会核对已提交的数据。");
63
+ }
64
+ console.log("数据导入完成,当前数据快照已保留。");
65
+ } catch (error) {
66
+ writeTransferJob(transferRoot(), {
67
+ ...job,
68
+ status: "error",
69
+ error: IMPORT_FAILURE,
70
+ });
71
+ console.error("数据导入未完成:", error.code || "校验或替换失败");
72
+ }
73
+ // Hand the launch claim directly to the new server so no other launch can
74
+ // open the database halfway through the swap.
75
+ const log = openSync(paths.log, "a", 0o600);
76
+ const child = spawn(
77
+ process.execPath,
78
+ [
79
+ ...(process.execArgv.includes("--use-env-proxy")
80
+ ? ["--use-env-proxy"]
81
+ : []),
82
+ join(paths.package, "server", "index.js"),
83
+ ],
84
+ {
85
+ cwd: paths.home,
86
+ env: { ...process.env, LUCKYTRI_LAUNCH_PID: String(process.pid) },
87
+ detached: true,
88
+ windowsHide: true,
89
+ stdio: ["ignore", log, log],
90
+ },
91
+ );
92
+ closeSync(log);
93
+ await new Promise((resolve, reject) => {
94
+ child.once("spawn", resolve);
95
+ child.once("error", reject);
96
+ });
97
+ child.unref();
98
+ for (let attempt = 0; attempt < 120; attempt++) {
99
+ try {
100
+ const info = JSON.parse(
101
+ (await import("node:fs")).readFileSync(
102
+ join(paths.data, "startup.json"),
103
+ "utf8",
104
+ ),
105
+ );
106
+ if (info.pid === child.pid && info.role === "server") {
107
+ claimed = false;
108
+ break;
109
+ }
110
+ } catch {}
111
+ await new Promise((resolve) => setTimeout(resolve, 250));
112
+ }
113
+ } catch (error) {
114
+ if (job) {
115
+ try {
116
+ writeTransferJob(transferRoot(), {
117
+ ...job,
118
+ status: "error",
119
+ error:
120
+ job.status === "done" ? IMPORT_RESTART_FAILURE : IMPORT_START_FAILURE,
121
+ });
122
+ } catch {}
123
+ }
124
+ console.error("导入启动失败:", error.code || "请查看数据任务状态");
125
+ process.exitCode = 1;
126
+ } finally {
127
+ if (claimed) clearStarting();
128
+ }
@@ -1,167 +1,167 @@
1
- import { execFileSync } from "node:child_process";
2
- import net from "node:net";
3
-
4
- const WINDOWS_INTERNET_SETTINGS =
5
- "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
6
- const LOCAL_BYPASS = ["localhost", "127.0.0.1", "::1"];
7
-
8
- function environmentProxy(env) {
9
- const http = env.http_proxy || env.HTTP_PROXY || "";
10
- const https = env.https_proxy || env.HTTPS_PROXY || http;
11
- return http || https ? { http, https } : null;
12
- }
13
-
14
- function readWindowsValue(name) {
15
- try {
16
- const output = execFileSync(
17
- "reg.exe",
18
- ["query", WINDOWS_INTERNET_SETTINGS, "/v", name],
19
- {
20
- encoding: "utf8",
21
- windowsHide: true,
22
- stdio: ["ignore", "pipe", "ignore"],
23
- },
24
- );
25
- return (
26
- output
27
- .match(new RegExp(`\\b${name}\\s+REG_\\w+\\s+(.+)$`, "im"))?.[1]
28
- ?.trim() || ""
29
- );
30
- } catch {
31
- return "";
32
- }
33
- }
34
-
35
- function parseWindowsProxyServer(value) {
36
- const routes = {};
37
- for (const part of String(value || "").split(";")) {
38
- const item = part.trim();
39
- if (!item) continue;
40
- const separator = item.indexOf("=");
41
- if (separator < 0) routes.default = item;
42
- else
43
- routes[item.slice(0, separator).trim().toLowerCase()] = item
44
- .slice(separator + 1)
45
- .trim();
46
- }
47
- const normalize = (item) => {
48
- if (!item) return "";
49
- try {
50
- const url = new URL(
51
- /^[a-z][a-z\d+.-]*:\/\//i.test(item) ? item : `http://${item}`,
52
- );
53
- return ["http:", "https:"].includes(url.protocol) ? url.href : "";
54
- } catch {
55
- return "";
56
- }
57
- };
58
- const fallback = routes.default || routes.http || routes.https || "";
59
- return {
60
- http: normalize(routes.http || fallback || routes.https),
61
- https: normalize(routes.https || fallback || routes.http),
62
- };
63
- }
64
-
65
- function isLoopbackProxy(url) {
66
- try {
67
- const host = new URL(url).hostname.toLowerCase();
68
- return host === "localhost" || host === "::1" || host.startsWith("127.");
69
- } catch {
70
- return false;
71
- }
72
- }
73
-
74
- function isListening(url) {
75
- return new Promise((resolve) => {
76
- try {
77
- const proxy = new URL(url);
78
- const socket = net.createConnection({
79
- host: proxy.hostname,
80
- port: Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80)),
81
- });
82
- const finish = (value) => {
83
- socket.destroy();
84
- resolve(value);
85
- };
86
- socket.setTimeout(700, () => finish(false));
87
- socket.once("connect", () => finish(true));
88
- socket.once("error", () => finish(false));
89
- } catch {
90
- resolve(false);
91
- }
92
- });
93
- }
94
-
95
- function mergeNoProxy(...values) {
96
- const hosts = new Set(LOCAL_BYPASS);
97
- for (const value of values) {
98
- for (const host of String(value || "").split(/[;,]/)) {
99
- const item = host.trim();
100
- if (item && item.toLowerCase() !== "<local>") hosts.add(item);
101
- }
102
- }
103
- return [...hosts].join(",");
104
- }
105
-
106
- export async function resolveProxyEnvironment(env = process.env) {
107
- const configured = environmentProxy(env);
108
- if (configured) {
109
- return {
110
- enabled: true,
111
- source: "environment",
112
- env: {
113
- HTTP_PROXY: configured.http || configured.https,
114
- HTTPS_PROXY: configured.https || configured.http,
115
- NO_PROXY: mergeNoProxy(env.NO_PROXY, env.no_proxy),
116
- },
117
- };
118
- }
119
-
120
- if (process.platform !== "win32")
121
- return { enabled: false, source: "none", env: {} };
122
-
123
- const enabled = /^\s*(?:1|0x1)\s*$/i.test(readWindowsValue("ProxyEnable"));
124
- const server = readWindowsValue("ProxyServer");
125
- const routes = parseWindowsProxyServer(server);
126
- const bypass = readWindowsValue("ProxyOverride");
127
- let source = enabled ? "windows-system" : "none";
128
-
129
- // Some proxy clients leave the Windows system-proxy toggle off while their
130
- // local HTTP proxy remains active. Honor that live endpoint for LuckyTri.
131
- if (
132
- !enabled &&
133
- routes.https &&
134
- isLoopbackProxy(routes.https) &&
135
- (await isListening(routes.https))
136
- )
137
- source = "windows-local-proxy";
138
-
139
- if (source === "none")
140
- return {
141
- enabled: false,
142
- source: readWindowsValue("AutoConfigURL")
143
- ? "windows-pac-unsupported"
144
- : source,
145
- env: {},
146
- };
147
-
148
- if (!routes.http && !routes.https)
149
- return { enabled: false, source: "windows-proxy-invalid", env: {} };
150
-
151
- return {
152
- enabled: true,
153
- source,
154
- env: {
155
- HTTP_PROXY: routes.http || routes.https,
156
- HTTPS_PROXY: routes.https || routes.http,
157
- NO_PROXY: mergeNoProxy(env.NO_PROXY, env.no_proxy, bypass),
158
- },
159
- };
160
- }
161
-
162
- export function supportsNodeEnvironmentProxy(version = process.versions.node) {
163
- const [major, minor] = version.split(".").map(Number);
164
- return (
165
- major > 24 || (major === 24 && minor >= 5) || (major === 22 && minor >= 21)
166
- );
167
- }
1
+ import { execFileSync } from "node:child_process";
2
+ import net from "node:net";
3
+
4
+ const WINDOWS_INTERNET_SETTINGS =
5
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
6
+ const LOCAL_BYPASS = ["localhost", "127.0.0.1", "::1"];
7
+
8
+ function environmentProxy(env) {
9
+ const http = env.http_proxy || env.HTTP_PROXY || "";
10
+ const https = env.https_proxy || env.HTTPS_PROXY || http;
11
+ return http || https ? { http, https } : null;
12
+ }
13
+
14
+ function readWindowsValue(name) {
15
+ try {
16
+ const output = execFileSync(
17
+ "reg.exe",
18
+ ["query", WINDOWS_INTERNET_SETTINGS, "/v", name],
19
+ {
20
+ encoding: "utf8",
21
+ windowsHide: true,
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ },
24
+ );
25
+ return (
26
+ output
27
+ .match(new RegExp(`\\b${name}\\s+REG_\\w+\\s+(.+)$`, "im"))?.[1]
28
+ ?.trim() || ""
29
+ );
30
+ } catch {
31
+ return "";
32
+ }
33
+ }
34
+
35
+ function parseWindowsProxyServer(value) {
36
+ const routes = {};
37
+ for (const part of String(value || "").split(";")) {
38
+ const item = part.trim();
39
+ if (!item) continue;
40
+ const separator = item.indexOf("=");
41
+ if (separator < 0) routes.default = item;
42
+ else
43
+ routes[item.slice(0, separator).trim().toLowerCase()] = item
44
+ .slice(separator + 1)
45
+ .trim();
46
+ }
47
+ const normalize = (item) => {
48
+ if (!item) return "";
49
+ try {
50
+ const url = new URL(
51
+ /^[a-z][a-z\d+.-]*:\/\//i.test(item) ? item : `http://${item}`,
52
+ );
53
+ return ["http:", "https:"].includes(url.protocol) ? url.href : "";
54
+ } catch {
55
+ return "";
56
+ }
57
+ };
58
+ const fallback = routes.default || routes.http || routes.https || "";
59
+ return {
60
+ http: normalize(routes.http || fallback || routes.https),
61
+ https: normalize(routes.https || fallback || routes.http),
62
+ };
63
+ }
64
+
65
+ function isLoopbackProxy(url) {
66
+ try {
67
+ const host = new URL(url).hostname.toLowerCase();
68
+ return host === "localhost" || host === "::1" || host.startsWith("127.");
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function isListening(url) {
75
+ return new Promise((resolve) => {
76
+ try {
77
+ const proxy = new URL(url);
78
+ const socket = net.createConnection({
79
+ host: proxy.hostname,
80
+ port: Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80)),
81
+ });
82
+ const finish = (value) => {
83
+ socket.destroy();
84
+ resolve(value);
85
+ };
86
+ socket.setTimeout(700, () => finish(false));
87
+ socket.once("connect", () => finish(true));
88
+ socket.once("error", () => finish(false));
89
+ } catch {
90
+ resolve(false);
91
+ }
92
+ });
93
+ }
94
+
95
+ function mergeNoProxy(...values) {
96
+ const hosts = new Set(LOCAL_BYPASS);
97
+ for (const value of values) {
98
+ for (const host of String(value || "").split(/[;,]/)) {
99
+ const item = host.trim();
100
+ if (item && item.toLowerCase() !== "<local>") hosts.add(item);
101
+ }
102
+ }
103
+ return [...hosts].join(",");
104
+ }
105
+
106
+ export async function resolveProxyEnvironment(env = process.env) {
107
+ const configured = environmentProxy(env);
108
+ if (configured) {
109
+ return {
110
+ enabled: true,
111
+ source: "environment",
112
+ env: {
113
+ HTTP_PROXY: configured.http || configured.https,
114
+ HTTPS_PROXY: configured.https || configured.http,
115
+ NO_PROXY: mergeNoProxy(env.NO_PROXY, env.no_proxy),
116
+ },
117
+ };
118
+ }
119
+
120
+ if (process.platform !== "win32")
121
+ return { enabled: false, source: "none", env: {} };
122
+
123
+ const enabled = /^\s*(?:1|0x1)\s*$/i.test(readWindowsValue("ProxyEnable"));
124
+ const server = readWindowsValue("ProxyServer");
125
+ const routes = parseWindowsProxyServer(server);
126
+ const bypass = readWindowsValue("ProxyOverride");
127
+ let source = enabled ? "windows-system" : "none";
128
+
129
+ // Some proxy clients leave the Windows system-proxy toggle off while their
130
+ // local HTTP proxy remains active. Honor that live endpoint for LuckyTri.
131
+ if (
132
+ !enabled &&
133
+ routes.https &&
134
+ isLoopbackProxy(routes.https) &&
135
+ (await isListening(routes.https))
136
+ )
137
+ source = "windows-local-proxy";
138
+
139
+ if (source === "none")
140
+ return {
141
+ enabled: false,
142
+ source: readWindowsValue("AutoConfigURL")
143
+ ? "windows-pac-unsupported"
144
+ : source,
145
+ env: {},
146
+ };
147
+
148
+ if (!routes.http && !routes.https)
149
+ return { enabled: false, source: "windows-proxy-invalid", env: {} };
150
+
151
+ return {
152
+ enabled: true,
153
+ source,
154
+ env: {
155
+ HTTP_PROXY: routes.http || routes.https,
156
+ HTTPS_PROXY: routes.https || routes.http,
157
+ NO_PROXY: mergeNoProxy(env.NO_PROXY, env.no_proxy, bypass),
158
+ },
159
+ };
160
+ }
161
+
162
+ export function supportsNodeEnvironmentProxy(version = process.versions.node) {
163
+ const [major, minor] = version.split(".").map(Number);
164
+ return (
165
+ major > 24 || (major === 24 && minor >= 5) || (major === 22 && minor >= 21)
166
+ );
167
+ }
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { processAlive } from "../server/runtime/update-manager.js";
4
+
5
+ const ACTIVE_UPDATE = new Set([
6
+ "queued",
7
+ "downloading",
8
+ "checking",
9
+ "stopping",
10
+ "backup",
11
+ "restarting",
12
+ "verifying",
13
+ "restoring",
14
+ ]);
15
+
16
+ function readJob(home) {
17
+ try {
18
+ return JSON.parse(readFileSync(join(home, "runtime", "job.json"), "utf8"));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
25
+
26
+ // Keep the foreground service alive while its update worker gracefully stops
27
+ // the old server. Otherwise systemd can tear down the worker with the old
28
+ // server's cgroup before it has started the replacement.
29
+ export async function updatedServerAfterExit(
30
+ home,
31
+ parentPid,
32
+ { read = () => readJob(home), alive = processAlive, sleep = pause } = {},
33
+ ) {
34
+ while (true) {
35
+ const job = read();
36
+ if (!job || job.parent !== parentPid) return { matched: false, pid: null };
37
+ if (!ACTIVE_UPDATE.has(job.status))
38
+ return { matched: true, pid: alive(job.childPid) ? job.childPid : null };
39
+ if (!alive(job.workerPid))
40
+ return { matched: true, pid: alive(job.childPid) ? job.childPid : null };
41
+ await sleep(250);
42
+ }
43
+ }
44
+
45
+ export async function waitForProcessExit(
46
+ pid,
47
+ { alive = processAlive, sleep = pause } = {},
48
+ ) {
49
+ while (alive(pid)) await sleep(250);
50
+ }