summrise-agent 1.2.453 → 1.2.455

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/bin/summrise.js CHANGED
@@ -60,6 +60,13 @@ exports.latestCdnVersion = latestCdnVersion;
60
60
  exports.latestNpmVersion = latestNpmVersion;
61
61
  exports.newestOf = newestOf;
62
62
  exports.latestReleaseVersion = latestReleaseVersion;
63
+ exports.cdnBase = cdnBase;
64
+ exports.componentUrl = componentUrl;
65
+ exports.componentKey = componentKey;
66
+ exports.componentPins = componentPins;
67
+ exports.sha256File = sha256File;
68
+ exports.resolveComponent = resolveComponent;
69
+ exports.ensureElectron = ensureElectron;
63
70
  exports.statusReport = statusReport;
64
71
  exports.isBehind = isBehind;
65
72
  exports.behindBy = behindBy;
@@ -81,6 +88,7 @@ exports.rollbackVersionOk = rollbackVersionOk;
81
88
  const child_process_1 = require("child_process");
82
89
  const crypto = __importStar(require("crypto"));
83
90
  const fs = __importStar(require("fs"));
91
+ const os = __importStar(require("os"));
84
92
  const path = __importStar(require("path"));
85
93
  const EXE_SRC = path.join(__dirname, "..", "summrise-agent.exe");
86
94
  // C1 (2026-08-28): the registry is the single source of truth for the install
@@ -797,6 +805,133 @@ function newestOf(a, b) {
797
805
  function latestReleaseVersion() {
798
806
  return newestOf(latestCdnVersion(), latestNpmVersion());
799
807
  }
808
+ /** The release host this CLI already trusts for version checks. */
809
+ function cdnBase() {
810
+ return (process.env.SUMMRISE_CDN || "https://agent.saisi.online").replace(/\/+$/, "");
811
+ }
812
+ /** Where a boxed component lives on that host. Pure — the tests pin it. */
813
+ function componentUrl(name) {
814
+ return `${cdnBase()}/summrise-agent/${name}`;
815
+ }
816
+ /** The release manifest's key for a component's FILE name (they are not the same string). */
817
+ function componentKey(fileName) {
818
+ if (fileName.startsWith("summrise-playwright"))
819
+ return "playwright";
820
+ if (fileName.startsWith("electron-"))
821
+ return "electron";
822
+ if (fileName.startsWith("cloudflared"))
823
+ return "cloudflared";
824
+ return null; // a file this release does not pin (e.g. fix-tunnel.ps1)
825
+ }
826
+ /** The manifest's component pins, or {} when it carries none (an older release). */
827
+ function componentPins() {
828
+ const r = (0, child_process_1.spawnSync)("curl", ["-s", "-m", "5", `${cdnBase()}/api/version`], {
829
+ encoding: "utf8",
830
+ timeout: 8000,
831
+ });
832
+ if (r.status !== 0 || !r.stdout)
833
+ return {};
834
+ try {
835
+ const j = JSON.parse(r.stdout);
836
+ const c = j && typeof j.components === "object" ? j.components : null;
837
+ return c || {};
838
+ }
839
+ catch {
840
+ return {};
841
+ }
842
+ }
843
+ /** sha256 of a file. Read whole: the electron runtime is 115 MB and node's heap is fine with it. */
844
+ function sha256File(p) {
845
+ return crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex");
846
+ }
847
+ /**
848
+ * A boxed component's local path: the copy inside the package if it is there,
849
+ * otherwise the release host's route for it, downloaded to a temp file.
850
+ *
851
+ * WHY THIS EXISTS (2026-09-23). The npm package carries NONE of the big
852
+ * binaries on purpose (cloudflared 54 MB, playwright 31 MB, electron 234 MB), so
853
+ * "not in the package" used to mean "you do not get it, ever" — setup printed a
854
+ * line and moved on. The same shape for cloudflared left a freshly migrated
855
+ * device with no tunnel, and a device with no tunnel is INVISIBLE TO THE
856
+ * CONSOLE while looking perfectly healthy from inside: an hour of that is what
857
+ * this function is for. The release host serves all three, and for cloudflared
858
+ * it proxies a PINNED upstream asset, which the agent's own sha256 pin then
859
+ * checks again on the path it uses.
860
+ *
861
+ * `null` when neither source works: every component here is optional to the
862
+ * AGENT, and a failed fetch must not fail the install.
863
+ */
864
+ function resolveComponent(name, pkgPath) {
865
+ if (fs.existsSync(pkgPath))
866
+ return pkgPath;
867
+ const dest = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "summrise-comp-")), name);
868
+ // -f: an HTTP error is a FAILURE, not a 404 page written to disk (the
869
+ // HTML-polluted download that round-54 exists to remember).
870
+ const r = (0, child_process_1.spawnSync)("curl", ["-fsSL", "-m", "300", "-o", dest, componentUrl(name)], { encoding: "utf8", timeout: 320000 });
871
+ if (r.status !== 0 || !fs.existsSync(dest) || fs.statSync(dest).size === 0) {
872
+ return null;
873
+ }
874
+ // VERIFY WHAT WAS FETCHED (grilling Q4). Until this existed, a worker serving
875
+ // different bytes would have been staged without complaint — the components are
876
+ // executed or loaded, so "the host said so" is not an anchor. The manifest that
877
+ // /api/version serves carries a sha256 per component (index/components.json,
878
+ // copied in at publish time); a pin that does not match REFUSES the component,
879
+ // because the failure it prevents is a device running bytes nobody published.
880
+ // No pin at all (an older release) is a warning, not a refusal: it must not make
881
+ // an install impossible.
882
+ const key = componentKey(name);
883
+ const pin = key ? (componentPins()[key] || {}).sha256 || "" : "";
884
+ if (pin) {
885
+ const got = sha256File(dest);
886
+ if (got !== pin) {
887
+ console.error(`setup: REFUSING ${name} — sha256 ${got.slice(0, 12)}… does not match the release manifest's ${pin.slice(0, 12)}… (the host served different bytes than it published)`);
888
+ return null;
889
+ }
890
+ console.log(`setup: ${name} verified against the release manifest (${pin.slice(0, 12)}…)`);
891
+ }
892
+ else if (key) {
893
+ console.log(`setup: ${name} fetched WITHOUT a manifest pin — not verified`);
894
+ }
895
+ console.log(`setup: ${name} fetched from the release host (not in the package)`);
896
+ return dest;
897
+ }
898
+ /**
899
+ * Ensure the desktop shell's electron RUNTIME. The shell's sources are ~90 KB
900
+ * and ship in the package; the runtime is 234 MB and deliberately does not.
901
+ *
902
+ * WHY IT IS FETCHED AND NOT `npx electron`: the release host serves a PINNED
903
+ * upstream proxy of the official build (v33.4.11 when this was written), so a
904
+ * device behind the GFW needs no GitHub access and no ELECTRON_MIRROR — the two
905
+ * things that made the hand-install on 2026-09-23 need a mirror variable at all.
906
+ * `npx electron` would also mean rewriting the launcher, which calls
907
+ * `<shell>\node_modules\electron\dist\electron.exe` directly.
908
+ *
909
+ * The zip's ROOT is the dist contents (`LICENSES.chromium.html` is its first
910
+ * entry — measured through the route, not assumed), so it expands into `dist`.
911
+ * Idempotent, and non-fatal: an install without a window still installs an agent.
912
+ */
913
+ function ensureElectron() {
914
+ const elDir = path.join(DESK_DIR, "node_modules", "electron");
915
+ const dist = path.join(elDir, "dist");
916
+ if (fs.existsSync(path.join(dist, "electron.exe")))
917
+ return dist;
918
+ const zip = resolveComponent("electron-win32-x64.zip", path.join(__dirname, "..", "electron-win32-x64.zip"));
919
+ if (!zip)
920
+ return null;
921
+ fs.mkdirSync(dist, { recursive: true });
922
+ sh(`powershell -NoProfile -Command "Expand-Archive -Force -Path '${(0, exports.psq)(zip)}' -DestinationPath '${(0, exports.psq)(dist)}'"`);
923
+ if (!fs.existsSync(path.join(dist, "electron.exe")))
924
+ return null;
925
+ try {
926
+ // The electron package's own lookup file; the launcher does not need it, but
927
+ // anything that resolves the package the npm way will.
928
+ fs.writeFileSync(path.join(elDir, "path.txt"), "electron.exe");
929
+ }
930
+ catch {
931
+ /* best-effort */
932
+ }
933
+ return dist;
934
+ }
800
935
  function statusReport(f) {
801
936
  const out = [];
802
937
  out.push(f.agentRunning === null
@@ -1467,8 +1602,8 @@ const commands = {
1467
1602
  // B2: stage the boxed playwright bundle (node_modules ONLY — node.exe is
1468
1603
  // NOT bundled; the system node detected below runs it). Single small
1469
1604
  // artifact in the npm package, Summrise version-locked.
1470
- const PW_ZIP = path.join(__dirname, "..", "summrise-playwright.zip");
1471
- if (fs.existsSync(PW_ZIP)) {
1605
+ const PW_ZIP = resolveComponent("summrise-playwright.zip", path.join(__dirname, "..", "summrise-playwright.zip"));
1606
+ if (PW_ZIP) {
1472
1607
  const pwDir = PW_DIR;
1473
1608
  fs.mkdirSync(pwDir, { recursive: true });
1474
1609
  sh(`powershell -NoProfile -Command "Expand-Archive -Force -Path '${(0, exports.psq)(PW_ZIP)}' -DestinationPath '${(0, exports.psq)(COMPONENTS_DIR)}'"`);
@@ -1503,7 +1638,7 @@ const commands = {
1503
1638
  }
1504
1639
  }
1505
1640
  else {
1506
- console.log("setup: summrise-playwright.zip not in package (browser tools disabled)");
1641
+ console.error("setup: WARNING -- the playwright bundle could not be obtained (not in the package, and the release host did not serve it); browser tools stay disabled.");
1507
1642
  }
1508
1643
  // Node runtime: the device has node (npm works), but the agent runs as
1509
1644
  // SYSTEM which may not see the user PATH — resolve the ABSOLUTE node path
@@ -1526,12 +1661,19 @@ const commands = {
1526
1661
  }
1527
1662
  // C2: stage the boxed cloudflared binary into components/ (optional — local
1528
1663
  // mode works without it; only used when the user opts into public access).
1529
- const CF_SRC = path.join(__dirname, "..", "cloudflared.exe");
1530
- if (fs.existsSync(CF_SRC)) {
1664
+ const CF_SRC = resolveComponent("cloudflared.exe", path.join(__dirname, "..", "cloudflared.exe"));
1665
+ if (CF_SRC) {
1531
1666
  fs.mkdirSync(COMPONENTS_DIR, { recursive: true });
1532
1667
  fs.copyFileSync(CF_SRC, path.join(COMPONENTS_DIR, "cloudflared.exe"));
1533
1668
  console.log("setup: cloudflared staged (tunnel optional -- `summrise tunnel install` to enable)");
1534
1669
  }
1670
+ else {
1671
+ // This branch used to be SILENT, and that silence is what the 2026-09-23
1672
+ // migration cost an hour of: a device with no cloudflared has no tunnel,
1673
+ // and a device with no tunnel cannot be reached from the console at all
1674
+ // while `summrise status` reports it healthy from inside.
1675
+ console.error("setup: WARNING -- cloudflared could NOT be staged (not in the package, and the release host did not serve it); this device will be unreachable from the console until the binary can be fetched.");
1676
+ }
1535
1677
  // P2-4: record the boxed-component versions (never fail-closed).
1536
1678
  writeBoxedVersions(DIR, path.join(__dirname, ".."));
1537
1679
  // round-330: Tauri summrise-desktop staging removed (retired).
@@ -1544,6 +1686,75 @@ const commands = {
1544
1686
  console.log(deskStaged > 0
1545
1687
  ? `setup: summrise-desktop-electron sources staged (${deskStaged} files)`
1546
1688
  : "setup: summrise-desktop-electron sources NOT staged -- the package did not ship them; the desktop shell will not update");
1689
+ // …and the runtime those sources need. Sources without a runtime is a shell
1690
+ // that cannot start: no window, and (before this) nothing in the output said
1691
+ // why — the operator saw only an absent window.
1692
+ const elDist = ensureElectron();
1693
+ console.log(elDist
1694
+ ? "setup: electron runtime ready for the desktop shell"
1695
+ : "setup: WARNING -- the electron runtime could not be obtained (not in the package, and the release host did not serve it); start-desktop.ps1 will not open a window until it can be fetched.");
1696
+ // ── Q9: leave a `summrise` COMMAND behind ──────────────────────────────────
1697
+ // `npx summrise-agent setup` installs the service and nothing puts the CLI on
1698
+ // PATH, so the very commands this output recommends (`summrise update`,
1699
+ // `summrise status`) are command-not-found — measured on d1 on 2026-09-23.
1700
+ // Installing THIS version by name keeps it deterministic and uses npm's own
1701
+ // shims rather than hand-written ones; when npm cannot, say the command.
1702
+ {
1703
+ const selfVer = String(require("../package.json").version || "");
1704
+ const pfx = (0, child_process_1.spawnSync)("npm", ["prefix", "-g"], { encoding: "utf8", shell: true });
1705
+ const pre = pfx.status === 0 ? String(pfx.stdout || "").trim() : "";
1706
+ if (selfVer && pre) {
1707
+ const inst = (0, child_process_1.spawnSync)("npm", [
1708
+ "i",
1709
+ "-g",
1710
+ `summrise-agent@${selfVer}`,
1711
+ "--prefix",
1712
+ pre,
1713
+ "--registry=https://registry.npmjs.org/",
1714
+ "--no-audit",
1715
+ "--no-fund",
1716
+ ], { encoding: "utf8", shell: true, timeout: 300000 });
1717
+ console.log(inst.status === 0
1718
+ ? `setup: ${selfVer} installed globally -- \`summrise status\` works from any shell`
1719
+ : `setup: WARNING -- could not install the CLI globally; run: npm i -g summrise-agent@${selfVer}`);
1720
+ }
1721
+ else {
1722
+ console.log(`setup: WARNING -- could not resolve npm's global prefix; run: npm i -g summrise-agent@${selfVer || "<version>"}`);
1723
+ }
1724
+ }
1725
+ // ── the desktop shell's task: ONLOGON + a 5-minute watchdog ────────────────
1726
+ // `summrise autostart` has always said "the desktop task comes from the
1727
+ // installer", and the NSIS installer is RETIRED — so NOTHING created it. Measured
1728
+ // on d1: the window vanished with the console that launched it and
1729
+ // SummriseDesktop did not exist at all. The shape is the app's own, written to a
1730
+ // .ps1 so it can be read rather than escaped into one unreadable line:
1731
+ // ensure-desktop.ps1 exits when electron is already alive (the watchdog never
1732
+ // steals focus), the .vbs wrapper runs it with no console flash, and the 5-minute
1733
+ // repetition is what reborns a dead shell.
1734
+ try {
1735
+ const reg = path.join(SCRIPTS_DIR, "register-desktop-task.ps1");
1736
+ fs.writeFileSync(reg, [
1737
+ "# written by `summrise setup` -- the desktop shell's ONLOGON task + watchdog.",
1738
+ "$ErrorActionPreference = 'Stop'",
1739
+ `$q = '${(0, exports.psq)(DIR)}'`,
1740
+ `$en = Join-Path $q 'scripts\\ensure-desktop.ps1'`,
1741
+ `$vb = Join-Path $q 'scripts\\desktop-pulse.vbs'`,
1742
+ `Set-Content -Path $en -Value ('if (Get-Process electron -ErrorAction SilentlyContinue) { exit }; & powershell -NoProfile -ExecutionPolicy Bypass -File "' + $q + '\\scripts\\start-desktop.ps1"') -Force`,
1743
+ `Set-Content -Path $vb -Value ('CreateObject("WScript.Shell").Run "powershell -NoProfile -ExecutionPolicy Bypass -File " & Chr(34) & "' + $en + '" & Chr(34), 0, False') -Force`,
1744
+ `$da = New-ScheduledTaskAction -Execute 'wscript.exe' -Argument ('"' + $vb + '"') -WorkingDirectory $q`,
1745
+ `$dt1 = New-ScheduledTaskTrigger -AtLogOn`,
1746
+ `$dw1 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(3) -RepetitionInterval (New-TimeSpan -Minutes 5)`,
1747
+ `$pr = New-ScheduledTaskPrincipal -UserId ('{0}\\{1}' -f $env:USERDOMAIN, $env:USERNAME) -LogonType Interactive -RunLevel Highest`,
1748
+ `$st = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew`,
1749
+ `Register-ScheduledTask SummriseDesktop -Action $da -Trigger @($dt1,$dw1) -Principal $pr -Settings $st -Force | Out-Null`,
1750
+ `Start-ScheduledTask -TaskName SummriseDesktop`,
1751
+ ].join("\r\n") + "\r\n");
1752
+ sh(`powershell -NoProfile -ExecutionPolicy Bypass -File '${(0, exports.psq)(reg)}'`);
1753
+ console.log("setup: SummriseDesktop registered (logon + a 5-minute watchdog) and started");
1754
+ }
1755
+ catch {
1756
+ console.log(`setup: WARNING -- could not register SummriseDesktop; run: powershell -File "${SCRIPTS_DIR}\\register-desktop-task.ps1"`);
1757
+ }
1547
1758
  // Layout v2: write the start-desktop.ps1 launcher into scripts\ (the
1548
1759
  // SummriseDesktop onlogon task + desktop Summrise.lnk both call it). Was never
1549
1760
  // written before — a real gap that left the shell unlaunchable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summrise-agent",
3
- "version": "1.2.453",
3
+ "version": "1.2.455",
4
4
  "description": "Summrise Agent — Windows device MCP server (terminal/SSH/serial) + panel. Self-hosted npm distribution.",
5
5
  "//build": "唯一构建路径:在本目录执行 npm run build。它是自包含的:经 npm exec 按需拉取 typescript@5,无需预装任何 node_modules(干净检出可跑;旧脚本引用 ../summrise-desktop-electron/node_modules/.bin/tsc,干净检出必 ENOENT)。必须用 npm exec --package … -- tsc 的形式:npx 会吃掉传给 tsc 的 -p/--outDir 等参数(round-435:npx 写法从未真正跑通过)。先编 electron TS(--noCheck 纯 emit,与 release.yml 一致)同步两处 src/*.js,再编 src/summrise.ts 到 bin/summrise.js 并校验 round-298 标记(弱 oracle,只证某次构建发生,新鲜性由 publish-release.sh 的 tsc 重编 + cmp 门保证)。",
6
6
  "bin": {
Binary file