summrise-agent 1.2.494 → 1.2.505

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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "summrise-agent",
3
- "version": "1.2.494",
4
- "description": "Summrise Agent — Windows device MCP server (terminal/SSH/serial) + panel. Self-hosted npm distribution.",
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 门保证)。",
3
+ "version": "1.2.505",
4
+ "description": "Summrise Agent \u2014 Windows device MCP server (terminal/SSH/serial) + panel. Self-hosted npm distribution.",
5
+ "//build": "\u552f\u4e00\u6784\u5efa\u8def\u5f84\uff1a\u5728\u672c\u76ee\u5f55\u6267\u884c npm run build\u3002\u5b83\u662f\u81ea\u5305\u542b\u7684\uff1a\u7ecf npm exec \u6309\u9700\u62c9\u53d6 typescript@5\uff0c\u65e0\u9700\u9884\u88c5\u4efb\u4f55 node_modules\uff08\u5e72\u51c0\u68c0\u51fa\u53ef\u8dd1\uff1b\u65e7\u811a\u672c\u5f15\u7528 ../summrise-desktop-electron/node_modules/.bin/tsc\uff0c\u5e72\u51c0\u68c0\u51fa\u5fc5 ENOENT\uff09\u3002\u5fc5\u987b\u7528 npm exec --package \u2026 -- tsc \u7684\u5f62\u5f0f\uff1anpx \u4f1a\u5403\u6389\u4f20\u7ed9 tsc \u7684 -p/--outDir \u7b49\u53c2\u6570\uff08round-435\uff1anpx \u5199\u6cd5\u4ece\u672a\u771f\u6b63\u8dd1\u901a\u8fc7\uff09\u3002\u5148\u7f16 electron TS\uff08--noCheck \u7eaf emit\uff0c\u4e0e release.yml \u4e00\u81f4\uff09\u540c\u6b65\u4e24\u5904 src/*.js\uff0c\u518d\u7f16 src/summrise.ts \u5230 bin/summrise.js \u5e76\u6821\u9a8c round-298 \u6807\u8bb0\uff08\u5f31 oracle\uff0c\u53ea\u8bc1\u67d0\u6b21\u6784\u5efa\u53d1\u751f\uff0c\u65b0\u9c9c\u6027\u7531 publish-release.sh \u7684 tsc \u91cd\u7f16 + cmp \u95e8\u4fdd\u8bc1\uff09\u3002",
6
6
  "bin": {
7
7
  "summrise": "bin/summrise.js"
8
8
  },
Binary file
@@ -692,6 +692,148 @@ ipcHandle("embedded-browser:place", (bounds) => {
692
692
  return { ok: true };
693
693
  });
694
694
  ipcHandle("embedded-browser:state", () => embeddedState());
695
+ // ── THE DSH VIEW: the harness's own UI, embedded in summrise ────────────────────────────────
696
+ //
697
+ // WHY LOOPBACK AND NOT THE PUBLIC HOSTNAME. The DSH runs on THIS machine (127.0.0.1:18081), and
698
+ // this view is a window ON this machine — so loopback is the correct address, not the remote
699
+ // one. Going out to the tunnel hostname instead would mean: a Cloudflare round trip for every
700
+ // asset, an Access login inside the shell, and a certificate story — for a service that is
701
+ // already here. (The spec's warning about loopback applies to the OTHER direction: a page viewed
702
+ // REMOTELY resolves 127.0.0.1 to the viewer's machine, which is why the panel's /dsh/ proxy was
703
+ // proposed. A native view on the device has no such problem.)
704
+ //
705
+ // A SECOND view, not a re-use of the browser one: the browser view is a single-tab internet
706
+ // browser an AI drives over CDP, and navigating it to the DSH would throw that session away.
707
+ let dshView = null;
708
+ let dshVisible = false;
709
+ let dshBounds = null;
710
+ /** The DSH's port. Env first (a deployment may move it), else the component's default. */
711
+ function resolveDshPort() {
712
+ const fromEnv = Number(process.env.SUMMRISE_DSH_PORT);
713
+ return Number.isInteger(fromEnv) && fromEnv > 0 && fromEnv < 65536 ? fromEnv : 18081;
714
+ }
715
+ /** The ONE load door for this view: its own origin, or nothing. */
716
+ function dshTarget(raw) {
717
+ return (0, url_policy_1.isDshUrl)(raw) ? new URL(raw).toString() : "about:blank";
718
+ }
719
+ function dshViewEnsure() {
720
+ if (dshView && !dshView.webContents.isDestroyed())
721
+ return dshView;
722
+ const view = new electron_1.WebContentsView({
723
+ webPreferences: {
724
+ contextIsolation: true,
725
+ nodeIntegration: false,
726
+ sandbox: true,
727
+ // No preload: the DSH SPA needs no Node surface, and a view that cannot reach one cannot
728
+ // be talked into using one.
729
+ },
730
+ });
731
+ view.setVisible(false);
732
+ dshVisible = false;
733
+ // The DSH opens links (docs, the operator's own pages) with target=_blank. Deny the POPUP but
734
+ // keep the click meaningful where the door allows it — and the door allows one origin, so in
735
+ // practice an external link is simply dropped rather than silently navigating this view away
736
+ // from the harness.
737
+ view.webContents.setWindowOpenHandler(({ url }) => {
738
+ const target = dshTarget(url);
739
+ if (target !== "about:blank")
740
+ dshNavigate(target);
741
+ return { action: "deny" };
742
+ });
743
+ view.webContents.on("render-process-gone", (_e, details) => {
744
+ try {
745
+ view.setVisible(false);
746
+ }
747
+ catch { /* already gone */ }
748
+ dshVisible = false;
749
+ // The SAME signal the browser view sends, for the same reason (round-256 there): without it
750
+ // the pane shows "starting…" forever over a view that will never paint, and the operator has
751
+ // no way to tell a slow load from a dead renderer.
752
+ if (win && !win.isDestroyed()) {
753
+ win.webContents.send("embedded-dsh:gone", { reason: details.reason, exitCode: details.exitCode });
754
+ }
755
+ });
756
+ dshView = view;
757
+ if (win)
758
+ win.contentView.addChildView(view);
759
+ return view;
760
+ }
761
+ function dshNavigate(url) {
762
+ const target = dshTarget(url);
763
+ const view = dshViewEnsure();
764
+ view.webContents.loadURL(target).catch(() => { });
765
+ return { ok: true, url: target };
766
+ }
767
+ /** Open (or re-focus) the DSH at its configured address. Idempotent. */
768
+ function dshOpen() {
769
+ return dshNavigate((0, url_policy_1.dshBase)() + "/");
770
+ }
771
+ /** Place the view over the SPA's slot. Empty bounds hide it (the same contract as the browser). */
772
+ function dshPlace(bounds) {
773
+ const view = dshViewEnsure();
774
+ if (!win)
775
+ return;
776
+ if (!bounds || bounds.width < 50 || bounds.height < 50) {
777
+ view.setVisible(false);
778
+ dshVisible = false;
779
+ return;
780
+ }
781
+ dshBounds = bounds;
782
+ view.setBounds(bounds);
783
+ view.setVisible(true);
784
+ dshVisible = true;
785
+ win.contentView.addChildView(view);
786
+ }
787
+ function dshReload() {
788
+ const view = dshView;
789
+ if (!view || view.webContents.isDestroyed())
790
+ return { ok: false };
791
+ try {
792
+ view.webContents.reload();
793
+ return { ok: true };
794
+ }
795
+ catch {
796
+ return { ok: false };
797
+ }
798
+ }
799
+ /** Recover after a renderer crash: a dead webContents never recovers, so re-create the view. */
800
+ function dshRecover() {
801
+ try {
802
+ if (dshView && !dshView.webContents.isDestroyed())
803
+ dshView.webContents.close({ waitForBeforeUnload: false });
804
+ if (dshView) {
805
+ try {
806
+ win?.contentView.removeChildView(dshView);
807
+ }
808
+ catch { /* already gone */ }
809
+ dshView = null;
810
+ }
811
+ }
812
+ catch { /* fall through to ensure() */ }
813
+ const view = dshViewEnsure();
814
+ if (!view || view.webContents.isDestroyed())
815
+ return { ok: false };
816
+ view.webContents.loadURL((0, url_policy_1.dshBase)() + "/").catch(() => { });
817
+ if (dshVisible && win && dshBounds) {
818
+ view.setBounds(dshBounds);
819
+ view.setVisible(true);
820
+ win.contentView.addChildView(view);
821
+ }
822
+ return { ok: true };
823
+ }
824
+ function dshState() {
825
+ const view = dshView;
826
+ const wc = view && !view.webContents.isDestroyed() ? view.webContents : null;
827
+ return { ok: true, url: wc ? wc.getURL() : "", title: wc ? wc.getTitle() : "", visible: dshVisible && !!wc };
828
+ }
829
+ ipcHandle("embedded-dsh:open", () => dshOpen());
830
+ ipcHandle("embedded-dsh:place", (bounds) => {
831
+ dshPlace(bounds);
832
+ return { ok: true };
833
+ });
834
+ ipcHandle("embedded-dsh:state", () => dshState());
835
+ ipcHandle("embedded-dsh:reload", () => dshReload());
836
+ ipcHandle("embedded-dsh:recover", () => dshRecover());
695
837
  // Desktop-app settings (auto-launch) — the Settings page toggles this. We
696
838
  // manage a per-user scheduled task ("SummriseDesktop", onlogon) instead of
697
839
  // Electron's setLoginItemSettings: in dev mode (electron .) the login-item
@@ -932,6 +1074,9 @@ if (gotTheLock) {
932
1074
  // Custom-port installs: pin every origin predicate + probe/load URL to
933
1075
  // the agent's actual bind port BEFORE any window or probe exists.
934
1076
  (0, url_policy_1.setAgentPort)(resolveAgentPort());
1077
+ // The DSH view's door, pinned the same way and for the same reason: a predicate that ran
1078
+ // before this line would check a port nothing is listening on.
1079
+ (0, url_policy_1.setDshPort)(resolveDshPort());
935
1080
  // review #7: with no handler Electron AUTO-GRANTS every permission
936
1081
  // request (media/geolocation/clipboard) — deny by default for all
937
1082
  // windows, esp. the remote-browser ones loading arbitrary pages.
@@ -56,6 +56,32 @@ electron_1.contextBridge.exposeInMainWorld("summriseEmbedded", {
56
56
  return () => electron_1.ipcRenderer.removeListener("embedded-browser:gone", listener);
57
57
  },
58
58
  });
59
+ // THE DSH VIEW (the shell's second embedded view). The harness's own UI runs on THIS
60
+ // machine's loopback, so the panel never learns its port: `open()` asks the MAIN process to
61
+ // navigate the view to its configured base, and the door that decides what that view may load
62
+ // (url-policy.isDshUrl) has exactly one origin to check. Same placeholder-bounds handshake as
63
+ // the browser view above, because it is the same problem: a native view overlays an empty slot
64
+ // the SPA positions.
65
+ electron_1.contextBridge.exposeInMainWorld("summriseDsh", {
66
+ open: () => electron_1.ipcRenderer.invoke("embedded-dsh:open"),
67
+ place: (bounds) => electron_1.ipcRenderer.invoke("embedded-dsh:place", bounds),
68
+ state: () => electron_1.ipcRenderer.invoke("embedded-dsh:state"),
69
+ reload: () => electron_1.ipcRenderer.invoke("embedded-dsh:reload"),
70
+ // Recovery after a renderer crash: the main process force-re-creates the view.
71
+ recover: () => electron_1.ipcRenderer.invoke("embedded-dsh:recover"),
72
+ // The view's renderer crashed (reason + exitCode) — without this the pane would show
73
+ // "starting…" forever over a view that will never paint. Returns an unsubscribe fn.
74
+ onGone: (handler) => {
75
+ const listener = (_e, d) => {
76
+ try {
77
+ handler(d);
78
+ }
79
+ catch { /* SPA-side */ }
80
+ };
81
+ electron_1.ipcRenderer.on("embedded-dsh:gone", listener);
82
+ return () => electron_1.ipcRenderer.removeListener("embedded-dsh:gone", listener);
83
+ },
84
+ });
59
85
  // Desktop-app settings + menu bridge (Electron-specific — hidden when running
60
86
  // in a plain browser; the SPA detects the bridge and shows the card only when
61
87
  // present).
@@ -21,6 +21,10 @@ exports.isDesktopSpaUrl = isDesktopSpaUrl;
21
21
  exports.isPrivateHost = isPrivateHost;
22
22
  exports.certBypassAllowed = certBypassAllowed;
23
23
  exports.sanitizeBrowserUrl = sanitizeBrowserUrl;
24
+ exports.setDshPort = setDshPort;
25
+ exports.getDshPort = getDshPort;
26
+ exports.dshBase = dshBase;
27
+ exports.isDshUrl = isDshUrl;
24
28
  exports.BASE = "http://127.0.0.1:18080";
25
29
  exports.BASE_ORIGIN = new URL(exports.BASE).origin;
26
30
  // Configurable agent port (custom-port installs): the shell follows the
@@ -203,3 +207,32 @@ function sanitizeBrowserUrl(url) {
203
207
  catch { /* fall through */ }
204
208
  return "about:blank";
205
209
  }
210
+ // THE DSH VIEW'S DOOR. The shell's SECOND embedded view shows the harness's own UI, which
211
+ // runs on THIS machine's loopback — so this door is one origin and nothing else, and it is a
212
+ // SEPARATE predicate from sanitizeBrowserUrl on purpose: the browser view's door is the open
213
+ // internet (an AI drives it), this one is a single port, and a door that is wide for one view
214
+ // must not quietly become wide for the other. The DSH view has no address bar and nothing
215
+ // drives it but the operator, so there is nothing here to widen it for.
216
+ //
217
+ // The port is the DSH's own (default 18081 — main.ts resolves SUMMRISE_DSH_PORT first), and
218
+ // it is deliberately NOT the agent's port: the agent's origin is where the panel lives, and a
219
+ // view that could load the panel could also load /api/* with the panel's own authority.
220
+ let dshPort = null;
221
+ function setDshPort(port) {
222
+ if (Number.isInteger(port) && port > 0 && port < 65536)
223
+ dshPort = port;
224
+ }
225
+ function getDshPort() {
226
+ return dshPort ?? 18081;
227
+ }
228
+ function dshBase() {
229
+ return `http://127.0.0.1:${getDshPort()}`;
230
+ }
231
+ function isDshUrl(url) {
232
+ try {
233
+ return new URL(url).origin === new URL(dshBase()).origin;
234
+ }
235
+ catch {
236
+ return false;
237
+ }
238
+ }