@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
- 命令支持 `huohuo [start|doctor|stop|upgrade|uninstall] [--home 目录] [--no-open]`。
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
 
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 readFile7, realpath as realpath5 } from "node:fs/promises";
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 join6, resolve as resolve5 } from "node:path";
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 artifact(a) {
136
- if (!a || !hash.test(a.sha256) || !integer(a.downloadBytes) || !integer(a.expandedBytes) || !a.downloadBytes || !a.expandedBytes) return false;
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(a.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
- async function downloadArtifact(artifact2, path, fetcher = fetch) {
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 allowed = /* @__PURE__ */ new Set([new URL(artifact2.url).hostname, ...artifact2.redirectHosts ?? []]);
196
- let url = artifact2.url;
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 (!location || redirects === 4) throw new Error("\u4E0B\u8F7D\u91CD\u5B9A\u5411\u65E0\u6548\u6216\u8FC7\u591A");
208
- url = new URL(location, url).href;
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 [runtimeCached, pythonCached, wheelsCached, inventory] = await Promise.all([
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: (runtimeCached ? 0 : release.runtime.downloadBytes) + (pythonCached ? 0 : release.python.downloadBytes) + (wheelsCached ? 0 : release.pythonDependencies.downloadBytes) },
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
- await downloadArtifact(release.runtime, paths.archive, hooks.fetcher);
731
- await downloadArtifact(release.python, paths.pythonArchive, hooks.fetcher);
732
- await downloadArtifact(release.pythonDependencies, join3(home, "cache", release.pythonDependencies.sha256 + ".tgz"), hooks.fetcher);
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
- await execute4(python, [
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 ?? join6(dirname5(fileURLToPath(import.meta.url)), "assets");
1083
- const path = join6(root, sha256 + ".tgz");
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 = join6(homedir(), ".huohuo"), noOpen = false, command = "start";
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 = join6(dirname5(fileURLToPath(import.meta.url)), "package.json");
1106
- const manifest = JSON.parse(await readFile7(packageFile, "utf8"));
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 progress = (stage) => console.log(`\u706B\u706B\uFF1A${labels[stage] ?? stage}`);
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 readFile7(releaseFile ?? join6(dirname5(fileURLToPath(import.meta.url)), "release.json"), "utf8"));
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, [join6(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "start", home, ...noOpen ? ["--no-open"] : []], home);
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, join6(home, "instances/default")];
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 readFile7(join6(runtime, "lib/distribution/managed.js"));
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 readFile7(releaseFile ?? join6(dirname5(fileURLToPath(import.meta.url)), "release.json"), "utf8"));
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 ? join6(dirname5(fileURLToPath(import.meta.url)), "assets") : void 0;
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, [join6(installationPaths(installedHome, record.release).runtime, "lib/distribution/managed.js"), "start", installedHome, ...noOpen ? ["--no-open"] : []], installedHome);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pab-iprg/huohuo",
3
- "version": "1.0.0-preview.3",
3
+ "version": "1.0.0-preview.4",
4
4
  "type": "module",
5
5
  "description": "火火投研伙伴安装入口",
6
6
  "bin": {
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/99aad243ba1aa9749053a7c136aa6b8e4d8fe043075cb3dfca7853359cb94a76.tgz",
17
- "sha256": "99aad243ba1aa9749053a7c136aa6b8e4d8fe043075cb3dfca7853359cb94a76",
18
- "integrity": "sha512-mXfudYnjEC4Oqwrg6mrOjnu8Q+S5wAxbK/gTSeAyPTzPF5uj9jyqNaWLE+nXe+gc2IazdRRhBii917dFsLUZyA==",
19
- "downloadBytes": 2543158,
20
- "expandedBytes": 8239765,
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,