@pab-iprg/huohuo 1.0.0-preview.3 → 1.0.0-preview.5

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
@@ -1,12 +1,12 @@
1
- # 火火安装入口
1
+ # 火火轻量入口(本地开发候选)
2
2
 
3
- 入口在设备预检后安装 DSH 和火火运行环境。npm 预览包内置火火运行文件,Python 运行时和依赖从固定上游地址获取并校验摘要。
3
+ 此目录是 `@pab-iprg/huohuo` 的私有构建模板。入口代码只使用 Node 内置模块;npm 按 OS/CPU 选择同版本的静态资源包,资源包不含安装脚本。设备预检前只准备入口和对应平台的压缩资源,不安装 DSH 或 Python。源码具备某个平台的选择逻辑,不代表该平台已完成实机验收。
4
4
 
5
- 构建时需要已完成平台测量与摘要绑定的 `release.json`。没有发行清单时入口明确停止,不从不可信页面选择资源,也不使用虚构的最低配置。
5
+ 构建时需要已完成平台测量与摘要绑定的 `release.json`,可以是旧版单平台清单,也可以是包含多个同版本发行的索引。首次安装与显式升级只选择本机系统/CPU 对应的发行,再按需下载运行包、独立 Python 和固定 wheels;其他平台的大包不下载。Linux 索引条目目前只接受 glibc,未收录的平台提前提示。没有完整发行清单时明确停止。
6
6
 
7
- 发布后的预览入口为 `npx --yes @pab-iprg/huohuo@next`。首版验收基线为 macOS Apple Silicon、Node 24.8.0 和 npm 11.6.0。默认持久目录为 `~/.huohuo`,普通重开保留已安装版本,安装中断后继续原目标。
7
+ 运行 npx 前仍需安装兼容的 Node/npm(当前入口要求 Node 24.8+,具体 Node 主版本由所选发行固定)。运行环境在用户目录中准备,不替换系统 Python;已校验的下载缓存会复用。Python 和 wheels 在构建端选定版本及 SHA;npm 预览包携带下载清单和构建好的 jsonpath wheel,其余 wheel 按固定上游 URL 下载并校验,不在用户机器编译。仍兼容旧的完整离线 wheel 包;npm 依赖按运行包锁文件安装。
8
8
 
9
- 命令支持 `huohuo [start|doctor|stop|upgrade|uninstall] [--home 目录] [--no-open]`。
9
+ 当前开发入口:`huohuo [start|status|doctor|stop|upgrade|uninstall] [--home 目录] [--no-open]`。`status --watch` 持续显示进度;安装前展示资源计划,`--yes` 可显式确认。默认持久目录 `~/.huohuo`;普通重开保留已安装版本,安装中断继续原目标。npx 缓存保存入口和所选平台资源;再次解析最新入口可能联网。
10
10
 
11
11
  显式升级检查旧迁移不可变、追加迁移顺序、执行库格式与 DSH 会话兼容范围,先一致性备份再迁移和验证,成功后切换;不自动恢复旧备份覆盖新数据。默认卸载保留实例、凭据、会话和备份,不提供直接彻底删除业务数据的快捷入口。
12
12
 
package/cli.js CHANGED
@@ -1,11 +1,11 @@
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 readFile9, realpath as realpath5 } from "node:fs/promises";
5
5
  import { Readable } from "node:stream";
6
- import { homedir } from "node:os";
7
- import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
8
- import { fileURLToPath } from "node:url";
6
+ import { homedir as homedir2 } from "node:os";
7
+ import { dirname as dirname6, join as join8, resolve as resolve5 } from "node:path";
8
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9
9
 
10
10
  // distribution/bootstrap/install.ts
11
11
  import { readFile as readFile4, mkdir as mkdir3, access, rename as rename3, rm as rm2, realpath as realpath3, readdir } from "node:fs/promises";
@@ -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) || 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 && !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 });
@@ -303,8 +364,8 @@ async function extractArchive(archive, root, expandedLimit) {
303
364
  }
304
365
  } else if (kind === "1" || kind === "2") {
305
366
  if (size || !linkText || linkText.startsWith("/") || linkText.includes("\\")) throw new Error("\u538B\u7F29\u5305\u94FE\u63A5\u65E0\u6548");
306
- const relative = kind === "2" ? posix.normalize(posix.join(posix.dirname(name), linkText)) : linkText;
307
- links.push({ target, source: safePath(root, relative), symbolic: kind === "2", text: linkText });
367
+ const relative2 = kind === "2" ? posix.normalize(posix.join(posix.dirname(name), linkText)) : linkText;
368
+ links.push({ target, source: safePath(root, relative2), symbolic: kind === "2", text: linkText });
308
369
  } else throw new Error(`\u538B\u7F29\u5305\u6761\u76EE\u7C7B\u578B\u4E0D\u53D7\u652F\u6301\uFF1A${kind}`);
309
370
  }
310
371
  if (size % 512) await take(512 - size % 512);
@@ -357,6 +418,13 @@ async function existingParent(path) {
357
418
  current = parent;
358
419
  }
359
420
  }
421
+ function linuxAvailableMemory(processAvailable, meminfo) {
422
+ if (processAvailable === null || !Number.isSafeInteger(processAvailable) || processAvailable <= 0) return null;
423
+ const match = /^MemAvailable:\s+(\d+) kB$/m.exec(meminfo);
424
+ if (!match) return processAvailable;
425
+ const hostAvailable = Number(match[1]) * 1024;
426
+ return Number.isSafeInteger(hostAvailable) && hostAvailable >= 0 ? Math.min(processAvailable, hostAvailable) : null;
427
+ }
360
428
  async function sampleResources(paths) {
361
429
  const limit = constrainedMemory();
362
430
  let effectiveMemoryBytes = Math.min(totalmem(), limit || Infinity);
@@ -389,16 +457,7 @@ async function sampleResources(paths) {
389
457
  } else if (process.platform === "linux") {
390
458
  try {
391
459
  const info = await readFile3("/proc/meminfo", "utf8");
392
- const available = /^MemAvailable:\s+(\d+) kB$/m.exec(info);
393
- if (available) availableMemoryBytes = Math.min(availableMemoryBytes ?? Infinity, Number(available[1]) * 1024);
394
- if (limit) {
395
- try {
396
- const current = Number((await readFile3("/sys/fs/cgroup/memory.current", "utf8")).trim());
397
- if (Number.isFinite(current)) availableMemoryBytes = Math.min(availableMemoryBytes ?? Infinity, Math.max(0, limit - current));
398
- } catch {
399
- availableMemoryBytes = null;
400
- }
401
- }
460
+ availableMemoryBytes = linuxAvailableMemory(availableMemoryBytes, info);
402
461
  const psi = await readFile3("/proc/pressure/memory", "utf8");
403
462
  const full = /^full avg10=(\d+(?:\.\d+)?)/m.exec(psi);
404
463
  if (full) pressure = Number(full[1]) > 0 ? "high" : "normal";
@@ -487,6 +546,50 @@ function npmCommand() {
487
546
  return { command: process.env.ComSpec ?? "cmd.exe", prefix: ["/d", "/s", "/c", "npm"] };
488
547
  }
489
548
 
549
+ // distribution/bootstrap/catalog.ts
550
+ function hostPlatform() {
551
+ const host = { os: process.platform, arch: process.arch };
552
+ if (host.os === "linux") {
553
+ const report = process.report?.getReport();
554
+ host.libc = report?.header?.glibcVersionRuntime ? "glibc" : "unknown";
555
+ }
556
+ return host;
557
+ }
558
+ function validateCatalog(input) {
559
+ const catalog = input;
560
+ if (!catalog || catalog.kind !== "huohuo-release-catalog" || catalog.format !== 1 || !Array.isArray(catalog.releases) || !catalog.releases.length || catalog.releases.length > 6) {
561
+ throw new Error("\u5E73\u53F0\u53D1\u884C\u7D22\u5F15\u65E0\u6548\uFF1B\u8BF7\u4F7F\u7528\u5B8C\u6574\u7684\u706B\u706B\u5B89\u88C5\u5305");
562
+ }
563
+ const platforms = /* @__PURE__ */ new Set();
564
+ for (const candidate of catalog.releases) {
565
+ const release = validateRelease(candidate);
566
+ const platform = `${release.platform.os}-${release.platform.arch}`;
567
+ if (release.version !== catalog.version || platforms.has(platform)) throw new Error("\u5E73\u53F0\u53D1\u884C\u7D22\u5F15\u542B\u6DF7\u5408\u7248\u672C\u6216\u91CD\u590D\u5E73\u53F0");
568
+ if (release.platform.os === "linux" && release.platform.libc !== "glibc") throw new Error("Linux \u5E73\u53F0\u53D1\u884C\u5FC5\u987B\u660E\u786E glibc \u8FD0\u884C\u73AF\u5883");
569
+ platforms.add(platform);
570
+ }
571
+ return catalog;
572
+ }
573
+ function validateReleaseSource(input) {
574
+ if (input && typeof input === "object" && ("kind" in input || "releases" in input)) return validateCatalog(input);
575
+ return validateRelease(input);
576
+ }
577
+ function selectRelease(input, host = hostPlatform()) {
578
+ const source = validateReleaseSource(input);
579
+ const releases = "releases" in source ? source.releases : [source];
580
+ const selected = releases.find((release) => release.platform.os === host.os && release.platform.arch === host.arch);
581
+ if (!selected) {
582
+ throw new Error(`\u8FD9\u4E2A\u5B89\u88C5\u5305\u5C1A\u672A\u63D0\u4F9B ${host.os}-${host.arch} \u7248\u672C\u3002\u5DF2\u6536\u5F55\uFF1A${releases.map((release) => `${release.platform.os}-${release.platform.arch}`).join("\u3001")}\u3002\u8BF7\u4F7F\u7528\u9002\u914D\u5F53\u524D\u8BBE\u5907\u7684\u53D1\u884C\u7248\u672C\uFF1B\u672A\u4E0B\u8F7D\u6216\u4FEE\u6539\u73B0\u6709\u5B89\u88C5\u3002`);
583
+ }
584
+ assertLibcCompatible(selected, host);
585
+ return selected;
586
+ }
587
+ function assertLibcCompatible(release, host = hostPlatform()) {
588
+ if (release.platform.libc && host.libc !== release.platform.libc) {
589
+ throw new Error("\u8FD9\u4E2A Linux \u5B89\u88C5\u5305\u9700\u8981 glibc \u73AF\u5883\uFF0C\u5F53\u524D\u7CFB\u7EDF\u65E0\u6CD5\u786E\u8BA4\u517C\u5BB9\uFF08\u4F8B\u5982 Alpine/musl\uFF09\u3002\u8BF7\u4F7F\u7528\u5DF2\u9A8C\u8BC1\u7684 glibc \u7CFB\u7EDF\uFF1B\u672A\u4E0B\u8F7D\u6216\u4FEE\u6539\u73B0\u6709\u5B89\u88C5\u3002");
590
+ }
591
+ }
592
+
490
593
  // distribution/bootstrap/install.ts
491
594
  var digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
492
595
  async function ownedDirectory(path, owner) {
@@ -561,13 +664,20 @@ function installationPaths(home, release) {
561
664
  };
562
665
  }
563
666
  function versionAtLeast(actual, minimum) {
667
+ if (![actual, minimum].every((value) => /^\d+(?:\.\d+)*$/.test(value))) return false;
564
668
  const a = actual.split(".").map(Number), b = minimum.split(".").map(Number);
565
- if ([...a, ...b].some((n) => !Number.isInteger(n))) return false;
669
+ if ([...a, ...b].some((n) => !Number.isSafeInteger(n))) return false;
566
670
  for (let i = 0; i < Math.max(a.length, b.length); i++) {
567
671
  if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
568
672
  }
569
673
  return true;
570
674
  }
675
+ function osReleaseAtLeast(actual, minimum, platform = process.platform) {
676
+ if (platform !== "linux") return versionAtLeast(actual, minimum);
677
+ const numericRelease = (value) => /^(\d+(?:\.\d+)*)(?:-[A-Za-z0-9][A-Za-z0-9.+_-]*)?$/.exec(value)?.[1];
678
+ const a = numericRelease(actual), b = numericRelease(minimum);
679
+ return a !== void 0 && b !== void 0 && versionAtLeast(a, b);
680
+ }
571
681
  async function install(home, candidate, hooks = {}, programsOnly = false) {
572
682
  home = resolve3(home);
573
683
  await mkdir3(home, { recursive: true, mode: 448 });
@@ -614,7 +724,8 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
614
724
  }
615
725
  }
616
726
  const release = record?.release ?? validateRelease(candidate);
617
- if (release.platform.os !== process.platform || release.platform.arch !== process.arch || !versionAtLeast(osRelease(), release.platform.minimumOsRelease) || !versionAtLeast(process.versions.node, "24.8.0") || Number(process.versions.node.split(".")[0]) !== release.nodeMajor) {
727
+ assertLibcCompatible(release);
728
+ if (release.platform.os !== process.platform || release.platform.arch !== process.arch || !osReleaseAtLeast(osRelease(), release.platform.minimumOsRelease) || !versionAtLeast(process.versions.node, "24.8.0") || Number(process.versions.node.split(".")[0]) !== release.nodeMajor) {
618
729
  throw new Error("\u6B64\u53D1\u884C\u5C1A\u672A\u9A8C\u8BC1\u5F53\u524D OS/CPU/Node \u7EC4\u5408\uFF1B\u672A\u5F00\u59CB\u91CD\u4E0B\u8F7D");
619
730
  }
620
731
  record ??= { format: 1, release, releaseDigest: digest(release), home, status: "installing", stage: "preflight", stages: {} };
@@ -622,6 +733,20 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
622
733
  const paths = installationPaths(home, release);
623
734
  const execute4 = hooks.run ?? run;
624
735
  const sample = hooks.sample ?? sampleResources;
736
+ const files = [
737
+ { name: "\u706B\u706B\u8FD0\u884C\u5305", artifact: release.runtime, path: paths.archive },
738
+ { 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: join3(home, "cache", release.pythonDependencies.sha256 + ".tgz") }
740
+ ];
741
+ const withActivity = async (label, action) => {
742
+ hooks.progress?.(label);
743
+ const timer = hooks.progress && setInterval(() => hooks.progress?.(`${label}\u4ECD\u5728\u8FDB\u884C\uFF0C\u8BF7\u7A0D\u5019`), 2e4);
744
+ try {
745
+ await action();
746
+ } finally {
747
+ if (timer) clearInterval(timer);
748
+ }
749
+ };
625
750
  const cached = async (path, artifact2) => {
626
751
  try {
627
752
  await verifyArtifact(path, artifact2);
@@ -656,15 +781,10 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
656
781
  }
657
782
  };
658
783
  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
- ]);
784
+ const [cacheHits, inventory] = await Promise.all([Promise.all(files.map((file) => cached(file.path, file.artifact))), installed()]);
665
785
  const remainingDependencies = inventory.dependencies ? 0 : release.budget.dependenciesBytes;
666
786
  const costs = [
667
- { path: join3(home, "cache"), bytes: (runtimeCached ? 0 : release.runtime.downloadBytes) + (pythonCached ? 0 : release.python.downloadBytes) + (wheelsCached ? 0 : release.pythonDependencies.downloadBytes) },
787
+ { path: join3(home, "cache"), bytes: files.reduce((sum, file, index) => sum + (cacheHits[index] ? 0 : file.artifact.downloadBytes), 0) },
668
788
  { path: paths.directory, bytes: inventory.dependencies ? 0 : release.runtime.expandedBytes + release.budget.installTemporaryBytes },
669
789
  { path: paths.python, bytes: inventory.python ? 0 : release.python.expandedBytes },
670
790
  { path: paths.environment, bytes: 0 },
@@ -690,6 +810,19 @@ async function install(home, candidate, hooks = {}, programsOnly = false) {
690
810
  ${details}
691
811
  \u5904\u7406\u540E\uFF0C\u91CD\u65B0\u8FD0\u884C\u521A\u624D\u7684\u5B89\u88C5\u547D\u4EE4\u3002`), { code: "RESOURCE_BLOCKED" });
692
812
  }
813
+ const pendingFiles = files.filter((_, index) => !cacheHits[index]);
814
+ return {
815
+ home,
816
+ resources: result,
817
+ cached: files.length - pendingFiles.length,
818
+ bundled: pendingFiles.filter((file) => file.artifact.bundled).length,
819
+ upstreamWheelCount: inventory.dependencies ? void 0 : release.pythonDependencies.upstreamWheelCount,
820
+ downloads: pendingFiles.filter((file) => !file.artifact.bundled).map((file) => ({
821
+ name: file.name,
822
+ size: file.artifact.downloadBytes,
823
+ host: new URL(file.artifact.url).hostname
824
+ }))
825
+ };
693
826
  };
694
827
  const stage = async (name, action) => {
695
828
  hooks.progress?.(name);
@@ -700,8 +833,8 @@ ${details}
700
833
  record.stages[name] = entry;
701
834
  await atomicJSON(recordPath, record);
702
835
  try {
703
- await check();
704
- const output = await action();
836
+ const plan = await check();
837
+ const output = await action(plan);
705
838
  entry.status = "verified";
706
839
  entry.outputDigest = digest(output ?? null);
707
840
  entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -709,13 +842,13 @@ ${details}
709
842
  entry.status = "failed";
710
843
  entry.endedAt = (/* @__PURE__ */ new Date()).toISOString();
711
844
  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";
845
+ record.status = entry.errorCode === "RESOURCE_BLOCKED" || entry.errorCode === "INSTALL_CANCELLED" ? "blocked" : "failed";
713
846
  await atomicJSON(recordPath, record);
714
847
  throw error;
715
848
  }
716
849
  await atomicJSON(recordPath, record);
717
850
  };
718
- await stage("preflight", async () => {
851
+ await stage("preflight", async (plan) => {
719
852
  const npm = npmCommand();
720
853
  const { stdout } = await promisify3(execFile3)(npm.command, [...npm.prefix, "--version"], { timeout: 5e3 });
721
854
  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 +858,25 @@ ${details}
725
858
  } finally {
726
859
  await rm2(probe, { force: true });
727
860
  }
861
+ if (hooks.confirm && !await hooks.confirm(plan)) {
862
+ throw Object.assign(new Error("\u5DF2\u53D6\u6D88\u5B89\u88C5\uFF0C\u672C\u6B21\u6CA1\u6709\u8FDB\u5165\u4E0B\u8F7D\u3002"), { code: "INSTALL_CANCELLED" });
863
+ }
728
864
  });
729
865
  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);
866
+ for (const [index, file] of files.entries()) {
867
+ const progress = (event) => hooks.downloadProgress?.({
868
+ name: file.name,
869
+ index: index + 1,
870
+ total: files.length,
871
+ size: file.artifact.downloadBytes
872
+ }, event);
873
+ try {
874
+ await downloadArtifact(file.artifact, file.path, hooks.fetcher, progress);
875
+ } catch (error) {
876
+ progress({ status: "failed", bytes: 0 });
877
+ throw new Error(`${file.name}\u5904\u7406\u5931\u8D25\u3002 ${error instanceof Error ? error.message : "\u8BF7\u91CD\u8BD5\u5B89\u88C5\u3002"}`, { cause: error });
878
+ }
879
+ }
733
880
  return [release.runtime.sha256, release.python.sha256, release.pythonDependencies.sha256];
734
881
  });
735
882
  await stage("install-runtime", async () => {
@@ -762,7 +909,7 @@ ${details}
762
909
  dependencies: { "@huohuo/research-agent": `file:${paths.archive}` }
763
910
  });
764
911
  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);
912
+ 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
913
  const runtime = JSON.parse(await readFile4(join3(paths.runtime, "package.json"), "utf8"));
767
914
  if (runtime.name !== release.runtime.name || runtime.version !== release.version) throw new Error("\u8FD0\u884C\u5305\u8EAB\u4EFD\u4E0D\u7B26");
768
915
  const requirements = await readFile4(join3(paths.runtime, "sidecar/requirements.lock"));
@@ -796,7 +943,12 @@ ${details}
796
943
  }
797
944
  if (!environmentReady) {
798
945
  await execute4(join3(paths.python, release.python.executable), ["-m", "venv", paths.environment], home);
799
- await execute4(python, [
946
+ if (release.pythonDependencies.bundled && hooks.progress) {
947
+ const lock = await readFile4(join3(wheels, "requirements.txt"), "utf8");
948
+ const upstream = lock.split("\n").filter((line) => /^[\w.-]+\s+@\s+https:\/\//.test(line)).length;
949
+ 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
+ }
951
+ await withActivity("\u5B89\u88C5 Python \u4F9D\u8D56", () => execute4(python, [
800
952
  "-m",
801
953
  "pip",
802
954
  "--isolated",
@@ -809,7 +961,7 @@ ${details}
809
961
  ...release.pythonDependencies.bundled ? ["--no-cache-dir"] : [],
810
962
  "-r",
811
963
  join3(wheels, "requirements.txt")
812
- ], home);
964
+ ], home));
813
965
  }
814
966
  await execute4(python, ["-m", "pip", "--isolated", "check"], home);
815
967
  await atomicJSON(join3(paths.environment, ".dependencies-ready.json"), { sha256: release.pythonDependencies.sha256 });
@@ -822,7 +974,7 @@ ${details}
822
974
  return record;
823
975
  }
824
976
  await stage("assemble-profiles", async () => {
825
- await execute4(process.execPath, [join3(paths.runtime, "lib/distribution/managed.js"), "prepare", home], home);
977
+ 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
978
  return { release: record.releaseDigest };
827
979
  });
828
980
  await stage("verify", async () => {
@@ -1071,6 +1223,220 @@ async function uninstall(home, hooks = {}) {
1071
1223
  });
1072
1224
  }
1073
1225
 
1226
+ // distribution/bootstrap/install-ui.ts
1227
+ import { createInterface } from "node:readline";
1228
+ import { homedir } from "node:os";
1229
+ import { relative, sep as sep3 } from "node:path";
1230
+ var gb = (bytes) => bytes === null ? "\u672A\u77E5" : `${(bytes / 1e9).toFixed(2)} GB`;
1231
+ var mb = (bytes) => bytes < 1e5 ? `${(bytes / 1e3).toFixed(1)} KB` : `${(bytes / 1e6).toFixed(1)} MB`;
1232
+ var memory = (bytes) => {
1233
+ if (bytes === null) return "\u672A\u77E5";
1234
+ return bytes < 2 ** 30 ? `${Math.floor(bytes / 2 ** 20)} MiB` : `${Number((bytes / 2 ** 30).toFixed(2))} GiB`;
1235
+ };
1236
+ function displayPath(path) {
1237
+ const local = relative(homedir(), path);
1238
+ if (!local) return "~";
1239
+ return local === ".." || local.startsWith(`..${sep3}`) ? path : `~${sep3}${local}`;
1240
+ }
1241
+ function installUI(assumeYes) {
1242
+ const interactive = process.stdin.isTTY && process.stderr.isTTY;
1243
+ const output = process.stderr;
1244
+ let lastUpdate = 0;
1245
+ let lineActive = false;
1246
+ const clear = () => {
1247
+ if (lineActive) {
1248
+ output.write("\r\x1B[2K");
1249
+ lineActive = false;
1250
+ }
1251
+ };
1252
+ const line = (text) => {
1253
+ clear();
1254
+ output.write(text + "\n");
1255
+ };
1256
+ const heading = (text) => line(interactive && process.env.TERM !== "dumb" && process.env.NO_COLOR === void 0 ? `\x1B[1m${text}\x1B[22m` : text);
1257
+ return {
1258
+ async confirm(plan) {
1259
+ const { sample, policy, disks } = plan.resources;
1260
+ const narrow = interactive && output.columns > 0 && output.columns < 48;
1261
+ line("");
1262
+ heading("\u706B\u706B\u5B89\u88C5 \xB7 \u786E\u8BA4\u8BA1\u5212");
1263
+ line(` \u5B89\u88C5\u5230 ${displayPath(plan.home)}`);
1264
+ line("");
1265
+ heading("\u672C\u673A\u8D44\u6E90");
1266
+ for (const [index, disk] of disks.entries()) {
1267
+ const location = sample.disks.find((item) => item.device === disk.device)?.paths[0] ?? plan.home;
1268
+ if (disks.length > 1) line(` \u78C1\u76D8 ${index + 1} ${displayPath(location)}`);
1269
+ const need = `\u9700\u7559 ${gb(disk.requiredBytes)}`;
1270
+ const free = `\u53EF\u7528 ${gb(disk.availableBytes)}`;
1271
+ line(narrow ? ` \u78C1\u76D8 ${need}
1272
+ ${free}` : ` \u78C1\u76D8 ${need} \xB7 ${free}`);
1273
+ }
1274
+ const available = `\u53EF\u7528 ${memory(sample.availableMemoryBytes)}`;
1275
+ const total = `\u603B\u91CF ${memory(sample.effectiveMemoryBytes)}`;
1276
+ line(narrow ? ` \u5185\u5B58 ${available}
1277
+ ${total}` : ` \u5185\u5B58 ${available} \xB7 ${total}`);
1278
+ line(` \u542F\u52A8\u9700\u7559 ${memory(policy.startupHeadroomBytes)}`);
1279
+ line(` \u6700\u4F4E\u5BB9\u91CF ${memory(policy.minimumEffectiveMemoryBytes)}`);
1280
+ if (policy.observedInstallRssBytes) line(` \u5386\u53F2\u5B89\u88C5\u5CF0\u503C\u7EA6 ${memory(policy.observedInstallRssBytes)}`);
1281
+ for (const reason of plan.resources.reasons) line(` \u63D0\u793A ${reason}`);
1282
+ line("");
1283
+ heading("\u4E0B\u8F7D\u8BA1\u5212");
1284
+ const bytes = plan.downloads.reduce((total2, file) => total2 + file.size, 0);
1285
+ line(plan.downloads.length ? ` \u56FA\u5B9A\u8D44\u6E90\u5F85\u4E0B\u8F7D ${plan.downloads.length} \u9879 \xB7 ${mb(bytes)}` : " \u56FA\u5B9A\u8D44\u6E90\u65E0\u9700\u8054\u7F51\u4E0B\u8F7D");
1286
+ const local = [plan.bundled ? `\u968F\u5305 ${plan.bundled} \u9879` : "", plan.cached ? `\u590D\u7528\u7F13\u5B58 ${plan.cached} \u9879` : ""].filter(Boolean);
1287
+ if (local.length) line(` ${local.join(" \xB7 ")}`);
1288
+ for (const file of plan.downloads) {
1289
+ line(` ${file.name} ${mb(file.size)}`);
1290
+ line(` \u6765\u6E90 ${file.host}`);
1291
+ }
1292
+ if (plan.downloads.length || plan.bundled) line(` \u4FDD\u5B58\u5230 ${displayPath(plan.home)}/cache`);
1293
+ if (plan.upstreamWheelCount !== void 0) line(` Python \u4E0A\u6E38\u6587\u4EF6 ${plan.upstreamWheelCount} \u9879`);
1294
+ line(" npm \u548C Python \u4F9D\u8D56\u4ECD\u53EF\u80FD\u8054\u7F51");
1295
+ line("");
1296
+ if (assumeYes) return true;
1297
+ 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");
1298
+ const terminal = createInterface({ input: process.stdin, output, terminal: true });
1299
+ try {
1300
+ const answers = terminal[Symbol.asyncIterator]();
1301
+ output.write("\u7EE7\u7EED\u5B89\u88C5\uFF1F[y/N] ");
1302
+ const answer = await answers.next();
1303
+ return !answer.done && /^(y|yes|是)$/i.test(answer.value.trim());
1304
+ } finally {
1305
+ terminal.close();
1306
+ }
1307
+ },
1308
+ downloadProgress(file, event) {
1309
+ const label = `[${file.index}/${file.total}] ${file.name}`;
1310
+ if (event.status === "probing") {
1311
+ line(`${label} \u6B63\u5728\u68C0\u67E5\u4E0B\u8F7D\u6E90\u662F\u5426\u53EF\u8FBE`);
1312
+ } else if (event.status === "start") {
1313
+ lastUpdate = Date.now();
1314
+ line(`${label} ${event.host ? `\u6B63\u5728\u8FDE\u63A5 ${event.host}` : "\u6B63\u5728\u8BFB\u53D6\u968F\u5305\u8D44\u6E90"} ${mb(file.size)}`);
1315
+ } else if (event.status === "cached") {
1316
+ line(`${label} \u5DF2\u6821\u9A8C\u5E76\u590D\u7528\u7F13\u5B58`);
1317
+ } else if (event.status === "verified") {
1318
+ line(`${label} \u5DF2\u5B8C\u6210\u5E76\u6821\u9A8C ${mb(file.size)}`);
1319
+ } else if (event.status === "retry") {
1320
+ line(`${label} ${event.host} \u4E0B\u8F7D\u672A\u5B8C\u6210\uFF0C\u51C6\u5907\u91CD\u8BD5`);
1321
+ } else if (event.status === "failed") {
1322
+ clear();
1323
+ } else {
1324
+ const now = Date.now();
1325
+ if (now - lastUpdate < (interactive ? 180 : 15e3)) return;
1326
+ lastUpdate = now;
1327
+ const percent = Math.min(99, Math.floor(100 * event.bytes / file.size));
1328
+ const filled = Math.floor(percent / 10);
1329
+ const value = `[${"#".repeat(filled)}${"-".repeat(10 - filled)}] ${percent}% ${mb(event.bytes)} / ${mb(file.size)}`;
1330
+ if (interactive && (output.columns ?? 0) >= value.length) {
1331
+ clear();
1332
+ output.write(value);
1333
+ lineActive = true;
1334
+ } else line(`${label} ${value}`);
1335
+ }
1336
+ }
1337
+ };
1338
+ }
1339
+
1340
+ // distribution/bootstrap/status-ui.ts
1341
+ import { readFile as readFile7 } from "node:fs/promises";
1342
+ import { join as join6 } from "node:path";
1343
+ import { setTimeout as delay2 } from "node:timers/promises";
1344
+ var mib = (bytes) => typeof bytes === "number" && Number.isFinite(bytes) ? `${Math.round(bytes / 2 ** 20)} MiB` : "\u672A\u77E5";
1345
+ var pressureLabels = { normal: "\u6B63\u5E38", high: "\u504F\u9AD8", critical: "\u4E25\u91CD", unknown: "\u672A\u77E5" };
1346
+ async function statusLines(home) {
1347
+ const title = "\u706B\u706B \xB7 \u8FD0\u884C\u72B6\u6001";
1348
+ const installed = await readInstallation(home);
1349
+ if (!installed) return [title, "\u5C1A\u672A\u5B89\u88C5\u706B\u706B\u3002"];
1350
+ if (installed.status !== "ready") return [title, "\u5B89\u88C5\u5C1A\u672A\u5B8C\u6210\u3002"];
1351
+ let admission;
1352
+ try {
1353
+ admission = JSON.parse(await readFile7(join6(home, "state/default-admission.json"), "utf8"));
1354
+ } catch (error) {
1355
+ if (error.code !== "ENOENT") return [title, "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u8D44\u6E90\u72B6\u6001\u3002"];
1356
+ }
1357
+ const now = Date.now() / 1e3;
1358
+ if (!admission || admission.format !== 1 || admission.releaseDigest !== installed.releaseDigest || !Number.isFinite(admission.sampledAt) || !(admission.validUntil > now) || typeof admission.allowHeavy !== "boolean" || !admission.resources?.sample) {
1359
+ 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"];
1360
+ }
1361
+ const { sample, reasons } = admission.resources;
1362
+ const needed = installed.release.budget.heavyJobHeadroomBytes;
1363
+ const gap = sample.availableMemoryBytes === null ? null : Math.max(0, needed - sample.availableMemoryBytes);
1364
+ const lines = [
1365
+ title,
1366
+ `\u540E\u53F0\u7814\u7A76 ${admission.allowHeavy ? "\u53EF\u63A5\u6536\u4EFB\u52A1" : "\u6682\u7F13\u65B0\u4EFB\u52A1"}`,
1367
+ `\u7CFB\u7EDF\u5185\u5B58 \u53EF\u7528 ${mib(sample.availableMemoryBytes)} / \u603B\u5BB9\u91CF ${mib(sample.effectiveMemoryBytes)}`,
1368
+ `\u4EFB\u52A1\u95E8\u69DB \u9884\u7559 ${mib(needed)}${gap ? `\uFF0C\u8FD8\u5DEE ${mib(gap)}` : ""}`,
1369
+ `\u5185\u5B58\u538B\u529B ${pressureLabels[sample.pressure] ?? "\u672A\u77E5"}`,
1370
+ `\u706B\u706B\u8FDB\u7A0B \u4F30\u7B97\u5360\u7528 ${mib(admission.rssSumEstimateBytes)}`
1371
+ ];
1372
+ if (!admission.allowHeavy) lines.push(reasons?.[0] ? `\u6682\u7F13\u539F\u56E0 ${reasons[0]}` : "\u6B63\u5728\u7B49\u5F85\u8FDE\u7EED\u5065\u5EB7\u91C7\u6837\u3002");
1373
+ lines.push(`\u91C7\u6837\u65F6\u95F4 ${new Date(admission.sampledAt * 1e3).toLocaleTimeString()}`);
1374
+ return lines;
1375
+ }
1376
+ async function showStatus(home, watch) {
1377
+ const output = process.stdout;
1378
+ if (!watch || !output.isTTY || process.env.TERM === "dumb") {
1379
+ output.write((await statusLines(home)).join("\n") + "\n");
1380
+ return;
1381
+ }
1382
+ const stop = new AbortController();
1383
+ const close = () => stop.abort();
1384
+ const input = process.stdin;
1385
+ const raw = input.isTTY;
1386
+ const wasRaw = input.isRaw;
1387
+ const onKey = (chunk) => {
1388
+ if (chunk.includes(3)) close();
1389
+ };
1390
+ process.once("SIGINT", close);
1391
+ process.once("SIGTERM", close);
1392
+ try {
1393
+ if (raw) {
1394
+ input.setRawMode(true);
1395
+ input.on("data", onKey);
1396
+ input.resume();
1397
+ }
1398
+ output.write("\x1B[?1049h\x1B[?25l");
1399
+ while (!stop.signal.aborted) {
1400
+ const lines = await statusLines(home);
1401
+ if (stop.signal.aborted) break;
1402
+ output.write("\x1B[H\x1B[0J" + [...lines, "", "\u6BCF 2 \u79D2\u5237\u65B0 \xB7 Ctrl+C \u5173\u95ED\u770B\u677F"].join("\n"));
1403
+ await delay2(2e3, void 0, { signal: stop.signal }).catch((error) => {
1404
+ if (!stop.signal.aborted) throw error;
1405
+ });
1406
+ }
1407
+ } finally {
1408
+ output.write("\x1B[?25h\x1B[?1049l");
1409
+ if (raw) {
1410
+ input.off("data", onKey);
1411
+ input.setRawMode(Boolean(wasRaw));
1412
+ input.pause();
1413
+ }
1414
+ process.off("SIGINT", close);
1415
+ process.off("SIGTERM", close);
1416
+ }
1417
+ }
1418
+
1419
+ // distribution/bootstrap/assets.ts
1420
+ import { createRequire } from "node:module";
1421
+ import { readFile as readFile8 } from "node:fs/promises";
1422
+ import { dirname as dirname5, join as join7 } from "node:path";
1423
+ import { fileURLToPath } from "node:url";
1424
+ async function assetsDirectory(release, entryURL = import.meta.url) {
1425
+ if (!release?.assetPackage) return join7(dirname5(fileURLToPath(entryURL)), "assets");
1426
+ const expected = release.assetPackage;
1427
+ let file;
1428
+ try {
1429
+ file = createRequire(entryURL).resolve(`${expected.name}/package.json`);
1430
+ } catch {
1431
+ throw new Error("\u6CA1\u6709\u627E\u5230\u5F53\u524D\u8BBE\u5907\u7684\u706B\u706B\u8D44\u6E90\u5305\u3002\u8BF7\u91CD\u65B0\u6267\u884C\u5B8C\u6574\u5B89\u88C5\u547D\u4EE4\uFF0C\u5E76\u786E\u4FDD\u6CA1\u6709\u8DF3\u8FC7 npm \u53EF\u9009\u4F9D\u8D56\u3002");
1432
+ }
1433
+ const manifest = JSON.parse(await readFile8(file, "utf8"));
1434
+ if (manifest.name !== expected.name || manifest.version !== expected.version) {
1435
+ 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
+ }
1437
+ return join7(dirname5(file), "assets");
1438
+ }
1439
+
1074
1440
  // distribution/bootstrap/entry.ts
1075
1441
  function artifactSource(source, directory) {
1076
1442
  return async (url, options) => {
@@ -1079,8 +1445,8 @@ function artifactSource(source, directory) {
1079
1445
  const artifact2 = source && [source.runtime, source.python, source.pythonDependencies].find((item) => item.url === String(url));
1080
1446
  const sha256 = bundled?.[1] ?? artifact2?.sha256;
1081
1447
  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");
1448
+ const root = directory ?? await assetsDirectory(source, import.meta.url);
1449
+ const path = join8(root, sha256 + ".tgz");
1084
1450
  const file = await open3(path).catch((error) => {
1085
1451
  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
1452
  throw error;
@@ -1089,7 +1455,7 @@ function artifactSource(source, directory) {
1089
1455
  };
1090
1456
  }
1091
1457
  async function main(args = process.argv.slice(2)) {
1092
- let home = join6(homedir(), ".huohuo"), noOpen = false, command = "start";
1458
+ let home = join8(homedir2(), ".huohuo"), noOpen = false, assumeYes = false, watch = false, command = "start";
1093
1459
  let releaseFile, artifactDirectory;
1094
1460
  for (let i = 0; i < args.length; i++) {
1095
1461
  if (args[i] === "--home") {
@@ -1101,19 +1467,23 @@ async function main(args = process.argv.slice(2)) {
1101
1467
  if (option === "--release") releaseFile = resolve5(args[++i]);
1102
1468
  else artifactDirectory = resolve5(args[++i]);
1103
1469
  } else if (args[i] === "--no-open") noOpen = true;
1470
+ else if (args[i] === "--yes") assumeYes = true;
1471
+ else if (args[i] === "--watch") watch = true;
1104
1472
  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"));
1473
+ const packageFile = join8(dirname6(fileURLToPath2(import.meta.url)), "package.json");
1474
+ const manifest = JSON.parse(await readFile9(packageFile, "utf8"));
1107
1475
  console.log(`${manifest.name} ${manifest.version}${manifest.private ? "\uFF08\u672C\u5730\u5019\u9009\uFF09" : ""}`);
1108
1476
  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]");
1477
+ } else if (["start", "status", "doctor", "stop", "upgrade", "uninstall"].includes(args[i])) command = args[i];
1478
+ else throw new Error("\u7528\u6CD5\uFF1Ahuohuo [start|status|doctor|stop|upgrade|uninstall] [--home \u76EE\u5F55] [--no-open]");
1111
1479
  }
1480
+ if (watch && command !== "status") throw new Error("--watch \u8BF7\u4E0E status \u4E00\u8D77\u4F7F\u7528");
1112
1481
  try {
1113
1482
  home = await realpath5(home);
1114
1483
  } catch (e) {
1115
1484
  if (e.code !== "ENOENT") throw e;
1116
1485
  }
1486
+ if (command === "status") return showStatus(home, watch);
1117
1487
  let record = await readInstallation(home);
1118
1488
  const labels = {
1119
1489
  preflight: "\u68C0\u67E5\u672C\u673A\u73AF\u5883",
@@ -1124,13 +1494,19 @@ async function main(args = process.argv.slice(2)) {
1124
1494
  verify: "\u68C0\u67E5\u5B89\u88C5\u7ED3\u679C",
1125
1495
  "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
1496
  };
1127
- const progress = (stage) => console.log(`\u706B\u706B\uFF1A${labels[stage] ?? stage}`);
1497
+ const installStages = ["preflight", "download", "install-runtime", "install-dependencies", "assemble-profiles", "verify"];
1498
+ const progress = (stage) => {
1499
+ const index = command === "start" ? installStages.indexOf(stage) : -1;
1500
+ const step = index < 0 ? "" : `[${index + 1}/${installStages.length}] `;
1501
+ console.log(`\u706B\u706B\uFF1A${step}${labels[stage] ?? stage}`);
1502
+ };
1503
+ const ui = installUI(assumeYes);
1128
1504
  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 });
1505
+ const candidate = selectRelease(JSON.parse(await readFile9(releaseFile ?? join8(dirname6(fileURLToPath2(import.meta.url)), "release.json"), "utf8")));
1506
+ await upgrade(home, candidate, { fetcher: artifactSource(candidate, artifactDirectory), progress, ...ui });
1131
1507
  const current = await readInstallation(home);
1132
1508
  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);
1509
+ await run(process.execPath, [join8(installationPaths(home, current.release).runtime, "lib/distribution/managed.js"), "start", home, ...noOpen ? ["--no-open"] : []], home);
1134
1510
  return;
1135
1511
  }
1136
1512
  if (command === "uninstall") {
@@ -1148,7 +1524,7 @@ async function main(args = process.argv.slice(2)) {
1148
1524
  return;
1149
1525
  }
1150
1526
  const paths = installationPaths(home, record.release);
1151
- const targets = [home, paths.directory, paths.environment, join6(home, "instances/default")];
1527
+ const targets = [home, paths.directory, paths.environment, join8(home, "instances/default")];
1152
1528
  const resources = evaluateResources(await sampleResources(targets), record.release.budget, targets.map((path) => ({ path, bytes: 0 })), "start");
1153
1529
  console.log(JSON.stringify({
1154
1530
  home,
@@ -1169,33 +1545,34 @@ async function main(args = process.argv.slice(2)) {
1169
1545
  }
1170
1546
  const runtime = installationPaths(home, record.release).runtime;
1171
1547
  try {
1172
- await readFile7(join6(runtime, "lib/distribution/managed.js"));
1548
+ await readFile9(join8(runtime, "lib/distribution/managed.js"));
1173
1549
  } catch (e) {
1174
1550
  if (e.code !== "ENOENT") throw e;
1175
1551
  record = void 0;
1176
1552
  }
1177
1553
  }
1178
1554
  if (!record || record.status !== "ready") {
1179
- let catalog = null;
1180
- if (!record || ["preflight", "download"].includes(record.stage)) {
1555
+ const persisted = await readInstallation(home);
1556
+ let source = persisted?.release;
1557
+ if (!source) {
1558
+ let catalog = null;
1181
1559
  try {
1182
- catalog = JSON.parse(await readFile7(releaseFile ?? join6(dirname5(fileURLToPath(import.meta.url)), "release.json"), "utf8"));
1560
+ catalog = JSON.parse(await readFile9(releaseFile ?? join8(dirname6(fileURLToPath2(import.meta.url)), "release.json"), "utf8"));
1183
1561
  } catch (e) {
1184
1562
  if (e.code !== "ENOENT") throw e;
1185
1563
  }
1564
+ source = selectRelease(catalog);
1186
1565
  }
1187
- const source = (await readInstallation(home))?.release ?? catalog;
1188
1566
  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 });
1567
+ record = await install(home, source, { fetcher, progress, ...ui });
1191
1568
  }
1192
1569
  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);
1570
+ await run(process.execPath, [join8(installationPaths(installedHome, record.release).runtime, "lib/distribution/managed.js"), "start", installedHome, ...noOpen ? ["--no-open"] : []], installedHome);
1194
1571
  }
1195
- if (process.argv[1] && await realpath5(resolve5(process.argv[1])).catch(() => "") === await realpath5(fileURLToPath(import.meta.url))) {
1572
+ if (process.argv[1] && await realpath5(resolve5(process.argv[1])).catch(() => "") === await realpath5(fileURLToPath2(import.meta.url))) {
1196
1573
  main().catch((error) => {
1197
1574
  console.error(error instanceof Error ? error.message : "\u5B89\u88C5\u6216\u542F\u52A8\u5931\u8D25");
1198
- process.exitCode = 1;
1575
+ if (error?.code !== "INSTALL_CANCELLED") process.exitCode = 1;
1199
1576
  });
1200
1577
  }
1201
1578
  export {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pab-iprg/huohuo",
3
- "version": "1.0.0-preview.3",
3
+ "version": "1.0.0-preview.5",
4
4
  "type": "module",
5
- "description": "火火投研伙伴安装入口",
5
+ "description": "火火双平台安装入口(实验预览)",
6
6
  "bin": {
7
7
  "huohuo": "./cli.js"
8
8
  },
@@ -12,11 +12,14 @@
12
12
  "files": [
13
13
  "cli.js",
14
14
  "release.json",
15
- "README.md",
16
- "assets"
15
+ "README.md"
17
16
  ],
17
+ "optionalDependencies": {
18
+ "@pab-iprg/huohuo-assets-darwin-arm64": "1.0.0-preview.5",
19
+ "@pab-iprg/huohuo-assets-linux-x64": "1.0.0-preview.5"
20
+ },
18
21
  "publishConfig": {
19
22
  "access": "public",
20
- "tag": "next"
23
+ "tag": "preview"
21
24
  }
22
25
  }
package/release.json CHANGED
@@ -1,93 +1,205 @@
1
1
  {
2
+ "kind": "huohuo-release-catalog",
2
3
  "format": 1,
3
- "bootstrapFormat": 1,
4
4
  "version": "1.0.0",
5
- "nodeMajor": 24,
6
- "npmMajor": 11,
7
- "qualification": "qualified",
8
- "platform": {
9
- "os": "darwin",
10
- "arch": "arm64",
11
- "minimumOsRelease": "25.6.0"
12
- },
13
- "runtime": {
14
- "name": "@huohuo/research-agent",
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,
21
- "bundled": true
22
- },
23
- "python": {
24
- "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",
25
- "sha256": "8392b6ea51d94391befb2c31ebe75463f045d0b69dfcf5e1bb3234e1f5fa3405",
26
- "downloadBytes": 25265774,
27
- "expandedBytes": 66020864,
28
- "redirectHosts": [
29
- "release-assets.githubusercontent.com"
30
- ],
31
- "executable": "python/bin/python3",
32
- "build": "cpython-3.13.18+20261026-aarch64-apple-darwin-install_only_stripped.tar.gz",
33
- "license": "python/lib/python3.13/LICENSE.txt"
34
- },
35
- "pythonDependencies": {
36
- "url": "https://bundled.invalid/4f008fe4491617729554e056ad59eee686ccc5d95c9f1d5259da705bf054ce21.tgz",
37
- "sha256": "4f008fe4491617729554e056ad59eee686ccc5d95c9f1d5259da705bf054ce21",
38
- "downloadBytes": 10235,
39
- "expandedBytes": 15007,
40
- "bundled": true
41
- },
42
- "dsh": {
43
- "version": "0.2.1-rc.2",
44
- "assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
45
- },
46
- "requirementsSha256": "57042fff45cee66fe33712472e2e5b88f03a084068ca20242588b2e18b3e167b",
47
- "profilesSha256": "bdd628f3a599a69a9835db9904fb55d36c57e0b3486b4f3f7dbec8d8a47f36f0",
48
- "budget": {
49
- "minimumEffectiveMemoryBytes": 34359738368,
50
- "startupHeadroomBytes": 134217728,
51
- "heavyJobHeadroomBytes": 1073741824,
52
- "diskReserveBytes": 1073741824,
53
- "recoverySamples": 3,
54
- "processTreeBudgetBytes": 17179869184,
55
- "sampleIntervalMs": 2000,
56
- "sampleMaxAgeMs": 10000,
57
- "installTemporaryBytes": 1073741824,
58
- "dependenciesBytes": 4294967296,
59
- "backupBytes": 0
60
- },
61
- "schema": {
62
- "minimum": 1,
63
- "maximum": 1,
64
- "migrationsSha256": "ba029fdbffa5320e61393b484d08accafce60e9aa8dacde5b6212fb79dd9146b",
65
- "files": {
66
- "001_init.sql": "b42b6192d872f0f65cfbbf9d0c7fb442d3aa49584a6cf321d3bcc641b31b70da",
67
- "002_contact_decision.sql": "af82cb9975a09fa09c0f970b70d04a7b55a960d72e0853f4573d571efaa46f0a",
68
- "003_schedules.sql": "2937f7be2693ce9b410ee9a73bd10c5e819c5081a10a6b3993c33f37ad42295f",
69
- "004_condition_state.sql": "f081d250dd525ad57a450bd6bca59a75f8fb1428177d670ae9fa142ed79721d6",
70
- "005_execution_handoff.sql": "ebf2e5c4890b7a73ae1ffb57a70bb15183f48a6c1fa2acf3a48b95ccb3d992b3",
71
- "006_entity_confirmation.sql": "d25ee376e7f6dd55df2edeb68d7591ef5fbaa34eeb3eef662cd76e183526d3c0",
72
- "007_research_tool_calls.sql": "df9c7d8d25b44b151f743e2d5167b34ae89c64e67423d8af554364fd792da942",
73
- "008_followup_notifications.sql": "98865d811879afd679846087a5f01ee2db371dcb90381a9c400c940a5c1c4b37",
74
- "008_investment.sql": "71cecf6215552c725797059efdcb501247e1225c7d9f662de606266d3a947e61",
75
- "009_notification_channels.sql": "572424d739203a8514ae59920a0ad69cf83f278479b48d48becb88bddda25d9a",
76
- "010_strategies.sql": "af023fab201db6bd2e15e2348125d112bc21cf3a59d8afd0a2d616fc05cee209",
77
- "011_strategy_run_subjects.sql": "53a1c216ed775a06549dcc201f9494ceabe308bee6dc52a214b685cca2f2fe8d",
78
- "012_strategy_schedule.sql": "ccddd81bb70d2c86356e6f1ad09132e7397837d17e926b515c8e281a362bdd58",
79
- "014_industry_chain.sql": "b656efffe746d14ca350ea1c9ecfcce2d98b5753e4b2f862c75d0456a062fd4a",
80
- "015_source_heads.sql": "652bf637892edbbe969ce5ed4ce45a4a0c43d03de395fce1b828fc4c0169f885",
81
- "016_legacy_transfer.sql": "b6e3c214b103487d741226b0f83525e0cacbb0c440bd1935d0e963dc9b9bfd07",
82
- "017_unified_strategy_work.sql": "8d055ca58f3e73482fb16c630c733ae93197244b37e4509cc90c7dec3bf88d60",
83
- "018_personalization.sql": "7f62a4eb74707fb071f56d6057b6d6517ed7648af23ebe290f3be15d260a103c",
84
- "019_position_risk.sql": "3c461fa435b352127fd4ee4e7ed3de2c924561a3727dce1535d7d3a47c436ca9",
85
- "020_notification_tests.sql": "3eb0d72eadf04b074208dbe0ef6a72ee614ec11a15143108a2471568b7b4fb22",
86
- "021_notification_priority.sql": "f742b4c4b796e918bfc6a08bac8c5ae206382075925acf7b1c430b033f5177e6",
87
- "022_research_job_priority.sql": "fcb0c0af025806faab7cd403097a9d2c55e07331ab0a78b2926c1927a87f4ed4",
88
- "023_adaptive_notifications.sql": "80630e2dad73844d48b265fbc33648b01d8321a6fd9a0587ef4dc01f362aadec",
89
- "023_notification_attention.sql": "5dd85fb001192558211c7b69e3452984c52119f215750ed6283b296d3dccc842",
90
- "024_t_trade_watch.sql": "aa38470ddab00e0cbca07ce6d5cc31399ba2b6e40c8d8d888e88ec093a9f89ef"
5
+ "releases": [
6
+ {
7
+ "format": 1,
8
+ "bootstrapFormat": 1,
9
+ "version": "1.0.0",
10
+ "nodeMajor": 24,
11
+ "npmMajor": 11,
12
+ "qualification": "controlled-acceptance-only",
13
+ "platform": {
14
+ "os": "darwin",
15
+ "arch": "arm64",
16
+ "minimumOsRelease": "25.6.0"
17
+ },
18
+ "runtime": {
19
+ "name": "@huohuo/research-agent",
20
+ "version": "1.0.0",
21
+ "url": "https://bundled.invalid/45b0bbe6c53759c5e1f50fbd506ea97ec74adf1f29b3ade98793210f3b66dfbc.tgz",
22
+ "bundled": true,
23
+ "sha256": "45b0bbe6c53759c5e1f50fbd506ea97ec74adf1f29b3ade98793210f3b66dfbc",
24
+ "integrity": "sha512-bFFRkVFn/4OgqU6CneroGm+qy0tP8hXaHZ4U0C6tW3DHGxvBX8c0gn0UTRYIRxrQUQ2QbcXbvcgAG7MsADw26A==",
25
+ "downloadBytes": 2609884,
26
+ "expandedBytes": 8326138
27
+ },
28
+ "python": {
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",
30
+ "sha256": "8392b6ea51d94391befb2c31ebe75463f045d0b69dfcf5e1bb3234e1f5fa3405",
31
+ "downloadBytes": 25265774,
32
+ "expandedBytes": 66020864,
33
+ "redirectHosts": [
34
+ "release-assets.githubusercontent.com"
35
+ ],
36
+ "executable": "python/bin/python3",
37
+ "build": "cpython-3.13.18+20261026-aarch64-apple-darwin-install_only_stripped.tar.gz",
38
+ "license": "python/lib/python3.13/LICENSE.txt"
39
+ },
40
+ "pythonDependencies": {
41
+ "url": "https://bundled.invalid/61bbfd92aa43f2e1ae8e339a03e88efb145ae3266f00ef8fc979c8bc1ed502a5.tgz",
42
+ "bundled": true,
43
+ "sha256": "61bbfd92aa43f2e1ae8e339a03e88efb145ae3266f00ef8fc979c8bc1ed502a5",
44
+ "downloadBytes": 10215,
45
+ "expandedBytes": 20480,
46
+ "upstreamWheelCount": 39
47
+ },
48
+ "dsh": {
49
+ "version": "0.2.1-rc.2",
50
+ "assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
51
+ },
52
+ "requirementsSha256": "57042fff45cee66fe33712472e2e5b88f03a084068ca20242588b2e18b3e167b",
53
+ "profilesSha256": "aae1b5ee7f19d24d384a0f57de3e62ea9a1501e316193266d630f773dac058f1",
54
+ "budget": {
55
+ "minimumEffectiveMemoryBytes": 34359738368,
56
+ "startupHeadroomBytes": 134217728,
57
+ "heavyJobHeadroomBytes": 1073741824,
58
+ "diskReserveBytes": 1073741824,
59
+ "recoverySamples": 3,
60
+ "processTreeBudgetBytes": 17179869184,
61
+ "sampleIntervalMs": 2000,
62
+ "sampleMaxAgeMs": 10000,
63
+ "installTemporaryBytes": 1073741824,
64
+ "dependenciesBytes": 4294967296,
65
+ "backupBytes": 0,
66
+ "observedInstallRssBytes": 1904771072
67
+ },
68
+ "schema": {
69
+ "minimum": 1,
70
+ "maximum": 1,
71
+ "migrationsSha256": "ba029fdbffa5320e61393b484d08accafce60e9aa8dacde5b6212fb79dd9146b",
72
+ "files": {
73
+ "001_init.sql": "b42b6192d872f0f65cfbbf9d0c7fb442d3aa49584a6cf321d3bcc641b31b70da",
74
+ "002_contact_decision.sql": "af82cb9975a09fa09c0f970b70d04a7b55a960d72e0853f4573d571efaa46f0a",
75
+ "003_schedules.sql": "2937f7be2693ce9b410ee9a73bd10c5e819c5081a10a6b3993c33f37ad42295f",
76
+ "004_condition_state.sql": "f081d250dd525ad57a450bd6bca59a75f8fb1428177d670ae9fa142ed79721d6",
77
+ "005_execution_handoff.sql": "ebf2e5c4890b7a73ae1ffb57a70bb15183f48a6c1fa2acf3a48b95ccb3d992b3",
78
+ "006_entity_confirmation.sql": "d25ee376e7f6dd55df2edeb68d7591ef5fbaa34eeb3eef662cd76e183526d3c0",
79
+ "007_research_tool_calls.sql": "df9c7d8d25b44b151f743e2d5167b34ae89c64e67423d8af554364fd792da942",
80
+ "008_followup_notifications.sql": "98865d811879afd679846087a5f01ee2db371dcb90381a9c400c940a5c1c4b37",
81
+ "008_investment.sql": "71cecf6215552c725797059efdcb501247e1225c7d9f662de606266d3a947e61",
82
+ "009_notification_channels.sql": "572424d739203a8514ae59920a0ad69cf83f278479b48d48becb88bddda25d9a",
83
+ "010_strategies.sql": "af023fab201db6bd2e15e2348125d112bc21cf3a59d8afd0a2d616fc05cee209",
84
+ "011_strategy_run_subjects.sql": "53a1c216ed775a06549dcc201f9494ceabe308bee6dc52a214b685cca2f2fe8d",
85
+ "012_strategy_schedule.sql": "ccddd81bb70d2c86356e6f1ad09132e7397837d17e926b515c8e281a362bdd58",
86
+ "014_industry_chain.sql": "b656efffe746d14ca350ea1c9ecfcce2d98b5753e4b2f862c75d0456a062fd4a",
87
+ "015_source_heads.sql": "652bf637892edbbe969ce5ed4ce45a4a0c43d03de395fce1b828fc4c0169f885",
88
+ "016_legacy_transfer.sql": "b6e3c214b103487d741226b0f83525e0cacbb0c440bd1935d0e963dc9b9bfd07",
89
+ "017_unified_strategy_work.sql": "8d055ca58f3e73482fb16c630c733ae93197244b37e4509cc90c7dec3bf88d60",
90
+ "018_personalization.sql": "7f62a4eb74707fb071f56d6057b6d6517ed7648af23ebe290f3be15d260a103c",
91
+ "019_position_risk.sql": "3c461fa435b352127fd4ee4e7ed3de2c924561a3727dce1535d7d3a47c436ca9",
92
+ "020_notification_tests.sql": "3eb0d72eadf04b074208dbe0ef6a72ee614ec11a15143108a2471568b7b4fb22",
93
+ "021_notification_priority.sql": "f742b4c4b796e918bfc6a08bac8c5ae206382075925acf7b1c430b033f5177e6",
94
+ "022_research_job_priority.sql": "fcb0c0af025806faab7cd403097a9d2c55e07331ab0a78b2926c1927a87f4ed4",
95
+ "023_adaptive_notifications.sql": "80630e2dad73844d48b265fbc33648b01d8321a6fd9a0587ef4dc01f362aadec",
96
+ "023_notification_attention.sql": "5dd85fb001192558211c7b69e3452984c52119f215750ed6283b296d3dccc842",
97
+ "024_t_trade_watch.sql": "aa38470ddab00e0cbca07ce6d5cc31399ba2b6e40c8d8d888e88ec093a9f89ef"
98
+ }
99
+ },
100
+ "assetPackage": {
101
+ "name": "@pab-iprg/huohuo-assets-darwin-arm64",
102
+ "version": "1.0.0-preview.5"
103
+ }
104
+ },
105
+ {
106
+ "format": 1,
107
+ "bootstrapFormat": 1,
108
+ "version": "1.0.0",
109
+ "nodeMajor": 24,
110
+ "npmMajor": 11,
111
+ "qualification": "controlled-acceptance-only",
112
+ "platform": {
113
+ "os": "linux",
114
+ "arch": "x64",
115
+ "minimumOsRelease": "6.8.0",
116
+ "libc": "glibc"
117
+ },
118
+ "runtime": {
119
+ "name": "@huohuo/research-agent",
120
+ "version": "1.0.0",
121
+ "url": "https://bundled.invalid/4bab6aed6c844ab711d0a91b27c2e108ca8d4f4de1f4b30f7668b73f922d8500.tgz",
122
+ "bundled": true,
123
+ "sha256": "4bab6aed6c844ab711d0a91b27c2e108ca8d4f4de1f4b30f7668b73f922d8500",
124
+ "integrity": "sha512-Iu0NMfgVLZmPXcxcUfYbW48X0uvHFDUzpQWhPP6unz06v2G1f178m10ehU0pxNzJaUXkwg/p8OH5/DAeuungRg==",
125
+ "downloadBytes": 2609889,
126
+ "expandedBytes": 8326156
127
+ },
128
+ "python": {
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",
130
+ "sha256": "df15fe60d3ff55bf9dcf25824b5572756bb8816ff93865a273d3301d7066a8b7",
131
+ "downloadBytes": 35395906,
132
+ "expandedBytes": 102403072,
133
+ "redirectHosts": [
134
+ "release-assets.githubusercontent.com"
135
+ ],
136
+ "executable": "python/bin/python3",
137
+ "build": "cpython-3.13.18+20261026-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz",
138
+ "license": "python/lib/python3.13/LICENSE.txt"
139
+ },
140
+ "pythonDependencies": {
141
+ "url": "https://bundled.invalid/7e42648979228aa347ad334e4d40271a2c3dd78b76ebf6c5c557b0fd8fe36ec2.tgz",
142
+ "bundled": true,
143
+ "sha256": "7e42648979228aa347ad334e4d40271a2c3dd78b76ebf6c5c557b0fd8fe36ec2",
144
+ "downloadBytes": 10367,
145
+ "expandedBytes": 20480,
146
+ "upstreamWheelCount": 40
147
+ },
148
+ "dsh": {
149
+ "version": "0.2.1-rc.2",
150
+ "assemblySha256": "08cf11de70e49981b4891c0aaa2a5e7b872a95b6aa07557bb629713fa8f9e249"
151
+ },
152
+ "requirementsSha256": "d4ff8161886fc286e6f8ddfcd070ac5812528971affe7b87baa03662dd242d45",
153
+ "profilesSha256": "aae1b5ee7f19d24d384a0f57de3e62ea9a1501e316193266d630f773dac058f1",
154
+ "budget": {
155
+ "minimumEffectiveMemoryBytes": 8589934592,
156
+ "startupHeadroomBytes": 1073741824,
157
+ "heavyJobHeadroomBytes": 1073741824,
158
+ "processTreeBudgetBytes": 4294967296,
159
+ "diskReserveBytes": 1073741824,
160
+ "recoverySamples": 3,
161
+ "sampleIntervalMs": 2000,
162
+ "sampleMaxAgeMs": 10000,
163
+ "installTemporaryBytes": 1073741824,
164
+ "dependenciesBytes": 4294967296,
165
+ "backupBytes": 1073741824
166
+ },
167
+ "schema": {
168
+ "minimum": 1,
169
+ "maximum": 1,
170
+ "migrationsSha256": "ba029fdbffa5320e61393b484d08accafce60e9aa8dacde5b6212fb79dd9146b",
171
+ "files": {
172
+ "001_init.sql": "b42b6192d872f0f65cfbbf9d0c7fb442d3aa49584a6cf321d3bcc641b31b70da",
173
+ "002_contact_decision.sql": "af82cb9975a09fa09c0f970b70d04a7b55a960d72e0853f4573d571efaa46f0a",
174
+ "003_schedules.sql": "2937f7be2693ce9b410ee9a73bd10c5e819c5081a10a6b3993c33f37ad42295f",
175
+ "004_condition_state.sql": "f081d250dd525ad57a450bd6bca59a75f8fb1428177d670ae9fa142ed79721d6",
176
+ "005_execution_handoff.sql": "ebf2e5c4890b7a73ae1ffb57a70bb15183f48a6c1fa2acf3a48b95ccb3d992b3",
177
+ "006_entity_confirmation.sql": "d25ee376e7f6dd55df2edeb68d7591ef5fbaa34eeb3eef662cd76e183526d3c0",
178
+ "007_research_tool_calls.sql": "df9c7d8d25b44b151f743e2d5167b34ae89c64e67423d8af554364fd792da942",
179
+ "008_followup_notifications.sql": "98865d811879afd679846087a5f01ee2db371dcb90381a9c400c940a5c1c4b37",
180
+ "008_investment.sql": "71cecf6215552c725797059efdcb501247e1225c7d9f662de606266d3a947e61",
181
+ "009_notification_channels.sql": "572424d739203a8514ae59920a0ad69cf83f278479b48d48becb88bddda25d9a",
182
+ "010_strategies.sql": "af023fab201db6bd2e15e2348125d112bc21cf3a59d8afd0a2d616fc05cee209",
183
+ "011_strategy_run_subjects.sql": "53a1c216ed775a06549dcc201f9494ceabe308bee6dc52a214b685cca2f2fe8d",
184
+ "012_strategy_schedule.sql": "ccddd81bb70d2c86356e6f1ad09132e7397837d17e926b515c8e281a362bdd58",
185
+ "014_industry_chain.sql": "b656efffe746d14ca350ea1c9ecfcce2d98b5753e4b2f862c75d0456a062fd4a",
186
+ "015_source_heads.sql": "652bf637892edbbe969ce5ed4ce45a4a0c43d03de395fce1b828fc4c0169f885",
187
+ "016_legacy_transfer.sql": "b6e3c214b103487d741226b0f83525e0cacbb0c440bd1935d0e963dc9b9bfd07",
188
+ "017_unified_strategy_work.sql": "8d055ca58f3e73482fb16c630c733ae93197244b37e4509cc90c7dec3bf88d60",
189
+ "018_personalization.sql": "7f62a4eb74707fb071f56d6057b6d6517ed7648af23ebe290f3be15d260a103c",
190
+ "019_position_risk.sql": "3c461fa435b352127fd4ee4e7ed3de2c924561a3727dce1535d7d3a47c436ca9",
191
+ "020_notification_tests.sql": "3eb0d72eadf04b074208dbe0ef6a72ee614ec11a15143108a2471568b7b4fb22",
192
+ "021_notification_priority.sql": "f742b4c4b796e918bfc6a08bac8c5ae206382075925acf7b1c430b033f5177e6",
193
+ "022_research_job_priority.sql": "fcb0c0af025806faab7cd403097a9d2c55e07331ab0a78b2926c1927a87f4ed4",
194
+ "023_adaptive_notifications.sql": "80630e2dad73844d48b265fbc33648b01d8321a6fd9a0587ef4dc01f362aadec",
195
+ "023_notification_attention.sql": "5dd85fb001192558211c7b69e3452984c52119f215750ed6283b296d3dccc842",
196
+ "024_t_trade_watch.sql": "aa38470ddab00e0cbca07ce6d5cc31399ba2b6e40c8d8d888e88ec093a9f89ef"
197
+ }
198
+ },
199
+ "assetPackage": {
200
+ "name": "@pab-iprg/huohuo-assets-linux-x64",
201
+ "version": "1.0.0-preview.5"
202
+ }
91
203
  }
92
- }
204
+ ]
93
205
  }