@moxxy/plugin-channel-web 0.27.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/channel.d.ts +152 -0
  3. package/dist/channel.d.ts.map +1 -0
  4. package/dist/channel.js +595 -0
  5. package/dist/channel.js.map +1 -0
  6. package/dist/index.d.ts +53 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +171 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/projector.d.ts +17 -0
  11. package/dist/projector.d.ts.map +1 -0
  12. package/dist/projector.js +96 -0
  13. package/dist/projector.js.map +1 -0
  14. package/dist/protocol.d.ts +145 -0
  15. package/dist/protocol.d.ts.map +1 -0
  16. package/dist/protocol.js +33 -0
  17. package/dist/protocol.js.map +1 -0
  18. package/dist/public/app.js +54 -0
  19. package/dist/public/index.html +178 -0
  20. package/dist/tunnel-settings.d.ts +17 -0
  21. package/dist/tunnel-settings.d.ts.map +1 -0
  22. package/dist/tunnel-settings.js +42 -0
  23. package/dist/tunnel-settings.js.map +1 -0
  24. package/package.json +71 -0
  25. package/src/channel.test.ts +514 -0
  26. package/src/channel.ts +669 -0
  27. package/src/discovery.test.ts +40 -0
  28. package/src/frontend/chat.test.ts +47 -0
  29. package/src/frontend/chat.tsx +138 -0
  30. package/src/frontend/index.html +178 -0
  31. package/src/frontend/main.tsx +87 -0
  32. package/src/frontend/render-diff.test.ts +86 -0
  33. package/src/frontend/render-diff.tsx +66 -0
  34. package/src/frontend/render.test.ts +166 -0
  35. package/src/frontend/render.tsx +274 -0
  36. package/src/frontend/socket.ts +212 -0
  37. package/src/frontend/url-safety.test.ts +0 -0
  38. package/src/frontend/url-safety.ts +33 -0
  39. package/src/frontend/view-store.test.ts +104 -0
  40. package/src/frontend/view-store.ts +120 -0
  41. package/src/index.ts +212 -0
  42. package/src/projector.test.ts +172 -0
  43. package/src/projector.ts +95 -0
  44. package/src/protocol.test.ts +59 -0
  45. package/src/protocol.ts +105 -0
  46. package/src/tunnel-settings.test.ts +64 -0
  47. package/src/tunnel-settings.ts +57 -0
  48. package/src/tunnel-tools.test.ts +120 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moxxy (moxxy.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,152 @@
1
+ import type { Channel, ChannelHandle, ChannelStartOptsBase, ClientSession, PermissionResolver, TunnelProviderDef } from '@moxxy/sdk';
2
+ /** Injectable seams so the kill path can be unit-tested without real processes. */
3
+ export interface FreePortDeps {
4
+ pidsListeningOn(port: number): Promise<ReadonlyArray<number>>;
5
+ pidCommand(pid: number): Promise<string>;
6
+ kill(pid: number, signal: number | NodeJS.Signals): void;
7
+ /** Grace delay between SIGTERM and the SIGKILL sweep. */
8
+ graceMs?: number;
9
+ }
10
+ /**
11
+ * Free a TCP port ONLY if every process holding it is a moxxy process
12
+ * (stale `moxxy serve` leftovers — legitimate self-healing). Returns true
13
+ * when a kill was attempted. Anything else holding the port (the default,
14
+ * 4040, is also ngrok's local-UI port!) is left alone — the caller falls
15
+ * back to an ephemeral port instead. SIGTERM → grace → SIGKILL.
16
+ */
17
+ export declare function freeTcpPortIfMoxxy(port: number, logger: WebChannelOptions['logger'], deps?: FreePortDeps): Promise<boolean>;
18
+ /** The path the tunnel exposes us under (e.g. `/web`), or `''` for the root. */
19
+ export declare function basePathFromUrl(url: string): string;
20
+ /** Inject the base path into index.html so relative assets (`app.js`) resolve
21
+ * under it and the client builds the right WS URL — works at root or `/web`. */
22
+ export declare function injectBaseHtml(html: string, basePath: string): string;
23
+ export interface WebChannelOptions {
24
+ readonly port?: number;
25
+ readonly host?: string;
26
+ /** Token gating every request + the WS handshake. Generated if unset. */
27
+ readonly authToken?: string;
28
+ /** Tools the model may call without a human prompt (no clicker in this loop). */
29
+ readonly allowedTools?: ReadonlyArray<string>;
30
+ /** Resolve the session's active tunnel provider (injected by the CLI builder). */
31
+ readonly getTunnel?: () => TunnelProviderDef | null;
32
+ /**
33
+ * Publish/clear the live surface so `present_view` can return the public URL
34
+ * the agent relays to the user. Called with the surface on start, null on stop.
35
+ */
36
+ readonly publishSurface?: (surface: {
37
+ url: string;
38
+ nextViewId: () => string;
39
+ } | null) => void;
40
+ /**
41
+ * Publish/clear live controls so the agent's `web_set_tunnel` tool can switch
42
+ * the tunnel without a restart. `retunnel` closes the current tunnel (no leak)
43
+ * and re-opens via the now-active provider, returning the new share URL.
44
+ */
45
+ readonly publishControls?: (controls: WebSurfaceControls | null) => void;
46
+ readonly logger?: {
47
+ info?(msg: string, meta?: Record<string, unknown>): void;
48
+ warn?(msg: string, meta?: Record<string, unknown>): void;
49
+ };
50
+ }
51
+ export interface WebSurfaceControls {
52
+ retunnel(): Promise<string | null>;
53
+ }
54
+ export interface WebStartOpts extends ChannelStartOptsBase {
55
+ readonly session: ClientSession;
56
+ }
57
+ export declare class WebChannel implements Channel<WebStartOpts> {
58
+ readonly name = "web";
59
+ readonly permissionResolver: PermissionResolver;
60
+ private port;
61
+ private readonly host;
62
+ private readonly token;
63
+ private readonly logger;
64
+ private readonly getTunnel;
65
+ private readonly publishSurface;
66
+ private readonly publishControls;
67
+ private server;
68
+ private wss;
69
+ private readonly clients;
70
+ /**
71
+ * Built screens replayed to a newly-connected browser, keyed by logical
72
+ * `name`. UNNAMED views all share {@link UNNAMED_VIEW_KEY} (latest wins), so
73
+ * an agent that presents many unnamed views over a long session can't leak
74
+ * one ViewDoc per render. Named views LRU-evict at {@link MAX_REPLAY_VIEWS}.
75
+ */
76
+ private readonly views;
77
+ private unsubscribe;
78
+ private session;
79
+ private busy;
80
+ private controller;
81
+ private tunnel;
82
+ private tunnelBase;
83
+ /** Public path the surface is served under (e.g. `/web` behind the relay; `''` local). */
84
+ private basePath;
85
+ private viewSeq;
86
+ private droppedFrames;
87
+ private lastDropWarnAt;
88
+ constructor(opts?: WebChannelOptions);
89
+ /** The local URL (token embedded). */
90
+ get url(): string;
91
+ /** The URL to hand the user — the tunnel base if open, else local. */
92
+ get shareUrl(): string;
93
+ start(startOpts: WebStartOpts): Promise<ChannelHandle>;
94
+ /**
95
+ * Bind the HTTP server, with recovery if the port is already in use.
96
+ * A stale `moxxy serve` from a prior install often leaves 4040 bound
97
+ * even after its unix socket has been released — if (and only if) the
98
+ * holder is verifiably a moxxy process we kill it and retry. Anything
99
+ * else (ngrok's local UI also defaults to 4040) is never signalled;
100
+ * we bind an ephemeral port instead and log loudly which port the
101
+ * surface actually got (the share URL embeds the real port either way).
102
+ */
103
+ private bindServerWithRetry;
104
+ /**
105
+ * (Re-)open the tunnel via the active provider, closing any prior one FIRST so
106
+ * a switch never leaks a subprocess. Non-fatal: on failure (e.g. the relay
107
+ * not installed) we fall back to the local URL.
108
+ */
109
+ private openTunnel;
110
+ /** Switch tunnels live (agent's web_set_tunnel) and republish the surface URL. */
111
+ private retunnel;
112
+ private stop;
113
+ private validToken;
114
+ /** Strip the public base path (if any) so internal routing stays root-relative.
115
+ * Tolerant: handles both `/web/app.js` (relay didn't strip) and `/app.js`
116
+ * (relay stripped the first request line). */
117
+ private stripBase;
118
+ private handleHttp;
119
+ /**
120
+ * Defense-in-depth headers for the internet-exposed surface (served over
121
+ * public tunnels). CSP contains any future renderer regression: scripts may
122
+ * only load from this origin (`'self'`) — no inline scripts — while the inline
123
+ * `<style>` block needs `style-src 'unsafe-inline'`; `connect-src` permits the
124
+ * same-origin WebSocket. `frame-ancestors 'none'` + `X-Frame-Options: DENY`
125
+ * stop the tokenized page from being framed (clickjacking); `Referrer-Policy:
126
+ * no-referrer` keeps the `?t` token out of the Referer header on agent-authored
127
+ * outbound links.
128
+ */
129
+ private securityHeaders;
130
+ private serveFile;
131
+ /**
132
+ * Record a view frame for replay with a bounded footprint. Named views key
133
+ * by their name (re-render replaces in place) under an LRU bounded at
134
+ * {@link MAX_REPLAY_VIEWS}; unnamed views all collapse into a single
135
+ * latest-wins slot so an unbounded stream of unnamed renders can't leak.
136
+ */
137
+ private rememberView;
138
+ private onConnection;
139
+ /**
140
+ * Handle a browser → server frame. This is a trust boundary (tunnels put
141
+ * it on the public internet): every frame is schema-validated before any
142
+ * field access, and invalid ones are dropped — a thrown error in a ws
143
+ * 'message' listener escalates to a process-level uncaughtException.
144
+ */
145
+ private onMessage;
146
+ /** Count a dropped inbound frame; warn at most once per window (no log spam). */
147
+ private dropFrame;
148
+ private drive;
149
+ private broadcast;
150
+ private send;
151
+ }
152
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAElB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAuEpB,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACzD,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EACnC,IAAI,GAAE,YAA+B,GACpC,OAAO,CAAC,OAAO,CAAC,CAoDlB;AAKD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;iFACiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,kFAAkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9F;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QACzD,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC1D,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAED,qBAAa,UAAW,YAAW,OAAO,CAAC,YAAY,CAAC;IACtD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsC;IACrE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuC;IACvE,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,UAAU,CAAuB;IACzC,0FAA0F;IAC1F,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAK;gBAEf,IAAI,GAAE,iBAAsB;IAoBxC,sCAAsC;IACtC,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,sEAAsE;IACtE,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAEK,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAoD5D;;;;;;;;OAQG;YACW,mBAAmB;IAmDjC;;;;OAIG;YACW,UAAU;IAyBxB,kFAAkF;YACpE,QAAQ;YAMR,IAAI;IA4BlB,OAAO,CAAC,UAAU;IAWlB;;mDAE+C;IAC/C,OAAO,CAAC,SAAS;YASH,UAAU;IAwBxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;YAYT,SAAS;IAiBvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,YAAY;IAUpB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAiCjB,iFAAiF;IACjF,OAAO,CAAC,SAAS;YAWH,KAAK;IAkBnB,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,IAAI;CAOb"}