@superblocksteam/gateway 2.0.155-next.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 (183) hide show
  1. package/LICENSE.txt +87 -0
  2. package/README.md +170 -0
  3. package/dist/agents/resolve-orchestrator-url.d.ts +22 -0
  4. package/dist/agents/resolve-orchestrator-url.js +38 -0
  5. package/dist/agents/resolve-orchestrator-url.js.map +1 -0
  6. package/dist/capabilities/import-prompt.d.ts +11 -0
  7. package/dist/capabilities/import-prompt.js +96 -0
  8. package/dist/capabilities/import-prompt.js.map +1 -0
  9. package/dist/capabilities/lifecycle.d.ts +73 -0
  10. package/dist/capabilities/lifecycle.js +2531 -0
  11. package/dist/capabilities/lifecycle.js.map +1 -0
  12. package/dist/capabilities/persisted-progress.d.ts +46 -0
  13. package/dist/capabilities/persisted-progress.js +246 -0
  14. package/dist/capabilities/persisted-progress.js.map +1 -0
  15. package/dist/capabilities/types.d.ts +446 -0
  16. package/dist/capabilities/types.js +47 -0
  17. package/dist/capabilities/types.js.map +1 -0
  18. package/dist/capture/browser-contract.d.ts +36 -0
  19. package/dist/capture/browser-contract.js +24 -0
  20. package/dist/capture/browser-contract.js.map +1 -0
  21. package/dist/capture/browser-instructions.d.ts +9 -0
  22. package/dist/capture/browser-instructions.js +17 -0
  23. package/dist/capture/browser-instructions.js.map +1 -0
  24. package/dist/capture/capture-library-probe.d.ts +55 -0
  25. package/dist/capture/capture-library-probe.js +177 -0
  26. package/dist/capture/capture-library-probe.js.map +1 -0
  27. package/dist/capture/capture-library.d.ts +52 -0
  28. package/dist/capture/capture-library.js +249 -0
  29. package/dist/capture/capture-library.js.map +1 -0
  30. package/dist/capture/classify-library-paint.d.ts +21 -0
  31. package/dist/capture/classify-library-paint.js +13 -0
  32. package/dist/capture/classify-library-paint.js.map +1 -0
  33. package/dist/capture/host-mcp-scan.d.ts +16 -0
  34. package/dist/capture/host-mcp-scan.js +64 -0
  35. package/dist/capture/host-mcp-scan.js.map +1 -0
  36. package/dist/capture/library-parent-script.d.ts +34 -0
  37. package/dist/capture/library-parent-script.js +82 -0
  38. package/dist/capture/library-parent-script.js.map +1 -0
  39. package/dist/capture/mode.d.ts +22 -0
  40. package/dist/capture/mode.js +45 -0
  41. package/dist/capture/mode.js.map +1 -0
  42. package/dist/config.d.ts +141 -0
  43. package/dist/config.js +109 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/debug.d.ts +2 -0
  46. package/dist/debug.js +9 -0
  47. package/dist/debug.js.map +1 -0
  48. package/dist/deps.d.ts +36 -0
  49. package/dist/deps.js +2 -0
  50. package/dist/deps.js.map +1 -0
  51. package/dist/events/cursor.d.ts +43 -0
  52. package/dist/events/cursor.js +78 -0
  53. package/dist/events/cursor.js.map +1 -0
  54. package/dist/events/memory-event-store.d.ts +34 -0
  55. package/dist/events/memory-event-store.js +110 -0
  56. package/dist/events/memory-event-store.js.map +1 -0
  57. package/dist/events/merge.d.ts +23 -0
  58. package/dist/events/merge.js +97 -0
  59. package/dist/events/merge.js.map +1 -0
  60. package/dist/events/normalized-collector.d.ts +62 -0
  61. package/dist/events/normalized-collector.js +156 -0
  62. package/dist/events/normalized-collector.js.map +1 -0
  63. package/dist/events/schema.d.ts +9 -0
  64. package/dist/events/schema.js +93 -0
  65. package/dist/events/schema.js.map +1 -0
  66. package/dist/events/snapshot.d.ts +32 -0
  67. package/dist/events/snapshot.js +57 -0
  68. package/dist/events/snapshot.js.map +1 -0
  69. package/dist/events/stream-key.d.ts +2 -0
  70. package/dist/events/stream-key.js +31 -0
  71. package/dist/events/stream-key.js.map +1 -0
  72. package/dist/events/types.d.ts +179 -0
  73. package/dist/events/types.js +66 -0
  74. package/dist/events/types.js.map +1 -0
  75. package/dist/health/readiness.d.ts +99 -0
  76. package/dist/health/readiness.js +202 -0
  77. package/dist/health/readiness.js.map +1 -0
  78. package/dist/index.d.ts +10 -0
  79. package/dist/index.js +10 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/integrations/filters.d.ts +8 -0
  82. package/dist/integrations/filters.js +40 -0
  83. package/dist/integrations/filters.js.map +1 -0
  84. package/dist/integrations/map.d.ts +26 -0
  85. package/dist/integrations/map.js +43 -0
  86. package/dist/integrations/map.js.map +1 -0
  87. package/dist/integrations/profile-key.d.ts +19 -0
  88. package/dist/integrations/profile-key.js +35 -0
  89. package/dist/integrations/profile-key.js.map +1 -0
  90. package/dist/main.d.ts +2 -0
  91. package/dist/main.js +5 -0
  92. package/dist/main.js.map +1 -0
  93. package/dist/orchestrator/client.d.ts +62 -0
  94. package/dist/orchestrator/client.js +160 -0
  95. package/dist/orchestrator/client.js.map +1 -0
  96. package/dist/playwright/ensure-chromium.d.ts +26 -0
  97. package/dist/playwright/ensure-chromium.js +60 -0
  98. package/dist/playwright/ensure-chromium.js.map +1 -0
  99. package/dist/preview/capture-screenshot.d.ts +52 -0
  100. package/dist/preview/capture-screenshot.js +158 -0
  101. package/dist/preview/capture-screenshot.js.map +1 -0
  102. package/dist/preview/viewer-url.d.ts +82 -0
  103. package/dist/preview/viewer-url.js +128 -0
  104. package/dist/preview/viewer-url.js.map +1 -0
  105. package/dist/process/fault-barrier.d.ts +55 -0
  106. package/dist/process/fault-barrier.js +113 -0
  107. package/dist/process/fault-barrier.js.map +1 -0
  108. package/dist/resume/memory-progress-store.d.ts +39 -0
  109. package/dist/resume/memory-progress-store.js +82 -0
  110. package/dist/resume/memory-progress-store.js.map +1 -0
  111. package/dist/resume/memory-recent-app-store.d.ts +14 -0
  112. package/dist/resume/memory-recent-app-store.js +27 -0
  113. package/dist/resume/memory-recent-app-store.js.map +1 -0
  114. package/dist/resume/memory-turn-store.d.ts +18 -0
  115. package/dist/resume/memory-turn-store.js +73 -0
  116. package/dist/resume/memory-turn-store.js.map +1 -0
  117. package/dist/resume/progress-key.d.ts +21 -0
  118. package/dist/resume/progress-key.js +58 -0
  119. package/dist/resume/progress-key.js.map +1 -0
  120. package/dist/resume/stores.d.ts +14 -0
  121. package/dist/resume/stores.js +18 -0
  122. package/dist/resume/stores.js.map +1 -0
  123. package/dist/resume/types.d.ts +124 -0
  124. package/dist/resume/types.js +13 -0
  125. package/dist/resume/types.js.map +1 -0
  126. package/dist/sabs/agent-facing-text.d.ts +33 -0
  127. package/dist/sabs/agent-facing-text.js +125 -0
  128. package/dist/sabs/agent-facing-text.js.map +1 -0
  129. package/dist/sabs/browser-context.d.ts +53 -0
  130. package/dist/sabs/browser-context.js +91 -0
  131. package/dist/sabs/browser-context.js.map +1 -0
  132. package/dist/sabs/editor-client-methods.d.ts +147 -0
  133. package/dist/sabs/editor-client-methods.js +715 -0
  134. package/dist/sabs/editor-client-methods.js.map +1 -0
  135. package/dist/sabs/recent-tool-runs.d.ts +14 -0
  136. package/dist/sabs/recent-tool-runs.js +61 -0
  137. package/dist/sabs/recent-tool-runs.js.map +1 -0
  138. package/dist/sabs/session-peer.d.ts +351 -0
  139. package/dist/sabs/session-peer.js +2 -0
  140. package/dist/sabs/session-peer.js.map +1 -0
  141. package/dist/sabs/turn-collector.d.ts +152 -0
  142. package/dist/sabs/turn-collector.js +573 -0
  143. package/dist/sabs/turn-collector.js.map +1 -0
  144. package/dist/sabs/websocket-session-peer.d.ts +514 -0
  145. package/dist/sabs/websocket-session-peer.js +2487 -0
  146. package/dist/sabs/websocket-session-peer.js.map +1 -0
  147. package/dist/security/constant-time.d.ts +9 -0
  148. package/dist/security/constant-time.js +13 -0
  149. package/dist/security/constant-time.js.map +1 -0
  150. package/dist/server/client.d.ts +371 -0
  151. package/dist/server/client.js +572 -0
  152. package/dist/server/client.js.map +1 -0
  153. package/dist/server/identity-reads.d.ts +76 -0
  154. package/dist/server/identity-reads.js +209 -0
  155. package/dist/server/identity-reads.js.map +1 -0
  156. package/dist/start.d.ts +14 -0
  157. package/dist/start.js +75 -0
  158. package/dist/start.js.map +1 -0
  159. package/dist/telemetry/metrics.d.ts +32 -0
  160. package/dist/telemetry/metrics.js +57 -0
  161. package/dist/telemetry/metrics.js.map +1 -0
  162. package/dist/transports/mcp/admin-tools.d.ts +47 -0
  163. package/dist/transports/mcp/admin-tools.js +164 -0
  164. package/dist/transports/mcp/admin-tools.js.map +1 -0
  165. package/dist/transports/mcp/app-status-html.d.ts +15 -0
  166. package/dist/transports/mcp/app-status-html.js +563 -0
  167. package/dist/transports/mcp/app-status-html.js.map +1 -0
  168. package/dist/transports/mcp/decision-elicitation.d.ts +74 -0
  169. package/dist/transports/mcp/decision-elicitation.js +233 -0
  170. package/dist/transports/mcp/decision-elicitation.js.map +1 -0
  171. package/dist/transports/mcp/dispatch-probe.d.ts +24 -0
  172. package/dist/transports/mcp/dispatch-probe.js +48 -0
  173. package/dist/transports/mcp/dispatch-probe.js.map +1 -0
  174. package/dist/transports/mcp/format-tool-content.d.ts +25 -0
  175. package/dist/transports/mcp/format-tool-content.js +48 -0
  176. package/dist/transports/mcp/format-tool-content.js.map +1 -0
  177. package/dist/transports/mcp/mount.d.ts +9 -0
  178. package/dist/transports/mcp/mount.js +469 -0
  179. package/dist/transports/mcp/mount.js.map +1 -0
  180. package/dist/transports/mcp/progress-notifier.d.ts +39 -0
  181. package/dist/transports/mcp/progress-notifier.js +52 -0
  182. package/dist/transports/mcp/progress-notifier.js.map +1 -0
  183. package/package.json +72 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Headless capture of a preview viewer URL for MCP image content.
3
+ *
4
+ * Best-effort: callers must succeed with URLs alone when this returns
5
+ * undefined. Output is capped for Claude Desktop tool-result size limits.
6
+ */
7
+ import { isLoopbackUrl } from "./viewer-url.js";
8
+ /** Soft cap on decoded bytes before base64 (~256 KiB). */
9
+ export const PREVIEW_SCREENSHOT_MAX_BYTES = 256 * 1024;
10
+ /** How long to keep waiting for the shell to subscribe before handshaking anyway. */
11
+ const HANDSHAKE_FALLBACK_MS = 2_000;
12
+ /**
13
+ * Settle time after the app's first element becomes visible, covering the
14
+ * layout and data-bound widgets that land just behind it. The app publishes
15
+ * no "finished rendering" signal an embedder could await.
16
+ */
17
+ const APP_PAINT_SETTLE_MS = 2_500;
18
+ /**
19
+ * Page source that hands the deployed shell its bearer, and the whole reason a
20
+ * headless capture can paint a real app.
21
+ *
22
+ * `ui-deployed-shell` only accepts a bearer through an `INITIALIZE_EMBED`
23
+ * message from its embedder, and ~300ms after mount it gives up and tries
24
+ * Auth0 silent login instead — which has no session to find here. At top level
25
+ * the shell's embedder port has no parent window to check messages against, so
26
+ * it accepts one posted by the page itself.
27
+ *
28
+ * Delivery happens exactly once: each `INITIALIZE_EMBED` replaces the shell's
29
+ * auth state and restarts its bootstrap, so a repeating handshake would keep
30
+ * the app from ever settling.
31
+ *
32
+ * This is source text rather than a function handed to `addInitScript` because
33
+ * Playwright ships a function by serializing it, which drags in whatever
34
+ * helpers the bundler injected into its body (`__name` under tsx) and throws a
35
+ * ReferenceError in the page.
36
+ *
37
+ * The result is only ever evaluated as script source (Playwright forwards it
38
+ * to CDP `addScriptToEvaluateOnNewDocument`), never interpolated into HTML, so
39
+ * `JSON.stringify` is sufficient to contain the token. Putting it in a
40
+ * `<script>` element would not be — the token would need `</script>` escaped.
41
+ */
42
+ export function buildEmbedHandshakeScript(token) {
43
+ return `(function () {
44
+ // The app iframe gets its own credentials from the shell; a handshake there
45
+ // would be rejected as not-from-parent anyway.
46
+ if (window.parent !== window) return;
47
+ var token = ${JSON.stringify(token)};
48
+ var sent = false;
49
+ function send() {
50
+ if (sent) return;
51
+ sent = true;
52
+ // Addressed to this window only. "*" would expose the bearer to every
53
+ // listener on the page, including third-party scripts the shell loads.
54
+ window.postMessage(
55
+ { data: { token: token }, type: "INITIALIZE_EMBED" },
56
+ window.location.origin,
57
+ );
58
+ }
59
+ // Handshake the moment the shell subscribes, so its very first render
60
+ // already has the bearer and no bootstrap is wasted on the Auth0 path.
61
+ var addEventListener = window.addEventListener;
62
+ window.addEventListener = function (type, listener, options) {
63
+ addEventListener.call(window, type, listener, options);
64
+ if (type === "message") send();
65
+ };
66
+ // Safety net for a shell that subscribes some other way: a late handshake
67
+ // still recovers, at the cost of one discarded bootstrap.
68
+ window.setTimeout(send, ${HANDSHAKE_FALLBACK_MS});
69
+ })();`;
70
+ }
71
+ /**
72
+ * Captures a JPEG screenshot via Playwright when available. Soft-fails when
73
+ * Chromium is missing or the page never becomes ready — callers must still
74
+ * return preview URLs when this returns undefined.
75
+ */
76
+ export const capturePreviewScreenshotWithPlaywright = async (input) => {
77
+ let chromium;
78
+ try {
79
+ // Optional runtime dep — demos without browsers still return preview URLs.
80
+ const mod = (await import(/* webpackIgnore: true */ "playwright"));
81
+ chromium = mod.chromium;
82
+ }
83
+ catch (error) {
84
+ console.warn("gateway preview screenshot: playwright unavailable", {
85
+ message: error instanceof Error ? error.message : String(error),
86
+ });
87
+ return undefined;
88
+ }
89
+ const timeoutMs = input.timeoutMs ?? 45_000;
90
+ let browser;
91
+ try {
92
+ // launch() must stay inside this try: a missing Chromium binary throws
93
+ // here, and that used to fail the whole get_app / preview_app capability.
94
+ browser = await chromium.launch({ headless: true });
95
+ const page = await browser.newPage();
96
+ if (input.embedToken) {
97
+ // The bearer would reach the app iframe on hosts where the shell hands
98
+ // the runtime token straight through, so it stays on the local stack
99
+ // this capture path exists for.
100
+ if (isLoopbackUrl(input.url)) {
101
+ await page.addInitScript({
102
+ content: buildEmbedHandshakeScript(input.embedToken),
103
+ });
104
+ }
105
+ else {
106
+ console.warn("gateway preview screenshot: shell is not local, capturing unauthenticated", { url: input.url });
107
+ }
108
+ }
109
+ await page.setViewportSize({ width: 1280, height: 800 });
110
+ // Not "networkidle": the shell holds a dev-server HMR socket open and
111
+ // the app streams telemetry, so idle never arrives and goto() would
112
+ // spend the entire timeout before throwing on an app that had painted.
113
+ await page.goto(input.url, {
114
+ timeout: timeoutMs,
115
+ waitUntil: "domcontentloaded",
116
+ });
117
+ // Wait on content *inside* the app frame, not the frame element: the
118
+ // shell attaches an about:blank iframe within ~50ms and only points it
119
+ // at the app once auth, metadata, and the CDN cookie prime land, so the
120
+ // element alone photographs a loading spinner.
121
+ await page
122
+ .frameLocator("iframe")
123
+ .locator("body > *")
124
+ .first()
125
+ .waitFor({ state: "visible", timeout: timeoutMs })
126
+ // A frame that never paints still deserves a screenshot: the error
127
+ // page it captures is what tells the caller what went wrong.
128
+ .catch(() => undefined);
129
+ await page.waitForTimeout(APP_PAINT_SETTLE_MS);
130
+ let quality = 80;
131
+ let buffer = await page.screenshot({ type: "jpeg", quality });
132
+ while (buffer.byteLength > PREVIEW_SCREENSHOT_MAX_BYTES && quality > 30) {
133
+ quality -= 10;
134
+ buffer = await page.screenshot({ type: "jpeg", quality });
135
+ }
136
+ if (buffer.byteLength > PREVIEW_SCREENSHOT_MAX_BYTES) {
137
+ console.warn("gateway preview screenshot exceeds size cap; skipping", {
138
+ bytes: buffer.byteLength,
139
+ });
140
+ return undefined;
141
+ }
142
+ return {
143
+ dataBase64: buffer.toString("base64"),
144
+ mimeType: "image/jpeg",
145
+ };
146
+ }
147
+ catch (error) {
148
+ console.warn("gateway preview screenshot failed", {
149
+ message: error instanceof Error ? error.message : String(error),
150
+ url: input.url,
151
+ });
152
+ return undefined;
153
+ }
154
+ finally {
155
+ await browser?.close().catch(() => undefined);
156
+ }
157
+ };
158
+ //# sourceMappingURL=capture-screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-screenshot.js","sourceRoot":"","sources":["../../src/preview/capture-screenshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Builds the URL that loads a code-mode preview without a Superblocks UI login.
3
+ *
4
+ * Deployed-shell contract: the CDN/shell accepts `commitId` + `jwt` as query
5
+ * params on the first request so it can set a `viewer-jwt` cookie (see
6
+ * csb-mock `deployed-cdn` and production shell). That first-hop query JWT is
7
+ * unavoidable for the shell itself today; scoped `apps:preview` tokens are
8
+ * issued by the Server with the platform default TTL (typically 24h).
9
+ *
10
+ * Gateway-facing openLink URLs put the JWT in the URL fragment instead, so
11
+ * tunnel/proxy access logs and Referer headers do not record the bearer. An
12
+ * interstitial page reads the fragment and redirects to the shell query form.
13
+ */
14
+ export declare function isHttpsUrl(url: string): boolean;
15
+ export declare function isLoopbackUrl(url: string): boolean;
16
+ /**
17
+ * The embed shell route for a preview commit.
18
+ *
19
+ * Unlike {@link buildDeployedShellViewerUrl}, which hands the browser the app
20
+ * bundle straight off the CDN, this loads `ui-deployed-shell`: the piece that
21
+ * resolves auth, fetches app metadata, primes the CDN cookie, and feeds the
22
+ * app iframe. A bare bundle has no way to authenticate its APIs, so only this
23
+ * URL can paint a working app. It carries no token — the shell takes its
24
+ * bearer from an `INITIALIZE_EMBED` message — and the commit comes from the
25
+ * application's current version.
26
+ *
27
+ * `deployedShellBaseUrl` is forwarded as `deployedUrl` because the shell reads
28
+ * its CDN origin from `/env.js`, which the local stack does not publish; it
29
+ * then falls back to a hardcoded `localhost:4041` that only matches slot 0.
30
+ * On every other slot the cookie prime is refused and the shell renders its
31
+ * error page instead of the app.
32
+ */
33
+ export declare function buildEmbedShellPreviewUrl(input: {
34
+ applicationId: string;
35
+ deployedShellBaseUrl?: string;
36
+ uiBaseUrl: string;
37
+ }): string;
38
+ /**
39
+ * Replaces the origin of `url` with `publicBaseUrl`'s origin, keeping path and
40
+ * query. Used so Claude Desktop can `openLink` an https URL while the actual
41
+ * app still lives on a slot-local host.
42
+ */
43
+ export declare function rewriteUrlOrigin(url: string, publicBaseUrl: string | undefined): string;
44
+ /**
45
+ * Deployed-shell URL for a preview commit, with the scoped JWT in the query.
46
+ *
47
+ * `deployedShellBaseUrl` may include `__SB_APP_ID` (production CDN pattern) or
48
+ * be a plain origin (local csb-mock CDN on :4041).
49
+ */
50
+ export declare function buildDeployedShellViewerUrl(input: {
51
+ applicationId: string;
52
+ commitId: string;
53
+ deployedShellBaseUrl: string;
54
+ jwt: string;
55
+ }): string;
56
+ export type OpenablePreviewUrls = {
57
+ /**
58
+ * True when `previewUrl` is https and Claude Desktop can call `openLink`.
59
+ */
60
+ openableViaHttps: boolean;
61
+ /**
62
+ * URL returned to MCP clients / Open preview button.
63
+ */
64
+ previewUrl: string;
65
+ /**
66
+ * Direct deployed-shell (or UI) URL used for headless screenshot capture.
67
+ * May be http localhost even when `previewUrl` is an https gateway redirect.
68
+ */
69
+ viewerUrl: string;
70
+ };
71
+ /**
72
+ * Picks the best preview URL for Claude: prefer an https front door, fall back
73
+ * to the local viewer / UI preview URL for copy-paste + screenshot.
74
+ */
75
+ export declare function buildOpenablePreviewUrls(input: {
76
+ applicationId: string;
77
+ commitId: string;
78
+ deployedShellBaseUrl: string | undefined;
79
+ jwt: string | undefined;
80
+ publicViewerBaseUrl: string | undefined;
81
+ uiPreviewUrl: string;
82
+ }): OpenablePreviewUrls;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Builds the URL that loads a code-mode preview without a Superblocks UI login.
3
+ *
4
+ * Deployed-shell contract: the CDN/shell accepts `commitId` + `jwt` as query
5
+ * params on the first request so it can set a `viewer-jwt` cookie (see
6
+ * csb-mock `deployed-cdn` and production shell). That first-hop query JWT is
7
+ * unavoidable for the shell itself today; scoped `apps:preview` tokens are
8
+ * issued by the Server with the platform default TTL (typically 24h).
9
+ *
10
+ * Gateway-facing openLink URLs put the JWT in the URL fragment instead, so
11
+ * tunnel/proxy access logs and Referer headers do not record the bearer. An
12
+ * interstitial page reads the fragment and redirects to the shell query form.
13
+ */
14
+ export function isHttpsUrl(url) {
15
+ try {
16
+ return new URL(url).protocol === "https:";
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "[::1]", "localhost"]);
23
+ export function isLoopbackUrl(url) {
24
+ try {
25
+ return LOOPBACK_HOSTNAMES.has(new URL(url).hostname);
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /**
32
+ * The embed shell route for a preview commit.
33
+ *
34
+ * Unlike {@link buildDeployedShellViewerUrl}, which hands the browser the app
35
+ * bundle straight off the CDN, this loads `ui-deployed-shell`: the piece that
36
+ * resolves auth, fetches app metadata, primes the CDN cookie, and feeds the
37
+ * app iframe. A bare bundle has no way to authenticate its APIs, so only this
38
+ * URL can paint a working app. It carries no token — the shell takes its
39
+ * bearer from an `INITIALIZE_EMBED` message — and the commit comes from the
40
+ * application's current version.
41
+ *
42
+ * `deployedShellBaseUrl` is forwarded as `deployedUrl` because the shell reads
43
+ * its CDN origin from `/env.js`, which the local stack does not publish; it
44
+ * then falls back to a hardcoded `localhost:4041` that only matches slot 0.
45
+ * On every other slot the cookie prime is refused and the shell renders its
46
+ * error page instead of the app.
47
+ */
48
+ export function buildEmbedShellPreviewUrl(input) {
49
+ const base = input.uiBaseUrl.trim().replace(/\/$/, "");
50
+ const url = `${base}/code-mode/embed/applications/preview/${encodeURIComponent(input.applicationId)}`;
51
+ const cdnBase = input.deployedShellBaseUrl?.trim();
52
+ if (!cdnBase) {
53
+ return url;
54
+ }
55
+ // Passed verbatim, `__SB_APP_ID` and all: this is the same template shape
56
+ // the shell expects from env.js, and it expands the placeholder itself.
57
+ return `${url}?deployedUrl=${encodeURIComponent(cdnBase)}`;
58
+ }
59
+ /**
60
+ * Replaces the origin of `url` with `publicBaseUrl`'s origin, keeping path and
61
+ * query. Used so Claude Desktop can `openLink` an https URL while the actual
62
+ * app still lives on a slot-local host.
63
+ */
64
+ export function rewriteUrlOrigin(url, publicBaseUrl) {
65
+ if (!publicBaseUrl?.trim()) {
66
+ return url;
67
+ }
68
+ try {
69
+ const original = new URL(url);
70
+ const publicBase = new URL(publicBaseUrl);
71
+ // Build from the public origin so a slot-local port (e.g. :4141) is not
72
+ // carried onto an https tunnel that listens on 443.
73
+ return new URL(`${original.pathname}${original.search}${original.hash}`, publicBase).toString();
74
+ }
75
+ catch {
76
+ return url;
77
+ }
78
+ }
79
+ /**
80
+ * Deployed-shell URL for a preview commit, with the scoped JWT in the query.
81
+ *
82
+ * `deployedShellBaseUrl` may include `__SB_APP_ID` (production CDN pattern) or
83
+ * be a plain origin (local csb-mock CDN on :4041).
84
+ */
85
+ export function buildDeployedShellViewerUrl(input) {
86
+ const base = input.deployedShellBaseUrl
87
+ .trim()
88
+ .replace(/\/$/, "")
89
+ .replaceAll("__SB_APP_ID", input.applicationId);
90
+ const url = new URL(base.includes("://") ? base : `http://${base}`);
91
+ // Ensure we hit the shell root when the base is only an origin.
92
+ if (!url.pathname || url.pathname === "") {
93
+ url.pathname = "/";
94
+ }
95
+ url.searchParams.set("commitId", input.commitId);
96
+ url.searchParams.set("jwt", input.jwt);
97
+ return url.toString();
98
+ }
99
+ /**
100
+ * Picks the best preview URL for Claude: prefer an https front door, fall back
101
+ * to the local viewer / UI preview URL for copy-paste + screenshot.
102
+ */
103
+ export function buildOpenablePreviewUrls(input) {
104
+ const viewerUrl = input.deployedShellBaseUrl && input.jwt
105
+ ? buildDeployedShellViewerUrl({
106
+ applicationId: input.applicationId,
107
+ commitId: input.commitId,
108
+ deployedShellBaseUrl: input.deployedShellBaseUrl,
109
+ jwt: input.jwt,
110
+ })
111
+ : input.uiPreviewUrl;
112
+ if (input.publicViewerBaseUrl && isHttpsUrl(input.publicViewerBaseUrl)) {
113
+ const previewUrl = rewriteUrlOrigin(viewerUrl, input.publicViewerBaseUrl);
114
+ return {
115
+ openableViaHttps: isHttpsUrl(previewUrl),
116
+ previewUrl,
117
+ viewerUrl,
118
+ };
119
+ }
120
+ // Stdio Gateway does not bind HTTP, so never advertise a gateway /viewer
121
+ // path as openable. Preview URLs are the editor or the deployed shell.
122
+ return {
123
+ openableViaHttps: isHttpsUrl(viewerUrl),
124
+ previewUrl: viewerUrl,
125
+ viewerUrl,
126
+ };
127
+ }
128
+ //# sourceMappingURL=viewer-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer-url.js","sourceRoot":"","sources":["../../src/preview/viewer-url.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Exit code the gateway uses for a fault it decided not to survive.
3
+ *
4
+ * Distinct from 0 (an operator asked it to stop) and from 1 (it never got far
5
+ * enough to serve), so a supervisor can tell "restart this" from "stop trying".
6
+ * `superviseGateway` in the CLI reads it; the two MUST stay in agreement.
7
+ */
8
+ export declare const GATEWAY_FAULT_EXIT_CODE = 97;
9
+ export type GatewayFaultKind = "uncaught_exception" | "unhandled_rejection";
10
+ export type GatewayFault = {
11
+ at: number;
12
+ /** Whether the gateway gave up its process over this. */
13
+ fatal: boolean;
14
+ kind: GatewayFaultKind;
15
+ message: string;
16
+ };
17
+ /**
18
+ * The slice of `process` this needs, so a test can emit a fault without arming
19
+ * the real handlers and taking the runner down with them.
20
+ */
21
+ export type FaultBarrierListener = (...args: unknown[]) => void;
22
+ export type FaultBarrierHost = {
23
+ listenerCount: (event: string) => number;
24
+ off: (event: string, handler: FaultBarrierListener) => void;
25
+ on: (event: string, handler: FaultBarrierListener) => void;
26
+ };
27
+ export type FaultBarrierOptions = {
28
+ drainDeadlineMs?: number;
29
+ exit?: (code: number) => void;
30
+ log?: (message: string, fault: GatewayFault) => void;
31
+ /** Where the fault is recorded so `/health` can stop claiming to be well. */
32
+ onFault: (fault: GatewayFault) => void;
33
+ process?: FaultBarrierHost;
34
+ /** Best-effort release of listeners and sockets before a fatal exit. */
35
+ shutdown?: () => Promise<void>;
36
+ };
37
+ /**
38
+ * Node's default for an unhandled rejection is to kill the process. That is
39
+ * wrong for this gateway: its fire-and-forget paths (a live-edit turn, a
40
+ * liveness beat, a progress notification) belong to one caller, and a
41
+ * dependency that stopped answering must cost that caller its call rather than
42
+ * cost every concurrent caller the process. See ENG-5809, where ~8 concurrent
43
+ * agents lost the gateway to rejections that trailed a 120s upstream timeout.
44
+ *
45
+ * An uncaught exception is treated differently on purpose. A rejection that
46
+ * escaped a promise is a call that failed; an exception nobody caught means an
47
+ * invariant broke somewhere the code did not expect to be, and the heap it left
48
+ * behind is not worth serving from. Those exit deterministically on
49
+ * {@link GATEWAY_FAULT_EXIT_CODE} so a supervisor restarts at once, which is
50
+ * the other half of ENG-5809: recovery took between 80 seconds and never.
51
+ *
52
+ * Returns a disposer that releases the handlers, for tests and for a second
53
+ * gateway in the same process.
54
+ */
55
+ export declare function installFaultBarrier(options: FaultBarrierOptions): () => void;
@@ -0,0 +1,113 @@
1
+ import { recordGatewayFault } from "../telemetry/metrics.js";
2
+ /**
3
+ * Exit code the gateway uses for a fault it decided not to survive.
4
+ *
5
+ * Distinct from 0 (an operator asked it to stop) and from 1 (it never got far
6
+ * enough to serve), so a supervisor can tell "restart this" from "stop trying".
7
+ * `superviseGateway` in the CLI reads it; the two MUST stay in agreement.
8
+ */
9
+ export const GATEWAY_FAULT_EXIT_CODE = 97;
10
+ /** How long a fatal fault waits for in-flight work before the process goes. */
11
+ const DEFAULT_DRAIN_DEADLINE_MS = 2_000;
12
+ /** Hosts this module has already armed, so a second install is a no-op. */
13
+ const armed = new WeakSet();
14
+ function messageOf(error) {
15
+ if (error instanceof Error) {
16
+ return error.message;
17
+ }
18
+ return String(error);
19
+ }
20
+ /**
21
+ * Node's default for an unhandled rejection is to kill the process. That is
22
+ * wrong for this gateway: its fire-and-forget paths (a live-edit turn, a
23
+ * liveness beat, a progress notification) belong to one caller, and a
24
+ * dependency that stopped answering must cost that caller its call rather than
25
+ * cost every concurrent caller the process. See ENG-5809, where ~8 concurrent
26
+ * agents lost the gateway to rejections that trailed a 120s upstream timeout.
27
+ *
28
+ * An uncaught exception is treated differently on purpose. A rejection that
29
+ * escaped a promise is a call that failed; an exception nobody caught means an
30
+ * invariant broke somewhere the code did not expect to be, and the heap it left
31
+ * behind is not worth serving from. Those exit deterministically on
32
+ * {@link GATEWAY_FAULT_EXIT_CODE} so a supervisor restarts at once, which is
33
+ * the other half of ENG-5809: recovery took between 80 seconds and never.
34
+ *
35
+ * Returns a disposer that releases the handlers, for tests and for a second
36
+ * gateway in the same process.
37
+ */
38
+ export function installFaultBarrier(options) {
39
+ const host = options.process ?? process;
40
+ const exit = options.exit ?? ((code) => process.exit(code));
41
+ const log = options.log ??
42
+ ((message, fault) => {
43
+ console.error(message, fault);
44
+ });
45
+ // Installed twice, one gateway would report every fault twice and race two
46
+ // exits. `gateway serve` boots through `startStdioGateway`, so this is
47
+ // reachable rather than theoretical. The MCP host owns process lifecycle;
48
+ // this barrier records the fault and exits instead of wrapping a grandchild.
49
+ //
50
+ // Keyed on our own installs rather than on how many listeners the host has.
51
+ // Counting foreign listeners looks equivalent and is not: any library that
52
+ // registers one - a test runner, an instrumentation SDK - would silently
53
+ // suppress this barrier, and the gateway would go back to dying on a
54
+ // rejection with nothing recording why.
55
+ if (armed.has(host)) {
56
+ return () => undefined;
57
+ }
58
+ armed.add(host);
59
+ const record = (kind, error, fatal) => {
60
+ const fault = {
61
+ at: Date.now(),
62
+ fatal,
63
+ kind,
64
+ message: messageOf(error),
65
+ };
66
+ recordGatewayFault(fault);
67
+ log(fatal
68
+ ? "gateway fatal fault; exiting for a supervised restart"
69
+ : "gateway survived an unhandled rejection", fault);
70
+ options.onFault(fault);
71
+ return fault;
72
+ };
73
+ const onRejection = (reason) => {
74
+ record("unhandled_rejection", reason, false);
75
+ };
76
+ const onException = (error) => {
77
+ record("uncaught_exception", error, true);
78
+ void drainThenExit();
79
+ };
80
+ let exiting = false;
81
+ const drainThenExit = async () => {
82
+ // A second exception during the drain must not start a second drain, or the
83
+ // deadline below stops being a deadline.
84
+ if (exiting) {
85
+ return;
86
+ }
87
+ exiting = true;
88
+ const deadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
89
+ try {
90
+ // The race is the point: a drain that hangs is the failure mode being
91
+ // fixed, so it never decides when the process leaves.
92
+ await Promise.race([
93
+ options.shutdown?.() ?? Promise.resolve(),
94
+ new Promise((resolve) => {
95
+ const timer = setTimeout(resolve, deadlineMs);
96
+ timer.unref?.();
97
+ }),
98
+ ]);
99
+ }
100
+ catch {
101
+ // A shutdown that threw changes nothing about needing to exit.
102
+ }
103
+ exit(GATEWAY_FAULT_EXIT_CODE);
104
+ };
105
+ host.on("unhandledRejection", onRejection);
106
+ host.on("uncaughtException", onException);
107
+ return () => {
108
+ armed.delete(host);
109
+ host.off("unhandledRejection", onRejection);
110
+ host.off("uncaughtException", onException);
111
+ };
112
+ }
113
+ //# sourceMappingURL=fault-barrier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fault-barrier.js","sourceRoot":"","sources":["../../src/process/fault-barrier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type AppScopeKey, type ProgressCursorKey, type ProgressCursorRecord, type ProgressStore } from "./types.js";
2
+ /**
3
+ * Cap on how many cursors and pointers a single process will hold.
4
+ *
5
+ * Redis expires keys on its own; a plain Map does not, and a gateway that ran
6
+ * for a week would hold one entry per (caller, application) it had ever
7
+ * served. Insertion order is iteration order for a Map, so the oldest write
8
+ * is the one dropped — a caller evicted this way re-reads the retained
9
+ * history rather than losing anything, which is the same outcome as its
10
+ * cursor having expired.
11
+ */
12
+ export declare const MAX_MEMORY_PROGRESS_ENTRIES = 2048;
13
+ /**
14
+ * Local-only fallback, matching the other in-memory stores.
15
+ *
16
+ * Not a second source of truth: two replicas with private maps would hand the
17
+ * same caller two different positions in the same stream, which is exactly
18
+ * the divergence Redis is required outside local to prevent.
19
+ */
20
+ export declare class MemoryProgressStore implements ProgressStore {
21
+ private readonly cursors;
22
+ private readonly lastActivity;
23
+ private readonly liveEdits;
24
+ getCursor(key: ProgressCursorKey): Promise<ProgressCursorRecord | undefined>;
25
+ getLastActivityAtMs(key: AppScopeKey): Promise<number | undefined>;
26
+ getLiveEditId(key: AppScopeKey): Promise<string | undefined>;
27
+ setCursor(input: ProgressCursorKey & {
28
+ record: ProgressCursorRecord;
29
+ ttlMs?: number;
30
+ }): Promise<void>;
31
+ setLastActivityAtMs(input: AppScopeKey & {
32
+ atMs: number;
33
+ ttlMs?: number;
34
+ }): Promise<void>;
35
+ setLiveEditId(input: AppScopeKey & {
36
+ liveEditId: string;
37
+ ttlMs?: number;
38
+ }): Promise<void>;
39
+ }