@pab-iprg/huohuo 1.0.0-preview.3 → 1.0.0-preview.4
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
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
发布后的预览入口为 `npx --yes @pab-iprg/huohuo@next`。首版验收基线为 macOS Apple Silicon、Node 24.8.0 和 npm 11.6.0。默认持久目录为 `~/.huohuo`,普通重开保留已安装版本,安装中断后继续原目标。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
安装前会列出磁盘预留量、内存要求、固定下载项和缓存位置。输入 `y` 继续,直接回车取消。自动化环境可用 `npx --yes @pab-iprg/huohuo@next --yes` 显式同意安装。固定资源显示实际接收量,下载失败可重跑并复用已经校验的文件。
|
|
10
|
+
|
|
11
|
+
发行清单可为同一份固定资源配置备用 HTTPS 地址。确认安装后会探测这些地址,优先从响应较快的可用地址下载,并核对同一个 SHA-256。只有一个已核验来源时,网络中断会自动重试一次。npm 和 pip 仍会按各自的依赖清单安装。
|
|
12
|
+
|
|
13
|
+
命令支持 `huohuo [start|status|doctor|stop|upgrade|uninstall] [--home 目录] [--no-open]`。在另一个终端运行 `npx --yes @pab-iprg/huohuo@next status --watch` 可打开实时状态看板,按 Ctrl+C 退出看板。
|
|
10
14
|
|
|
11
15
|
显式升级检查旧迁移不可变、追加迁移顺序、执行库格式与 DSH 会话兼容范围,先一致性备份再迁移和验证,成功后切换;不自动恢复旧备份覆盖新数据。默认卸载保留实例、凭据、会话和备份,不提供直接彻底删除业务数据的快捷入口。
|
|
12
16
|
|
|
Binary file
|
package/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// distribution/bootstrap/entry.ts
|
|
4
|
-
import { open as open3, readFile as
|
|
4
|
+
import { open as open3, readFile as readFile8, realpath as realpath5 } from "node:fs/promises";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
-
import { dirname as dirname5, join as
|
|
7
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
10
|
// distribution/bootstrap/install.ts
|
|
@@ -132,20 +132,24 @@ var hash = /^[a-f0-9]{64}$/;
|
|
|
132
132
|
function integer(n) {
|
|
133
133
|
return Number.isSafeInteger(n) && Number(n) >= 0;
|
|
134
134
|
}
|
|
135
|
-
function
|
|
136
|
-
if (
|
|
137
|
-
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;
|
|
138
|
-
if (a.bundled !== void 0) return a.bundled === true && a.url === `https://bundled.invalid/${a.sha256}.tgz` && a.redirectHosts === void 0;
|
|
135
|
+
function httpsURL(value) {
|
|
136
|
+
if (typeof value !== "string") return false;
|
|
139
137
|
try {
|
|
140
|
-
const u = new URL(
|
|
138
|
+
const u = new URL(value);
|
|
141
139
|
return u.protocol === "https:" && !u.username && !u.password && !u.hash;
|
|
142
140
|
} catch {
|
|
143
141
|
return false;
|
|
144
142
|
}
|
|
145
143
|
}
|
|
144
|
+
function artifact(a) {
|
|
145
|
+
if (!a || !hash.test(a.sha256) || !integer(a.downloadBytes) || !integer(a.expandedBytes) || !a.downloadBytes || !a.expandedBytes) return false;
|
|
146
|
+
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
|
+
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
|
+
return httpsURL(a.url) && (a.mirrors === void 0 || Array.isArray(a.mirrors) && a.mirrors.length <= 3 && a.mirrors.every(httpsURL));
|
|
149
|
+
}
|
|
146
150
|
function validateRelease(input) {
|
|
147
151
|
const r = input;
|
|
148
|
-
if (!r || r.format !== 1 || r.bootstrapFormat !== 1 || !version.test(r.version) || !integer(r.nodeMajor) || r.nodeMajor < 24 || !integer(r.npmMajor) || r.npmMajor < 11 || !r.platform?.minimumOsRelease || !["darwin", "linux", "win32"].includes(r.platform.os) || !["arm64", "x64"].includes(r.platform.arch) || !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.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 || ![
|
|
152
|
+
if (!r || r.format !== 1 || r.bootstrapFormat !== 1 || !version.test(r.version) || !integer(r.nodeMajor) || r.nodeMajor < 24 || !integer(r.npmMajor) || r.npmMajor < 11 || !r.platform?.minimumOsRelease || !["darwin", "linux", "win32"].includes(r.platform.os) || !["arm64", "x64"].includes(r.platform.arch) || !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 && !integer(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 || ![
|
|
149
153
|
"minimumEffectiveMemoryBytes",
|
|
150
154
|
"startupHeadroomBytes",
|
|
151
155
|
"heavyJobHeadroomBytes",
|
|
@@ -157,7 +161,7 @@ function validateRelease(input) {
|
|
|
157
161
|
"processTreeBudgetBytes",
|
|
158
162
|
"sampleIntervalMs",
|
|
159
163
|
"sampleMaxAgeMs"
|
|
160
|
-
].every((k) => integer(r.budget[k])) || 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 || !integer(r.schema.minimum) || !integer(r.schema.maximum) || r.schema.minimum > r.schema.maximum) {
|
|
164
|
+
].every((k) => integer(r.budget[k])) || r.budget.observedInstallRssBytes !== void 0 && (!integer(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 || !integer(r.schema.minimum) || !integer(r.schema.maximum) || r.schema.minimum > r.schema.maximum) {
|
|
161
165
|
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");
|
|
162
166
|
}
|
|
163
167
|
if (!hash.test(r.schema.migrationsSha256)) throw new Error("\u7F3A\u5C11\u6570\u636E\u5E93\u8FC1\u79FB\u88C5\u914D\u6458\u8981");
|
|
@@ -181,45 +185,102 @@ async function verifyArtifact(path, artifact2) {
|
|
|
181
185
|
}
|
|
182
186
|
if (bytes !== artifact2.downloadBytes || digest2.digest("hex") !== artifact2.sha256 || "integrity" in artifact2 && artifact2.integrity !== `sha512-${integrity.digest("base64")}`) throw new Error("\u4E0B\u8F7D\u4EA7\u7269\u5927\u5C0F\u6216\u6458\u8981\u4E0D\u7B26");
|
|
183
187
|
}
|
|
184
|
-
|
|
188
|
+
var RetryableDownload = class extends Error {
|
|
189
|
+
};
|
|
190
|
+
async function downloadArtifact(artifact2, path, fetcher = fetch, progress) {
|
|
185
191
|
try {
|
|
186
192
|
await verifyArtifact(path, artifact2);
|
|
193
|
+
progress?.({ status: "cached", bytes: artifact2.downloadBytes });
|
|
187
194
|
return;
|
|
188
195
|
} catch (e) {
|
|
189
196
|
if (e.code !== "ENOENT") throw e;
|
|
190
197
|
}
|
|
191
198
|
await mkdir2(dirname2(path), { recursive: true, mode: 448 });
|
|
199
|
+
const sources = [.../* @__PURE__ */ new Set([artifact2.url, ...artifact2.mirrors ?? []])];
|
|
200
|
+
if (sources.length > 1) {
|
|
201
|
+
progress?.({ status: "probing", bytes: 0 });
|
|
202
|
+
const results = await Promise.all(sources.map(async (source) => {
|
|
203
|
+
const started = performance.now();
|
|
204
|
+
try {
|
|
205
|
+
const response = await requestSource(artifact2, source, fetcher, AbortSignal.timeout(5e3), "HEAD");
|
|
206
|
+
await response.body?.cancel();
|
|
207
|
+
return { source, latency: response.ok ? performance.now() - started : Infinity };
|
|
208
|
+
} catch {
|
|
209
|
+
return { source, latency: Infinity };
|
|
210
|
+
}
|
|
211
|
+
}));
|
|
212
|
+
results.sort((a, b) => a.latency - b.latency);
|
|
213
|
+
sources.splice(0, sources.length, ...results.map((result) => result.source));
|
|
214
|
+
} else if (!artifact2.bundled) sources.push(artifact2.url);
|
|
215
|
+
for (const [index, source] of sources.entries()) {
|
|
216
|
+
const host = artifact2.bundled ? void 0 : new URL(source).hostname;
|
|
217
|
+
progress?.({ status: "start", bytes: 0, host });
|
|
218
|
+
try {
|
|
219
|
+
await transfer(artifact2, path, source, fetcher, (bytes) => progress?.({ status: "receiving", bytes, host }));
|
|
220
|
+
progress?.({ status: "verified", bytes: artifact2.downloadBytes, host });
|
|
221
|
+
return;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (!(error instanceof RetryableDownload)) throw error;
|
|
224
|
+
if (index === sources.length - 1) throw new Error(`${host} \u4E0B\u8F7D\u672A\u5B8C\u6210\uFF08${error.message}\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8DD1\uFF0C\u5DF2\u6821\u9A8C\u7684\u6587\u4EF6\u4F1A\u590D\u7528\u3002`, { cause: error });
|
|
225
|
+
progress?.({ status: "retry", bytes: 0, host });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function requestSource(artifact2, source, fetcher, signal, method, onResponse) {
|
|
230
|
+
const allowed = /* @__PURE__ */ new Set([new URL(source).hostname, ...artifact2.redirectHosts ?? []]);
|
|
231
|
+
let url = source;
|
|
232
|
+
for (let redirects = 0; redirects <= 4; redirects++) {
|
|
233
|
+
const next = new URL(url);
|
|
234
|
+
if (next.protocol !== "https:" || next.username || next.password || !allowed.has(next.hostname)) throw new Error("\u4E0B\u8F7D\u91CD\u5B9A\u5411\u4E0D\u5728\u56FA\u5B9A\u53D1\u884C\u5141\u8BB8\u6765\u6E90\u5185");
|
|
235
|
+
const response = await fetcher(url, { signal, method, redirect: "manual" });
|
|
236
|
+
onResponse?.();
|
|
237
|
+
if (![301, 302, 303, 307, 308].includes(response.status)) return response;
|
|
238
|
+
const location = response.headers.get("location");
|
|
239
|
+
await response.body?.cancel();
|
|
240
|
+
if (!location || redirects === 4) throw new Error("\u4E0B\u8F7D\u91CD\u5B9A\u5411\u65E0\u6548\u6216\u8FC7\u591A");
|
|
241
|
+
url = new URL(location, url).href;
|
|
242
|
+
}
|
|
243
|
+
throw new Error("\u4E0B\u8F7D\u672A\u8FD4\u56DE\u54CD\u5E94");
|
|
244
|
+
}
|
|
245
|
+
async function transfer(artifact2, path, source, fetcher, onBytes) {
|
|
192
246
|
const pending = `${path}.${randomUUID2()}.partial`;
|
|
193
247
|
const handle = await open2(pending, "wx", 384);
|
|
248
|
+
const idle = new AbortController();
|
|
249
|
+
let timer;
|
|
250
|
+
const keepAlive = () => {
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
timer = setTimeout(() => idle.abort(), 45e3);
|
|
253
|
+
timer.unref();
|
|
254
|
+
};
|
|
255
|
+
const signal = AbortSignal.any([idle.signal, AbortSignal.timeout(30 * 6e4)]);
|
|
256
|
+
keepAlive();
|
|
194
257
|
try {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
let response;
|
|
198
|
-
const transferMs = Math.ceil(artifact2.downloadBytes / (64 * 1024)) * 1e3;
|
|
199
|
-
const signal = AbortSignal.timeout(Math.min(9e5, Math.max(12e4, 3e4 + transferMs)));
|
|
200
|
-
for (let redirects = 0; redirects <= 4; redirects++) {
|
|
201
|
-
const next = new URL(url);
|
|
202
|
-
if (next.protocol !== "https:" || next.username || next.password || !allowed.has(next.hostname)) throw new Error("\u4E0B\u8F7D\u91CD\u5B9A\u5411\u4E0D\u5728\u56FA\u5B9A\u53D1\u884C\u5141\u8BB8\u6765\u6E90\u5185");
|
|
203
|
-
response = await fetcher(url, { signal, redirect: "manual" });
|
|
204
|
-
if (![301, 302, 303, 307, 308].includes(response.status)) break;
|
|
205
|
-
const location = response.headers.get("location");
|
|
258
|
+
const response = await requestSource(artifact2, source, fetcher, signal, "GET", keepAlive);
|
|
259
|
+
if (!response.ok || !response.body) {
|
|
206
260
|
await response.body?.cancel();
|
|
207
|
-
if (!
|
|
208
|
-
|
|
261
|
+
if (!artifact2.bundled && (response.status === 408 || response.status === 429 || response.status >= 500 || artifact2.mirrors?.length && [403, 404].includes(response.status))) {
|
|
262
|
+
throw new RetryableDownload(`HTTP ${response.status}`);
|
|
263
|
+
}
|
|
264
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${response.status}`);
|
|
209
265
|
}
|
|
210
|
-
if (!response) throw new Error("\u4E0B\u8F7D\u672A\u8FD4\u56DE\u54CD\u5E94");
|
|
211
|
-
if (!response.ok || !response.body) throw new Error(`\u4E0B\u8F7D\u5931\u8D25\uFF1AHTTP ${response.status}`);
|
|
212
266
|
let received = 0;
|
|
213
267
|
for await (const chunk of response.body) {
|
|
214
268
|
received += chunk.byteLength;
|
|
269
|
+
keepAlive();
|
|
215
270
|
if (received > artifact2.downloadBytes) throw new Error("\u4E0B\u8F7D\u8D85\u8FC7\u53D1\u884C\u6E05\u5355\u5927\u5C0F");
|
|
216
271
|
await handle.writeFile(chunk);
|
|
272
|
+
onBytes(received);
|
|
217
273
|
}
|
|
218
274
|
await handle.sync();
|
|
219
275
|
await handle.close();
|
|
220
276
|
await verifyArtifact(pending, artifact2);
|
|
221
277
|
await rename2(pending, path);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (!artifact2.bundled && signal.aborted) throw new RetryableDownload(idle.signal.aborted ? "\u8FDE\u7EED 45 \u79D2\u672A\u6536\u5230\u6570\u636E" : "\u8D85\u8FC7\u5355\u6B21\u4E0B\u8F7D\u65F6\u9650", { cause: error });
|
|
280
|
+
if (!artifact2.bundled && error instanceof TypeError) throw new RetryableDownload("\u7F51\u7EDC\u8FDE\u63A5\u4E2D\u65AD", { cause: error });
|
|
281
|
+
throw error;
|
|
222
282
|
} finally {
|
|
283
|
+
clearTimeout(timer);
|
|
223
284
|
await handle.close().catch(() => {
|
|
224
285
|
});
|
|
225
286
|
await rm(pending, { force: true });
|
|
@@ -622,6 +683,20 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
622
683
|
const paths = installationPaths(home, release);
|
|
623
684
|
const execute4 = hooks.run ?? run;
|
|
624
685
|
const sample = hooks.sample ?? sampleResources;
|
|
686
|
+
const files = [
|
|
687
|
+
{ name: "\u706B\u706B\u8FD0\u884C\u5305", artifact: release.runtime, path: paths.archive },
|
|
688
|
+
{ name: "Python \u8FD0\u884C\u73AF\u5883", artifact: release.python, path: paths.pythonArchive },
|
|
689
|
+
{ name: "Python \u4F9D\u8D56\u6E05\u5355\u6216\u79BB\u7EBF\u5305", artifact: release.pythonDependencies, path: join3(home, "cache", release.pythonDependencies.sha256 + ".tgz") }
|
|
690
|
+
];
|
|
691
|
+
const withActivity = async (label, action) => {
|
|
692
|
+
hooks.progress?.(label);
|
|
693
|
+
const timer = hooks.progress && setInterval(() => hooks.progress?.(`${label}\u4ECD\u5728\u8FDB\u884C\uFF0C\u8BF7\u7A0D\u5019`), 2e4);
|
|
694
|
+
try {
|
|
695
|
+
await action();
|
|
696
|
+
} finally {
|
|
697
|
+
if (timer) clearInterval(timer);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
625
700
|
const cached = async (path, artifact2) => {
|
|
626
701
|
try {
|
|
627
702
|
await verifyArtifact(path, artifact2);
|
|
@@ -656,15 +731,10 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
656
731
|
}
|
|
657
732
|
};
|
|
658
733
|
const check = async () => {
|
|
659
|
-
const [
|
|
660
|
-
cached(paths.archive, release.runtime),
|
|
661
|
-
cached(paths.pythonArchive, release.python),
|
|
662
|
-
cached(join3(home, "cache", release.pythonDependencies.sha256 + ".tgz"), release.pythonDependencies),
|
|
663
|
-
installed()
|
|
664
|
-
]);
|
|
734
|
+
const [cacheHits, inventory] = await Promise.all([Promise.all(files.map((file) => cached(file.path, file.artifact))), installed()]);
|
|
665
735
|
const remainingDependencies = inventory.dependencies ? 0 : release.budget.dependenciesBytes;
|
|
666
736
|
const costs = [
|
|
667
|
-
{ path: join3(home, "cache"), bytes: (
|
|
737
|
+
{ path: join3(home, "cache"), bytes: files.reduce((sum, file, index) => sum + (cacheHits[index] ? 0 : file.artifact.downloadBytes), 0) },
|
|
668
738
|
{ path: paths.directory, bytes: inventory.dependencies ? 0 : release.runtime.expandedBytes + release.budget.installTemporaryBytes },
|
|
669
739
|
{ path: paths.python, bytes: inventory.python ? 0 : release.python.expandedBytes },
|
|
670
740
|
{ path: paths.environment, bytes: 0 },
|
|
@@ -690,6 +760,19 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
|
|
|
690
760
|
${details}
|
|
691
761
|
\u5904\u7406\u540E\uFF0C\u91CD\u65B0\u8FD0\u884C\u521A\u624D\u7684\u5B89\u88C5\u547D\u4EE4\u3002`), { code: "RESOURCE_BLOCKED" });
|
|
692
762
|
}
|
|
763
|
+
const pendingFiles = files.filter((_, index) => !cacheHits[index]);
|
|
764
|
+
return {
|
|
765
|
+
home,
|
|
766
|
+
resources: result,
|
|
767
|
+
cached: files.length - pendingFiles.length,
|
|
768
|
+
bundled: pendingFiles.filter((file) => file.artifact.bundled).length,
|
|
769
|
+
upstreamWheelCount: inventory.dependencies ? void 0 : release.pythonDependencies.upstreamWheelCount,
|
|
770
|
+
downloads: pendingFiles.filter((file) => !file.artifact.bundled).map((file) => ({
|
|
771
|
+
name: file.name,
|
|
772
|
+
size: file.artifact.downloadBytes,
|
|
773
|
+
host: new URL(file.artifact.url).hostname
|
|
774
|
+
}))
|
|
775
|
+
};
|
|
693
776
|
};
|
|
694
777
|
const stage = async (name, action) => {
|
|
695
778
|
hooks.progress?.(name);
|
|
@@ -700,8 +783,8 @@ ${details}
|
|
|
700
783
|
record.stages[name] = entry;
|
|
701
784
|
await atomicJSON(recordPath, record);
|
|
702
785
|
try {
|
|
703
|
-
await check();
|
|
704
|
-
const output = await action();
|
|
786
|
+
const plan = await check();
|
|
787
|
+
const output = await action(plan);
|
|
705
788
|
entry.status = "verified";
|
|
706
789
|
entry.outputDigest = digest(output ?? null);
|
|
707
790
|
entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -709,13 +792,13 @@ ${details}
|
|
|
709
792
|
entry.status = "failed";
|
|
710
793
|
entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
711
794
|
entry.errorCode = typeof error.code === "string" && /^[A-Z0-9_]+$/.test(error.code) ? error.code : "STAGE_FAILED";
|
|
712
|
-
record.status = entry.errorCode === "RESOURCE_BLOCKED" ? "blocked" : "failed";
|
|
795
|
+
record.status = entry.errorCode === "RESOURCE_BLOCKED" || entry.errorCode === "INSTALL_CANCELLED" ? "blocked" : "failed";
|
|
713
796
|
await atomicJSON(recordPath, record);
|
|
714
797
|
throw error;
|
|
715
798
|
}
|
|
716
799
|
await atomicJSON(recordPath, record);
|
|
717
800
|
};
|
|
718
|
-
await stage("preflight", async () => {
|
|
801
|
+
await stage("preflight", async (plan) => {
|
|
719
802
|
const npm = npmCommand();
|
|
720
803
|
const { stdout } = await promisify3(execFile3)(npm.command, [...npm.prefix, "--version"], { timeout: 5e3 });
|
|
721
804
|
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");
|
|
@@ -725,11 +808,25 @@ ${details}
|
|
|
725
808
|
} finally {
|
|
726
809
|
await rm2(probe, { force: true });
|
|
727
810
|
}
|
|
811
|
+
if (hooks.confirm && !await hooks.confirm(plan)) {
|
|
812
|
+
throw Object.assign(new Error("\u5DF2\u53D6\u6D88\u5B89\u88C5\uFF0C\u672C\u6B21\u6CA1\u6709\u8FDB\u5165\u4E0B\u8F7D\u3002"), { code: "INSTALL_CANCELLED" });
|
|
813
|
+
}
|
|
728
814
|
});
|
|
729
815
|
await stage("download", async () => {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
816
|
+
for (const [index, file] of files.entries()) {
|
|
817
|
+
const progress = (event) => hooks.downloadProgress?.({
|
|
818
|
+
name: file.name,
|
|
819
|
+
index: index + 1,
|
|
820
|
+
total: files.length,
|
|
821
|
+
size: file.artifact.downloadBytes
|
|
822
|
+
}, event);
|
|
823
|
+
try {
|
|
824
|
+
await downloadArtifact(file.artifact, file.path, hooks.fetcher, progress);
|
|
825
|
+
} catch (error) {
|
|
826
|
+
progress({ status: "failed", bytes: 0 });
|
|
827
|
+
throw new Error(`${file.name}\u5904\u7406\u5931\u8D25\u3002 ${error instanceof Error ? error.message : "\u8BF7\u91CD\u8BD5\u5B89\u88C5\u3002"}`, { cause: error });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
733
830
|
return [release.runtime.sha256, release.python.sha256, release.pythonDependencies.sha256];
|
|
734
831
|
});
|
|
735
832
|
await stage("install-runtime", async () => {
|
|
@@ -762,7 +859,7 @@ ${details}
|
|
|
762
859
|
dependencies: { "@huohuo/research-agent": `file:${paths.archive}` }
|
|
763
860
|
});
|
|
764
861
|
const npm = npmCommand();
|
|
765
|
-
await execute4(npm.command, [...npm.prefix, "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--prefer-offline", "--cache", join3(home, "cache/npm")], paths.directory);
|
|
862
|
+
await withActivity("\u5B89\u88C5 npm \u4F9D\u8D56", () => execute4(npm.command, [...npm.prefix, "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--prefer-offline", "--cache", join3(home, "cache/npm")], paths.directory));
|
|
766
863
|
const runtime = JSON.parse(await readFile4(join3(paths.runtime, "package.json"), "utf8"));
|
|
767
864
|
if (runtime.name !== release.runtime.name || runtime.version !== release.version) throw new Error("\u8FD0\u884C\u5305\u8EAB\u4EFD\u4E0D\u7B26");
|
|
768
865
|
const requirements = await readFile4(join3(paths.runtime, "sidecar/requirements.lock"));
|
|
@@ -796,7 +893,12 @@ ${details}
|
|
|
796
893
|
}
|
|
797
894
|
if (!environmentReady) {
|
|
798
895
|
await execute4(join3(paths.python, release.python.executable), ["-m", "venv", paths.environment], home);
|
|
799
|
-
|
|
896
|
+
if (release.pythonDependencies.bundled && hooks.progress) {
|
|
897
|
+
const lock = await readFile4(join3(wheels, "requirements.txt"), "utf8");
|
|
898
|
+
const upstream = lock.split("\n").filter((line) => /^[\w.-]+\s+@\s+https:\/\//.test(line)).length;
|
|
899
|
+
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`);
|
|
900
|
+
}
|
|
901
|
+
await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () => execute4(python, [
|
|
800
902
|
"-m",
|
|
801
903
|
"pip",
|
|
802
904
|
"--isolated",
|
|
@@ -809,7 +911,7 @@ ${details}
|
|
|
809
911
|
...release.pythonDependencies.bundled ? ["--no-cache-dir"] : [],
|
|
810
912
|
"-r",
|
|
811
913
|
join3(wheels, "requirements.txt")
|
|
812
|
-
], home);
|
|
914
|
+
], home));
|
|
813
915
|
}
|
|
814
916
|
await execute4(python, ["-m", "pip", "--isolated", "check"], home);
|
|
815
917
|
await atomicJSON(join3(paths.environment, ".dependencies-ready.json"), { sha256: release.pythonDependencies.sha256 });
|
|
@@ -822,7 +924,7 @@ ${details}
|
|
|
822
924
|
return record;
|
|
823
925
|
}
|
|
824
926
|
await stage("assemble-profiles", async () => {
|
|
825
|
-
await execute4(process.execPath, [join3(paths.runtime, "lib/distribution/managed.js"), "prepare", home], home);
|
|
927
|
+
await withActivity("\u914D\u7F6E\u5BF9\u8BDD\u4E0E\u540E\u53F0\u8FD0\u884C\u4F9D\u8D56", () => execute4(process.execPath, [join3(paths.runtime, "lib/distribution/managed.js"), "prepare", home], home));
|
|
826
928
|
return { release: record.releaseDigest };
|
|
827
929
|
});
|
|
828
930
|
await stage("verify", async () => {
|
|
@@ -1071,6 +1173,166 @@ async function uninstall(home, hooks = {}) {
|
|
|
1071
1173
|
});
|
|
1072
1174
|
}
|
|
1073
1175
|
|
|
1176
|
+
// distribution/bootstrap/install-ui.ts
|
|
1177
|
+
import { createInterface } from "node:readline";
|
|
1178
|
+
var gb = (bytes) => bytes === null ? "\u672A\u77E5" : `${(bytes / 1e9).toFixed(2)} GB`;
|
|
1179
|
+
var mb = (bytes) => bytes < 1e5 ? `${(bytes / 1e3).toFixed(1)} KB` : `${(bytes / 1e6).toFixed(1)} MB`;
|
|
1180
|
+
function installUI(assumeYes) {
|
|
1181
|
+
const interactive = process.stdin.isTTY && process.stderr.isTTY;
|
|
1182
|
+
const output = process.stderr;
|
|
1183
|
+
let lastUpdate = 0;
|
|
1184
|
+
let lineActive = false;
|
|
1185
|
+
const clear = () => {
|
|
1186
|
+
if (lineActive) {
|
|
1187
|
+
output.write("\r\x1B[2K");
|
|
1188
|
+
lineActive = false;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
const line = (text) => {
|
|
1192
|
+
clear();
|
|
1193
|
+
output.write(text + "\n");
|
|
1194
|
+
};
|
|
1195
|
+
return {
|
|
1196
|
+
async confirm(plan) {
|
|
1197
|
+
const { sample, policy, disks } = plan.resources;
|
|
1198
|
+
line(`\u5B89\u88C5\u76EE\u5F55 ${plan.home}`);
|
|
1199
|
+
for (const [index, disk] of disks.entries()) {
|
|
1200
|
+
const location = sample.disks.find((item) => item.device === disk.device)?.paths[0] ?? plan.home;
|
|
1201
|
+
line(`\u78C1\u76D8 ${index + 1}\uFF08${location}\uFF09 \u5B89\u88C5\u9884\u7559 ${gb(disk.requiredBytes)}\uFF0C\u5F53\u524D\u53EF\u7528 ${gb(disk.availableBytes)}`);
|
|
1202
|
+
}
|
|
1203
|
+
line(`\u5185\u5B58 \u672C\u673A\u5BB9\u91CF ${gb(sample.effectiveMemoryBytes)}\uFF0C\u5F53\u524D\u53EF\u7528 ${gb(sample.availableMemoryBytes)}`);
|
|
1204
|
+
line(`\u5185\u5B58\u8981\u6C42 \u6700\u4F4E\u5BB9\u91CF ${gb(policy.minimumEffectiveMemoryBytes)}\uFF0C\u542F\u52A8\u9700\u7559\u51FA ${gb(policy.startupHeadroomBytes)}`);
|
|
1205
|
+
line(policy.observedInstallRssBytes ? `\u5B89\u88C5\u5185\u5B58\u53C2\u8003 \u540C\u5E73\u53F0\u5386\u53F2\u8FDB\u7A0B\u5185\u5B58\u5CF0\u503C ${gb(policy.observedInstallRssBytes)}\uFF0C\u5B9E\u9645\u5360\u7528\u4F1A\u53D8\u5316` : "\u5B89\u88C5\u5185\u5B58\u5CF0\u503C\u968F\u4F9D\u8D56\u548C\u672C\u673A\u72B6\u6001\u53D8\u5316\uFF0C\u5F53\u524D\u6E05\u5355\u6682\u65E0\u5B9E\u6D4B\u503C");
|
|
1206
|
+
for (const reason of plan.resources.reasons) line(reason);
|
|
1207
|
+
const bytes = plan.downloads.reduce((total, file) => total + file.size, 0);
|
|
1208
|
+
line(`\u56FA\u5B9A\u8D44\u6E90 \u8054\u7F51\u4E0B\u8F7D ${plan.downloads.length} \u9879\uFF08${mb(bytes)}\uFF09\uFF0C\u968F\u5305 ${plan.bundled} \u9879\uFF0C\u7F13\u5B58 ${plan.cached} \u9879`);
|
|
1209
|
+
for (const file of plan.downloads) line(` ${file.name} ${mb(file.size)} ${file.host}`);
|
|
1210
|
+
line(`\u56FA\u5B9A\u8D44\u6E90\u4FDD\u5B58\u81F3 ${plan.home}/cache`);
|
|
1211
|
+
if (plan.upstreamWheelCount !== void 0) line(`Python \u4F9D\u8D56\u6E05\u5355 ${plan.upstreamWheelCount} \u9879\u56FA\u5B9A\u4E0A\u6E38\u6587\u4EF6\uFF0C\u5DF2\u5B89\u88C5\u9879\u53EF\u80FD\u8DF3\u8FC7\u3002`);
|
|
1212
|
+
line("\u540E\u7EED\u8FD8\u4F1A\u5B89\u88C5 npm \u548C Python \u4F9D\u8D56\uFF0C\u5B9E\u9645\u8054\u7F51\u4E0B\u8F7D\u91CF\u53D6\u51B3\u4E8E\u672C\u673A\u7F13\u5B58\u548C\u4F9D\u8D56\u89E3\u6790\u7ED3\u679C\u3002");
|
|
1213
|
+
if (assumeYes) return true;
|
|
1214
|
+
if (!interactive) throw new Error("\u9700\u8981\u786E\u8BA4\u5B89\u88C5\u3002\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C\uFF0C\u6216\u5728\u706B\u706B\u547D\u4EE4\u672B\u5C3E\u52A0\u4E0A --yes\u3002");
|
|
1215
|
+
const terminal = createInterface({ input: process.stdin, output, terminal: true });
|
|
1216
|
+
try {
|
|
1217
|
+
const answers = terminal[Symbol.asyncIterator]();
|
|
1218
|
+
output.write("\u7EE7\u7EED\u5B89\u88C5\uFF1F[y/N] ");
|
|
1219
|
+
const answer = await answers.next();
|
|
1220
|
+
return !answer.done && /^(y|yes|是)$/i.test(answer.value.trim());
|
|
1221
|
+
} finally {
|
|
1222
|
+
terminal.close();
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
downloadProgress(file, event) {
|
|
1226
|
+
const label = `[${file.index}/${file.total}] ${file.name}`;
|
|
1227
|
+
if (event.status === "probing") {
|
|
1228
|
+
line(`${label} \u6B63\u5728\u68C0\u67E5\u4E0B\u8F7D\u6E90\u662F\u5426\u53EF\u8FBE`);
|
|
1229
|
+
} else if (event.status === "start") {
|
|
1230
|
+
lastUpdate = Date.now();
|
|
1231
|
+
line(`${label} ${event.host ? `\u6B63\u5728\u8FDE\u63A5 ${event.host}` : "\u6B63\u5728\u8BFB\u53D6\u968F\u5305\u8D44\u6E90"} ${mb(file.size)}`);
|
|
1232
|
+
} else if (event.status === "cached") {
|
|
1233
|
+
line(`${label} \u5DF2\u6821\u9A8C\u5E76\u590D\u7528\u7F13\u5B58`);
|
|
1234
|
+
} else if (event.status === "verified") {
|
|
1235
|
+
line(`${label} \u5DF2\u5B8C\u6210\u5E76\u6821\u9A8C ${mb(file.size)}`);
|
|
1236
|
+
} else if (event.status === "retry") {
|
|
1237
|
+
line(`${label} ${event.host} \u4E0B\u8F7D\u672A\u5B8C\u6210\uFF0C\u51C6\u5907\u91CD\u8BD5`);
|
|
1238
|
+
} else if (event.status === "failed") {
|
|
1239
|
+
clear();
|
|
1240
|
+
} else {
|
|
1241
|
+
const now = Date.now();
|
|
1242
|
+
if (now - lastUpdate < (interactive ? 180 : 15e3)) return;
|
|
1243
|
+
lastUpdate = now;
|
|
1244
|
+
const percent = Math.min(99, Math.floor(100 * event.bytes / file.size));
|
|
1245
|
+
const filled = Math.floor(percent / 10);
|
|
1246
|
+
const value = `[${"#".repeat(filled)}${"-".repeat(10 - filled)}] ${percent}% ${mb(event.bytes)} / ${mb(file.size)}`;
|
|
1247
|
+
if (interactive && (output.columns ?? 0) >= value.length) {
|
|
1248
|
+
clear();
|
|
1249
|
+
output.write(value);
|
|
1250
|
+
lineActive = true;
|
|
1251
|
+
} else line(`${label} ${value}`);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// distribution/bootstrap/status-ui.ts
|
|
1258
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
1259
|
+
import { join as join6 } from "node:path";
|
|
1260
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
1261
|
+
var mib = (bytes) => typeof bytes === "number" && Number.isFinite(bytes) ? `${Math.round(bytes / 2 ** 20)} MiB` : "\u672A\u77E5";
|
|
1262
|
+
var pressureLabels = { normal: "\u6B63\u5E38", high: "\u504F\u9AD8", critical: "\u4E25\u91CD", unknown: "\u672A\u77E5" };
|
|
1263
|
+
async function statusLines(home) {
|
|
1264
|
+
const title = "\u706B\u706B \xB7 \u8FD0\u884C\u72B6\u6001";
|
|
1265
|
+
const installed = await readInstallation(home);
|
|
1266
|
+
if (!installed) return [title, "\u5C1A\u672A\u5B89\u88C5\u706B\u706B\u3002"];
|
|
1267
|
+
if (installed.status !== "ready") return [title, "\u5B89\u88C5\u5C1A\u672A\u5B8C\u6210\u3002"];
|
|
1268
|
+
let admission;
|
|
1269
|
+
try {
|
|
1270
|
+
admission = JSON.parse(await readFile7(join6(home, "state/default-admission.json"), "utf8"));
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
if (error.code !== "ENOENT") return [title, "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u8D44\u6E90\u72B6\u6001\u3002"];
|
|
1273
|
+
}
|
|
1274
|
+
const now = Date.now() / 1e3;
|
|
1275
|
+
if (!admission || admission.format !== 1 || admission.releaseDigest !== installed.releaseDigest || !Number.isFinite(admission.sampledAt) || !(admission.validUntil > now) || typeof admission.allowHeavy !== "boolean" || !admission.resources?.sample) {
|
|
1276
|
+
return [title, "\u6682\u65E0\u6709\u6548\u7684\u8FD0\u884C\u91C7\u6837\u3002\u8BF7\u68C0\u67E5\u706B\u706B\u670D\u52A1\u662F\u5426\u6B63\u5728\u8FD0\u884C\u3002"];
|
|
1277
|
+
}
|
|
1278
|
+
const { sample, reasons } = admission.resources;
|
|
1279
|
+
const needed = installed.release.budget.heavyJobHeadroomBytes;
|
|
1280
|
+
const gap = sample.availableMemoryBytes === null ? null : Math.max(0, needed - sample.availableMemoryBytes);
|
|
1281
|
+
const lines = [
|
|
1282
|
+
title,
|
|
1283
|
+
`\u540E\u53F0\u7814\u7A76 ${admission.allowHeavy ? "\u53EF\u63A5\u6536\u4EFB\u52A1" : "\u6682\u7F13\u65B0\u4EFB\u52A1"}`,
|
|
1284
|
+
`\u7CFB\u7EDF\u5185\u5B58 \u53EF\u7528 ${mib(sample.availableMemoryBytes)} / \u603B\u5BB9\u91CF ${mib(sample.effectiveMemoryBytes)}`,
|
|
1285
|
+
`\u4EFB\u52A1\u95E8\u69DB \u9884\u7559 ${mib(needed)}${gap ? `\uFF0C\u8FD8\u5DEE ${mib(gap)}` : ""}`,
|
|
1286
|
+
`\u5185\u5B58\u538B\u529B ${pressureLabels[sample.pressure] ?? "\u672A\u77E5"}`,
|
|
1287
|
+
`\u706B\u706B\u8FDB\u7A0B \u4F30\u7B97\u5360\u7528 ${mib(admission.rssSumEstimateBytes)}`
|
|
1288
|
+
];
|
|
1289
|
+
if (!admission.allowHeavy) lines.push(reasons?.[0] ? `\u6682\u7F13\u539F\u56E0 ${reasons[0]}` : "\u6B63\u5728\u7B49\u5F85\u8FDE\u7EED\u5065\u5EB7\u91C7\u6837\u3002");
|
|
1290
|
+
lines.push(`\u91C7\u6837\u65F6\u95F4 ${new Date(admission.sampledAt * 1e3).toLocaleTimeString()}`);
|
|
1291
|
+
return lines;
|
|
1292
|
+
}
|
|
1293
|
+
async function showStatus(home, watch) {
|
|
1294
|
+
const output = process.stdout;
|
|
1295
|
+
if (!watch || !output.isTTY || process.env.TERM === "dumb") {
|
|
1296
|
+
output.write((await statusLines(home)).join("\n") + "\n");
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const stop = new AbortController();
|
|
1300
|
+
const close = () => stop.abort();
|
|
1301
|
+
const input = process.stdin;
|
|
1302
|
+
const raw = input.isTTY;
|
|
1303
|
+
const wasRaw = input.isRaw;
|
|
1304
|
+
const onKey = (chunk) => {
|
|
1305
|
+
if (chunk.includes(3)) close();
|
|
1306
|
+
};
|
|
1307
|
+
process.once("SIGINT", close);
|
|
1308
|
+
process.once("SIGTERM", close);
|
|
1309
|
+
try {
|
|
1310
|
+
if (raw) {
|
|
1311
|
+
input.setRawMode(true);
|
|
1312
|
+
input.on("data", onKey);
|
|
1313
|
+
input.resume();
|
|
1314
|
+
}
|
|
1315
|
+
output.write("\x1B[?1049h\x1B[?25l");
|
|
1316
|
+
while (!stop.signal.aborted) {
|
|
1317
|
+
const lines = await statusLines(home);
|
|
1318
|
+
if (stop.signal.aborted) break;
|
|
1319
|
+
output.write("\x1B[H\x1B[0J" + [...lines, "", "\u6BCF 2 \u79D2\u5237\u65B0 \xB7 Ctrl+C \u5173\u95ED\u770B\u677F"].join("\n"));
|
|
1320
|
+
await delay2(2e3, void 0, { signal: stop.signal }).catch((error) => {
|
|
1321
|
+
if (!stop.signal.aborted) throw error;
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
} finally {
|
|
1325
|
+
output.write("\x1B[?25h\x1B[?1049l");
|
|
1326
|
+
if (raw) {
|
|
1327
|
+
input.off("data", onKey);
|
|
1328
|
+
input.setRawMode(Boolean(wasRaw));
|
|
1329
|
+
input.pause();
|
|
1330
|
+
}
|
|
1331
|
+
process.off("SIGINT", close);
|
|
1332
|
+
process.off("SIGTERM", close);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1074
1336
|
// distribution/bootstrap/entry.ts
|
|
1075
1337
|
function artifactSource(source, directory) {
|
|
1076
1338
|
return async (url, options) => {
|
|
@@ -1079,8 +1341,8 @@ function artifactSource(source, directory) {
|
|
|
1079
1341
|
const artifact2 = source && [source.runtime, source.python, source.pythonDependencies].find((item) => item.url === String(url));
|
|
1080
1342
|
const sha256 = bundled?.[1] ?? artifact2?.sha256;
|
|
1081
1343
|
if (!sha256) throw new Error("\u672C\u673A\u4EA7\u7269\u76EE\u5F55\u7F3A\u5C11\u8BE5\u56FA\u5B9A\u53D1\u884C\u8D44\u6E90");
|
|
1082
|
-
const root = directory ??
|
|
1083
|
-
const path =
|
|
1344
|
+
const root = directory ?? join7(dirname5(fileURLToPath(import.meta.url)), "assets");
|
|
1345
|
+
const path = join7(root, sha256 + ".tgz");
|
|
1084
1346
|
const file = await open3(path).catch((error) => {
|
|
1085
1347
|
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");
|
|
1086
1348
|
throw error;
|
|
@@ -1089,7 +1351,7 @@ function artifactSource(source, directory) {
|
|
|
1089
1351
|
};
|
|
1090
1352
|
}
|
|
1091
1353
|
async function main(args = process.argv.slice(2)) {
|
|
1092
|
-
let home =
|
|
1354
|
+
let home = join7(homedir(), ".huohuo"), noOpen = false, assumeYes = false, watch = false, command = "start";
|
|
1093
1355
|
let releaseFile, artifactDirectory;
|
|
1094
1356
|
for (let i = 0; i < args.length; i++) {
|
|
1095
1357
|
if (args[i] === "--home") {
|
|
@@ -1101,19 +1363,23 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1101
1363
|
if (option === "--release") releaseFile = resolve5(args[++i]);
|
|
1102
1364
|
else artifactDirectory = resolve5(args[++i]);
|
|
1103
1365
|
} else if (args[i] === "--no-open") noOpen = true;
|
|
1366
|
+
else if (args[i] === "--yes") assumeYes = true;
|
|
1367
|
+
else if (args[i] === "--watch") watch = true;
|
|
1104
1368
|
else if (args[i] === "--version") {
|
|
1105
|
-
const packageFile =
|
|
1106
|
-
const manifest = JSON.parse(await
|
|
1369
|
+
const packageFile = join7(dirname5(fileURLToPath(import.meta.url)), "package.json");
|
|
1370
|
+
const manifest = JSON.parse(await readFile8(packageFile, "utf8"));
|
|
1107
1371
|
console.log(`${manifest.name} ${manifest.version}${manifest.private ? "\uFF08\u672C\u5730\u5019\u9009\uFF09" : ""}`);
|
|
1108
1372
|
return;
|
|
1109
|
-
} else if (["start", "doctor", "stop", "upgrade", "uninstall"].includes(args[i])) command = args[i];
|
|
1110
|
-
else throw new Error("\u7528\u6CD5\uFF1Ahuohuo [start|doctor|stop|upgrade|uninstall] [--home \u76EE\u5F55] [--no-open]");
|
|
1373
|
+
} else if (["start", "status", "doctor", "stop", "upgrade", "uninstall"].includes(args[i])) command = args[i];
|
|
1374
|
+
else throw new Error("\u7528\u6CD5\uFF1Ahuohuo [start|status|doctor|stop|upgrade|uninstall] [--home \u76EE\u5F55] [--no-open]");
|
|
1111
1375
|
}
|
|
1376
|
+
if (watch && command !== "status") throw new Error("--watch \u8BF7\u4E0E status \u4E00\u8D77\u4F7F\u7528");
|
|
1112
1377
|
try {
|
|
1113
1378
|
home = await realpath5(home);
|
|
1114
1379
|
} catch (e) {
|
|
1115
1380
|
if (e.code !== "ENOENT") throw e;
|
|
1116
1381
|
}
|
|
1382
|
+
if (command === "status") return showStatus(home, watch);
|
|
1117
1383
|
let record = await readInstallation(home);
|
|
1118
1384
|
const labels = {
|
|
1119
1385
|
preflight: "\u68C0\u67E5\u672C\u673A\u73AF\u5883",
|
|
@@ -1124,13 +1390,19 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1124
1390
|
verify: "\u68C0\u67E5\u5B89\u88C5\u7ED3\u679C",
|
|
1125
1391
|
"resume-current-package": "\u4E0A\u6B21\u5B89\u88C5\u5C1A\u672A\u5199\u5165\u8FD0\u884C\u73AF\u5883\uFF0C\u5C06\u4F7F\u7528\u5F53\u524D\u7248\u672C\u91CD\u65B0\u68C0\u67E5"
|
|
1126
1392
|
};
|
|
1127
|
-
const
|
|
1393
|
+
const installStages = ["preflight", "download", "install-runtime", "install-dependencies", "assemble-profiles", "verify"];
|
|
1394
|
+
const progress = (stage) => {
|
|
1395
|
+
const index = command === "start" ? installStages.indexOf(stage) : -1;
|
|
1396
|
+
const step = index < 0 ? "" : `[${index + 1}/${installStages.length}] `;
|
|
1397
|
+
console.log(`\u706B\u706B\uFF1A${step}${labels[stage] ?? stage}`);
|
|
1398
|
+
};
|
|
1399
|
+
const ui = installUI(assumeYes);
|
|
1128
1400
|
if (command === "upgrade") {
|
|
1129
|
-
const candidate = JSON.parse(await
|
|
1130
|
-
await upgrade(home, candidate, { fetcher: artifactSource(candidate, artifactDirectory), progress });
|
|
1401
|
+
const candidate = JSON.parse(await readFile8(releaseFile ?? join7(dirname5(fileURLToPath(import.meta.url)), "release.json"), "utf8"));
|
|
1402
|
+
await upgrade(home, candidate, { fetcher: artifactSource(candidate, artifactDirectory), progress, ...ui });
|
|
1131
1403
|
const current = await readInstallation(home);
|
|
1132
1404
|
if (await control(home, current.releaseDigest, noOpen ? "status" : "open")) return;
|
|
1133
|
-
await run(process.execPath, [
|
|
1405
|
+
await run(process.execPath, [join7(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "start", home, ...noOpen ? ["--no-open"] : []], home);
|
|
1134
1406
|
return;
|
|
1135
1407
|
}
|
|
1136
1408
|
if (command === "uninstall") {
|
|
@@ -1148,7 +1420,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1148
1420
|
return;
|
|
1149
1421
|
}
|
|
1150
1422
|
const paths = installationPaths(home, record.release);
|
|
1151
|
-
const targets = [home, paths.directory, paths.environment,
|
|
1423
|
+
const targets = [home, paths.directory, paths.environment, join7(home, "instances/default")];
|
|
1152
1424
|
const resources = evaluateResources(await sampleResources(targets), record.release.budget, targets.map((path) => ({ path, bytes: 0 })), "start");
|
|
1153
1425
|
console.log(JSON.stringify({
|
|
1154
1426
|
home,
|
|
@@ -1169,7 +1441,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1169
1441
|
}
|
|
1170
1442
|
const runtime = installationPaths(home, record.release).runtime;
|
|
1171
1443
|
try {
|
|
1172
|
-
await
|
|
1444
|
+
await readFile8(join7(runtime, "lib/distribution/managed.js"));
|
|
1173
1445
|
} catch (e) {
|
|
1174
1446
|
if (e.code !== "ENOENT") throw e;
|
|
1175
1447
|
record = void 0;
|
|
@@ -1179,23 +1451,23 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1179
1451
|
let catalog = null;
|
|
1180
1452
|
if (!record || ["preflight", "download"].includes(record.stage)) {
|
|
1181
1453
|
try {
|
|
1182
|
-
catalog = JSON.parse(await
|
|
1454
|
+
catalog = JSON.parse(await readFile8(releaseFile ?? join7(dirname5(fileURLToPath(import.meta.url)), "release.json"), "utf8"));
|
|
1183
1455
|
} catch (e) {
|
|
1184
1456
|
if (e.code !== "ENOENT") throw e;
|
|
1185
1457
|
}
|
|
1186
1458
|
}
|
|
1187
1459
|
const source = (await readInstallation(home))?.release ?? catalog;
|
|
1188
1460
|
const fetcher = artifactSource(source, artifactDirectory);
|
|
1189
|
-
const bundleDirectory = !releaseFile && !artifactDirectory ?
|
|
1190
|
-
record = await install(home, catalog, { fetcher, progress, bundleDirectory });
|
|
1461
|
+
const bundleDirectory = !releaseFile && !artifactDirectory ? join7(dirname5(fileURLToPath(import.meta.url)), "assets") : void 0;
|
|
1462
|
+
record = await install(home, catalog, { fetcher, progress, bundleDirectory, ...ui });
|
|
1191
1463
|
}
|
|
1192
1464
|
const installedHome = record.home;
|
|
1193
|
-
await run(process.execPath, [
|
|
1465
|
+
await run(process.execPath, [join7(installationPaths(installedHome, record.release).runtime, "lib/distribution/managed.js"), "start", installedHome, ...noOpen ? ["--no-open"] : []], installedHome);
|
|
1194
1466
|
}
|
|
1195
1467
|
if (process.argv[1] && await realpath5(resolve5(process.argv[1])).catch(() => "") === await realpath5(fileURLToPath(import.meta.url))) {
|
|
1196
1468
|
main().catch((error) => {
|
|
1197
1469
|
console.error(error instanceof Error ? error.message : "\u5B89\u88C5\u6216\u542F\u52A8\u5931\u8D25");
|
|
1198
|
-
process.exitCode = 1;
|
|
1470
|
+
if (error?.code !== "INSTALL_CANCELLED") process.exitCode = 1;
|
|
1199
1471
|
});
|
|
1200
1472
|
}
|
|
1201
1473
|
export {
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"runtime": {
|
|
14
14
|
"name": "@huohuo/research-agent",
|
|
15
15
|
"version": "1.0.0",
|
|
16
|
-
"url": "https://bundled.invalid/
|
|
17
|
-
"sha256": "
|
|
18
|
-
"integrity": "sha512-
|
|
19
|
-
"downloadBytes":
|
|
20
|
-
"expandedBytes":
|
|
16
|
+
"url": "https://bundled.invalid/550095205f2f92e0641b286f15fbb01ece0b29339ecf7c5ee18539a2b7f5def8.tgz",
|
|
17
|
+
"sha256": "550095205f2f92e0641b286f15fbb01ece0b29339ecf7c5ee18539a2b7f5def8",
|
|
18
|
+
"integrity": "sha512-AI87lAFcPyGM5qtQ4B2Ji3CJWE10twn48t0tSnuEeIxlVAKVEJBWRe7aSCkOKwLvpeai8CRts50oEMh0juDWrA==",
|
|
19
|
+
"downloadBytes": 2548683,
|
|
20
|
+
"expandedBytes": 8255144,
|
|
21
21
|
"bundled": true
|
|
22
22
|
},
|
|
23
23
|
"python": {
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"sha256": "4f008fe4491617729554e056ad59eee686ccc5d95c9f1d5259da705bf054ce21",
|
|
38
38
|
"downloadBytes": 10235,
|
|
39
39
|
"expandedBytes": 15007,
|
|
40
|
-
"bundled": true
|
|
40
|
+
"bundled": true,
|
|
41
|
+
"upstreamWheelCount": 39
|
|
41
42
|
},
|
|
42
43
|
"dsh": {
|
|
43
44
|
"version": "0.2.1-rc.2",
|
|
@@ -56,7 +57,8 @@
|
|
|
56
57
|
"sampleMaxAgeMs": 10000,
|
|
57
58
|
"installTemporaryBytes": 1073741824,
|
|
58
59
|
"dependenciesBytes": 4294967296,
|
|
59
|
-
"backupBytes": 0
|
|
60
|
+
"backupBytes": 0,
|
|
61
|
+
"observedInstallRssBytes": 1904771072
|
|
60
62
|
},
|
|
61
63
|
"schema": {
|
|
62
64
|
"minimum": 1,
|