@pab-iprg/huohuo 1.0.0-preview.5 → 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 +317 -164
- package/package.json +5 -4
- package/release.json +112 -14
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) {
|
|
@@ -425,6 +554,17 @@ function linuxAvailableMemory(processAvailable, meminfo) {
|
|
|
425
554
|
const hostAvailable = Number(match[1]) * 1024;
|
|
426
555
|
return Number.isSafeInteger(hostAvailable) && hostAvailable >= 0 ? Math.min(processAvailable, hostAvailable) : null;
|
|
427
556
|
}
|
|
557
|
+
function darwinAvailableMemory(processAvailable, effective, pressure, vmstat, constrained = false) {
|
|
558
|
+
if (processAvailable === null || !Number.isSafeInteger(processAvailable) || processAvailable <= 0) return null;
|
|
559
|
+
if (pressure !== "normal" || constrained || effective === null || !Number.isSafeInteger(effective) || effective <= 0) return processAvailable;
|
|
560
|
+
const pageSize = Number(/page size of (\d+) bytes/.exec(vmstat)?.[1]);
|
|
561
|
+
const counts = ["Pages free", "Pages purgeable", "Pages speculative", "File-backed pages"].map((label) => Number(new RegExp(`^${label}:\\s+(\\d+)\\.\\s*$`, "m").exec(vmstat)?.[1]));
|
|
562
|
+
if (![pageSize, ...counts].every(Number.isSafeInteger) || pageSize <= 0 || counts.some((n) => n < 0)) return processAvailable;
|
|
563
|
+
const [free, purgeable, speculative, fileBacked] = counts;
|
|
564
|
+
const estimate = pageSize * (free + Math.max(purgeable, Math.max(0, fileBacked - speculative)));
|
|
565
|
+
if (!Number.isSafeInteger(estimate) || estimate < 0 || estimate > effective) return processAvailable;
|
|
566
|
+
return Math.min(effective, Math.max(processAvailable, estimate));
|
|
567
|
+
}
|
|
428
568
|
async function sampleResources(paths) {
|
|
429
569
|
const limit = constrainedMemory();
|
|
430
570
|
let effectiveMemoryBytes = Math.min(totalmem(), limit || Infinity);
|
|
@@ -438,19 +578,14 @@ async function sampleResources(paths) {
|
|
|
438
578
|
if (!(effectiveMemoryBytes > 0)) effectiveMemoryBytes = null;
|
|
439
579
|
if (process.platform === "darwin") {
|
|
440
580
|
try {
|
|
441
|
-
const { stdout } = await
|
|
581
|
+
const { stdout } = await execute3("/usr/sbin/sysctl", ["-n", "kern.memorystatus_vm_pressure_level"], { timeout: 2e3 });
|
|
442
582
|
pressure = { 1: "normal", 2: "high", 4: "critical" }[Number(stdout.trim())] ?? "unknown";
|
|
443
583
|
} catch {
|
|
444
584
|
}
|
|
445
585
|
if (pressure === "normal" && availableMemoryBytes !== null && effectiveMemoryBytes !== null) {
|
|
446
586
|
try {
|
|
447
|
-
const { stdout } = await
|
|
448
|
-
|
|
449
|
-
const pageCounts = ["free", "purgeable"].map((kind) => Number(new RegExp(`^Pages ${kind}:\\s+(\\d+)\\.`, "m").exec(stdout)?.[1]));
|
|
450
|
-
const reclaimable = pageBytes * pageCounts.reduce((sum, pages) => sum + pages, 0);
|
|
451
|
-
if (Number.isSafeInteger(reclaimable) && reclaimable > 0) {
|
|
452
|
-
availableMemoryBytes = Math.min(effectiveMemoryBytes, Math.max(availableMemoryBytes, reclaimable));
|
|
453
|
-
}
|
|
587
|
+
const { stdout } = await execute3("/usr/bin/vm_stat", [], { timeout: 2e3 });
|
|
588
|
+
availableMemoryBytes = darwinAvailableMemory(availableMemoryBytes, effectiveMemoryBytes, pressure, stdout, limit > 0);
|
|
454
589
|
} catch {
|
|
455
590
|
}
|
|
456
591
|
}
|
|
@@ -464,6 +599,16 @@ async function sampleResources(paths) {
|
|
|
464
599
|
} catch {
|
|
465
600
|
}
|
|
466
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
|
+
}
|
|
467
612
|
const disks = [];
|
|
468
613
|
for (const path of [...new Set(paths.map((p) => resolve2(p)))]) {
|
|
469
614
|
try {
|
|
@@ -538,10 +683,10 @@ function evaluateResources(sample, policy, costs, phase) {
|
|
|
538
683
|
|
|
539
684
|
// runtime/npm.mjs
|
|
540
685
|
import { existsSync } from "node:fs";
|
|
541
|
-
import { dirname as dirname4, join as
|
|
686
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
542
687
|
function npmCommand() {
|
|
543
688
|
if (process.platform !== "win32") return { command: "npm", prefix: [] };
|
|
544
|
-
const cli =
|
|
689
|
+
const cli = join3(dirname4(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
|
|
545
690
|
if (existsSync(cli)) return { command: process.execPath, prefix: [cli] };
|
|
546
691
|
return { command: process.env.ComSpec ?? "cmd.exe", prefix: ["/d", "/s", "/c", "npm"] };
|
|
547
692
|
}
|
|
@@ -596,29 +741,33 @@ async function ownedDirectory(path, owner) {
|
|
|
596
741
|
await mkdir3(resolve3(path, ".."), { recursive: true, mode: 448 });
|
|
597
742
|
try {
|
|
598
743
|
await mkdir3(path, { mode: 448 });
|
|
599
|
-
await atomicJSON(
|
|
744
|
+
await atomicJSON(join4(path, ".huohuo-owner.json"), { format: 1, owner });
|
|
600
745
|
} catch (e) {
|
|
601
746
|
if (e.code !== "EEXIST") throw e;
|
|
602
|
-
const marker = JSON.parse(await readFile4(
|
|
747
|
+
const marker = JSON.parse(await readFile4(join4(path, ".huohuo-owner.json"), "utf8"));
|
|
603
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");
|
|
604
749
|
}
|
|
605
750
|
}
|
|
606
|
-
async function run(command, args, cwd, env = process.env) {
|
|
751
|
+
async function run(command, args, cwd, env = process.env, onInterrupt) {
|
|
607
752
|
await new Promise((done, fail) => {
|
|
608
753
|
const child = spawn(command, args, { cwd, env, stdio: "inherit", detached: process.platform !== "win32" });
|
|
609
754
|
let interrupted = false;
|
|
610
755
|
let timer;
|
|
611
756
|
const kill = (signal) => {
|
|
612
|
-
|
|
613
|
-
try {
|
|
614
|
-
process.kill(process.platform === "win32" ? child.pid : -child.pid, signal);
|
|
615
|
-
} catch {
|
|
616
|
-
}
|
|
757
|
+
interruptProcessTree(child, signal);
|
|
617
758
|
};
|
|
618
759
|
const stop = () => {
|
|
760
|
+
if (interrupted) return;
|
|
619
761
|
interrupted = true;
|
|
620
|
-
|
|
621
|
-
|
|
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
|
+
}
|
|
622
771
|
};
|
|
623
772
|
process.on("SIGINT", stop);
|
|
624
773
|
process.on("SIGTERM", stop);
|
|
@@ -633,14 +782,14 @@ async function run(command, args, cwd, env = process.env) {
|
|
|
633
782
|
});
|
|
634
783
|
child.once("close", (code) => {
|
|
635
784
|
cleanup();
|
|
636
|
-
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}` }));
|
|
637
786
|
});
|
|
638
787
|
});
|
|
639
788
|
}
|
|
640
789
|
async function readInstallation(home, recordFile = "installation.json") {
|
|
641
790
|
let record;
|
|
642
791
|
try {
|
|
643
|
-
record = JSON.parse(await readFile4(
|
|
792
|
+
record = JSON.parse(await readFile4(join4(home, recordFile), "utf8"));
|
|
644
793
|
} catch (e) {
|
|
645
794
|
if (e.code === "ENOENT") return;
|
|
646
795
|
throw new Error("\u5B89\u88C5\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u672A\u91CD\u5EFA\u5B9E\u4F8B", { cause: e });
|
|
@@ -652,15 +801,15 @@ async function readInstallation(home, recordFile = "installation.json") {
|
|
|
652
801
|
}
|
|
653
802
|
function installationPaths(home, release) {
|
|
654
803
|
const id = `${release.version}-${digest(release).slice(0, 16)}`;
|
|
655
|
-
const directory =
|
|
804
|
+
const directory = join4(home, "releases", id);
|
|
656
805
|
const dependencyBundle = release.pythonDependencies.bundled ? `-${release.pythonDependencies.sha256.slice(0, 16)}` : "";
|
|
657
806
|
return {
|
|
658
807
|
directory,
|
|
659
|
-
runtime:
|
|
660
|
-
python:
|
|
661
|
-
environment:
|
|
662
|
-
archive:
|
|
663
|
-
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")
|
|
664
813
|
};
|
|
665
814
|
}
|
|
666
815
|
function versionAtLeast(actual, minimum) {
|
|
@@ -682,20 +831,20 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
682
831
|
home = resolve3(home);
|
|
683
832
|
await mkdir3(home, { recursive: true, mode: 448 });
|
|
684
833
|
home = await realpath3(home);
|
|
685
|
-
return withLock(
|
|
834
|
+
return withLock(join4(home, "state/install.lock"), async () => {
|
|
686
835
|
if (!programsOnly) {
|
|
687
836
|
try {
|
|
688
|
-
await access(
|
|
837
|
+
await access(join4(home, "state/default-run.lock"));
|
|
689
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");
|
|
690
839
|
} catch (e) {
|
|
691
840
|
if (e.code !== "ENOENT") throw e;
|
|
692
841
|
}
|
|
693
842
|
}
|
|
694
843
|
const recordFile = programsOnly ? "state/upgrade-target.json" : "installation.json";
|
|
695
|
-
const recordPath =
|
|
844
|
+
const recordPath = join4(home, recordFile);
|
|
696
845
|
let record = await readInstallation(home, recordFile);
|
|
697
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");
|
|
698
|
-
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");
|
|
699
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");
|
|
700
849
|
const bundleDirectory = hooks.bundleDirectory;
|
|
701
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")) {
|
|
@@ -712,12 +861,12 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
712
861
|
const oldAssets = bundled(record.release);
|
|
713
862
|
const available = await Promise.all(oldAssets.map(async (artifact2) => {
|
|
714
863
|
const file = artifact2.sha256 + ".tgz";
|
|
715
|
-
return await hasFile(
|
|
864
|
+
return await hasFile(join4(bundleDirectory, file)) || await hasFile(join4(home, "cache", file));
|
|
716
865
|
}));
|
|
717
866
|
if (available.includes(false)) {
|
|
718
867
|
const next = validateRelease(candidate);
|
|
719
868
|
const nextAssets = bundled(next);
|
|
720
|
-
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)) {
|
|
721
870
|
record = void 0;
|
|
722
871
|
hooks.progress?.("resume-current-package");
|
|
723
872
|
}
|
|
@@ -731,12 +880,12 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
731
880
|
record ??= { format: 1, release, releaseDigest: digest(release), home, status: "installing", stage: "preflight", stages: {} };
|
|
732
881
|
await atomicJSON(recordPath, record);
|
|
733
882
|
const paths = installationPaths(home, release);
|
|
734
|
-
const
|
|
883
|
+
const execute5 = hooks.run ?? run;
|
|
735
884
|
const sample = hooks.sample ?? sampleResources;
|
|
736
885
|
const files = [
|
|
737
886
|
{ name: "\u706B\u706B\u8FD0\u884C\u5305", artifact: release.runtime, path: paths.archive },
|
|
738
887
|
{ name: "Python \u8FD0\u884C\u73AF\u5883", artifact: release.python, path: paths.pythonArchive },
|
|
739
|
-
{ 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") }
|
|
740
889
|
];
|
|
741
890
|
const withActivity = async (label, action) => {
|
|
742
891
|
hooks.progress?.(label);
|
|
@@ -747,7 +896,7 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
747
896
|
if (timer) clearInterval(timer);
|
|
748
897
|
}
|
|
749
898
|
};
|
|
750
|
-
const
|
|
899
|
+
const cached2 = async (path, artifact2) => {
|
|
751
900
|
try {
|
|
752
901
|
await verifyArtifact(path, artifact2);
|
|
753
902
|
return true;
|
|
@@ -758,22 +907,22 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
758
907
|
};
|
|
759
908
|
const installed = async () => {
|
|
760
909
|
try {
|
|
761
|
-
const owner = JSON.parse(await readFile4(
|
|
762
|
-
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));
|
|
763
912
|
const python = owner.owner === release.python.sha256;
|
|
764
|
-
const root =
|
|
765
|
-
const initialization = JSON.parse(await readFile4(
|
|
766
|
-
const dependencies = JSON.parse(await readFile4(
|
|
767
|
-
const worker = JSON.parse(await readFile4(
|
|
768
|
-
const chat = JSON.parse(await readFile4(
|
|
769
|
-
for (const profile of ["huohuo-chat", "huohuo-worker", "huohuo-strategy-worker"]) await access(
|
|
770
|
-
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"));
|
|
771
920
|
return { python, dependencies: initialization.package_sha256 === release.runtime.sha256 && initialization.status === "complete" && dependencies.sha256 === release.pythonDependencies.sha256 && worker.complete === true && chat.complete === true };
|
|
772
921
|
} catch (error) {
|
|
773
922
|
if (error.code !== "ENOENT") throw error;
|
|
774
923
|
try {
|
|
775
|
-
const owner = JSON.parse(await readFile4(
|
|
776
|
-
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));
|
|
777
926
|
return { python: owner.owner === release.python.sha256, dependencies: false };
|
|
778
927
|
} catch {
|
|
779
928
|
return { python: false, dependencies: false };
|
|
@@ -781,19 +930,19 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
781
930
|
}
|
|
782
931
|
};
|
|
783
932
|
const check = async () => {
|
|
784
|
-
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()]);
|
|
785
934
|
const remainingDependencies = inventory.dependencies ? 0 : release.budget.dependenciesBytes;
|
|
786
935
|
const costs = [
|
|
787
|
-
{ path:
|
|
936
|
+
{ path: join4(home, "cache"), bytes: files.reduce((sum, file, index) => sum + (cacheHits[index] ? 0 : file.artifact.downloadBytes), 0) },
|
|
788
937
|
{ path: paths.directory, bytes: inventory.dependencies ? 0 : release.runtime.expandedBytes + release.budget.installTemporaryBytes },
|
|
789
938
|
{ path: paths.python, bytes: inventory.python ? 0 : release.python.expandedBytes },
|
|
790
939
|
{ path: paths.environment, bytes: 0 },
|
|
791
|
-
{ path:
|
|
940
|
+
{ path: join4(home, "instances/default"), bytes: 0 },
|
|
792
941
|
{ path: paths.directory, bytes: inventory.dependencies ? 0 : release.pythonDependencies.expandedBytes },
|
|
793
|
-
{ path:
|
|
942
|
+
{ path: join4(home, "backups"), bytes: release.budget.backupBytes }
|
|
794
943
|
];
|
|
795
944
|
const observation = await sample(costs.map((c) => c.path));
|
|
796
|
-
const dependencyPaths = [paths.directory, paths.environment,
|
|
945
|
+
const dependencyPaths = [paths.directory, paths.environment, join4(home, "instances/default")];
|
|
797
946
|
const volumes = /* @__PURE__ */ new Set();
|
|
798
947
|
for (const path of dependencyPaths) {
|
|
799
948
|
const device = diskBudgets(observation, [{ path, bytes: 0 }], 0)[0].device;
|
|
@@ -803,7 +952,7 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
803
952
|
}
|
|
804
953
|
}
|
|
805
954
|
const result = evaluateResources(observation, release.budget, costs, "install");
|
|
806
|
-
await atomicJSON(
|
|
955
|
+
await atomicJSON(join4(home, "state/resources.json"), result);
|
|
807
956
|
if (result.status === "unknown" || result.status === "blocked") {
|
|
808
957
|
const details = result.reasons.map((reason) => `- ${reason}`).join("\n");
|
|
809
958
|
throw Object.assign(new Error(`\u706B\u706B\u6682\u65F6\u65E0\u6CD5\u7EE7\u7EED\u5B89\u88C5\u3002
|
|
@@ -850,9 +999,9 @@ ${details}
|
|
|
850
999
|
};
|
|
851
1000
|
await stage("preflight", async (plan) => {
|
|
852
1001
|
const npm = npmCommand();
|
|
853
|
-
const { stdout } = await
|
|
1002
|
+
const { stdout } = await promisify4(execFile5)(npm.command, [...npm.prefix, "--version"], { timeout: 5e3 });
|
|
854
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");
|
|
855
|
-
const probe =
|
|
1004
|
+
const probe = join4(home, `.write-probe-${randomUUID3()}`);
|
|
856
1005
|
try {
|
|
857
1006
|
await atomicJSON(probe, { probe: true });
|
|
858
1007
|
} finally {
|
|
@@ -882,19 +1031,19 @@ ${details}
|
|
|
882
1031
|
await stage("install-runtime", async () => {
|
|
883
1032
|
try {
|
|
884
1033
|
await access(paths.python);
|
|
885
|
-
const marker = JSON.parse(await readFile4(
|
|
1034
|
+
const marker = JSON.parse(await readFile4(join4(paths.python, ".huohuo-owner.json"), "utf8"));
|
|
886
1035
|
if (marker.owner !== release.python.sha256) throw new Error("\u53D7\u7BA1 Python \u5F52\u5C5E\u4E0D\u7B26");
|
|
887
|
-
await access(
|
|
1036
|
+
await access(join4(paths.python, release.python.executable));
|
|
888
1037
|
return release.python.sha256;
|
|
889
1038
|
} catch (e) {
|
|
890
1039
|
if (e.code !== "ENOENT") throw e;
|
|
891
1040
|
}
|
|
892
|
-
await mkdir3(
|
|
1041
|
+
await mkdir3(join4(home, "runtimes/python"), { recursive: true, mode: 448 });
|
|
893
1042
|
const staging = `${paths.python}.${randomUUID3()}.staging`;
|
|
894
1043
|
try {
|
|
895
1044
|
await extractArchive(paths.pythonArchive, staging, release.python.expandedBytes);
|
|
896
|
-
await access(
|
|
897
|
-
await atomicJSON(
|
|
1045
|
+
await access(join4(staging, release.python.executable));
|
|
1046
|
+
await atomicJSON(join4(staging, ".huohuo-owner.json"), { format: 1, owner: release.python.sha256 });
|
|
898
1047
|
await rename3(staging, paths.python);
|
|
899
1048
|
} finally {
|
|
900
1049
|
await rm2(staging, { recursive: true, force: true });
|
|
@@ -903,30 +1052,30 @@ ${details}
|
|
|
903
1052
|
});
|
|
904
1053
|
await stage("install-dependencies", async () => {
|
|
905
1054
|
await ownedDirectory(paths.directory, record.releaseDigest);
|
|
906
|
-
await atomicJSON(
|
|
1055
|
+
await atomicJSON(join4(paths.directory, "package.json"), {
|
|
907
1056
|
name: "huohuo-managed-release",
|
|
908
1057
|
private: true,
|
|
909
1058
|
dependencies: { "@huohuo/research-agent": `file:${paths.archive}` }
|
|
910
1059
|
});
|
|
911
1060
|
const npm = npmCommand();
|
|
912
|
-
await withActivity("\u5B89\u88C5 npm \u4F9D\u8D56", () =>
|
|
913
|
-
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"));
|
|
914
1063
|
if (runtime.name !== release.runtime.name || runtime.version !== release.version) throw new Error("\u8FD0\u884C\u5305\u8EAB\u4EFD\u4E0D\u7B26");
|
|
915
|
-
const requirements = await readFile4(
|
|
1064
|
+
const requirements = await readFile4(join4(paths.runtime, "sidecar/requirements.lock"));
|
|
916
1065
|
if (createHash2("sha256").update(requirements).digest("hex") !== release.requirementsSha256) throw new Error("Python \u4F9D\u8D56\u9501\u6458\u8981\u4E0D\u7B26");
|
|
917
|
-
const assembly = await readFile4(
|
|
1066
|
+
const assembly = await readFile4(join4(paths.runtime, "npm-shrinkwrap.json"));
|
|
918
1067
|
if (createHash2("sha256").update(assembly).digest("hex") !== release.dsh.assemblySha256) throw new Error("DSH \u88C5\u914D\u9501\u6458\u8981\u4E0D\u7B26");
|
|
919
1068
|
await ownedDirectory(paths.environment, `${release.python.sha256}:${release.requirementsSha256}`);
|
|
920
|
-
const python =
|
|
921
|
-
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}`);
|
|
922
1071
|
try {
|
|
923
|
-
await access(
|
|
1072
|
+
await access(join4(wheels, "requirements.txt"));
|
|
924
1073
|
} catch (e) {
|
|
925
1074
|
if (e.code !== "ENOENT") throw e;
|
|
926
1075
|
const staging = `${wheels}.${randomUUID3()}.staging`;
|
|
927
1076
|
try {
|
|
928
|
-
await extractArchive(
|
|
929
|
-
await access(
|
|
1077
|
+
await extractArchive(join4(home, "cache", release.pythonDependencies.sha256 + ".tgz"), staging, release.pythonDependencies.expandedBytes);
|
|
1078
|
+
await access(join4(staging, "requirements.txt"));
|
|
930
1079
|
await rename3(staging, wheels);
|
|
931
1080
|
} finally {
|
|
932
1081
|
await rm2(staging, { recursive: true, force: true });
|
|
@@ -934,7 +1083,7 @@ ${details}
|
|
|
934
1083
|
}
|
|
935
1084
|
let environmentReady = false;
|
|
936
1085
|
try {
|
|
937
|
-
const ready = JSON.parse(await readFile4(
|
|
1086
|
+
const ready = JSON.parse(await readFile4(join4(paths.environment, ".dependencies-ready.json"), "utf8"));
|
|
938
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");
|
|
939
1088
|
await access(python);
|
|
940
1089
|
environmentReady = true;
|
|
@@ -942,13 +1091,13 @@ ${details}
|
|
|
942
1091
|
if (e.code !== "ENOENT") throw e;
|
|
943
1092
|
}
|
|
944
1093
|
if (!environmentReady) {
|
|
945
|
-
await
|
|
1094
|
+
await execute5(join4(paths.python, release.python.executable), ["-m", "venv", paths.environment], home);
|
|
946
1095
|
if (release.pythonDependencies.bundled && hooks.progress) {
|
|
947
|
-
const lock = await readFile4(
|
|
1096
|
+
const lock = await readFile4(join4(wheels, "requirements.txt"), "utf8");
|
|
948
1097
|
const upstream = lock.split("\n").filter((line) => /^[\w.-]+\s+@\s+https:\/\//.test(line)).length;
|
|
949
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`);
|
|
950
1099
|
}
|
|
951
|
-
await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () =>
|
|
1100
|
+
await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () => execute5(python, [
|
|
952
1101
|
"-m",
|
|
953
1102
|
"pip",
|
|
954
1103
|
"--isolated",
|
|
@@ -960,11 +1109,15 @@ ${details}
|
|
|
960
1109
|
"--only-binary=:all:",
|
|
961
1110
|
...release.pythonDependencies.bundled ? ["--no-cache-dir"] : [],
|
|
962
1111
|
"-r",
|
|
963
|
-
|
|
1112
|
+
join4(wheels, "requirements.txt")
|
|
964
1113
|
], home));
|
|
965
1114
|
}
|
|
966
|
-
await
|
|
967
|
-
|
|
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 });
|
|
968
1121
|
return { python, runtime: release.runtime.sha256 };
|
|
969
1122
|
});
|
|
970
1123
|
if (programsOnly) {
|
|
@@ -974,11 +1127,11 @@ ${details}
|
|
|
974
1127
|
return record;
|
|
975
1128
|
}
|
|
976
1129
|
await stage("assemble-profiles", async () => {
|
|
977
|
-
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));
|
|
978
1131
|
return { release: record.releaseDigest };
|
|
979
1132
|
});
|
|
980
1133
|
await stage("verify", async () => {
|
|
981
|
-
await
|
|
1134
|
+
await execute5(process.execPath, [join4(paths.runtime, "lib/distribution/managed.js"), "verify-live", home], home);
|
|
982
1135
|
return { release: record.releaseDigest };
|
|
983
1136
|
});
|
|
984
1137
|
record.status = "ready";
|
|
@@ -991,14 +1144,14 @@ ${details}
|
|
|
991
1144
|
// distribution/bootstrap/control.ts
|
|
992
1145
|
import { createServer, request as httpRequest } from "node:http";
|
|
993
1146
|
import { readFile as readFile5, rm as rm3 } from "node:fs/promises";
|
|
994
|
-
import { join as
|
|
995
|
-
import { execFile as
|
|
996
|
-
import { promisify as
|
|
997
|
-
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);
|
|
998
1151
|
async function control(home, releaseDigest, command) {
|
|
999
1152
|
let binding;
|
|
1000
1153
|
try {
|
|
1001
|
-
binding = JSON.parse(await readFile5(
|
|
1154
|
+
binding = JSON.parse(await readFile5(join5(home, "state/default-control.json"), "utf8"));
|
|
1002
1155
|
} catch (e) {
|
|
1003
1156
|
if (e.code === "ENOENT") return false;
|
|
1004
1157
|
throw new Error("\u672C\u673A\u8FD0\u884C\u8BB0\u5F55\u65E0\u6CD5\u6838\u9A8C");
|
|
@@ -1034,7 +1187,7 @@ async function control(home, releaseDigest, command) {
|
|
|
1034
1187
|
req.end();
|
|
1035
1188
|
});
|
|
1036
1189
|
} catch (error) {
|
|
1037
|
-
if (error.code === "ECONNREFUSED" && await isLockOwnerDead(
|
|
1190
|
+
if (error.code === "ECONNREFUSED" && await isLockOwnerDead(join5(home, "state/default-run.lock"), binding.runOwner)) return false;
|
|
1038
1191
|
throw error;
|
|
1039
1192
|
}
|
|
1040
1193
|
return true;
|
|
@@ -1042,13 +1195,13 @@ async function control(home, releaseDigest, command) {
|
|
|
1042
1195
|
|
|
1043
1196
|
// distribution/bootstrap/maintenance.ts
|
|
1044
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";
|
|
1045
|
-
import { join as
|
|
1198
|
+
import { join as join6, resolve as resolve4, sep as sep3 } from "node:path";
|
|
1046
1199
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1047
1200
|
import { setTimeout as delay } from "node:timers/promises";
|
|
1048
1201
|
async function upgradeJournal(home) {
|
|
1049
1202
|
try {
|
|
1050
|
-
const value = JSON.parse(await readFile6(
|
|
1051
|
-
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");
|
|
1052
1205
|
return value;
|
|
1053
1206
|
} catch (error) {
|
|
1054
1207
|
if (error.code === "ENOENT") return;
|
|
@@ -1072,7 +1225,7 @@ async function stopOwned(home, releaseDigest) {
|
|
|
1072
1225
|
if (!await control(home, releaseDigest, "stop")) return;
|
|
1073
1226
|
for (let i = 0; i < 100; i++) {
|
|
1074
1227
|
try {
|
|
1075
|
-
await access2(
|
|
1228
|
+
await access2(join6(home, "state/default-run.lock"));
|
|
1076
1229
|
} catch (error) {
|
|
1077
1230
|
if (error.code === "ENOENT") return;
|
|
1078
1231
|
throw error;
|
|
@@ -1087,9 +1240,9 @@ async function backupBytes(directory) {
|
|
|
1087
1240
|
for (const item of await readdir2(path, { withFileTypes: true })) {
|
|
1088
1241
|
if (["node_modules", "cache", "python", "__pycache__"].includes(item.name)) continue;
|
|
1089
1242
|
if (item.isSymbolicLink()) throw new Error("\u5907\u4EFD\u4E0D\u63A5\u7BA1\u672A\u77E5\u7684\u5B9E\u4F8B\u94FE\u63A5");
|
|
1090
|
-
if (item.isDirectory()) await visit(
|
|
1243
|
+
if (item.isDirectory()) await visit(join6(path, item.name));
|
|
1091
1244
|
else {
|
|
1092
|
-
const info = await lstat2(
|
|
1245
|
+
const info = await lstat2(join6(path, item.name));
|
|
1093
1246
|
bytes += info.size + 512;
|
|
1094
1247
|
}
|
|
1095
1248
|
}
|
|
@@ -1107,19 +1260,19 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1107
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");
|
|
1108
1261
|
if (current.releaseDigest === digest(next)) {
|
|
1109
1262
|
if (journal && journal.to === current.releaseDigest && journal.stage !== "complete") {
|
|
1110
|
-
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);
|
|
1111
1264
|
journal.stage = "complete";
|
|
1112
|
-
await atomicJSON(
|
|
1265
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1113
1266
|
}
|
|
1114
1267
|
hooks.progress?.("\u5DF2\u7ECF\u662F\u540C\u4E00\u53D1\u884C\uFF0C\u65E0\u9700\u8FC1\u79FB");
|
|
1115
1268
|
return;
|
|
1116
1269
|
}
|
|
1117
1270
|
assertCompatibleUpgrade(current.release, next);
|
|
1118
1271
|
if (journal?.stage === "complete") {
|
|
1119
|
-
await withLock(
|
|
1120
|
-
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`));
|
|
1121
1274
|
try {
|
|
1122
|
-
await rename4(
|
|
1275
|
+
await rename4(join6(home, "state/upgrade-target.json"), join6(home, `state/upgrade-target-${randomUUID4()}.json`));
|
|
1123
1276
|
} catch (error) {
|
|
1124
1277
|
if (error.code !== "ENOENT") throw error;
|
|
1125
1278
|
}
|
|
@@ -1128,58 +1281,58 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1128
1281
|
}
|
|
1129
1282
|
const target = await install(home, next, hooks, true);
|
|
1130
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");
|
|
1131
|
-
const
|
|
1132
|
-
await withLock(
|
|
1284
|
+
const execute5 = hooks.run ?? run;
|
|
1285
|
+
await withLock(join6(home, "state/install.lock"), async () => {
|
|
1133
1286
|
current = await readInstallation(home);
|
|
1134
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");
|
|
1135
1288
|
journal ??= {
|
|
1136
1289
|
format: 1,
|
|
1137
1290
|
from: current.releaseDigest,
|
|
1138
1291
|
to: target.releaseDigest,
|
|
1139
|
-
backup:
|
|
1292
|
+
backup: join6(home, "backups", `upgrade-${target.releaseDigest.slice(0, 16)}-${randomUUID4()}`),
|
|
1140
1293
|
stage: "stopping"
|
|
1141
1294
|
};
|
|
1142
|
-
await atomicJSON(
|
|
1295
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1143
1296
|
await stopOwned(home, current.releaseDigest);
|
|
1144
|
-
const releaseRun = await acquireLock(
|
|
1297
|
+
const releaseRun = await acquireLock(join6(home, "state/default-run.lock"));
|
|
1145
1298
|
try {
|
|
1146
|
-
const bytes = await backupBytes(
|
|
1299
|
+
const bytes = await backupBytes(join6(home, "instances/default"));
|
|
1147
1300
|
const result = evaluateResources(
|
|
1148
|
-
await (hooks.sample ?? sampleResources)([
|
|
1301
|
+
await (hooks.sample ?? sampleResources)([join6(home, "backups")]),
|
|
1149
1302
|
target.release.budget,
|
|
1150
|
-
[{ path:
|
|
1303
|
+
[{ path: join6(home, "backups"), bytes }],
|
|
1151
1304
|
"install"
|
|
1152
1305
|
);
|
|
1153
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");
|
|
1154
1307
|
if (journal.stage === "stopping") {
|
|
1155
|
-
await
|
|
1308
|
+
await execute5(process.execPath, [join6(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "backup", home], home);
|
|
1156
1309
|
journal.stage = "backed-up";
|
|
1157
|
-
await atomicJSON(
|
|
1310
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1158
1311
|
}
|
|
1159
1312
|
const runtime = installationPaths(home, target.release).runtime;
|
|
1160
|
-
await
|
|
1313
|
+
await execute5(process.execPath, [join6(runtime, "lib/distribution/managed.js"), "prepare-upgrade", home], home);
|
|
1161
1314
|
journal.stage = "profiles";
|
|
1162
|
-
await atomicJSON(
|
|
1315
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1163
1316
|
if (current.release.schema.migrationsSha256 !== target.release.schema.migrationsSha256) {
|
|
1164
1317
|
const space = evaluateResources(
|
|
1165
|
-
await (hooks.sample ?? sampleResources)([
|
|
1318
|
+
await (hooks.sample ?? sampleResources)([join6(home, "instances/default")]),
|
|
1166
1319
|
target.release.budget,
|
|
1167
|
-
[{ path:
|
|
1320
|
+
[{ path: join6(home, "instances/default"), bytes: bytes + target.release.budget.installTemporaryBytes }],
|
|
1168
1321
|
"install"
|
|
1169
1322
|
);
|
|
1170
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");
|
|
1171
|
-
await
|
|
1324
|
+
await execute5(process.execPath, [join6(runtime, "lib/distribution/managed.js"), "migrate-upgrade", home], home);
|
|
1172
1325
|
}
|
|
1173
1326
|
journal.stage = "migrated";
|
|
1174
|
-
await atomicJSON(
|
|
1175
|
-
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);
|
|
1176
1329
|
journal.stage = "verified";
|
|
1177
|
-
await atomicJSON(
|
|
1330
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1178
1331
|
target.status = "ready";
|
|
1179
1332
|
target.stage = "ready";
|
|
1180
|
-
await atomicJSON(
|
|
1333
|
+
await atomicJSON(join6(home, "installation.json"), target);
|
|
1181
1334
|
journal.stage = "complete";
|
|
1182
|
-
await atomicJSON(
|
|
1335
|
+
await atomicJSON(join6(home, "state/upgrade.json"), journal);
|
|
1183
1336
|
hooks.progress?.("\u5347\u7EA7\u9A8C\u8BC1\u901A\u8FC7\uFF0C\u5DF2\u5207\u6362\u53D1\u884C\u7ED1\u5B9A\uFF1B\u5907\u4EFD\u4FDD\u7559");
|
|
1184
1337
|
} finally {
|
|
1185
1338
|
await releaseRun();
|
|
@@ -1188,7 +1341,7 @@ async function upgrade(home, candidate, hooks = {}) {
|
|
|
1188
1341
|
}
|
|
1189
1342
|
async function removeOwned(path, expected) {
|
|
1190
1343
|
try {
|
|
1191
|
-
const marker = JSON.parse(await readFile6(
|
|
1344
|
+
const marker = JSON.parse(await readFile6(join6(path, ".huohuo-owner.json"), "utf8"));
|
|
1192
1345
|
if (marker.format !== 1 || marker.owner !== expected) throw new Error("\u53D7\u7BA1\u76EE\u5F55\u5F52\u5C5E\u4E0D\u7B26\uFF0C\u672A\u5220\u9664");
|
|
1193
1346
|
await rm4(path, { recursive: true });
|
|
1194
1347
|
} catch (error) {
|
|
@@ -1197,25 +1350,25 @@ async function removeOwned(path, expected) {
|
|
|
1197
1350
|
}
|
|
1198
1351
|
async function uninstall(home, hooks = {}) {
|
|
1199
1352
|
home = await realpath4(resolve4(home));
|
|
1200
|
-
await withLock(
|
|
1353
|
+
await withLock(join6(home, "state/install.lock"), async () => {
|
|
1201
1354
|
const current = await readInstallation(home);
|
|
1202
1355
|
if (!current) throw new Error("\u6CA1\u6709\u53EF\u6838\u9A8C\u7684\u6301\u4E45\u5B89\u88C5");
|
|
1203
1356
|
const journal = await upgradeJournal(home);
|
|
1204
1357
|
if (journal && journal.stage !== "complete") throw new Error("\u5347\u7EA7\u5C1A\u672A\u5BF9\u8D26\uFF0C\u4E0D\u80FD\u5378\u8F7D\u7A0B\u5E8F");
|
|
1205
1358
|
await stopOwned(home, current.releaseDigest);
|
|
1206
|
-
const releaseRun = await acquireLock(
|
|
1359
|
+
const releaseRun = await acquireLock(join6(home, "state/default-run.lock"));
|
|
1207
1360
|
try {
|
|
1208
1361
|
const paths = installationPaths(home, current.release);
|
|
1209
|
-
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");
|
|
1210
1363
|
if (current.status !== "uninstalled") {
|
|
1211
|
-
await (hooks.run ?? run)(process.execPath, [
|
|
1364
|
+
await (hooks.run ?? run)(process.execPath, [join6(paths.runtime, "lib/distribution/managed.js"), "uninstall-programs", home], home);
|
|
1212
1365
|
current.status = "uninstalled";
|
|
1213
1366
|
current.stage = "uninstalled";
|
|
1214
|
-
await atomicJSON(
|
|
1367
|
+
await atomicJSON(join6(home, "installation.json"), current);
|
|
1215
1368
|
}
|
|
1216
1369
|
await removeOwned(paths.directory, current.releaseDigest);
|
|
1217
1370
|
await removeOwned(paths.environment, `${current.release.python.sha256}:${current.release.requirementsSha256}`);
|
|
1218
|
-
if (!(await readdir2(
|
|
1371
|
+
if (!(await readdir2(join6(home, "releases"))).length) await removeOwned(paths.python, current.release.python.sha256);
|
|
1219
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}`);
|
|
1220
1373
|
} finally {
|
|
1221
1374
|
await releaseRun();
|
|
@@ -1226,7 +1379,7 @@ async function uninstall(home, hooks = {}) {
|
|
|
1226
1379
|
// distribution/bootstrap/install-ui.ts
|
|
1227
1380
|
import { createInterface } from "node:readline";
|
|
1228
1381
|
import { homedir } from "node:os";
|
|
1229
|
-
import { relative, sep as
|
|
1382
|
+
import { relative, sep as sep4 } from "node:path";
|
|
1230
1383
|
var gb = (bytes) => bytes === null ? "\u672A\u77E5" : `${(bytes / 1e9).toFixed(2)} GB`;
|
|
1231
1384
|
var mb = (bytes) => bytes < 1e5 ? `${(bytes / 1e3).toFixed(1)} KB` : `${(bytes / 1e6).toFixed(1)} MB`;
|
|
1232
1385
|
var memory = (bytes) => {
|
|
@@ -1236,7 +1389,7 @@ var memory = (bytes) => {
|
|
|
1236
1389
|
function displayPath(path) {
|
|
1237
1390
|
const local = relative(homedir(), path);
|
|
1238
1391
|
if (!local) return "~";
|
|
1239
|
-
return local === ".." || local.startsWith(`..${
|
|
1392
|
+
return local === ".." || local.startsWith(`..${sep4}`) ? path : `~${sep4}${local}`;
|
|
1240
1393
|
}
|
|
1241
1394
|
function installUI(assumeYes) {
|
|
1242
1395
|
const interactive = process.stdin.isTTY && process.stderr.isTTY;
|
|
@@ -1339,7 +1492,7 @@ function installUI(assumeYes) {
|
|
|
1339
1492
|
|
|
1340
1493
|
// distribution/bootstrap/status-ui.ts
|
|
1341
1494
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
1342
|
-
import { join as
|
|
1495
|
+
import { join as join7 } from "node:path";
|
|
1343
1496
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
1344
1497
|
var mib = (bytes) => typeof bytes === "number" && Number.isFinite(bytes) ? `${Math.round(bytes / 2 ** 20)} MiB` : "\u672A\u77E5";
|
|
1345
1498
|
var pressureLabels = { normal: "\u6B63\u5E38", high: "\u504F\u9AD8", critical: "\u4E25\u91CD", unknown: "\u672A\u77E5" };
|
|
@@ -1350,7 +1503,7 @@ async function statusLines(home) {
|
|
|
1350
1503
|
if (installed.status !== "ready") return [title, "\u5B89\u88C5\u5C1A\u672A\u5B8C\u6210\u3002"];
|
|
1351
1504
|
let admission;
|
|
1352
1505
|
try {
|
|
1353
|
-
admission = JSON.parse(await readFile7(
|
|
1506
|
+
admission = JSON.parse(await readFile7(join7(home, "state/default-admission.json"), "utf8"));
|
|
1354
1507
|
} catch (error) {
|
|
1355
1508
|
if (error.code !== "ENOENT") return [title, "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u8D44\u6E90\u72B6\u6001\u3002"];
|
|
1356
1509
|
}
|
|
@@ -1419,10 +1572,10 @@ async function showStatus(home, watch) {
|
|
|
1419
1572
|
// distribution/bootstrap/assets.ts
|
|
1420
1573
|
import { createRequire } from "node:module";
|
|
1421
1574
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
1422
|
-
import { dirname as dirname5, join as
|
|
1575
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
1423
1576
|
import { fileURLToPath } from "node:url";
|
|
1424
1577
|
async function assetsDirectory(release, entryURL = import.meta.url) {
|
|
1425
|
-
if (!release?.assetPackage) return
|
|
1578
|
+
if (!release?.assetPackage) return join8(dirname5(fileURLToPath(entryURL)), "assets");
|
|
1426
1579
|
const expected = release.assetPackage;
|
|
1427
1580
|
let file;
|
|
1428
1581
|
try {
|
|
@@ -1434,7 +1587,7 @@ async function assetsDirectory(release, entryURL = import.meta.url) {
|
|
|
1434
1587
|
if (manifest.name !== expected.name || manifest.version !== expected.version) {
|
|
1435
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");
|
|
1436
1589
|
}
|
|
1437
|
-
return
|
|
1590
|
+
return join8(dirname5(file), "assets");
|
|
1438
1591
|
}
|
|
1439
1592
|
|
|
1440
1593
|
// distribution/bootstrap/entry.ts
|
|
@@ -1446,7 +1599,7 @@ function artifactSource(source, directory) {
|
|
|
1446
1599
|
const sha256 = bundled?.[1] ?? artifact2?.sha256;
|
|
1447
1600
|
if (!sha256) throw new Error("\u672C\u673A\u4EA7\u7269\u76EE\u5F55\u7F3A\u5C11\u8BE5\u56FA\u5B9A\u53D1\u884C\u8D44\u6E90");
|
|
1448
1601
|
const root = directory ?? await assetsDirectory(source, import.meta.url);
|
|
1449
|
-
const path =
|
|
1602
|
+
const path = join9(root, sha256 + ".tgz");
|
|
1450
1603
|
const file = await open3(path).catch((error) => {
|
|
1451
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");
|
|
1452
1605
|
throw error;
|
|
@@ -1455,7 +1608,7 @@ function artifactSource(source, directory) {
|
|
|
1455
1608
|
};
|
|
1456
1609
|
}
|
|
1457
1610
|
async function main(args = process.argv.slice(2)) {
|
|
1458
|
-
let home =
|
|
1611
|
+
let home = join9(homedir2(), ".huohuo"), noOpen = false, assumeYes = false, watch = false, command = "start";
|
|
1459
1612
|
let releaseFile, artifactDirectory;
|
|
1460
1613
|
for (let i = 0; i < args.length; i++) {
|
|
1461
1614
|
if (args[i] === "--home") {
|
|
@@ -1470,7 +1623,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1470
1623
|
else if (args[i] === "--yes") assumeYes = true;
|
|
1471
1624
|
else if (args[i] === "--watch") watch = true;
|
|
1472
1625
|
else if (args[i] === "--version") {
|
|
1473
|
-
const packageFile =
|
|
1626
|
+
const packageFile = join9(dirname6(fileURLToPath2(import.meta.url)), "package.json");
|
|
1474
1627
|
const manifest = JSON.parse(await readFile9(packageFile, "utf8"));
|
|
1475
1628
|
console.log(`${manifest.name} ${manifest.version}${manifest.private ? "\uFF08\u672C\u5730\u5019\u9009\uFF09" : ""}`);
|
|
1476
1629
|
return;
|
|
@@ -1502,11 +1655,11 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1502
1655
|
};
|
|
1503
1656
|
const ui = installUI(assumeYes);
|
|
1504
1657
|
if (command === "upgrade") {
|
|
1505
|
-
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")));
|
|
1506
1659
|
await upgrade(home, candidate, { fetcher: artifactSource(candidate, artifactDirectory), progress, ...ui });
|
|
1507
1660
|
const current = await readInstallation(home);
|
|
1508
1661
|
if (await control(home, current.releaseDigest, noOpen ? "status" : "open")) return;
|
|
1509
|
-
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"));
|
|
1510
1663
|
return;
|
|
1511
1664
|
}
|
|
1512
1665
|
if (command === "uninstall") {
|
|
@@ -1524,7 +1677,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1524
1677
|
return;
|
|
1525
1678
|
}
|
|
1526
1679
|
const paths = installationPaths(home, record.release);
|
|
1527
|
-
const targets = [home, paths.directory, paths.environment,
|
|
1680
|
+
const targets = [home, paths.directory, paths.environment, join9(home, "instances/default")];
|
|
1528
1681
|
const resources = evaluateResources(await sampleResources(targets), record.release.budget, targets.map((path) => ({ path, bytes: 0 })), "start");
|
|
1529
1682
|
console.log(JSON.stringify({
|
|
1530
1683
|
home,
|
|
@@ -1545,7 +1698,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1545
1698
|
}
|
|
1546
1699
|
const runtime = installationPaths(home, record.release).runtime;
|
|
1547
1700
|
try {
|
|
1548
|
-
await readFile9(
|
|
1701
|
+
await readFile9(join9(runtime, "lib/distribution/managed.js"));
|
|
1549
1702
|
} catch (e) {
|
|
1550
1703
|
if (e.code !== "ENOENT") throw e;
|
|
1551
1704
|
record = void 0;
|
|
@@ -1557,7 +1710,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1557
1710
|
if (!source) {
|
|
1558
1711
|
let catalog = null;
|
|
1559
1712
|
try {
|
|
1560
|
-
catalog = JSON.parse(await readFile9(releaseFile ??
|
|
1713
|
+
catalog = JSON.parse(await readFile9(releaseFile ?? join9(dirname6(fileURLToPath2(import.meta.url)), "release.json"), "utf8"));
|
|
1561
1714
|
} catch (e) {
|
|
1562
1715
|
if (e.code !== "ENOENT") throw e;
|
|
1563
1716
|
}
|
|
@@ -1567,7 +1720,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1567
1720
|
record = await install(home, source, { fetcher, progress, ...ui });
|
|
1568
1721
|
}
|
|
1569
1722
|
const installedHome = record.home;
|
|
1570
|
-
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"));
|
|
1571
1724
|
}
|
|
1572
1725
|
if (process.argv[1] && await realpath5(resolve5(process.argv[1])).catch(() => "") === await realpath5(fileURLToPath2(import.meta.url))) {
|
|
1573
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
|
|
51
51
|
},
|
|
52
52
|
"requirementsSha256": "57042fff45cee66fe33712472e2e5b88f03a084068ca20242588b2e18b3e167b",
|
|
53
|
-
"profilesSha256": "
|
|
53
|
+
"profilesSha256": "fb3540e0d604f26280128a21f97313575848f6348733a221f7e230233fdb8e2d",
|
|
54
54
|
"budget": {
|
|
55
55
|
"minimumEffectiveMemoryBytes": 34359738368,
|
|
56
56
|
"startupHeadroomBytes": 134217728,
|
|
@@ -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",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
|
|
151
151
|
},
|
|
152
152
|
"requirementsSha256": "d4ff8161886fc286e6f8ddfcd070ac5812528971affe7b87baa03662dd242d45",
|
|
153
|
-
"profilesSha256": "
|
|
153
|
+
"profilesSha256": "fb3540e0d604f26280128a21f97313575848f6348733a221f7e230233fdb8e2d",
|
|
154
154
|
"budget": {
|
|
155
155
|
"minimumEffectiveMemoryBytes": 8589934592,
|
|
156
156
|
"startupHeadroomBytes": 1073741824,
|
|
@@ -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
|
]
|