@pab-iprg/huohuo 1.0.0-preview.6 → 1.0.0-preview.7
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.
- package/README.md +20 -0
- package/cli.js +305 -158
- package/package.json +5 -4
- package/release.json +110 -12
package/README.md
CHANGED
|
@@ -11,3 +11,23 @@
|
|
|
11
11
|
显式升级检查旧迁移不可变、追加迁移顺序、执行库格式与 DSH 会话兼容范围,先一致性备份再迁移和验证,成功后切换;不自动恢复旧备份覆盖新数据。默认卸载保留实例、凭据、会话和备份,不提供直接彻底删除业务数据的快捷入口。
|
|
12
12
|
|
|
13
13
|
首版终端前台管理生命周期,Ctrl+C 停止本次实例。退出终端、休眠或关机后不承诺持续运行。模型凭据在现有设置页面填写,安装检查不调用真实模型或通知。
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Windows x64 候选沿用同一入口,使用系统自带 Windows PowerShell/CIM 读取本机状态,自动准备独立 Python 和固定 Windows wheels;不要求预装 Python、编译器、Git Bash、管理员权限或开发者模式。需要兼容的 Node 24.x(至少24.8)和自带 npm。候选构建成功不等于 Windows 实机已通过;实际支持范围以发行清单和验收记录为准。PowerShell 若拦截 npm/npx 的 .ps1 启动脚本,可使用 Node 自带的 `npx.cmd`,无需更改系统执行策略。
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Windows 用户可以直接使用命令提示符(CMD)或 PowerShell,两者调用同一安装器,无需先切换终端。候选发布到 preview 标签后:
|
|
20
|
+
|
|
21
|
+
CMD:
|
|
22
|
+
|
|
23
|
+
```bat
|
|
24
|
+
npx --yes --prefer-online @pab-iprg/huohuo@preview
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
PowerShell:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
npx.cmd --yes --prefer-online @pab-iprg/huohuo@preview
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
默认目录无需手动填写。需要独立试用目录时,CMD 使用 `--home "%USERPROFILE%\.huohuo-windows-trial-01"`,PowerShell 使用 `--home "$env:USERPROFILE\.huohuo-windows-trial-01"`。程序内部使用系统自带 PowerShell 采样,不要求用户用 PowerShell 启动。两种终端的首装、Ctrl+C、再次启动及数据保留均需分别实机验收。
|
package/cli.js
CHANGED
|
@@ -4,28 +4,150 @@
|
|
|
4
4
|
import { open as open3, readFile as readFile9, realpath as realpath5 } from "node:fs/promises";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
6
6
|
import { homedir as homedir2 } from "node:os";
|
|
7
|
-
import { dirname as dirname6, join as
|
|
7
|
+
import { dirname as dirname6, join as join9, resolve as resolve5 } from "node:path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9
9
|
|
|
10
|
+
// distribution/processes.ts
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
function interruptProcessTree(child, signal) {
|
|
14
|
+
if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
|
|
15
|
+
if (process.platform === "win32") {
|
|
16
|
+
const executable = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe");
|
|
17
|
+
execFile(executable, ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, timeout: 5e3 }, (error) => {
|
|
18
|
+
if (error && child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(-child.pid, signal);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
// distribution/bootstrap/install.ts
|
|
11
29
|
import { readFile as readFile4, mkdir as mkdir3, access, rename as rename3, rm as rm2, realpath as realpath3, readdir } from "node:fs/promises";
|
|
12
30
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
13
|
-
import { join as
|
|
31
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
14
32
|
import { release as osRelease } from "node:os";
|
|
15
|
-
import { spawn, execFile as
|
|
16
|
-
import { promisify as
|
|
33
|
+
import { spawn, execFile as execFile5 } from "node:child_process";
|
|
34
|
+
import { promisify as promisify4 } from "node:util";
|
|
17
35
|
|
|
18
36
|
// distribution/bootstrap/state.ts
|
|
19
37
|
import { open, rename, unlink, readFile, mkdir } from "node:fs/promises";
|
|
20
38
|
import { dirname } from "node:path";
|
|
21
39
|
import { randomUUID } from "node:crypto";
|
|
22
|
-
import { execFile } from "node:child_process";
|
|
40
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
41
|
+
import { promisify as promisify2 } from "node:util";
|
|
42
|
+
|
|
43
|
+
// distribution/windows.ts
|
|
44
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
45
|
+
import { win32 } from "node:path";
|
|
23
46
|
import { promisify } from "node:util";
|
|
24
|
-
var execute = promisify(
|
|
47
|
+
var execute = promisify(execFile2);
|
|
48
|
+
var MAX_OUTPUT = 8 * 1024 * 1024;
|
|
49
|
+
var prelude = "$ErrorActionPreference='Stop';[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);";
|
|
50
|
+
var sidPattern = /^S-\d+(?:-\d+){1,15}$/;
|
|
51
|
+
async function powershell(script) {
|
|
52
|
+
if (process.platform !== "win32") throw new Error("Windows \u63A2\u9488\u53EA\u80FD\u5728 Windows \u672C\u673A\u6267\u884C");
|
|
53
|
+
const root = process.env.SystemRoot ?? "C:\\Windows";
|
|
54
|
+
if (!win32.isAbsolute(root)) throw new Error("Windows \u7CFB\u7EDF\u76EE\u5F55\u65E0\u6548");
|
|
55
|
+
const executable = win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
56
|
+
const { stdout } = await execute(
|
|
57
|
+
executable,
|
|
58
|
+
[
|
|
59
|
+
"-NoLogo",
|
|
60
|
+
"-NoProfile",
|
|
61
|
+
"-NonInteractive",
|
|
62
|
+
"-EncodedCommand",
|
|
63
|
+
Buffer.from(prelude + script, "utf16le").toString("base64")
|
|
64
|
+
],
|
|
65
|
+
{ timeout: 5e3, maxBuffer: MAX_OUTPUT, windowsHide: true, encoding: "utf8" }
|
|
66
|
+
);
|
|
67
|
+
return stdout;
|
|
68
|
+
}
|
|
69
|
+
function document(stdout) {
|
|
70
|
+
if (Buffer.byteLength(stdout, "utf8") > MAX_OUTPUT) throw new Error("Windows \u63A2\u9488\u54CD\u5E94\u8D85\u9650");
|
|
71
|
+
const value = JSON.parse(stdout.trim());
|
|
72
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.format !== 1) throw new Error("Windows \u63A2\u9488\u54CD\u5E94\u65E0\u6548");
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function sid(value) {
|
|
76
|
+
if (typeof value !== "string" || value.length > 184 || !sidPattern.test(value)) throw new Error("Windows \u7528\u6237\u8EAB\u4EFD\u672A\u77E5");
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function integer(value, label) {
|
|
80
|
+
if (typeof value !== "number" && !(typeof value === "string" && /^(0|[1-9]\d*)$/.test(value))) throw new Error(`Windows ${label}\u63A2\u9488\u65E0\u6548`);
|
|
81
|
+
const n = Number(value);
|
|
82
|
+
if (!Number.isSafeInteger(n) || n < 0) throw new Error(`Windows ${label}\u63A2\u9488\u65E0\u6548`);
|
|
83
|
+
return n;
|
|
84
|
+
}
|
|
85
|
+
function parseWindowsUserIdentity(stdout) {
|
|
86
|
+
return sid(document(stdout).sid);
|
|
87
|
+
}
|
|
88
|
+
async function windowsUserIdentity() {
|
|
89
|
+
return parseWindowsUserIdentity(await powershell("@{format=1;sid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value}|ConvertTo-Json -Compress"));
|
|
90
|
+
}
|
|
91
|
+
function parseWindowsProcessIdentity(stdout) {
|
|
92
|
+
const value = document(stdout);
|
|
93
|
+
if (value.exists === false && Object.keys(value).every((key) => ["format", "exists"].includes(key))) return null;
|
|
94
|
+
if (value.exists !== true || typeof value.created !== "string" || !/^[1-9]\d{15,18}$/.test(value.created)) throw new Error("Windows \u8FDB\u7A0B\u542F\u52A8\u8EAB\u4EFD\u672A\u77E5");
|
|
95
|
+
return `${sid(value.sid)}:${value.created}`;
|
|
96
|
+
}
|
|
97
|
+
async function windowsProcessIdentity(pid) {
|
|
98
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid > 4294967295) throw new Error("Windows \u8FDB\u7A0B PID \u65E0\u6548");
|
|
99
|
+
return parseWindowsProcessIdentity(await powershell(`$p=Get-CimInstance -ClassName Win32_Process -Filter 'ProcessId=${pid}' -ErrorAction Stop;
|
|
100
|
+
if($null -eq $p){@{format=1;exists=$false}|ConvertTo-Json -Compress}else{
|
|
101
|
+
if($null -eq $p.CreationDate){throw 'Process creation time unavailable'};
|
|
102
|
+
$owner=Invoke-CimMethod -InputObject $p -MethodName GetOwnerSid -ErrorAction Stop;
|
|
103
|
+
if($owner.ReturnValue -ne 0 -or [string]::IsNullOrEmpty($owner.Sid)){throw 'Process owner unavailable'};
|
|
104
|
+
@{format=1;exists=$true;sid=$owner.Sid;created=$p.CreationDate.ToUniversalTime().Ticks.ToString([Globalization.CultureInfo]::InvariantCulture)}|ConvertTo-Json -Compress
|
|
105
|
+
}`));
|
|
106
|
+
}
|
|
107
|
+
function parseWindowsResourceSnapshot(stdout) {
|
|
108
|
+
const value = document(stdout);
|
|
109
|
+
const totalBytes = integer(value.totalKiB, "\u5185\u5B58\u5BB9\u91CF") * 1024;
|
|
110
|
+
const availableBytes = integer(value.availableKiB, "\u53EF\u7528\u5185\u5B58") * 1024;
|
|
111
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes <= 0 || !Number.isSafeInteger(availableBytes) || availableBytes > totalBytes || !Array.isArray(value.processes) || !value.processes.length || value.processes.length > 1e5) throw new Error("Windows \u8D44\u6E90\u5FEB\u7167\u65E0\u6548");
|
|
112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
113
|
+
const processes = value.processes.map((row) => {
|
|
114
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) throw new Error("Windows \u8FDB\u7A0B\u5FEB\u7167\u65E0\u6548");
|
|
115
|
+
const pid = integer(row.pid, "PID"), parent = integer(row.parent, "\u7236\u8FDB\u7A0B"), rss = integer(row.rss, "\u5DE5\u4F5C\u96C6");
|
|
116
|
+
if (pid > 4294967295 || parent > 4294967295 || seen.has(pid) || pid !== 0 && pid === parent) throw new Error("Windows \u8FDB\u7A0B\u5FEB\u7167\u65E0\u6548");
|
|
117
|
+
seen.add(pid);
|
|
118
|
+
return { pid, parent, rss };
|
|
119
|
+
});
|
|
120
|
+
return { totalBytes, availableBytes, processes };
|
|
121
|
+
}
|
|
122
|
+
function windowsMemoryPressure(availableBytes, totalBytes) {
|
|
123
|
+
if (!Number.isSafeInteger(availableBytes) || availableBytes < 0 || !Number.isSafeInteger(totalBytes) || totalBytes <= 0 || availableBytes > totalBytes) return "unknown";
|
|
124
|
+
const ratio = availableBytes / totalBytes;
|
|
125
|
+
return ratio <= 0.02 ? "critical" : ratio <= 0.05 ? "high" : "normal";
|
|
126
|
+
}
|
|
127
|
+
var cached;
|
|
128
|
+
async function windowsResourceSnapshot() {
|
|
129
|
+
const now = performance.now();
|
|
130
|
+
if (!cached || now - cached.sampledAt >= 1e3) {
|
|
131
|
+
const pending = powershell(`$memory=Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop;
|
|
132
|
+
$rows=@(Get-CimInstance -ClassName Win32_Process -ErrorAction Stop|ForEach-Object {@{pid=[string]$_.ProcessId;parent=[string]$_.ParentProcessId;rss=[string]$_.WorkingSetSize}});
|
|
133
|
+
@{format=1;totalKiB=[string]$memory.TotalVisibleMemorySize;availableKiB=[string]$memory.FreePhysicalMemory;processes=$rows}|ConvertTo-Json -Compress -Depth 4`).then(parseWindowsResourceSnapshot);
|
|
134
|
+
const entry = { sampledAt: now, pending };
|
|
135
|
+
cached = entry;
|
|
136
|
+
void pending.catch(() => {
|
|
137
|
+
if (cached === entry) cached = void 0;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return structuredClone(await cached.pending);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// distribution/bootstrap/state.ts
|
|
144
|
+
var execute2 = promisify2(execFile3);
|
|
145
|
+
var lockUser = () => process.platform === "win32" ? windowsUserIdentity() : Promise.resolve(process.getuid?.());
|
|
25
146
|
async function processIdentity(pid) {
|
|
147
|
+
if (process.platform === "win32") return windowsProcessIdentity(pid);
|
|
26
148
|
if (!Number.isSafeInteger(pid) || pid <= 0 || !["darwin", "linux"].includes(process.platform)) throw new Error("\u8FDB\u7A0B\u542F\u52A8\u8EAB\u4EFD\u63A2\u9488\u4E0D\u652F\u6301\u6216\u65E0\u6548");
|
|
27
149
|
try {
|
|
28
|
-
const { stdout } = await
|
|
150
|
+
const { stdout } = await execute2("ps", ["-p", String(pid), "-o", "lstart=,uid="], { timeout: 2e3 });
|
|
29
151
|
if (!stdout.trim()) throw new Error("\u8FDB\u7A0B\u542F\u52A8\u8EAB\u4EFD\u63A2\u9488\u8FD4\u56DE\u7A7A\u503C");
|
|
30
152
|
return stdout.trim().replace(/\s+/g, " ");
|
|
31
153
|
} catch (e) {
|
|
@@ -42,7 +164,7 @@ async function processIdentity(pid) {
|
|
|
42
164
|
async function isLockOwnerDead(path, token) {
|
|
43
165
|
try {
|
|
44
166
|
const previous = JSON.parse(await readFile(path, "utf8"));
|
|
45
|
-
return previous.format === 1 && previous.path === path && previous.uid ===
|
|
167
|
+
return previous.format === 1 && previous.path === path && previous.uid === await lockUser() && typeof previous.processStart === "string" && !!previous.processStart && typeof previous.token === "string" && /^[a-f0-9-]{36}$/.test(previous.token) && (token === void 0 || previous.token === token) && Number.isSafeInteger(previous.pid) && await processIdentity(previous.pid) === null;
|
|
46
168
|
} catch {
|
|
47
169
|
return false;
|
|
48
170
|
}
|
|
@@ -62,11 +184,13 @@ async function atomicJSON(path, value) {
|
|
|
62
184
|
}
|
|
63
185
|
try {
|
|
64
186
|
await rename(pending, path);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
187
|
+
if (process.platform !== "win32") {
|
|
188
|
+
const directory = await open(dirname(path), "r");
|
|
189
|
+
try {
|
|
190
|
+
await directory.sync();
|
|
191
|
+
} finally {
|
|
192
|
+
await directory.close();
|
|
193
|
+
}
|
|
70
194
|
}
|
|
71
195
|
} finally {
|
|
72
196
|
await unlink(pending).catch(() => {
|
|
@@ -100,7 +224,7 @@ async function acquireLock(path) {
|
|
|
100
224
|
try {
|
|
101
225
|
const processStart = await processIdentity(process.pid);
|
|
102
226
|
if (!processStart) throw new Error("\u5F53\u524D\u8FDB\u7A0B\u542F\u52A8\u8EAB\u4EFD\u672A\u77E5");
|
|
103
|
-
await lock.writeFile(JSON.stringify({ format: 1, path, uid:
|
|
227
|
+
await lock.writeFile(JSON.stringify({ format: 1, path, uid: await lockUser(), pid: process.pid, processStart, startedAt: (/* @__PURE__ */ new Date()).toISOString(), token }));
|
|
104
228
|
await lock.sync();
|
|
105
229
|
} catch (error) {
|
|
106
230
|
await lock.close();
|
|
@@ -129,7 +253,7 @@ async function withLock(path, action) {
|
|
|
129
253
|
// distribution/bootstrap/release.ts
|
|
130
254
|
var version = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/;
|
|
131
255
|
var hash = /^[a-f0-9]{64}$/;
|
|
132
|
-
function
|
|
256
|
+
function integer2(n) {
|
|
133
257
|
return Number.isSafeInteger(n) && Number(n) >= 0;
|
|
134
258
|
}
|
|
135
259
|
function httpsURL(value) {
|
|
@@ -142,14 +266,14 @@ function httpsURL(value) {
|
|
|
142
266
|
}
|
|
143
267
|
}
|
|
144
268
|
function artifact(a) {
|
|
145
|
-
if (!a || !hash.test(a.sha256) || !
|
|
269
|
+
if (!a || !hash.test(a.sha256) || !integer2(a.downloadBytes) || !integer2(a.expandedBytes) || !a.downloadBytes || !a.expandedBytes) return false;
|
|
146
270
|
if (a.redirectHosts !== void 0 && (!Array.isArray(a.redirectHosts) || a.redirectHosts.length > 4 || !a.redirectHosts.every((s) => typeof s === "string" && /^[a-z0-9.-]+$/.test(s)))) return false;
|
|
147
271
|
if (a.bundled !== void 0) return a.bundled === true && a.url === `https://bundled.invalid/${a.sha256}.tgz` && a.redirectHosts === void 0 && a.mirrors === void 0;
|
|
148
272
|
return httpsURL(a.url) && (a.mirrors === void 0 || Array.isArray(a.mirrors) && a.mirrors.length <= 3 && a.mirrors.every(httpsURL));
|
|
149
273
|
}
|
|
150
274
|
function validateRelease(input) {
|
|
151
275
|
const r = input;
|
|
152
|
-
if (!r || r.format !== 1 || r.bootstrapFormat !== 1 || !version.test(r.version) || !
|
|
276
|
+
if (!r || r.format !== 1 || r.bootstrapFormat !== 1 || !version.test(r.version) || !integer2(r.nodeMajor) || r.nodeMajor < 24 || !integer2(r.npmMajor) || r.npmMajor < 11 || !r.platform?.minimumOsRelease || !["darwin", "linux", "win32"].includes(r.platform.os) || !["arm64", "x64"].includes(r.platform.arch) || r.platform.libc !== void 0 && (r.platform.os !== "linux" || r.platform.libc !== "glibc") || r.assetPackage !== void 0 && (r.assetPackage.name !== `@pab-iprg/huohuo-assets-${r.platform.os}-${r.platform.arch}` || !version.test(r.assetPackage.version) || !r.runtime?.bundled || !r.pythonDependencies?.bundled) || !artifact(r.runtime) || r.runtime.name !== "@huohuo/research-agent" || r.runtime.version !== r.version || !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(r.runtime.integrity) || !artifact(r.python) || !artifact(r.pythonDependencies) || !r.python.build || !r.python.license || r.pythonDependencies.upstreamWheelCount !== void 0 && !integer2(r.pythonDependencies.upstreamWheelCount) || !r.python.executable || r.python.executable.startsWith("/") || r.python.executable.split(/[\\/]/).some((p) => p === ".." || !p) || !version.test(r.dsh?.version) || !hash.test(r.dsh?.assemblySha256) || !hash.test(r.requirementsSha256) || !hash.test(r.profilesSha256) || !r.budget || ![
|
|
153
277
|
"minimumEffectiveMemoryBytes",
|
|
154
278
|
"startupHeadroomBytes",
|
|
155
279
|
"heavyJobHeadroomBytes",
|
|
@@ -161,7 +285,7 @@ function validateRelease(input) {
|
|
|
161
285
|
"processTreeBudgetBytes",
|
|
162
286
|
"sampleIntervalMs",
|
|
163
287
|
"sampleMaxAgeMs"
|
|
164
|
-
].every((k) =>
|
|
288
|
+
].every((k) => integer2(r.budget[k])) || r.budget.observedInstallRssBytes !== void 0 && (!integer2(r.budget.observedInstallRssBytes) || r.budget.observedInstallRssBytes <= 0) || r.budget.startupHeadroomBytes <= 0 || r.budget.minimumEffectiveMemoryBytes <= 0 || r.budget.diskReserveBytes <= 0 || r.budget.recoverySamples < 2 || r.budget.heavyJobHeadroomBytes < r.budget.startupHeadroomBytes || r.budget.processTreeBudgetBytes <= 0 || r.budget.sampleIntervalMs < 1e3 || r.budget.sampleMaxAgeMs < r.budget.sampleIntervalMs * 2 || r.budget.sampleMaxAgeMs > 3e4 || !r.schema || !integer2(r.schema.minimum) || !integer2(r.schema.maximum) || r.schema.minimum > r.schema.maximum) {
|
|
165
289
|
throw new Error("\u53D1\u884C\u6E05\u5355\u65E0\u6548\u6216\u5C1A\u672A\u5B8C\u6210\u5E73\u53F0\u3001\u6458\u8981\u4E0E\u8D44\u6E90\u6D4B\u91CF\uFF1B\u4E0D\u80FD\u5B89\u88C5");
|
|
166
290
|
}
|
|
167
291
|
if (!hash.test(r.schema.migrationsSha256)) throw new Error("\u7F3A\u5C11\u6570\u636E\u5E93\u8FC1\u79FB\u88C5\u914D\u6458\u8981");
|
|
@@ -172,7 +296,7 @@ function validateRelease(input) {
|
|
|
172
296
|
// distribution/bootstrap/artifact.ts
|
|
173
297
|
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
174
298
|
import { createReadStream } from "node:fs";
|
|
175
|
-
import { mkdir as mkdir2, open as open2, rename as rename2, rm, realpath, symlink, link, lstat } from "node:fs/promises";
|
|
299
|
+
import { mkdir as mkdir2, open as open2, rename as rename2, rm, realpath, symlink, link, lstat, copyFile } from "node:fs/promises";
|
|
176
300
|
import { dirname as dirname2, resolve, sep, posix } from "node:path";
|
|
177
301
|
import { createGunzip } from "node:zlib";
|
|
178
302
|
async function verifyArtifact(path, artifact2) {
|
|
@@ -286,8 +410,12 @@ async function transfer(artifact2, path, source, fetcher, onBytes) {
|
|
|
286
410
|
await rm(pending, { force: true });
|
|
287
411
|
}
|
|
288
412
|
}
|
|
289
|
-
function
|
|
413
|
+
function validateArchiveName(path, platform = process.platform) {
|
|
290
414
|
if (!path || path.includes("\\") || path.includes("\0") || path.startsWith("/") || /^[A-Za-z]:/.test(path) || path.split("/").includes("..")) throw new Error("\u538B\u7F29\u5305\u8DEF\u5F84\u8D8A\u754C");
|
|
415
|
+
if (platform === "win32" && path.split("/").some((part) => part && part !== "." && (/[<>:"|?*\x00-\x1f]/.test(part) || /[. ]$/.test(part) || /^(?:con|prn|aux|nul|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])$/i.test(part.split(".")[0].trimEnd())))) throw new Error("\u538B\u7F29\u5305\u542B Windows \u4E0D\u652F\u6301\u7684\u8DEF\u5F84\u540D\u79F0");
|
|
416
|
+
}
|
|
417
|
+
function safePath(root, path) {
|
|
418
|
+
validateArchiveName(path);
|
|
291
419
|
const target = resolve(root, path);
|
|
292
420
|
if (target !== root && !target.startsWith(root + sep)) throw new Error("\u538B\u7F29\u5305\u8DEF\u5F84\u8D8A\u754C");
|
|
293
421
|
return target;
|
|
@@ -385,7 +513,8 @@ async function extractArchive(archive, root, expandedLimit) {
|
|
|
385
513
|
throw e;
|
|
386
514
|
}
|
|
387
515
|
if (!source.startsWith(root + sep) || !(await lstat(source)).isFile()) throw new Error("\u94FE\u63A5\u5FC5\u987B\u6307\u5411\u5305\u5185\u666E\u901A\u6587\u4EF6");
|
|
388
|
-
if (
|
|
516
|
+
if (process.platform === "win32") await copyFile(source, item.target, 1);
|
|
517
|
+
else if (item.symbolic) await symlink(item.text, item.target);
|
|
389
518
|
else await link(source, item.target);
|
|
390
519
|
}
|
|
391
520
|
if (later.length === pending.length) throw new Error("\u538B\u7F29\u5305\u94FE\u63A5\u7F3A\u5931\u6216\u5FAA\u73AF");
|
|
@@ -401,9 +530,9 @@ import { availableParallelism, totalmem } from "node:os";
|
|
|
401
530
|
import { availableMemory, constrainedMemory } from "node:process";
|
|
402
531
|
import { readFile as readFile3, realpath as realpath2, stat, statfs } from "node:fs/promises";
|
|
403
532
|
import { dirname as dirname3, resolve as resolve2, sep as sep2 } from "node:path";
|
|
404
|
-
import { execFile as
|
|
405
|
-
import { promisify as
|
|
406
|
-
var
|
|
533
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
534
|
+
import { promisify as promisify3 } from "node:util";
|
|
535
|
+
var execute3 = promisify3(execFile4);
|
|
407
536
|
async function existingParent(path) {
|
|
408
537
|
let current = resolve2(path);
|
|
409
538
|
while (true) {
|
|
@@ -449,13 +578,13 @@ async function sampleResources(paths) {
|
|
|
449
578
|
if (!(effectiveMemoryBytes > 0)) effectiveMemoryBytes = null;
|
|
450
579
|
if (process.platform === "darwin") {
|
|
451
580
|
try {
|
|
452
|
-
const { stdout } = await
|
|
581
|
+
const { stdout } = await execute3("/usr/sbin/sysctl", ["-n", "kern.memorystatus_vm_pressure_level"], { timeout: 2e3 });
|
|
453
582
|
pressure = { 1: "normal", 2: "high", 4: "critical" }[Number(stdout.trim())] ?? "unknown";
|
|
454
583
|
} catch {
|
|
455
584
|
}
|
|
456
585
|
if (pressure === "normal" && availableMemoryBytes !== null && effectiveMemoryBytes !== null) {
|
|
457
586
|
try {
|
|
458
|
-
const { stdout } = await
|
|
587
|
+
const { stdout } = await execute3("/usr/bin/vm_stat", [], { timeout: 2e3 });
|
|
459
588
|
availableMemoryBytes = darwinAvailableMemory(availableMemoryBytes, effectiveMemoryBytes, pressure, stdout, limit > 0);
|
|
460
589
|
} catch {
|
|
461
590
|
}
|
|
@@ -470,6 +599,16 @@ async function sampleResources(paths) {
|
|
|
470
599
|
} catch {
|
|
471
600
|
}
|
|
472
601
|
}
|
|
602
|
+
if (process.platform === "win32") {
|
|
603
|
+
try {
|
|
604
|
+
const snapshot = await windowsResourceSnapshot();
|
|
605
|
+
effectiveMemoryBytes = Math.min(effectiveMemoryBytes ?? Infinity, snapshot.totalBytes);
|
|
606
|
+
availableMemoryBytes = availableMemoryBytes === null ? null : Math.min(availableMemoryBytes, snapshot.availableBytes);
|
|
607
|
+
pressure = windowsMemoryPressure(snapshot.availableBytes, snapshot.totalBytes);
|
|
608
|
+
} catch {
|
|
609
|
+
pressure = "unknown";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
473
612
|
const disks = [];
|
|
474
613
|
for (const path of [...new Set(paths.map((p) => resolve2(p)))]) {
|
|
475
614
|
try {
|
|
@@ -544,10 +683,10 @@ function evaluateResources(sample, policy, costs, phase) {
|
|
|
544
683
|
|
|
545
684
|
// runtime/npm.mjs
|
|
546
685
|
import { existsSync } from "node:fs";
|
|
547
|
-
import { dirname as dirname4, join as
|
|
686
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
548
687
|
function npmCommand() {
|
|
549
688
|
if (process.platform !== "win32") return { command: "npm", prefix: [] };
|
|
550
|
-
const cli =
|
|
689
|
+
const cli = join3(dirname4(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
|
|
551
690
|
if (existsSync(cli)) return { command: process.execPath, prefix: [cli] };
|
|
552
691
|
return { command: process.env.ComSpec ?? "cmd.exe", prefix: ["/d", "/s", "/c", "npm"] };
|
|
553
692
|
}
|
|
@@ -602,29 +741,33 @@ async function ownedDirectory(path, owner) {
|
|
|
602
741
|
await mkdir3(resolve3(path, ".."), { recursive: true, mode: 448 });
|
|
603
742
|
try {
|
|
604
743
|
await mkdir3(path, { mode: 448 });
|
|
605
|
-
await atomicJSON(
|
|
744
|
+
await atomicJSON(join4(path, ".huohuo-owner.json"), { format: 1, owner });
|
|
606
745
|
} catch (e) {
|
|
607
746
|
if (e.code !== "EEXIST") throw e;
|
|
608
|
-
const marker = JSON.parse(await readFile4(
|
|
747
|
+
const marker = JSON.parse(await readFile4(join4(path, ".huohuo-owner.json"), "utf8"));
|
|
609
748
|
if (marker.format !== 1 || marker.owner !== owner) throw new Error("\u76EE\u5F55\u5F52\u5C5E\u65E0\u6CD5\u6838\u9A8C\uFF1B\u672A\u63A5\u7BA1\u6216\u8986\u76D6");
|
|
610
749
|
}
|
|
611
750
|
}
|
|
612
|
-
async function run(command, args, cwd, env = process.env) {
|
|
751
|
+
async function run(command, args, cwd, env = process.env, onInterrupt) {
|
|
613
752
|
await new Promise((done, fail) => {
|
|
614
753
|
const child = spawn(command, args, { cwd, env, stdio: "inherit", detached: process.platform !== "win32" });
|
|
615
754
|
let interrupted = false;
|
|
616
755
|
let timer;
|
|
617
756
|
const kill = (signal) => {
|
|
618
|
-
|
|
619
|
-
try {
|
|
620
|
-
process.kill(process.platform === "win32" ? child.pid : -child.pid, signal);
|
|
621
|
-
} catch {
|
|
622
|
-
}
|
|
757
|
+
interruptProcessTree(child, signal);
|
|
623
758
|
};
|
|
624
759
|
const stop = () => {
|
|
760
|
+
if (interrupted) return;
|
|
625
761
|
interrupted = true;
|
|
626
|
-
|
|
627
|
-
|
|
762
|
+
if (process.platform === "win32" && onInterrupt) {
|
|
763
|
+
void onInterrupt().then((stopping) => {
|
|
764
|
+
if (!stopping) kill("SIGTERM");
|
|
765
|
+
}, () => kill("SIGTERM"));
|
|
766
|
+
timer = setTimeout(() => kill("SIGKILL"), 8e3);
|
|
767
|
+
} else {
|
|
768
|
+
kill("SIGTERM");
|
|
769
|
+
timer = setTimeout(() => kill("SIGKILL"), 3e3);
|
|
770
|
+
}
|
|
628
771
|
};
|
|
629
772
|
process.on("SIGINT", stop);
|
|
630
773
|
process.on("SIGTERM", stop);
|
|
@@ -639,14 +782,14 @@ async function run(command, args, cwd, env = process.env) {
|
|
|
639
782
|
});
|
|
640
783
|
child.once("close", (code) => {
|
|
641
784
|
cleanup();
|
|
642
|
-
code === 0 && !interrupted ? done() : fail(Object.assign(new Error("\u672C\u673A\u5B89\u88C5\u5B50\u8FDB\u7A0B\u5931\u8D25\u6216\u4E2D\u6B62\uFF0C\u4FDD\u7559\u539F\u9636\u6BB5\u4E0E\u5B9E\u4F8B"), { code: interrupted ? "INTERRUPTED" : `CHILD_${code}` }));
|
|
785
|
+
code === 0 && (!interrupted || process.platform === "win32" && onInterrupt !== void 0) ? done() : fail(Object.assign(new Error("\u672C\u673A\u5B89\u88C5\u5B50\u8FDB\u7A0B\u5931\u8D25\u6216\u4E2D\u6B62\uFF0C\u4FDD\u7559\u539F\u9636\u6BB5\u4E0E\u5B9E\u4F8B"), { code: interrupted ? "INTERRUPTED" : `CHILD_${code}` }));
|
|
643
786
|
});
|
|
644
787
|
});
|
|
645
788
|
}
|
|
646
789
|
async function readInstallation(home, recordFile = "installation.json") {
|
|
647
790
|
let record;
|
|
648
791
|
try {
|
|
649
|
-
record = JSON.parse(await readFile4(
|
|
792
|
+
record = JSON.parse(await readFile4(join4(home, recordFile), "utf8"));
|
|
650
793
|
} catch (e) {
|
|
651
794
|
if (e.code === "ENOENT") return;
|
|
652
795
|
throw new Error("\u5B89\u88C5\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u672A\u91CD\u5EFA\u5B9E\u4F8B", { cause: e });
|
|
@@ -658,15 +801,15 @@ async function readInstallation(home, recordFile = "installation.json") {
|
|
|
658
801
|
}
|
|
659
802
|
function installationPaths(home, release) {
|
|
660
803
|
const id = `${release.version}-${digest(release).slice(0, 16)}`;
|
|
661
|
-
const directory =
|
|
804
|
+
const directory = join4(home, "releases", id);
|
|
662
805
|
const dependencyBundle = release.pythonDependencies.bundled ? `-${release.pythonDependencies.sha256.slice(0, 16)}` : "";
|
|
663
806
|
return {
|
|
664
807
|
directory,
|
|
665
|
-
runtime:
|
|
666
|
-
python:
|
|
667
|
-
environment:
|
|
668
|
-
archive:
|
|
669
|
-
pythonArchive:
|
|
808
|
+
runtime: join4(directory, "node_modules/@huohuo/research-agent"),
|
|
809
|
+
python: join4(home, "runtimes/python", release.python.sha256),
|
|
810
|
+
environment: join4(home, "environments", `${release.python.sha256.slice(0, 16)}-${release.requirementsSha256.slice(0, 16)}${dependencyBundle}`),
|
|
811
|
+
archive: join4(home, "cache", release.runtime.sha256 + ".tgz"),
|
|
812
|
+
pythonArchive: join4(home, "cache", release.python.sha256 + ".tgz")
|
|
670
813
|
};
|
|
671
814
|
}
|
|
672
815
|
function versionAtLeast(actual, minimum) {
|
|
@@ -688,20 +831,20 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
688
831
|
home = resolve3(home);
|
|
689
832
|
await mkdir3(home, { recursive: true, mode: 448 });
|
|
690
833
|
home = await realpath3(home);
|
|
691
|
-
return withLock(
|
|
834
|
+
return withLock(join4(home, "state/install.lock"), async () => {
|
|
692
835
|
if (!programsOnly) {
|
|
693
836
|
try {
|
|
694
|
-
await access(
|
|
837
|
+
await access(join4(home, "state/default-run.lock"));
|
|
695
838
|
throw new Error("\u5B9E\u4F8B\u4ECD\u6709\u8FD0\u884C\u9501\uFF1B\u5148\u6B63\u5E38\u505C\u6B62\u81EA\u5DF1\u7684\u5B9E\u4F8B\u518D\u4FEE\u590D\u7A0B\u5E8F");
|
|
696
839
|
} catch (e) {
|
|
697
840
|
if (e.code !== "ENOENT") throw e;
|
|
698
841
|
}
|
|
699
842
|
}
|
|
700
843
|
const recordFile = programsOnly ? "state/upgrade-target.json" : "installation.json";
|
|
701
|
-
const recordPath =
|
|
844
|
+
const recordPath = join4(home, recordFile);
|
|
702
845
|
let record = await readInstallation(home, recordFile);
|
|
703
846
|
if (!programsOnly && !record && (await readdir(home)).some((name) => name !== "state")) throw new Error("\u76EE\u5F55\u542B\u65E0\u5B89\u88C5\u8BB0\u5F55\u7684\u65E2\u6709\u5185\u5BB9\uFF1B\u8BF7\u6838\u67E5\u5F52\u5C5E\u6216\u9009\u62E9\u7A7A\u76EE\u5F55");
|
|
704
|
-
if (!programsOnly && !record && (await readdir(
|
|
847
|
+
if (!programsOnly && !record && (await readdir(join4(home, "state"))).some((name) => name !== "install.lock")) throw new Error("\u76EE\u5F55\u542B\u65E0\u6CD5\u786E\u8BA4\u5F52\u5C5E\u7684\u72B6\u6001\uFF1B\u672A\u91CD\u5EFA\u5B89\u88C5");
|
|
705
848
|
if (programsOnly && (await readInstallation(home))?.status !== "ready") throw new Error("\u5F53\u524D\u5B89\u88C5\u4E0D\u5B8C\u6574\uFF0C\u5148\u4FEE\u590D\u539F\u7248\u672C");
|
|
706
849
|
const bundleDirectory = hooks.bundleDirectory;
|
|
707
850
|
if (!programsOnly && record && bundleDirectory && candidate && ["installing", "blocked", "failed"].includes(record.status) && ["preflight", "download"].includes(record.stage) && Object.keys(record.stages).every((stage2) => stage2 === "preflight" || stage2 === "download")) {
|
|
@@ -718,12 +861,12 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
718
861
|
const oldAssets = bundled(record.release);
|
|
719
862
|
const available = await Promise.all(oldAssets.map(async (artifact2) => {
|
|
720
863
|
const file = artifact2.sha256 + ".tgz";
|
|
721
|
-
return await hasFile(
|
|
864
|
+
return await hasFile(join4(bundleDirectory, file)) || await hasFile(join4(home, "cache", file));
|
|
722
865
|
}));
|
|
723
866
|
if (available.includes(false)) {
|
|
724
867
|
const next = validateRelease(candidate);
|
|
725
868
|
const nextAssets = bundled(next);
|
|
726
|
-
if (next.runtime.bundled && next.version === record.release.version && (await Promise.all(nextAssets.map((artifact2) => hasFile(
|
|
869
|
+
if (next.runtime.bundled && next.version === record.release.version && (await Promise.all(nextAssets.map((artifact2) => hasFile(join4(bundleDirectory, artifact2.sha256 + ".tgz"))))).every(Boolean)) {
|
|
727
870
|
record = void 0;
|
|
728
871
|
hooks.progress?.("resume-current-package");
|
|
729
872
|
}
|
|
@@ -737,12 +880,12 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
737
880
|
record ??= { format: 1, release, releaseDigest: digest(release), home, status: "installing", stage: "preflight", stages: {} };
|
|
738
881
|
await atomicJSON(recordPath, record);
|
|
739
882
|
const paths = installationPaths(home, release);
|
|
740
|
-
const
|
|
883
|
+
const execute5 = hooks.run ?? run;
|
|
741
884
|
const sample = hooks.sample ?? sampleResources;
|
|
742
885
|
const files = [
|
|
743
886
|
{ name: "\u706B\u706B\u8FD0\u884C\u5305", artifact: release.runtime, path: paths.archive },
|
|
744
887
|
{ name: "Python \u8FD0\u884C\u73AF\u5883", artifact: release.python, path: paths.pythonArchive },
|
|
745
|
-
{ name: "Python \u4F9D\u8D56\u6E05\u5355\u6216\u79BB\u7EBF\u5305", artifact: release.pythonDependencies, path:
|
|
888
|
+
{ name: "Python \u4F9D\u8D56\u6E05\u5355\u6216\u79BB\u7EBF\u5305", artifact: release.pythonDependencies, path: join4(home, "cache", release.pythonDependencies.sha256 + ".tgz") }
|
|
746
889
|
];
|
|
747
890
|
const withActivity = async (label, action) => {
|
|
748
891
|
hooks.progress?.(label);
|
|
@@ -753,7 +896,7 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
753
896
|
if (timer) clearInterval(timer);
|
|
754
897
|
}
|
|
755
898
|
};
|
|
756
|
-
const
|
|
899
|
+
const cached2 = async (path, artifact2) => {
|
|
757
900
|
try {
|
|
758
901
|
await verifyArtifact(path, artifact2);
|
|
759
902
|
return true;
|
|
@@ -764,22 +907,22 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
764
907
|
};
|
|
765
908
|
const installed = async () => {
|
|
766
909
|
try {
|
|
767
|
-
const owner = JSON.parse(await readFile4(
|
|
768
|
-
await access(
|
|
910
|
+
const owner = JSON.parse(await readFile4(join4(paths.python, ".huohuo-owner.json"), "utf8"));
|
|
911
|
+
await access(join4(paths.python, release.python.executable));
|
|
769
912
|
const python = owner.owner === release.python.sha256;
|
|
770
|
-
const root =
|
|
771
|
-
const initialization = JSON.parse(await readFile4(
|
|
772
|
-
const dependencies = JSON.parse(await readFile4(
|
|
773
|
-
const worker = JSON.parse(await readFile4(
|
|
774
|
-
const chat = JSON.parse(await readFile4(
|
|
775
|
-
for (const profile of ["huohuo-chat", "huohuo-worker", "huohuo-strategy-worker"]) await access(
|
|
776
|
-
await access(
|
|
913
|
+
const root = join4(home, "instances/default");
|
|
914
|
+
const initialization = JSON.parse(await readFile4(join4(root, "initialization.json"), "utf8"));
|
|
915
|
+
const dependencies = JSON.parse(await readFile4(join4(paths.environment, ".dependencies-ready.json"), "utf8"));
|
|
916
|
+
const worker = JSON.parse(await readFile4(join4(root, "worker-install.json"), "utf8"));
|
|
917
|
+
const chat = JSON.parse(await readFile4(join4(root, "dsh/profiles/huohuo-chat/installed.json"), "utf8"));
|
|
918
|
+
for (const profile of ["huohuo-chat", "huohuo-worker", "huohuo-strategy-worker"]) await access(join4(root, "dsh/profiles", profile, "node_modules/@huohuo/research-agent/package.json"));
|
|
919
|
+
await access(join4(paths.runtime, "lib/distribution/managed.js"));
|
|
777
920
|
return { python, dependencies: initialization.package_sha256 === release.runtime.sha256 && initialization.status === "complete" && dependencies.sha256 === release.pythonDependencies.sha256 && worker.complete === true && chat.complete === true };
|
|
778
921
|
} catch (error) {
|
|
779
922
|
if (error.code !== "ENOENT") throw error;
|
|
780
923
|
try {
|
|
781
|
-
const owner = JSON.parse(await readFile4(
|
|
782
|
-
await access(
|
|
924
|
+
const owner = JSON.parse(await readFile4(join4(paths.python, ".huohuo-owner.json"), "utf8"));
|
|
925
|
+
await access(join4(paths.python, release.python.executable));
|
|
783
926
|
return { python: owner.owner === release.python.sha256, dependencies: false };
|
|
784
927
|
} catch {
|
|
785
928
|
return { python: false, dependencies: false };
|
|
@@ -787,19 +930,19 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
787
930
|
}
|
|
788
931
|
};
|
|
789
932
|
const check = async () => {
|
|
790
|
-
const [cacheHits, inventory] = await Promise.all([Promise.all(files.map((file) =>
|
|
933
|
+
const [cacheHits, inventory] = await Promise.all([Promise.all(files.map((file) => cached2(file.path, file.artifact))), installed()]);
|
|
791
934
|
const remainingDependencies = inventory.dependencies ? 0 : release.budget.dependenciesBytes;
|
|
792
935
|
const costs = [
|
|
793
|
-
{ path:
|
|
936
|
+
{ path: join4(home, "cache"), bytes: files.reduce((sum, file, index) => sum + (cacheHits[index] ? 0 : file.artifact.downloadBytes), 0) },
|
|
794
937
|
{ path: paths.directory, bytes: inventory.dependencies ? 0 : release.runtime.expandedBytes + release.budget.installTemporaryBytes },
|
|
795
938
|
{ path: paths.python, bytes: inventory.python ? 0 : release.python.expandedBytes },
|
|
796
939
|
{ path: paths.environment, bytes: 0 },
|
|
797
|
-
{ path:
|
|
940
|
+
{ path: join4(home, "instances/default"), bytes: 0 },
|
|
798
941
|
{ path: paths.directory, bytes: inventory.dependencies ? 0 : release.pythonDependencies.expandedBytes },
|
|
799
|
-
{ path:
|
|
942
|
+
{ path: join4(home, "backups"), bytes: release.budget.backupBytes }
|
|
800
943
|
];
|
|
801
944
|
const observation = await sample(costs.map((c) => c.path));
|
|
802
|
-
const dependencyPaths = [paths.directory, paths.environment,
|
|
945
|
+
const dependencyPaths = [paths.directory, paths.environment, join4(home, "instances/default")];
|
|
803
946
|
const volumes = /* @__PURE__ */ new Set();
|
|
804
947
|
for (const path of dependencyPaths) {
|
|
805
948
|
const device = diskBudgets(observation, [{ path, bytes: 0 }], 0)[0].device;
|
|
@@ -809,7 +952,7 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
809
952
|
}
|
|
810
953
|
}
|
|
811
954
|
const result = evaluateResources(observation, release.budget, costs, "install");
|
|
812
|
-
await atomicJSON(
|
|
955
|
+
await atomicJSON(join4(home, "state/resources.json"), result);
|
|
813
956
|
if (result.status === "unknown" || result.status === "blocked") {
|
|
814
957
|
const details = result.reasons.map((reason) => `- ${reason}`).join("\n");
|
|
815
958
|
throw Object.assign(new Error(`\u706B\u706B\u6682\u65F6\u65E0\u6CD5\u7EE7\u7EED\u5B89\u88C5\u3002
|
|
@@ -856,9 +999,9 @@ ${details}
|
|
|
856
999
|
};
|
|
857
1000
|
await stage("preflight", async (plan) => {
|
|
858
1001
|
const npm = npmCommand();
|
|
859
|
-
const { stdout } = await
|
|
1002
|
+
const { stdout } = await promisify4(execFile5)(npm.command, [...npm.prefix, "--version"], { timeout: 5e3 });
|
|
860
1003
|
if (!/^\d+\.\d+\.\d+\s*$/.test(stdout) || Number(stdout.split(".")[0]) !== release.npmMajor) throw new Error("npm \u7248\u672C\u672A\u77E5\u6216\u4E0D\u5728\u6B64\u53D1\u884C\u5DF2\u9A8C\u8BC1\u8303\u56F4\u5185");
|
|
861
|
-
const probe =
|
|
1004
|
+
const probe = join4(home, `.write-probe-${randomUUID3()}`);
|
|
862
1005
|
try {
|
|
863
1006
|
await atomicJSON(probe, { probe: true });
|
|
864
1007
|
} finally {
|
|
@@ -888,19 +1031,19 @@ ${details}
|
|
|
888
1031
|
await stage("install-runtime", async () => {
|
|
889
1032
|
try {
|
|
890
1033
|
await access(paths.python);
|
|
891
|
-
const marker = JSON.parse(await readFile4(
|
|
1034
|
+
const marker = JSON.parse(await readFile4(join4(paths.python, ".huohuo-owner.json"), "utf8"));
|
|
892
1035
|
if (marker.owner !== release.python.sha256) throw new Error("\u53D7\u7BA1 Python \u5F52\u5C5E\u4E0D\u7B26");
|
|
893
|
-
await access(
|
|
1036
|
+
await access(join4(paths.python, release.python.executable));
|
|
894
1037
|
return release.python.sha256;
|
|
895
1038
|
} catch (e) {
|
|
896
1039
|
if (e.code !== "ENOENT") throw e;
|
|
897
1040
|
}
|
|
898
|
-
await mkdir3(
|
|
1041
|
+
await mkdir3(join4(home, "runtimes/python"), { recursive: true, mode: 448 });
|
|
899
1042
|
const staging = `${paths.python}.${randomUUID3()}.staging`;
|
|
900
1043
|
try {
|
|
901
1044
|
await extractArchive(paths.pythonArchive, staging, release.python.expandedBytes);
|
|
902
|
-
await access(
|
|
903
|
-
await atomicJSON(
|
|
1045
|
+
await access(join4(staging, release.python.executable));
|
|
1046
|
+
await atomicJSON(join4(staging, ".huohuo-owner.json"), { format: 1, owner: release.python.sha256 });
|
|
904
1047
|
await rename3(staging, paths.python);
|
|
905
1048
|
} finally {
|
|
906
1049
|
await rm2(staging, { recursive: true, force: true });
|
|
@@ -909,30 +1052,30 @@ ${details}
|
|
|
909
1052
|
});
|
|
910
1053
|
await stage("install-dependencies", async () => {
|
|
911
1054
|
await ownedDirectory(paths.directory, record.releaseDigest);
|
|
912
|
-
await atomicJSON(
|
|
1055
|
+
await atomicJSON(join4(paths.directory, "package.json"), {
|
|
913
1056
|
name: "huohuo-managed-release",
|
|
914
1057
|
private: true,
|
|
915
1058
|
dependencies: { "@huohuo/research-agent": `file:${paths.archive}` }
|
|
916
1059
|
});
|
|
917
1060
|
const npm = npmCommand();
|
|
918
|
-
await withActivity("\u5B89\u88C5 npm \u4F9D\u8D56", () =>
|
|
919
|
-
const runtime = JSON.parse(await readFile4(
|
|
1061
|
+
await withActivity("\u5B89\u88C5 npm \u4F9D\u8D56", () => execute5(npm.command, [...npm.prefix, "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--prefer-offline", "--cache", join4(home, "cache/npm")], paths.directory));
|
|
1062
|
+
const runtime = JSON.parse(await readFile4(join4(paths.runtime, "package.json"), "utf8"));
|
|
920
1063
|
if (runtime.name !== release.runtime.name || runtime.version !== release.version) throw new Error("\u8FD0\u884C\u5305\u8EAB\u4EFD\u4E0D\u7B26");
|
|
921
|
-
const requirements = await readFile4(
|
|
1064
|
+
const requirements = await readFile4(join4(paths.runtime, "sidecar/requirements.lock"));
|
|
922
1065
|
if (createHash2("sha256").update(requirements).digest("hex") !== release.requirementsSha256) throw new Error("Python \u4F9D\u8D56\u9501\u6458\u8981\u4E0D\u7B26");
|
|
923
|
-
const assembly = await readFile4(
|
|
1066
|
+
const assembly = await readFile4(join4(paths.runtime, "npm-shrinkwrap.json"));
|
|
924
1067
|
if (createHash2("sha256").update(assembly).digest("hex") !== release.dsh.assemblySha256) throw new Error("DSH \u88C5\u914D\u9501\u6458\u8981\u4E0D\u7B26");
|
|
925
1068
|
await ownedDirectory(paths.environment, `${release.python.sha256}:${release.requirementsSha256}`);
|
|
926
|
-
const python =
|
|
927
|
-
const wheels =
|
|
1069
|
+
const python = join4(paths.environment, process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
|
|
1070
|
+
const wheels = join4(paths.directory, `wheels-${release.pythonDependencies.sha256}`);
|
|
928
1071
|
try {
|
|
929
|
-
await access(
|
|
1072
|
+
await access(join4(wheels, "requirements.txt"));
|
|
930
1073
|
} catch (e) {
|
|
931
1074
|
if (e.code !== "ENOENT") throw e;
|
|
932
1075
|
const staging = `${wheels}.${randomUUID3()}.staging`;
|
|
933
1076
|
try {
|
|
934
|
-
await extractArchive(
|
|
935
|
-
await access(
|
|
1077
|
+
await extractArchive(join4(home, "cache", release.pythonDependencies.sha256 + ".tgz"), staging, release.pythonDependencies.expandedBytes);
|
|
1078
|
+
await access(join4(staging, "requirements.txt"));
|
|
936
1079
|
await rename3(staging, wheels);
|
|
937
1080
|
} finally {
|
|
938
1081
|
await rm2(staging, { recursive: true, force: true });
|
|
@@ -940,7 +1083,7 @@ ${details}
|
|
|
940
1083
|
}
|
|
941
1084
|
let environmentReady = false;
|
|
942
1085
|
try {
|
|
943
|
-
const ready = JSON.parse(await readFile4(
|
|
1086
|
+
const ready = JSON.parse(await readFile4(join4(paths.environment, ".dependencies-ready.json"), "utf8"));
|
|
944
1087
|
if (ready.sha256 !== release.pythonDependencies.sha256) throw new Error("\u5DF2\u6709\u73AF\u5883\u7ED1\u5B9A\u4E0D\u540C wheel \u4EA7\u7269\uFF1B\u4E0D\u80FD\u539F\u4F4D\u6539\u5199\u5171\u4EAB\u73AF\u5883");
|
|
945
1088
|
await access(python);
|
|
946
1089
|
environmentReady = true;
|
|
@@ -948,13 +1091,13 @@ ${details}
|
|
|
948
1091
|
if (e.code !== "ENOENT") throw e;
|
|
949
1092
|
}
|
|
950
1093
|
if (!environmentReady) {
|
|
951
|
-
await
|
|
1094
|
+
await execute5(join4(paths.python, release.python.executable), ["-m", "venv", paths.environment], home);
|
|
952
1095
|
if (release.pythonDependencies.bundled && hooks.progress) {
|
|
953
|
-
const lock = await readFile4(
|
|
1096
|
+
const lock = await readFile4(join4(wheels, "requirements.txt"), "utf8");
|
|
954
1097
|
const upstream = lock.split("\n").filter((line) => /^[\w.-]+\s+@\s+https:\/\//.test(line)).length;
|
|
955
1098
|
if (upstream) hooks.progress?.(`Python \u4F9D\u8D56\u6E05\u5355\u6709 ${upstream} \u4E2A\u4E0A\u6E38\u6587\u4EF6\uFF0C\u4E0B\u65B9\u663E\u793A\u6B63\u5728\u5904\u7406\u7684\u6587\u4EF6`);
|
|
956
1099
|
}
|
|
957
|
-
await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () =>
|
|
1100
|
+
await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () => execute5(python, [
|
|
958
1101
|
"-m",
|
|
959
1102
|
"pip",
|
|
960
1103
|
"--isolated",
|
|
@@ -966,11 +1109,15 @@ ${details}
|
|
|
966
1109
|
"--only-binary=:all:",
|
|
967
1110
|
...release.pythonDependencies.bundled ? ["--no-cache-dir"] : [],
|
|
968
1111
|
"-r",
|
|
969
|
-
|
|
1112
|
+
join4(wheels, "requirements.txt")
|
|
970
1113
|
], home));
|
|
971
1114
|
}
|
|
972
|
-
await
|
|
973
|
-
|
|
1115
|
+
await execute5(python, ["-m", "pip", "--isolated", "check"], home);
|
|
1116
|
+
if (process.platform === "win32") {
|
|
1117
|
+
hooks.progress?.("\u68C0\u67E5 Windows Python \u4E0E\u672C\u5730\u4F9D\u8D56\u8FD0\u884C\u5E93");
|
|
1118
|
+
await execute5(python, ["-c", 'import numpy,pandas,lxml.etree,pydantic_core,akshare; from py_mini_racer import MiniRacer; ctx=MiniRacer(); assert ctx.eval("1+1")==2; ctx.close(); print("Windows Python \u4F9D\u8D56\u52A0\u8F7D\u68C0\u67E5\u901A\u8FC7")'], home);
|
|
1119
|
+
}
|
|
1120
|
+
await atomicJSON(join4(paths.environment, ".dependencies-ready.json"), { sha256: release.pythonDependencies.sha256 });
|
|
974
1121
|
return { python, runtime: release.runtime.sha256 };
|
|
975
1122
|
});
|
|
976
1123
|
if (programsOnly) {
|
|
@@ -980,11 +1127,11 @@ ${details}
|
|
|
980
1127
|
return record;
|
|
981
1128
|
}
|
|
982
1129
|
await stage("assemble-profiles", async () => {
|
|
983
|
-
await withActivity("\u914D\u7F6E\u5BF9\u8BDD\u4E0E\u540E\u53F0\u8FD0\u884C\u4F9D\u8D56", () =>
|
|
1130
|
+
await withActivity("\u914D\u7F6E\u5BF9\u8BDD\u4E0E\u540E\u53F0\u8FD0\u884C\u4F9D\u8D56", () => execute5(process.execPath, [join4(paths.runtime, "lib/distribution/managed.js"), "prepare", home], home));
|
|
984
1131
|
return { release: record.releaseDigest };
|
|
985
1132
|
});
|
|
986
1133
|
await stage("verify", async () => {
|
|
987
|
-
await
|
|
1134
|
+
await execute5(process.execPath, [join4(paths.runtime, "lib/distribution/managed.js"), "verify-live", home], home);
|
|
988
1135
|
return { release: record.releaseDigest };
|
|
989
1136
|
});
|
|
990
1137
|
record.status = "ready";
|
|
@@ -997,14 +1144,14 @@ ${details}
|
|
|
997
1144
|
// distribution/bootstrap/control.ts
|
|
998
1145
|
import { createServer, request as httpRequest } from "node:http";
|
|
999
1146
|
import { readFile as readFile5, rm as rm3 } from "node:fs/promises";
|
|
1000
|
-
import { join as
|
|
1001
|
-
import { execFile as
|
|
1002
|
-
import { promisify as
|
|
1003
|
-
var
|
|
1147
|
+
import { join as join5 } from "node:path";
|
|
1148
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
1149
|
+
import { promisify as promisify5 } from "node:util";
|
|
1150
|
+
var execute4 = promisify5(execFile6);
|
|
1004
1151
|
async function control(home, releaseDigest, command) {
|
|
1005
1152
|
let binding;
|
|
1006
1153
|
try {
|
|
1007
|
-
binding = JSON.parse(await readFile5(
|
|
1154
|
+
binding = JSON.parse(await readFile5(join5(home, "state/default-control.json"), "utf8"));
|
|
1008
1155
|
} catch (e) {
|
|
1009
1156
|
if (e.code === "ENOENT") return false;
|
|
1010
1157
|
throw new Error("\u672C\u673A\u8FD0\u884C\u8BB0\u5F55\u65E0\u6CD5\u6838\u9A8C");
|
|
@@ -1040,7 +1187,7 @@ async function control(home, releaseDigest, command) {
|
|
|
1040
1187
|
req.end();
|
|
1041
1188
|
});
|
|
1042
1189
|
} catch (error) {
|
|
1043
|
-
if (error.code === "ECONNREFUSED" && await isLockOwnerDead(
|
|
1190
|
+
if (error.code === "ECONNREFUSED" && await isLockOwnerDead(join5(home, "state/default-run.lock"), binding.runOwner)) return false;
|
|
1044
1191
|
throw error;
|
|
1045
1192
|
}
|
|
1046
1193
|
return true;
|
|
@@ -1048,13 +1195,13 @@ async function control(home, releaseDigest, command) {
|
|
|
1048
1195
|
|
|
1049
1196
|
// distribution/bootstrap/maintenance.ts
|
|
1050
1197
|
import { access as access2, readFile as readFile6, readdir as readdir2, lstat as lstat2, rename as rename4, rm as rm4, realpath as realpath4 } from "node:fs/promises";
|
|
1051
|
-
import { join as
|
|
1198
|
+
import { join as join6, resolve as resolve4, sep as sep3 } from "node:path";
|
|
1052
1199
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1053
1200
|
import { setTimeout as delay } from "node:timers/promises";
|
|
1054
1201
|
async function upgradeJournal(home) {
|
|
1055
1202
|
try {
|
|
1056
|
-
const value = JSON.parse(await readFile6(
|
|
1057
|
-
if (value.format !== 1 || !/^[a-f0-9]{64}$/.test(value.from) || !/^[a-f0-9]{64}$/.test(value.to) || !["stopping", "backed-up", "schema-unchanged", "profiles", "migrated", "verified", "complete"].includes(value.stage) || !value.backup.startsWith(
|
|
1203
|
+
const value = JSON.parse(await readFile6(join6(home, "state/upgrade.json"), "utf8"));
|
|
1204
|
+
if (value.format !== 1 || !/^[a-f0-9]{64}$/.test(value.from) || !/^[a-f0-9]{64}$/.test(value.to) || !["stopping", "backed-up", "schema-unchanged", "profiles", "migrated", "verified", "complete"].includes(value.stage) || !value.backup.startsWith(join6(home, "backups") + sep3)) throw new Error("\u5347\u7EA7\u8BB0\u5F55\u65E0\u6548");
|
|
1058
1205
|
return value;
|
|
1059
1206
|
} catch (error) {
|
|
1060
1207
|
if (error.code === "ENOENT") return;
|
|
@@ -1078,7 +1225,7 @@ async function stopOwned(home, releaseDigest) {
|
|
|
1078
1225
|
if (!await control(home, releaseDigest, "stop")) return;
|
|
1079
1226
|
for (let i = 0; i < 100; i++) {
|
|
1080
1227
|
try {
|
|
1081
|
-
await access2(
|
|
1228
|
+
await access2(join6(home, "state/default-run.lock"));
|
|
1082
1229
|
} catch (error) {
|
|
1083
1230
|
if (error.code === "ENOENT") return;
|
|
1084
1231
|
throw error;
|
|
@@ -1093,9 +1240,9 @@ async function backupBytes(directory) {
|
|
|
1093
1240
|
for (const item of await readdir2(path, { withFileTypes: true })) {
|
|
1094
1241
|
if (["node_modules", "cache", "python", "__pycache__"].includes(item.name)) continue;
|
|
1095
1242
|
if (item.isSymbolicLink()) throw new Error("\u5907\u4EFD\u4E0D\u63A5\u7BA1\u672A\u77E5\u7684\u5B9E\u4F8B\u94FE\u63A5");
|
|
1096
|
-
if (item.isDirectory()) await visit(
|
|
1243
|
+
if (item.isDirectory()) await visit(join6(path, item.name));
|
|
1097
1244
|
else {
|
|
1098
|
-
const info = await lstat2(
|
|
1245
|
+
const info = await lstat2(join6(path, item.name));
|
|
1099
1246
|
bytes += info.size + 512;
|
|
1100
1247
|
}
|
|
1101
1248
|
}
|
|
@@ -1113,19 +1260,19 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1113
1260
|
if (journal && journal.stage !== "complete" && journal.to !== digest(next)) throw new Error("\u5B58\u5728\u672A\u5B8C\u6210\u5347\u7EA7\uFF1B\u5FC5\u987B\u5148\u7EE7\u7EED\u539F\u56FA\u5B9A\u76EE\u6807");
|
|
1114
1261
|
if (current.releaseDigest === digest(next)) {
|
|
1115
1262
|
if (journal && journal.to === current.releaseDigest && journal.stage !== "complete") {
|
|
1116
|
-
await (hooks.run ?? run)(process.execPath, [
|
|
1263
|
+
await (hooks.run ?? run)(process.execPath, [join6(installationPaths(home, next).runtime, "lib/distribution/managed.js"), "verify-live", home], home);
|
|
1117
1264
|
journal.stage = "complete";
|
|
1118
|
-
await atomicJSON(
|
|
1265
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1119
1266
|
}
|
|
1120
1267
|
hooks.progress?.("\u5DF2\u7ECF\u662F\u540C\u4E00\u53D1\u884C\uFF0C\u65E0\u9700\u8FC1\u79FB");
|
|
1121
1268
|
return;
|
|
1122
1269
|
}
|
|
1123
1270
|
assertCompatibleUpgrade(current.release, next);
|
|
1124
1271
|
if (journal?.stage === "complete") {
|
|
1125
|
-
await withLock(
|
|
1126
|
-
await rename4(
|
|
1272
|
+
await withLock(join6(home, "state/install.lock"), async () => {
|
|
1273
|
+
await rename4(join6(home, "state/upgrade.json"), join6(home, `state/upgrade-${randomUUID4()}.json`));
|
|
1127
1274
|
try {
|
|
1128
|
-
await rename4(
|
|
1275
|
+
await rename4(join6(home, "state/upgrade-target.json"), join6(home, `state/upgrade-target-${randomUUID4()}.json`));
|
|
1129
1276
|
} catch (error) {
|
|
1130
1277
|
if (error.code !== "ENOENT") throw error;
|
|
1131
1278
|
}
|
|
@@ -1134,58 +1281,58 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1134
1281
|
}
|
|
1135
1282
|
const target = await install(home, next, hooks, true);
|
|
1136
1283
|
if (target.releaseDigest !== digest(next)) throw new Error("\u5347\u7EA7\u6062\u590D\u76EE\u6807\u4E0E\u8BF7\u6C42\u4E0D\u540C\uFF1B\u4FDD\u7559\u539F\u56FA\u5B9A\u76EE\u6807");
|
|
1137
|
-
const
|
|
1138
|
-
await withLock(
|
|
1284
|
+
const execute5 = hooks.run ?? run;
|
|
1285
|
+
await withLock(join6(home, "state/install.lock"), async () => {
|
|
1139
1286
|
current = await readInstallation(home);
|
|
1140
1287
|
if (journal && journal.from !== current.releaseDigest) throw new Error("\u5347\u7EA7\u671F\u95F4\u539F\u53D1\u884C\u7ED1\u5B9A\u53D8\u5316\uFF1B\u8BF7\u5148\u5BF9\u8D26");
|
|
1141
1288
|
journal ??= {
|
|
1142
1289
|
format: 1,
|
|
1143
1290
|
from: current.releaseDigest,
|
|
1144
1291
|
to: target.releaseDigest,
|
|
1145
|
-
backup:
|
|
1292
|
+
backup: join6(home, "backups", `upgrade-${target.releaseDigest.slice(0, 16)}-${randomUUID4()}`),
|
|
1146
1293
|
stage: "stopping"
|
|
1147
1294
|
};
|
|
1148
|
-
await atomicJSON(
|
|
1295
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1149
1296
|
await stopOwned(home, current.releaseDigest);
|
|
1150
|
-
const releaseRun = await acquireLock(
|
|
1297
|
+
const releaseRun = await acquireLock(join6(home, "state/default-run.lock"));
|
|
1151
1298
|
try {
|
|
1152
|
-
const bytes = await backupBytes(
|
|
1299
|
+
const bytes = await backupBytes(join6(home, "instances/default"));
|
|
1153
1300
|
const result = evaluateResources(
|
|
1154
|
-
await (hooks.sample ?? sampleResources)([
|
|
1301
|
+
await (hooks.sample ?? sampleResources)([join6(home, "backups")]),
|
|
1155
1302
|
target.release.budget,
|
|
1156
|
-
[{ path:
|
|
1303
|
+
[{ path: join6(home, "backups"), bytes }],
|
|
1157
1304
|
"install"
|
|
1158
1305
|
);
|
|
1159
1306
|
if (result.status === "unknown" || result.status === "blocked") throw new Error("\u4E00\u81F4\u6027\u5907\u4EFD\u8D44\u6E90\u4E0D\u8DB3\uFF1B\u539F\u6570\u636E\u4FDD\u7559");
|
|
1160
1307
|
if (journal.stage === "stopping") {
|
|
1161
|
-
await
|
|
1308
|
+
await execute5(process.execPath, [join6(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "backup", home], home);
|
|
1162
1309
|
journal.stage = "backed-up";
|
|
1163
|
-
await atomicJSON(
|
|
1310
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1164
1311
|
}
|
|
1165
1312
|
const runtime = installationPaths(home, target.release).runtime;
|
|
1166
|
-
await
|
|
1313
|
+
await execute5(process.execPath, [join6(runtime, "lib/distribution/managed.js"), "prepare-upgrade", home], home);
|
|
1167
1314
|
journal.stage = "profiles";
|
|
1168
|
-
await atomicJSON(
|
|
1315
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1169
1316
|
if (current.release.schema.migrationsSha256 !== target.release.schema.migrationsSha256) {
|
|
1170
1317
|
const space = evaluateResources(
|
|
1171
|
-
await (hooks.sample ?? sampleResources)([
|
|
1318
|
+
await (hooks.sample ?? sampleResources)([join6(home, "instances/default")]),
|
|
1172
1319
|
target.release.budget,
|
|
1173
|
-
[{ path:
|
|
1320
|
+
[{ path: join6(home, "instances/default"), bytes: bytes + target.release.budget.installTemporaryBytes }],
|
|
1174
1321
|
"install"
|
|
1175
1322
|
);
|
|
1176
1323
|
if (space.status === "blocked" || space.status === "unknown") throw new Error("\u8FC1\u79FB\u524D\u8D44\u6E90\u4E0D\u8DB3\uFF1B\u4FDD\u7559\u5347\u7EA7\u65E5\u5FD7\u548C\u5F53\u524D\u6570\u636E");
|
|
1177
|
-
await
|
|
1324
|
+
await execute5(process.execPath, [join6(runtime, "lib/distribution/managed.js"), "migrate-upgrade", home], home);
|
|
1178
1325
|
}
|
|
1179
1326
|
journal.stage = "migrated";
|
|
1180
|
-
await atomicJSON(
|
|
1181
|
-
await
|
|
1327
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1328
|
+
await execute5(process.execPath, [join6(runtime, "lib/distribution/managed.js"), "verify-upgrade", home], home);
|
|
1182
1329
|
journal.stage = "verified";
|
|
1183
|
-
await atomicJSON(
|
|
1330
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1184
1331
|
target.status = "ready";
|
|
1185
1332
|
target.stage = "ready";
|
|
1186
|
-
await atomicJSON(
|
|
1333
|
+
await atomicJSON(join6(home, "installation.json"), target);
|
|
1187
1334
|
journal.stage = "complete";
|
|
1188
|
-
await atomicJSON(
|
|
1335
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1189
1336
|
hooks.progress?.("\u5347\u7EA7\u9A8C\u8BC1\u901A\u8FC7\uFF0C\u5DF2\u5207\u6362\u53D1\u884C\u7ED1\u5B9A\uFF1B\u5907\u4EFD\u4FDD\u7559");
|
|
1190
1337
|
} finally {
|
|
1191
1338
|
await releaseRun();
|
|
@@ -1194,7 +1341,7 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1194
1341
|
}
|
|
1195
1342
|
async function removeOwned(path, expected) {
|
|
1196
1343
|
try {
|
|
1197
|
-
const marker = JSON.parse(await readFile6(
|
|
1344
|
+
const marker = JSON.parse(await readFile6(join6(path, ".huohuo-owner.json"), "utf8"));
|
|
1198
1345
|
if (marker.format !== 1 || marker.owner !== expected) throw new Error("\u53D7\u7BA1\u76EE\u5F55\u5F52\u5C5E\u4E0D\u7B26\uFF0C\u672A\u5220\u9664");
|
|
1199
1346
|
await rm4(path, { recursive: true });
|
|
1200
1347
|
} catch (error) {
|
|
@@ -1203,25 +1350,25 @@ async function removeOwned(path, expected) {
|
|
|
1203
1350
|
}
|
|
1204
1351
|
async function uninstall(home, hooks = {}) {
|
|
1205
1352
|
home = await realpath4(resolve4(home));
|
|
1206
|
-
await withLock(
|
|
1353
|
+
await withLock(join6(home, "state/install.lock"), async () => {
|
|
1207
1354
|
const current = await readInstallation(home);
|
|
1208
1355
|
if (!current) throw new Error("\u6CA1\u6709\u53EF\u6838\u9A8C\u7684\u6301\u4E45\u5B89\u88C5");
|
|
1209
1356
|
const journal = await upgradeJournal(home);
|
|
1210
1357
|
if (journal && journal.stage !== "complete") throw new Error("\u5347\u7EA7\u5C1A\u672A\u5BF9\u8D26\uFF0C\u4E0D\u80FD\u5378\u8F7D\u7A0B\u5E8F");
|
|
1211
1358
|
await stopOwned(home, current.releaseDigest);
|
|
1212
|
-
const releaseRun = await acquireLock(
|
|
1359
|
+
const releaseRun = await acquireLock(join6(home, "state/default-run.lock"));
|
|
1213
1360
|
try {
|
|
1214
1361
|
const paths = installationPaths(home, current.release);
|
|
1215
|
-
if ((await readdir2(
|
|
1362
|
+
if ((await readdir2(join6(home, "instances"))).some((name) => name !== "default")) throw new Error("\u68C0\u6D4B\u5230\u5176\u4ED6\u5B9E\u4F8B\uFF1B\u5171\u4EAB\u8FD0\u884C\u73AF\u5883\u5F52\u5C5E\u9700\u5148\u6838\u67E5\uFF0C\u672A\u5220\u9664");
|
|
1216
1363
|
if (current.status !== "uninstalled") {
|
|
1217
|
-
await (hooks.run ?? run)(process.execPath, [
|
|
1364
|
+
await (hooks.run ?? run)(process.execPath, [join6(paths.runtime, "lib/distribution/managed.js"), "uninstall-programs", home], home);
|
|
1218
1365
|
current.status = "uninstalled";
|
|
1219
1366
|
current.stage = "uninstalled";
|
|
1220
|
-
await atomicJSON(
|
|
1367
|
+
await atomicJSON(join6(home, "installation.json"), current);
|
|
1221
1368
|
}
|
|
1222
1369
|
await removeOwned(paths.directory, current.releaseDigest);
|
|
1223
1370
|
await removeOwned(paths.environment, `${current.release.python.sha256}:${current.release.requirementsSha256}`);
|
|
1224
|
-
if (!(await readdir2(
|
|
1371
|
+
if (!(await readdir2(join6(home, "releases"))).length) await removeOwned(paths.python, current.release.python.sha256);
|
|
1225
1372
|
hooks.progress?.(`\u5DF2\u5378\u8F7D\u53EF\u518D\u751F\u6210\u7A0B\u5E8F\uFF1B\u5B9E\u4F8B\u3001\u51ED\u636E\u3001\u4F1A\u8BDD\u548C\u5907\u4EFD\u4FDD\u7559\u5728 ${home}`);
|
|
1226
1373
|
} finally {
|
|
1227
1374
|
await releaseRun();
|
|
@@ -1232,7 +1379,7 @@ async function uninstall(home, hooks = {}) {
|
|
|
1232
1379
|
// distribution/bootstrap/install-ui.ts
|
|
1233
1380
|
import { createInterface } from "node:readline";
|
|
1234
1381
|
import { homedir } from "node:os";
|
|
1235
|
-
import { relative, sep as
|
|
1382
|
+
import { relative, sep as sep4 } from "node:path";
|
|
1236
1383
|
var gb = (bytes) => bytes === null ? "\u672A\u77E5" : `${(bytes / 1e9).toFixed(2)} GB`;
|
|
1237
1384
|
var mb = (bytes) => bytes < 1e5 ? `${(bytes / 1e3).toFixed(1)} KB` : `${(bytes / 1e6).toFixed(1)} MB`;
|
|
1238
1385
|
var memory = (bytes) => {
|
|
@@ -1242,7 +1389,7 @@ var memory = (bytes) => {
|
|
|
1242
1389
|
function displayPath(path) {
|
|
1243
1390
|
const local = relative(homedir(), path);
|
|
1244
1391
|
if (!local) return "~";
|
|
1245
|
-
return local === ".." || local.startsWith(`..${
|
|
1392
|
+
return local === ".." || local.startsWith(`..${sep4}`) ? path : `~${sep4}${local}`;
|
|
1246
1393
|
}
|
|
1247
1394
|
function installUI(assumeYes) {
|
|
1248
1395
|
const interactive = process.stdin.isTTY && process.stderr.isTTY;
|
|
@@ -1345,7 +1492,7 @@ function installUI(assumeYes) {
|
|
|
1345
1492
|
|
|
1346
1493
|
// distribution/bootstrap/status-ui.ts
|
|
1347
1494
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
1348
|
-
import { join as
|
|
1495
|
+
import { join as join7 } from "node:path";
|
|
1349
1496
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
1350
1497
|
var mib = (bytes) => typeof bytes === "number" && Number.isFinite(bytes) ? `${Math.round(bytes / 2 ** 20)} MiB` : "\u672A\u77E5";
|
|
1351
1498
|
var pressureLabels = { normal: "\u6B63\u5E38", high: "\u504F\u9AD8", critical: "\u4E25\u91CD", unknown: "\u672A\u77E5" };
|
|
@@ -1356,7 +1503,7 @@ async function statusLines(home) {
|
|
|
1356
1503
|
if (installed.status !== "ready") return [title, "\u5B89\u88C5\u5C1A\u672A\u5B8C\u6210\u3002"];
|
|
1357
1504
|
let admission;
|
|
1358
1505
|
try {
|
|
1359
|
-
admission = JSON.parse(await readFile7(
|
|
1506
|
+
admission = JSON.parse(await readFile7(join7(home, "state/default-admission.json"), "utf8"));
|
|
1360
1507
|
} catch (error) {
|
|
1361
1508
|
if (error.code !== "ENOENT") return [title, "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u8D44\u6E90\u72B6\u6001\u3002"];
|
|
1362
1509
|
}
|
|
@@ -1425,10 +1572,10 @@ async function showStatus(home, watch) {
|
|
|
1425
1572
|
// distribution/bootstrap/assets.ts
|
|
1426
1573
|
import { createRequire } from "node:module";
|
|
1427
1574
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
1428
|
-
import { dirname as dirname5, join as
|
|
1575
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
1429
1576
|
import { fileURLToPath } from "node:url";
|
|
1430
1577
|
async function assetsDirectory(release, entryURL = import.meta.url) {
|
|
1431
|
-
if (!release?.assetPackage) return
|
|
1578
|
+
if (!release?.assetPackage) return join8(dirname5(fileURLToPath(entryURL)), "assets");
|
|
1432
1579
|
const expected = release.assetPackage;
|
|
1433
1580
|
let file;
|
|
1434
1581
|
try {
|
|
@@ -1440,7 +1587,7 @@ async function assetsDirectory(release, entryURL = import.meta.url) {
|
|
|
1440
1587
|
if (manifest.name !== expected.name || manifest.version !== expected.version) {
|
|
1441
1588
|
throw new Error("\u706B\u706B\u5165\u53E3\u4E0E\u5E73\u53F0\u8D44\u6E90\u5305\u7248\u672C\u4E0D\u4E00\u81F4\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C\u540C\u4E00\u7248\u672C\u7684\u5B8C\u6574\u5B89\u88C5\u547D\u4EE4\u3002");
|
|
1442
1589
|
}
|
|
1443
|
-
return
|
|
1590
|
+
return join8(dirname5(file), "assets");
|
|
1444
1591
|
}
|
|
1445
1592
|
|
|
1446
1593
|
// distribution/bootstrap/entry.ts
|
|
@@ -1452,7 +1599,7 @@ function artifactSource(source, directory) {
|
|
|
1452
1599
|
const sha256 = bundled?.[1] ?? artifact2?.sha256;
|
|
1453
1600
|
if (!sha256) throw new Error("\u672C\u673A\u4EA7\u7269\u76EE\u5F55\u7F3A\u5C11\u8BE5\u56FA\u5B9A\u53D1\u884C\u8D44\u6E90");
|
|
1454
1601
|
const root = directory ?? await assetsDirectory(source, import.meta.url);
|
|
1455
|
-
const path =
|
|
1602
|
+
const path = join9(root, sha256 + ".tgz");
|
|
1456
1603
|
const file = await open3(path).catch((error) => {
|
|
1457
1604
|
if (error.code === "ENOENT" && bundled) throw new Error("\u7F3A\u5C11\u4E0A\u6B21\u5B89\u88C5\u4F7F\u7528\u7684\u6587\u4EF6\u3002\u8BF7\u7528\u4E0A\u6B21\u5B89\u88C5\u65F6\u7684\u7248\u672C\u7EE7\u7EED\u3002");
|
|
1458
1605
|
throw error;
|
|
@@ -1461,7 +1608,7 @@ function artifactSource(source, directory) {
|
|
|
1461
1608
|
};
|
|
1462
1609
|
}
|
|
1463
1610
|
async function main(args = process.argv.slice(2)) {
|
|
1464
|
-
let home =
|
|
1611
|
+
let home = join9(homedir2(), ".huohuo"), noOpen = false, assumeYes = false, watch = false, command = "start";
|
|
1465
1612
|
let releaseFile, artifactDirectory;
|
|
1466
1613
|
for (let i = 0; i < args.length; i++) {
|
|
1467
1614
|
if (args[i] === "--home") {
|
|
@@ -1476,7 +1623,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1476
1623
|
else if (args[i] === "--yes") assumeYes = true;
|
|
1477
1624
|
else if (args[i] === "--watch") watch = true;
|
|
1478
1625
|
else if (args[i] === "--version") {
|
|
1479
|
-
const packageFile =
|
|
1626
|
+
const packageFile = join9(dirname6(fileURLToPath2(import.meta.url)), "package.json");
|
|
1480
1627
|
const manifest = JSON.parse(await readFile9(packageFile, "utf8"));
|
|
1481
1628
|
console.log(`${manifest.name} ${manifest.version}${manifest.private ? "\uFF08\u672C\u5730\u5019\u9009\uFF09" : ""}`);
|
|
1482
1629
|
return;
|
|
@@ -1508,11 +1655,11 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1508
1655
|
};
|
|
1509
1656
|
const ui = installUI(assumeYes);
|
|
1510
1657
|
if (command === "upgrade") {
|
|
1511
|
-
const candidate = selectRelease(JSON.parse(await readFile9(releaseFile ??
|
|
1658
|
+
const candidate = selectRelease(JSON.parse(await readFile9(releaseFile ?? join9(dirname6(fileURLToPath2(import.meta.url)), "release.json"), "utf8")));
|
|
1512
1659
|
await upgrade(home, candidate, { fetcher: artifactSource(candidate, artifactDirectory), progress, ...ui });
|
|
1513
1660
|
const current = await readInstallation(home);
|
|
1514
1661
|
if (await control(home, current.releaseDigest, noOpen ? "status" : "open")) return;
|
|
1515
|
-
await run(process.execPath, [
|
|
1662
|
+
await run(process.execPath, [join9(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "start", home, ...noOpen ? ["--no-open"] : []], home, process.env, () => control(home, current.releaseDigest, "stop"));
|
|
1516
1663
|
return;
|
|
1517
1664
|
}
|
|
1518
1665
|
if (command === "uninstall") {
|
|
@@ -1530,7 +1677,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1530
1677
|
return;
|
|
1531
1678
|
}
|
|
1532
1679
|
const paths = installationPaths(home, record.release);
|
|
1533
|
-
const targets = [home, paths.directory, paths.environment,
|
|
1680
|
+
const targets = [home, paths.directory, paths.environment, join9(home, "instances/default")];
|
|
1534
1681
|
const resources = evaluateResources(await sampleResources(targets), record.release.budget, targets.map((path) => ({ path, bytes: 0 })), "start");
|
|
1535
1682
|
console.log(JSON.stringify({
|
|
1536
1683
|
home,
|
|
@@ -1551,7 +1698,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1551
1698
|
}
|
|
1552
1699
|
const runtime = installationPaths(home, record.release).runtime;
|
|
1553
1700
|
try {
|
|
1554
|
-
await readFile9(
|
|
1701
|
+
await readFile9(join9(runtime, "lib/distribution/managed.js"));
|
|
1555
1702
|
} catch (e) {
|
|
1556
1703
|
if (e.code !== "ENOENT") throw e;
|
|
1557
1704
|
record = void 0;
|
|
@@ -1563,7 +1710,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1563
1710
|
if (!source) {
|
|
1564
1711
|
let catalog = null;
|
|
1565
1712
|
try {
|
|
1566
|
-
catalog = JSON.parse(await readFile9(releaseFile ??
|
|
1713
|
+
catalog = JSON.parse(await readFile9(releaseFile ?? join9(dirname6(fileURLToPath2(import.meta.url)), "release.json"), "utf8"));
|
|
1567
1714
|
} catch (e) {
|
|
1568
1715
|
if (e.code !== "ENOENT") throw e;
|
|
1569
1716
|
}
|
|
@@ -1573,7 +1720,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1573
1720
|
record = await install(home, source, { fetcher, progress, ...ui });
|
|
1574
1721
|
}
|
|
1575
1722
|
const installedHome = record.home;
|
|
1576
|
-
await run(process.execPath, [
|
|
1723
|
+
await run(process.execPath, [join9(installationPaths(installedHome, record.release).runtime, "lib/distribution/managed.js"), "start", installedHome, ...noOpen ? ["--no-open"] : []], installedHome, process.env, () => control(installedHome, record.releaseDigest, "stop"));
|
|
1577
1724
|
}
|
|
1578
1725
|
if (process.argv[1] && await realpath5(resolve5(process.argv[1])).catch(() => "") === await realpath5(fileURLToPath2(import.meta.url))) {
|
|
1579
1726
|
main().catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pab-iprg/huohuo",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.7",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "火火多平台安装入口(实验预览)",
|
|
6
6
|
"bin": {
|
|
7
7
|
"huohuo": "./cli.js"
|
|
8
8
|
},
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
"README.md"
|
|
16
16
|
],
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@pab-iprg/huohuo-assets-darwin-arm64": "1.0.0-preview.
|
|
19
|
-
"@pab-iprg/huohuo-assets-linux-x64": "1.0.0-preview.
|
|
18
|
+
"@pab-iprg/huohuo-assets-darwin-arm64": "1.0.0-preview.7",
|
|
19
|
+
"@pab-iprg/huohuo-assets-linux-x64": "1.0.0-preview.7",
|
|
20
|
+
"@pab-iprg/huohuo-assets-win32-x64": "1.0.0-preview.7"
|
|
20
21
|
},
|
|
21
22
|
"publishConfig": {
|
|
22
23
|
"access": "public",
|
package/release.json
CHANGED
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"runtime": {
|
|
19
19
|
"name": "@huohuo/research-agent",
|
|
20
20
|
"version": "1.0.0",
|
|
21
|
-
"url": "https://bundled.invalid/
|
|
21
|
+
"url": "https://bundled.invalid/32e96855b2579f293877c67dc6c5645b2b1fb4e9300f70f3e7a783a6d67a4120.tgz",
|
|
22
22
|
"bundled": true,
|
|
23
|
-
"sha256": "
|
|
24
|
-
"integrity": "sha512-
|
|
25
|
-
"downloadBytes":
|
|
26
|
-
"expandedBytes":
|
|
23
|
+
"sha256": "32e96855b2579f293877c67dc6c5645b2b1fb4e9300f70f3e7a783a6d67a4120",
|
|
24
|
+
"integrity": "sha512-6IAa0KnmqVFw7Z6ogyyLlaeOCHCFPJ7VxMIz7nzemffiZwBYGt8+SvNjkk4CWKzjtJppYQQl2NAPooqZ6keteg==",
|
|
25
|
+
"downloadBytes": 2613970,
|
|
26
|
+
"expandedBytes": 8341044
|
|
27
27
|
},
|
|
28
28
|
"python": {
|
|
29
29
|
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-aarch64-apple-darwin-install_only_stripped.tar.gz",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
},
|
|
100
100
|
"assetPackage": {
|
|
101
101
|
"name": "@pab-iprg/huohuo-assets-darwin-arm64",
|
|
102
|
-
"version": "1.0.0-preview.
|
|
102
|
+
"version": "1.0.0-preview.7"
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
105
|
{
|
|
@@ -118,12 +118,12 @@
|
|
|
118
118
|
"runtime": {
|
|
119
119
|
"name": "@huohuo/research-agent",
|
|
120
120
|
"version": "1.0.0",
|
|
121
|
-
"url": "https://bundled.invalid/
|
|
121
|
+
"url": "https://bundled.invalid/79cbf018a4363d256fee64f07dc3cd8e434ef1e89ba1dcfdcbdf4c2b0a424f65.tgz",
|
|
122
122
|
"bundled": true,
|
|
123
|
-
"sha256": "
|
|
124
|
-
"integrity": "sha512-
|
|
125
|
-
"downloadBytes":
|
|
126
|
-
"expandedBytes":
|
|
123
|
+
"sha256": "79cbf018a4363d256fee64f07dc3cd8e434ef1e89ba1dcfdcbdf4c2b0a424f65",
|
|
124
|
+
"integrity": "sha512-8KMx3WCkkFxvcD6XQcV7N9FNTgU/Q5SNezCkyJizOnlT4lt4bqWdbxlBfhRcTiVmaP2/3PjsyzFDB1KEaaev3g==",
|
|
125
|
+
"downloadBytes": 2613972,
|
|
126
|
+
"expandedBytes": 8341062
|
|
127
127
|
},
|
|
128
128
|
"python": {
|
|
129
129
|
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
|
|
@@ -198,7 +198,105 @@
|
|
|
198
198
|
},
|
|
199
199
|
"assetPackage": {
|
|
200
200
|
"name": "@pab-iprg/huohuo-assets-linux-x64",
|
|
201
|
-
"version": "1.0.0-preview.
|
|
201
|
+
"version": "1.0.0-preview.7"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"format": 1,
|
|
206
|
+
"bootstrapFormat": 1,
|
|
207
|
+
"version": "1.0.0",
|
|
208
|
+
"nodeMajor": 24,
|
|
209
|
+
"npmMajor": 11,
|
|
210
|
+
"qualification": "controlled-acceptance-only",
|
|
211
|
+
"platform": {
|
|
212
|
+
"os": "win32",
|
|
213
|
+
"arch": "x64",
|
|
214
|
+
"minimumOsRelease": "10.0.19045"
|
|
215
|
+
},
|
|
216
|
+
"runtime": {
|
|
217
|
+
"name": "@huohuo/research-agent",
|
|
218
|
+
"version": "1.0.0",
|
|
219
|
+
"url": "https://bundled.invalid/32f67fca6cae53021035f6b972953335a657357907620c1fa5a618290cc552eb.tgz",
|
|
220
|
+
"bundled": true,
|
|
221
|
+
"sha256": "32f67fca6cae53021035f6b972953335a657357907620c1fa5a618290cc552eb",
|
|
222
|
+
"integrity": "sha512-NVB0sYN/W8BWmBu6ShAl75t8jncWzSprwQ5NfS1k6M6KzJtqY+oEY/zQYZz500W6/na2yzCJC6TKmkxCugXfwA==",
|
|
223
|
+
"downloadBytes": 2613974,
|
|
224
|
+
"expandedBytes": 8341060
|
|
225
|
+
},
|
|
226
|
+
"python": {
|
|
227
|
+
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20261026/cpython-3.13.18%2B20261026-x86_64-pc-windows-msvc-install_only_stripped.tar.gz",
|
|
228
|
+
"sha256": "7369f7a35f261222a6ac8b3a87c1c88eeebee8af945d85346202591a64038dd3",
|
|
229
|
+
"downloadBytes": 22023022,
|
|
230
|
+
"expandedBytes": 65192448,
|
|
231
|
+
"redirectHosts": [
|
|
232
|
+
"release-assets.githubusercontent.com"
|
|
233
|
+
],
|
|
234
|
+
"executable": "python/python.exe",
|
|
235
|
+
"license": "python/LICENSE.txt",
|
|
236
|
+
"build": "cpython-3.13.18+20261026-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
|
237
|
+
},
|
|
238
|
+
"pythonDependencies": {
|
|
239
|
+
"url": "https://bundled.invalid/fd54d77024b4e1a82b58144bbdc4c130ba576e857789439dc473b7cb37ca5aa5.tgz",
|
|
240
|
+
"bundled": true,
|
|
241
|
+
"sha256": "fd54d77024b4e1a82b58144bbdc4c130ba576e857789439dc473b7cb37ca5aa5",
|
|
242
|
+
"downloadBytes": 14578,
|
|
243
|
+
"expandedBytes": 51200,
|
|
244
|
+
"upstreamWheelCount": 40
|
|
245
|
+
},
|
|
246
|
+
"dsh": {
|
|
247
|
+
"version": "0.2.1-rc.2",
|
|
248
|
+
"assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
|
|
249
|
+
},
|
|
250
|
+
"requirementsSha256": "acb5dea55d39bda017e8c63c70fcfcd5dfb74eb987376d4612161257a9fd0900",
|
|
251
|
+
"profilesSha256": "fb3540e0d604f26280128a21f97313575848f6348733a221f7e230233fdb8e2d",
|
|
252
|
+
"budget": {
|
|
253
|
+
"minimumEffectiveMemoryBytes": 4294967296,
|
|
254
|
+
"startupHeadroomBytes": 536870912,
|
|
255
|
+
"heavyJobHeadroomBytes": 1073741824,
|
|
256
|
+
"processTreeBudgetBytes": 3221225472,
|
|
257
|
+
"diskReserveBytes": 1073741824,
|
|
258
|
+
"recoverySamples": 3,
|
|
259
|
+
"sampleIntervalMs": 2000,
|
|
260
|
+
"sampleMaxAgeMs": 10000,
|
|
261
|
+
"installTemporaryBytes": 1073741824,
|
|
262
|
+
"dependenciesBytes": 4294967296,
|
|
263
|
+
"backupBytes": 1073741824
|
|
264
|
+
},
|
|
265
|
+
"schema": {
|
|
266
|
+
"minimum": 1,
|
|
267
|
+
"maximum": 1,
|
|
268
|
+
"migrationsSha256": "ba029fdbffa5320e61393b484d08accafce60e9aa8dacde5b6212fb79dd9146b",
|
|
269
|
+
"files": {
|
|
270
|
+
"001_init.sql": "b42b6192d872f0f65cfbbf9d0c7fb442d3aa49584a6cf321d3bcc641b31b70da",
|
|
271
|
+
"002_contact_decision.sql": "af82cb9975a09fa09c0f970b70d04a7b55a960d72e0853f4573d571efaa46f0a",
|
|
272
|
+
"003_schedules.sql": "2937f7be2693ce9b410ee9a73bd10c5e819c5081a10a6b3993c33f37ad42295f",
|
|
273
|
+
"004_condition_state.sql": "f081d250dd525ad57a450bd6bca59a75f8fb1428177d670ae9fa142ed79721d6",
|
|
274
|
+
"005_execution_handoff.sql": "ebf2e5c4890b7a73ae1ffb57a70bb15183f48a6c1fa2acf3a48b95ccb3d992b3",
|
|
275
|
+
"006_entity_confirmation.sql": "d25ee376e7f6dd55df2edeb68d7591ef5fbaa34eeb3eef662cd76e183526d3c0",
|
|
276
|
+
"007_research_tool_calls.sql": "df9c7d8d25b44b151f743e2d5167b34ae89c64e67423d8af554364fd792da942",
|
|
277
|
+
"008_followup_notifications.sql": "98865d811879afd679846087a5f01ee2db371dcb90381a9c400c940a5c1c4b37",
|
|
278
|
+
"008_investment.sql": "71cecf6215552c725797059efdcb501247e1225c7d9f662de606266d3a947e61",
|
|
279
|
+
"009_notification_channels.sql": "572424d739203a8514ae59920a0ad69cf83f278479b48d48becb88bddda25d9a",
|
|
280
|
+
"010_strategies.sql": "af023fab201db6bd2e15e2348125d112bc21cf3a59d8afd0a2d616fc05cee209",
|
|
281
|
+
"011_strategy_run_subjects.sql": "53a1c216ed775a06549dcc201f9494ceabe308bee6dc52a214b685cca2f2fe8d",
|
|
282
|
+
"012_strategy_schedule.sql": "ccddd81bb70d2c86356e6f1ad09132e7397837d17e926b515c8e281a362bdd58",
|
|
283
|
+
"014_industry_chain.sql": "b656efffe746d14ca350ea1c9ecfcce2d98b5753e4b2f862c75d0456a062fd4a",
|
|
284
|
+
"015_source_heads.sql": "652bf637892edbbe969ce5ed4ce45a4a0c43d03de395fce1b828fc4c0169f885",
|
|
285
|
+
"016_legacy_transfer.sql": "b6e3c214b103487d741226b0f83525e0cacbb0c440bd1935d0e963dc9b9bfd07",
|
|
286
|
+
"017_unified_strategy_work.sql": "8d055ca58f3e73482fb16c630c733ae93197244b37e4509cc90c7dec3bf88d60",
|
|
287
|
+
"018_personalization.sql": "7f62a4eb74707fb071f56d6057b6d6517ed7648af23ebe290f3be15d260a103c",
|
|
288
|
+
"019_position_risk.sql": "3c461fa435b352127fd4ee4e7ed3de2c924561a3727dce1535d7d3a47c436ca9",
|
|
289
|
+
"020_notification_tests.sql": "3eb0d72eadf04b074208dbe0ef6a72ee614ec11a15143108a2471568b7b4fb22",
|
|
290
|
+
"021_notification_priority.sql": "f742b4c4b796e918bfc6a08bac8c5ae206382075925acf7b1c430b033f5177e6",
|
|
291
|
+
"022_research_job_priority.sql": "fcb0c0af025806faab7cd403097a9d2c55e07331ab0a78b2926c1927a87f4ed4",
|
|
292
|
+
"023_adaptive_notifications.sql": "80630e2dad73844d48b265fbc33648b01d8321a6fd9a0587ef4dc01f362aadec",
|
|
293
|
+
"023_notification_attention.sql": "5dd85fb001192558211c7b69e3452984c52119f215750ed6283b296d3dccc842",
|
|
294
|
+
"024_t_trade_watch.sql": "aa38470ddab00e0cbca07ce6d5cc31399ba2b6e40c8d8d888e88ec093a9f89ef"
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"assetPackage": {
|
|
298
|
+
"name": "@pab-iprg/huohuo-assets-win32-x64",
|
|
299
|
+
"version": "1.0.0-preview.7"
|
|
202
300
|
}
|
|
203
301
|
}
|
|
204
302
|
]
|