luckytri 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +7 -3
  3. package/README.md +7 -3
  4. package/docs/en/CHANGELOG.md +35 -16
  5. package/docs/en/chat-style.md +28 -9
  6. package/docs/en/guide.md +5 -5
  7. package/docs/en/her-life.md +2 -2
  8. package/docs/en/webui.md +1 -1
  9. package/docs/zh/CHANGELOG.md +35 -16
  10. package/docs/zh/chat-style.md +30 -9
  11. package/docs/zh/guide.md +5 -5
  12. package/docs/zh/her-life.md +2 -2
  13. package/docs/zh/webui.md +1 -1
  14. package/package.json +1 -1
  15. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  16. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  17. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  18. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  19. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  20. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  21. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  22. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  23. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  24. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  25. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  26. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  27. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  28. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  29. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  30. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  31. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  32. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  33. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  34. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  35. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  36. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  37. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  38. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  39. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  40. package/public/app/index.html +5 -6
  41. package/public/guide.en.html +3 -3
  42. package/public/guide.html +3 -3
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/update-instance.js +201 -201
  46. package/server/channels/adapters.js +27 -27
  47. package/server/channels/onebot/adapter.js +288 -288
  48. package/server/channels/qqbot/adapter.js +253 -253
  49. package/server/channels/qqbot/api.js +199 -199
  50. package/server/channels/qqbot/credentials.js +11 -11
  51. package/server/channels/qqbot/gateway.js +274 -274
  52. package/server/channels/qqbot/index.js +389 -389
  53. package/server/channels/segments.js +40 -40
  54. package/server/channels/session-key.js +153 -153
  55. package/server/channels/state.js +42 -42
  56. package/server/core/api.js +564 -564
  57. package/server/core/context-builder.js +288 -288
  58. package/server/core/context-compactor.js +454 -454
  59. package/server/core/conversation-cues.js +385 -325
  60. package/server/core/conversation-grounding.js +304 -0
  61. package/server/core/conversation-manager.js +87 -87
  62. package/server/core/dialogue-context.js +244 -0
  63. package/server/core/dialogue-contract.js +35 -0
  64. package/server/core/feedback.js +8 -8
  65. package/server/core/group-style.js +72 -72
  66. package/server/core/initiative-context.js +68 -66
  67. package/server/core/input-budget.js +32 -32
  68. package/server/core/local-demo.js +98 -98
  69. package/server/core/message-manager.js +118 -118
  70. package/server/core/message-scheduler.js +78 -78
  71. package/server/core/model-json.js +190 -190
  72. package/server/core/model-manager.js +5 -8
  73. package/server/core/model-presets.js +900 -900
  74. package/server/core/name-call.js +29 -29
  75. package/server/core/network.js +157 -157
  76. package/server/core/person-name.js +22 -22
  77. package/server/core/persona-manager.js +359 -269
  78. package/server/core/reader-check.js +62 -0
  79. package/server/core/reply-delivery.js +537 -404
  80. package/server/core/reply-target-resolver.js +50 -50
  81. package/server/core/response-generator.js +92 -50
  82. package/server/core/response-validator.js +53 -32
  83. package/server/core/retired-prompts.js +51 -51
  84. package/server/core/sessions.js +109 -109
  85. package/server/core/speaker-names.js +92 -92
  86. package/server/core/topic-tracker.js +65 -65
  87. package/server/core/turn.js +82 -15
  88. package/server/http.js +18 -18
  89. package/server/i18n/decisions.js +209 -209
  90. package/server/i18n/errors.js +2 -0
  91. package/server/i18n/index.js +144 -144
  92. package/server/i18n/labels.js +6 -10
  93. package/server/i18n/models.js +111 -111
  94. package/server/knowledge/api.js +342 -342
  95. package/server/knowledge/embedding-profile.js +114 -114
  96. package/server/knowledge/index.js +13 -13
  97. package/server/knowledge/retrieval.js +93 -93
  98. package/server/knowledge/schema.js +158 -158
  99. package/server/knowledge/vector-index.js +90 -90
  100. package/server/mind/affect.js +265 -265
  101. package/server/mind/attention.js +200 -200
  102. package/server/mind/bonds.js +565 -565
  103. package/server/mind/clock.js +83 -83
  104. package/server/mind/context-selection.js +16 -4
  105. package/server/mind/continuity.js +562 -562
  106. package/server/mind/conversation-origin.js +175 -175
  107. package/server/mind/days.js +210 -210
  108. package/server/mind/evidence-read.js +45 -45
  109. package/server/mind/expression-grounding.js +21 -0
  110. package/server/mind/faces.js +373 -373
  111. package/server/mind/guard.js +68 -60
  112. package/server/mind/initiative.js +338 -338
  113. package/server/mind/life/activities.js +21 -21
  114. package/server/mind/life/context.js +330 -330
  115. package/server/mind/life/growth.js +261 -261
  116. package/server/mind/life/presence.js +305 -305
  117. package/server/mind/meetings.js +1069 -1069
  118. package/server/mind/memory-policy.js +48 -48
  119. package/server/mind/memory.js +888 -888
  120. package/server/mind/nature.js +358 -358
  121. package/server/mind/novelty.js +19 -19
  122. package/server/mind/own-voice.js +35 -0
  123. package/server/mind/periods.js +118 -118
  124. package/server/mind/reading.js +132 -120
  125. package/server/mind/relationship-context.js +71 -71
  126. package/server/mind/relationships.js +390 -390
  127. package/server/mind/salience.js +201 -201
  128. package/server/mind/schema.js +567 -567
  129. package/server/mind/self.js +496 -484
  130. package/server/mind/thoughts.js +274 -274
  131. package/server/mind/time/agenda.js +242 -242
  132. package/server/mind/time/api.js +200 -200
  133. package/server/mind/time/attention.js +34 -34
  134. package/server/mind/time/availability.js +23 -1
  135. package/server/mind/time/executor.js +4 -2
  136. package/server/mind/time/games.js +20 -11
  137. package/server/mind/time/index.js +9 -2
  138. package/server/mind/time/kinds.js +29 -2
  139. package/server/mind/time/material-relevance.js +19 -0
  140. package/server/mind/time/model-material.js +77 -77
  141. package/server/mind/time/presentation.js +35 -51
  142. package/server/mind/time/reading-input.js +31 -0
  143. package/server/mind/time/schedule.js +83 -83
  144. package/server/mind/time/schema.js +84 -84
  145. package/server/mind/time/search.js +383 -383
  146. package/server/mind/time/sharing.js +482 -472
  147. package/server/mind/time/task-links.js +311 -311
  148. package/server/mind/time/tasks.js +41 -3
  149. package/server/mind/time/works.js +527 -432
  150. package/server/mind/traits.js +191 -191
  151. package/server/mind/view.js +62 -12
  152. package/server/runtime/installation.js +116 -116
  153. package/server/runtime/update-manager.js +140 -140
  154. package/server/storage/data-package.js +298 -298
  155. package/server/storage/data-transfer-api.js +50 -50
  156. package/server/storage/data-transfer-worker.js +18 -18
  157. package/server/storage/data-transfer.js +416 -416
  158. package/server/studio/display-names.js +139 -139
  159. package/server/studio/management.js +102 -102
  160. package/server/studio/updates.js +144 -144
  161. package/server/version.js +10 -10
  162. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  163. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  164. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  165. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  166. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  167. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  168. package/public/app/assets/TimePage-C-guwPk2.css +0 -1
@@ -1,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
+ }