@superblocksteam/gateway 2.0.0-SNAPSHOT.1d4c72c0ab174c21

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 (248) hide show
  1. package/LICENSE.txt +87 -0
  2. package/README.md +291 -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/integration-metadata.d.ts +28 -0
  7. package/dist/capabilities/integration-metadata.js +174 -0
  8. package/dist/capabilities/integration-metadata.js.map +1 -0
  9. package/dist/capabilities/lifecycle.d.ts +93 -0
  10. package/dist/capabilities/lifecycle.js +3450 -0
  11. package/dist/capabilities/lifecycle.js.map +1 -0
  12. package/dist/capabilities/query-integration.d.ts +14 -0
  13. package/dist/capabilities/query-integration.js +172 -0
  14. package/dist/capabilities/query-integration.js.map +1 -0
  15. package/dist/capabilities/source-files-archive.d.ts +11 -0
  16. package/dist/capabilities/source-files-archive.js +54 -0
  17. package/dist/capabilities/source-files-archive.js.map +1 -0
  18. package/dist/capabilities/types.d.ts +542 -0
  19. package/dist/capabilities/types.js +86 -0
  20. package/dist/capabilities/types.js.map +1 -0
  21. package/dist/capture/browser-contract.d.ts +46 -0
  22. package/dist/capture/browser-contract.js +34 -0
  23. package/dist/capture/browser-contract.js.map +1 -0
  24. package/dist/capture/browser-instructions.d.ts +10 -0
  25. package/dist/capture/browser-instructions.js +18 -0
  26. package/dist/capture/browser-instructions.js.map +1 -0
  27. package/dist/capture/capture-library-probe.d.ts +55 -0
  28. package/dist/capture/capture-library-probe.js +177 -0
  29. package/dist/capture/capture-library-probe.js.map +1 -0
  30. package/dist/capture/capture-library.d.ts +52 -0
  31. package/dist/capture/capture-library.js +249 -0
  32. package/dist/capture/capture-library.js.map +1 -0
  33. package/dist/capture/classify-library-paint.d.ts +21 -0
  34. package/dist/capture/classify-library-paint.js +13 -0
  35. package/dist/capture/classify-library-paint.js.map +1 -0
  36. package/dist/capture/host-mcp-scan.d.ts +16 -0
  37. package/dist/capture/host-mcp-scan.js +64 -0
  38. package/dist/capture/host-mcp-scan.js.map +1 -0
  39. package/dist/capture/library-parent-script.d.ts +35 -0
  40. package/dist/capture/library-parent-script.js +82 -0
  41. package/dist/capture/library-parent-script.js.map +1 -0
  42. package/dist/capture/mode.d.ts +29 -0
  43. package/dist/capture/mode.js +65 -0
  44. package/dist/capture/mode.js.map +1 -0
  45. package/dist/config.d.ts +148 -0
  46. package/dist/config.js +111 -0
  47. package/dist/config.js.map +1 -0
  48. package/dist/debug.d.ts +2 -0
  49. package/dist/debug.js +9 -0
  50. package/dist/debug.js.map +1 -0
  51. package/dist/deps.d.ts +38 -0
  52. package/dist/deps.js +2 -0
  53. package/dist/deps.js.map +1 -0
  54. package/dist/health/readiness.d.ts +99 -0
  55. package/dist/health/readiness.js +202 -0
  56. package/dist/health/readiness.js.map +1 -0
  57. package/dist/index.d.ts +8 -0
  58. package/dist/index.js +8 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/integrations/filters.d.ts +8 -0
  61. package/dist/integrations/filters.js +40 -0
  62. package/dist/integrations/filters.js.map +1 -0
  63. package/dist/integrations/map.d.ts +27 -0
  64. package/dist/integrations/map.js +43 -0
  65. package/dist/integrations/map.js.map +1 -0
  66. package/dist/integrations/profile-key.d.ts +19 -0
  67. package/dist/integrations/profile-key.js +35 -0
  68. package/dist/integrations/profile-key.js.map +1 -0
  69. package/dist/integrations/read-only-postgres-query.d.ts +2 -0
  70. package/dist/integrations/read-only-postgres-query.js +164 -0
  71. package/dist/integrations/read-only-postgres-query.js.map +1 -0
  72. package/dist/main.d.ts +2 -0
  73. package/dist/main.js +5 -0
  74. package/dist/main.js.map +1 -0
  75. package/dist/orchestrator/client.d.ts +62 -0
  76. package/dist/orchestrator/client.js +160 -0
  77. package/dist/orchestrator/client.js.map +1 -0
  78. package/dist/playwright/ensure-chromium.d.ts +24 -0
  79. package/dist/playwright/ensure-chromium.js +60 -0
  80. package/dist/playwright/ensure-chromium.js.map +1 -0
  81. package/dist/preview/capture-screenshot.d.ts +67 -0
  82. package/dist/preview/capture-screenshot.js +179 -0
  83. package/dist/preview/capture-screenshot.js.map +1 -0
  84. package/dist/preview/viewer-url.d.ts +82 -0
  85. package/dist/preview/viewer-url.js +130 -0
  86. package/dist/preview/viewer-url.js.map +1 -0
  87. package/dist/process/fault-barrier.d.ts +55 -0
  88. package/dist/process/fault-barrier.js +113 -0
  89. package/dist/process/fault-barrier.js.map +1 -0
  90. package/dist/sabs/agent-facing-text.d.ts +35 -0
  91. package/dist/sabs/agent-facing-text.js +165 -0
  92. package/dist/sabs/agent-facing-text.js.map +1 -0
  93. package/dist/sabs/app-state.d.ts +126 -0
  94. package/dist/sabs/app-state.js +332 -0
  95. package/dist/sabs/app-state.js.map +1 -0
  96. package/dist/sabs/awaited-decision.d.ts +47 -0
  97. package/dist/sabs/awaited-decision.js +124 -0
  98. package/dist/sabs/awaited-decision.js.map +1 -0
  99. package/dist/sabs/browser-context.d.ts +53 -0
  100. package/dist/sabs/browser-context.js +92 -0
  101. package/dist/sabs/browser-context.js.map +1 -0
  102. package/dist/sabs/editor-client-methods.d.ts +164 -0
  103. package/dist/sabs/editor-client-methods.js +750 -0
  104. package/dist/sabs/editor-client-methods.js.map +1 -0
  105. package/dist/sabs/editor-socket.d.ts +84 -0
  106. package/dist/sabs/editor-socket.js +64 -0
  107. package/dist/sabs/editor-socket.js.map +1 -0
  108. package/dist/sabs/recent-tool-runs.d.ts +14 -0
  109. package/dist/sabs/recent-tool-runs.js +61 -0
  110. package/dist/sabs/recent-tool-runs.js.map +1 -0
  111. package/dist/sabs/session-peer.d.ts +392 -0
  112. package/dist/sabs/session-peer.js +11 -0
  113. package/dist/sabs/session-peer.js.map +1 -0
  114. package/dist/sabs/streamed-reply.d.ts +42 -0
  115. package/dist/sabs/streamed-reply.js +110 -0
  116. package/dist/sabs/streamed-reply.js.map +1 -0
  117. package/dist/sabs/turn-collector.d.ts +169 -0
  118. package/dist/sabs/turn-collector.js +506 -0
  119. package/dist/sabs/turn-collector.js.map +1 -0
  120. package/dist/sabs/websocket-session-peer.d.ts +540 -0
  121. package/dist/sabs/websocket-session-peer.js +3289 -0
  122. package/dist/sabs/websocket-session-peer.js.map +1 -0
  123. package/dist/security/constant-time.d.ts +9 -0
  124. package/dist/security/constant-time.js +13 -0
  125. package/dist/security/constant-time.js.map +1 -0
  126. package/dist/server/client.d.ts +405 -0
  127. package/dist/server/client.js +684 -0
  128. package/dist/server/client.js.map +1 -0
  129. package/dist/server/identity-reads.d.ts +76 -0
  130. package/dist/server/identity-reads.js +209 -0
  131. package/dist/server/identity-reads.js.map +1 -0
  132. package/dist/start.d.ts +24 -0
  133. package/dist/start.js +81 -0
  134. package/dist/start.js.map +1 -0
  135. package/dist/stores/memory.d.ts +42 -0
  136. package/dist/stores/memory.js +121 -0
  137. package/dist/stores/memory.js.map +1 -0
  138. package/dist/stores/types.d.ts +74 -0
  139. package/dist/stores/types.js +11 -0
  140. package/dist/stores/types.js.map +1 -0
  141. package/dist/telemetry/mcp-client.d.ts +22 -0
  142. package/dist/telemetry/mcp-client.js +140 -0
  143. package/dist/telemetry/mcp-client.js.map +1 -0
  144. package/dist/telemetry/metrics.d.ts +41 -0
  145. package/dist/telemetry/metrics.js +103 -0
  146. package/dist/telemetry/metrics.js.map +1 -0
  147. package/dist/telemetry/runtime.d.ts +9 -0
  148. package/dist/telemetry/runtime.js +118 -0
  149. package/dist/telemetry/runtime.js.map +1 -0
  150. package/dist/transports/mcp/admin-tools.d.ts +56 -0
  151. package/dist/transports/mcp/admin-tools.js +252 -0
  152. package/dist/transports/mcp/admin-tools.js.map +1 -0
  153. package/dist/transports/mcp/app-status-html.d.ts +11 -0
  154. package/dist/transports/mcp/app-status-html.js +899 -0
  155. package/dist/transports/mcp/app-status-html.js.map +1 -0
  156. package/dist/transports/mcp/client-presentation.d.ts +16 -0
  157. package/dist/transports/mcp/client-presentation.js +13 -0
  158. package/dist/transports/mcp/client-presentation.js.map +1 -0
  159. package/dist/transports/mcp/cowork-editor-url.d.ts +6 -0
  160. package/dist/transports/mcp/cowork-editor-url.js +10 -0
  161. package/dist/transports/mcp/cowork-editor-url.js.map +1 -0
  162. package/dist/transports/mcp/decision-card-html.d.ts +26 -0
  163. package/dist/transports/mcp/decision-card-html.js +876 -0
  164. package/dist/transports/mcp/decision-card-html.js.map +1 -0
  165. package/dist/transports/mcp/decision-elicitation.d.ts +116 -0
  166. package/dist/transports/mcp/decision-elicitation.js +292 -0
  167. package/dist/transports/mcp/decision-elicitation.js.map +1 -0
  168. package/dist/transports/mcp/dispatch-probe.d.ts +24 -0
  169. package/dist/transports/mcp/dispatch-probe.js +48 -0
  170. package/dist/transports/mcp/dispatch-probe.js.map +1 -0
  171. package/dist/transports/mcp/editor-document-probe.d.ts +4 -0
  172. package/dist/transports/mcp/editor-document-probe.js +35 -0
  173. package/dist/transports/mcp/editor-document-probe.js.map +1 -0
  174. package/dist/transports/mcp/editor-integration-setup-url.d.ts +8 -0
  175. package/dist/transports/mcp/editor-integration-setup-url.js +23 -0
  176. package/dist/transports/mcp/editor-integration-setup-url.js.map +1 -0
  177. package/dist/transports/mcp/format-tool-content.d.ts +20 -0
  178. package/dist/transports/mcp/format-tool-content.js +89 -0
  179. package/dist/transports/mcp/format-tool-content.js.map +1 -0
  180. package/dist/transports/mcp/instructions/index.d.ts +23 -0
  181. package/dist/transports/mcp/instructions/index.js +81 -0
  182. package/dist/transports/mcp/instructions/index.js.map +1 -0
  183. package/dist/transports/mcp/instructions/result.d.ts +18 -0
  184. package/dist/transports/mcp/instructions/result.js +58 -0
  185. package/dist/transports/mcp/instructions/result.js.map +1 -0
  186. package/dist/transports/mcp/instructions/tools/ask-user.d.ts +2 -0
  187. package/dist/transports/mcp/instructions/tools/ask-user.js +17 -0
  188. package/dist/transports/mcp/instructions/tools/ask-user.js.map +1 -0
  189. package/dist/transports/mcp/instructions/tools/check-app-progress.d.ts +3 -0
  190. package/dist/transports/mcp/instructions/tools/check-app-progress.js +75 -0
  191. package/dist/transports/mcp/instructions/tools/check-app-progress.js.map +1 -0
  192. package/dist/transports/mcp/instructions/tools/check-publish-progress.d.ts +3 -0
  193. package/dist/transports/mcp/instructions/tools/check-publish-progress.js +23 -0
  194. package/dist/transports/mcp/instructions/tools/check-publish-progress.js.map +1 -0
  195. package/dist/transports/mcp/instructions/tools/copy.d.ts +14 -0
  196. package/dist/transports/mcp/instructions/tools/copy.js +35 -0
  197. package/dist/transports/mcp/instructions/tools/copy.js.map +1 -0
  198. package/dist/transports/mcp/instructions/tools/create-integration.d.ts +4 -0
  199. package/dist/transports/mcp/instructions/tools/create-integration.js +41 -0
  200. package/dist/transports/mcp/instructions/tools/create-integration.js.map +1 -0
  201. package/dist/transports/mcp/instructions/tools/edit-app.d.ts +2 -0
  202. package/dist/transports/mcp/instructions/tools/edit-app.js +18 -0
  203. package/dist/transports/mcp/instructions/tools/edit-app.js.map +1 -0
  204. package/dist/transports/mcp/instructions/tools/get-app.d.ts +3 -0
  205. package/dist/transports/mcp/instructions/tools/get-app.js +47 -0
  206. package/dist/transports/mcp/instructions/tools/get-app.js.map +1 -0
  207. package/dist/transports/mcp/instructions/tools/get-integration-metadata.d.ts +2 -0
  208. package/dist/transports/mcp/instructions/tools/get-integration-metadata.js +13 -0
  209. package/dist/transports/mcp/instructions/tools/get-integration-metadata.js.map +1 -0
  210. package/dist/transports/mcp/instructions/tools/index.d.ts +7 -0
  211. package/dist/transports/mcp/instructions/tools/index.js +26 -0
  212. package/dist/transports/mcp/instructions/tools/index.js.map +1 -0
  213. package/dist/transports/mcp/instructions/tools/preview-app.d.ts +3 -0
  214. package/dist/transports/mcp/instructions/tools/preview-app.js +14 -0
  215. package/dist/transports/mcp/instructions/tools/preview-app.js.map +1 -0
  216. package/dist/transports/mcp/instructions/tools/publish-app.d.ts +3 -0
  217. package/dist/transports/mcp/instructions/tools/publish-app.js +23 -0
  218. package/dist/transports/mcp/instructions/tools/publish-app.js.map +1 -0
  219. package/dist/transports/mcp/instructions/tools/start-app.d.ts +2 -0
  220. package/dist/transports/mcp/instructions/tools/start-app.js +22 -0
  221. package/dist/transports/mcp/instructions/tools/start-app.js.map +1 -0
  222. package/dist/transports/mcp/instructions/tools/upload-artifact.d.ts +2 -0
  223. package/dist/transports/mcp/instructions/tools/upload-artifact.js +13 -0
  224. package/dist/transports/mcp/instructions/tools/upload-artifact.js.map +1 -0
  225. package/dist/transports/mcp/mcp-app-brand-css.d.ts +1 -0
  226. package/dist/transports/mcp/mcp-app-brand-css.js +180 -0
  227. package/dist/transports/mcp/mcp-app-brand-css.js.map +1 -0
  228. package/dist/transports/mcp/mount.d.ts +22 -0
  229. package/dist/transports/mcp/mount.js +1040 -0
  230. package/dist/transports/mcp/mount.js.map +1 -0
  231. package/dist/transports/mcp/native-browser-presence.d.ts +45 -0
  232. package/dist/transports/mcp/native-browser-presence.js +158 -0
  233. package/dist/transports/mcp/native-browser-presence.js.map +1 -0
  234. package/dist/transports/mcp/plan-approval.d.ts +38 -0
  235. package/dist/transports/mcp/plan-approval.js +175 -0
  236. package/dist/transports/mcp/plan-approval.js.map +1 -0
  237. package/dist/transports/mcp/progress-notifier.d.ts +39 -0
  238. package/dist/transports/mcp/progress-notifier.js +52 -0
  239. package/dist/transports/mcp/progress-notifier.js.map +1 -0
  240. package/dist/transports/mcp/session-directory.d.ts +7 -0
  241. package/dist/transports/mcp/session-directory.js +18 -0
  242. package/dist/transports/mcp/session-directory.js.map +1 -0
  243. package/dist/transports/mcp/tool-names.d.ts +2 -0
  244. package/dist/transports/mcp/tool-names.js +2 -0
  245. package/dist/transports/mcp/tool-names.js.map +1 -0
  246. package/package.json +78 -0
  247. package/skills/superblocks-build/SKILL.md +59 -0
  248. package/skills/superblocks-import/SKILL.md +88 -0
@@ -0,0 +1,684 @@
1
+ import { uploadAttachmentToServerApi } from "@superblocksteam/shared";
2
+ import { recordIdentityReadOutcome } from "../telemetry/metrics.js";
3
+ import { withGuardedIdentityReads, } from "./identity-reads.js";
4
+ const MAX_DETAIL_LENGTH = 300;
5
+ /**
6
+ * Pulls the server's own explanation out of an error body.
7
+ *
8
+ * Bounded because the body is attacker-influenced in principle and ends up in
9
+ * logs and in tool output the caller sees.
10
+ */
11
+ function detailSuffix(body) {
12
+ if (!body.trim()) {
13
+ return "";
14
+ }
15
+ let detail = body;
16
+ try {
17
+ const parsed = JSON.parse(body);
18
+ if (parsed && typeof parsed === "object") {
19
+ const record = parsed;
20
+ const message = record.responseMeta?.message ?? record.message ?? record.error;
21
+ if (typeof message === "string" && message.trim()) {
22
+ detail = message;
23
+ }
24
+ }
25
+ }
26
+ catch {
27
+ // Not JSON — fall back to the raw body.
28
+ }
29
+ const trimmed = detail.trim().slice(0, MAX_DETAIL_LENGTH);
30
+ return ` — ${trimmed}`;
31
+ }
32
+ export class ServerHttpError extends Error {
33
+ status;
34
+ constructor(message, status) {
35
+ super(message);
36
+ this.status = status;
37
+ this.name = "ServerHttpError";
38
+ }
39
+ }
40
+ /**
41
+ * Generous enough for a sandbox start.
42
+ *
43
+ * It is not short enough to keep a tool call from hanging, which is what this
44
+ * used to claim: a tool call waits on the identity read, and that one is bounded
45
+ * by {@link DEFAULT_IDENTITY_TIMEOUT_MS} instead.
46
+ */
47
+ const DEFAULT_TIMEOUT_MS = 120_000;
48
+ /**
49
+ * The identity read gets its own, much tighter bound.
50
+ *
51
+ * {@link DEFAULT_TIMEOUT_MS} is sized for starting a sandbox. Nothing waits on
52
+ * that; every tool call waits on `/users/me`. Giving the two the same budget is
53
+ * what let a single wedged route hold each call for two minutes.
54
+ *
55
+ * Still well above the ~10s a caller waits (see
56
+ * {@link withGuardedIdentityReads}) so that an endpoint which answers slowly
57
+ * rather than never can still finish and fill the cache.
58
+ */
59
+ const DEFAULT_IDENTITY_TIMEOUT_MS = 30_000;
60
+ /**
61
+ * Whether a failed identity read means the endpoint is unhealthy rather than
62
+ * that this one credential is bad.
63
+ *
64
+ * A 4xx is a definite answer about a single caller, so it must not trip the
65
+ * breaker and start failing everyone else fast. A 5xx, a timeout or a
66
+ * transport error says nothing about the credential and everything about the
67
+ * route.
68
+ *
69
+ * 429 is the exception on the 4xx side: a shared rate limit is a statement
70
+ * about the route's capacity, not about this credential, and it is exactly the
71
+ * case where hammering on is worst. Treating it as the caller's fault left it
72
+ * neither retried (only >= 500 is) nor able to open the breaker, so every call
73
+ * kept going straight at an endpoint already asking for less load.
74
+ */
75
+ function identityEndpointUnhealthy(error) {
76
+ return !(error instanceof ServerHttpError &&
77
+ error.status < 500 &&
78
+ error.status !== 429);
79
+ }
80
+ /**
81
+ * Extra attempts for reads that failed with a server-side error.
82
+ *
83
+ * Only GETs are retried: they carry no side effects, so a repeat is free. A
84
+ * failed POST may well have taken effect, and re-sending one can deploy or
85
+ * commit twice.
86
+ */
87
+ const READ_RETRY_ATTEMPTS = 2;
88
+ const READ_RETRY_BASE_DELAY_MS = 100;
89
+ function delay(ms) {
90
+ return new Promise((resolve) => setTimeout(resolve, ms));
91
+ }
92
+ const DEPLOY_STATUSES = [
93
+ "awaiting_ack",
94
+ "deployed",
95
+ "failed",
96
+ "queued",
97
+ ];
98
+ function isDeployStatus(value) {
99
+ return DEPLOY_STATUSES.includes(value);
100
+ }
101
+ /**
102
+ * SABS's build status as this client's own vocabulary.
103
+ *
104
+ * It answers with `BUILD_STATUS_*` strings through the server, and with the
105
+ * matching protobuf ordinals when the response is not re-serialized on the way
106
+ * — the editor's own client normalizes both, and so does this.
107
+ */
108
+ const BUILD_PROGRESS_BY_ORDINAL = {
109
+ 0: "none",
110
+ 1: "running",
111
+ 2: "success",
112
+ 3: "failed",
113
+ 4: "timed_out",
114
+ };
115
+ const BUILD_PROGRESS_BY_NAME = {
116
+ FAILED: "failed",
117
+ RUNNING: "running",
118
+ SUCCESS: "success",
119
+ TIMED_OUT: "timed_out",
120
+ UNSPECIFIED: "none",
121
+ };
122
+ function buildProgress(status) {
123
+ if (typeof status === "number") {
124
+ return BUILD_PROGRESS_BY_ORDINAL[status] ?? "none";
125
+ }
126
+ if (typeof status !== "string") {
127
+ return "none";
128
+ }
129
+ const name = status
130
+ .trim()
131
+ .toUpperCase()
132
+ .replace(/^BUILD_STATUS_/, "");
133
+ return BUILD_PROGRESS_BY_NAME[name] ?? "none";
134
+ }
135
+ function liveEditStatus(status) {
136
+ const normalized = String(status)
137
+ .replace(/^LIVE_EDIT_STATUS_/, "")
138
+ .toUpperCase();
139
+ switch (normalized) {
140
+ case "1":
141
+ case "STARTING":
142
+ return "starting";
143
+ case "2":
144
+ case "ACTIVE":
145
+ return "active";
146
+ case "3":
147
+ case "TERMINATED":
148
+ return "terminated";
149
+ case "5":
150
+ case "TERMINATING":
151
+ return "terminating";
152
+ default:
153
+ return "unknown";
154
+ }
155
+ }
156
+ export class HttpSuperblocksServerClient {
157
+ identityTimeoutMs;
158
+ readIdentity;
159
+ serverUrl;
160
+ timeoutMs;
161
+ constructor(serverUrl, options = {}) {
162
+ this.serverUrl = serverUrl;
163
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
164
+ this.identityTimeoutMs =
165
+ options.identityTimeoutMs ?? DEFAULT_IDENTITY_TIMEOUT_MS;
166
+ this.readIdentity = withGuardedIdentityReads((apiKey) => this.readIdentityFromServer(apiKey), {
167
+ onOutcome: recordIdentityReadOutcome,
168
+ opensBreaker: identityEndpointUnhealthy,
169
+ ...options.identityReads,
170
+ });
171
+ }
172
+ baseUrl() {
173
+ return this.serverUrl.replace(/\/$/, "");
174
+ }
175
+ /**
176
+ * Unwraps the Superblocks `ResponseDto` envelope.
177
+ *
178
+ * Every v1/v2/v3 route the gateway calls replies with
179
+ * `{ responseMeta, data }`. Reading the body directly yields `undefined` for
180
+ * every field, which is indistinguishable from a legitimately empty response
181
+ * — so this is deliberately strict rather than falling back to the raw body.
182
+ */
183
+ async requestData(path, init) {
184
+ const body = await this.request(path, init);
185
+ if (body?.data === undefined) {
186
+ throw new Error(`Server ${path} returned no data field; expected a { responseMeta, data } envelope`);
187
+ }
188
+ return body.data;
189
+ }
190
+ /**
191
+ * Issues the request, retrying a transient status when the verb makes that
192
+ * safe.
193
+ *
194
+ * A transient failure on the identity lookup used to fail the whole tool call
195
+ * and leave the caller guessing whether their account was broken.
196
+ *
197
+ * 429 counts as transient alongside 5xx: it is the route reporting its own
198
+ * capacity, and backing off is the whole point of it.
199
+ */
200
+ async request(path, init) {
201
+ const retryable = init.retry !== false && (init.method ?? "GET").toUpperCase() === "GET";
202
+ let lastError;
203
+ for (let attempt = 0; attempt <= (retryable ? READ_RETRY_ATTEMPTS : 0);) {
204
+ try {
205
+ return await this.send(path, init);
206
+ }
207
+ catch (error) {
208
+ lastError = error;
209
+ const transient = error instanceof ServerHttpError &&
210
+ (error.status >= 500 || error.status === 429);
211
+ attempt += 1;
212
+ if (!retryable || !transient || attempt > READ_RETRY_ATTEMPTS) {
213
+ throw error;
214
+ }
215
+ await delay(READ_RETRY_BASE_DELAY_MS * attempt);
216
+ }
217
+ }
218
+ throw lastError;
219
+ }
220
+ async send(path, init) {
221
+ const { apiKey, headers: callerHeaders, retry: _retry, signal: callerSignal, timeoutMs, ...rest } = init;
222
+ const bound = timeoutMs ?? this.timeoutMs;
223
+ const mergedHeaders = new Headers(callerHeaders);
224
+ // Auth and content-type are owned by this client — never override from callers.
225
+ mergedHeaders.set("Authorization", `Bearer ${apiKey}`);
226
+ if (!mergedHeaders.has("Content-Type")) {
227
+ mergedHeaders.set("Content-Type", "application/json");
228
+ }
229
+ const timeoutSignal = AbortSignal.timeout(bound);
230
+ let response;
231
+ try {
232
+ response = await fetch(`${this.baseUrl()}${path}`, {
233
+ ...rest,
234
+ headers: mergedHeaders,
235
+ signal: callerSignal
236
+ ? AbortSignal.any([callerSignal, timeoutSignal])
237
+ : timeoutSignal,
238
+ });
239
+ }
240
+ catch (error) {
241
+ if (timeoutSignal.aborted &&
242
+ error instanceof Error &&
243
+ error.name === "TimeoutError") {
244
+ throw new Error(`Server ${path} timed out after ${bound}ms`);
245
+ }
246
+ throw error;
247
+ }
248
+ if (!response.ok) {
249
+ throw new ServerHttpError(`Server ${path} failed: ${response.status}${detailSuffix(await response.text().catch(() => ""))}`, response.status);
250
+ }
251
+ return (await response.json());
252
+ }
253
+ /**
254
+ * The Superblocks user behind a downstream credential.
255
+ *
256
+ * Cached, collapsed, deadlined and circuit-broken: this is the first thing
257
+ * every MCP tool call does, so reading it straight through coupled the whole
258
+ * tool surface to the health of one route.
259
+ */
260
+ async validateApiKey(apiKey) {
261
+ return this.readIdentity(apiKey);
262
+ }
263
+ /** The unguarded read behind {@link validateApiKey}. */
264
+ async readIdentityFromServer(apiKey) {
265
+ try {
266
+ const data = await this.requestData("/api/v1/users/me", {
267
+ apiKey,
268
+ method: "GET",
269
+ timeoutMs: this.identityTimeoutMs,
270
+ });
271
+ const user = data.user;
272
+ const organizationId = user?.currentOrganizationId ?? data.organizations?.[0]?.id;
273
+ if (!user || !organizationId) {
274
+ return undefined;
275
+ }
276
+ return {
277
+ email: user.email,
278
+ organizationId,
279
+ userId: user.id,
280
+ };
281
+ }
282
+ catch (error) {
283
+ // Auth failures → undefined (caller elicits login). Transport errors rethrow.
284
+ if (error instanceof ServerHttpError && error.status === 401) {
285
+ return undefined;
286
+ }
287
+ throw error;
288
+ }
289
+ }
290
+ async getAgentInventory(input) {
291
+ const data = await this.requestData("/api/v1/users/me", {
292
+ apiKey: input.apiKey,
293
+ method: "GET",
294
+ // Same route, so the same bound. This one runs on the build path rather
295
+ // than per tool call, and left at the sandbox timeout a wedged
296
+ // /users/me held a start_app open for two minutes here instead.
297
+ timeoutMs: this.identityTimeoutMs,
298
+ });
299
+ const organization = data.organizations?.find((org) => org.id === input.organizationId) ??
300
+ data.organizations?.find((org) => org.id === data.user?.currentOrganizationId) ??
301
+ data.organizations?.[0];
302
+ if (organization?.agentType === undefined) {
303
+ throw new Error(`Server /api/v1/users/me returned no agentType for organization ${input.organizationId}`);
304
+ }
305
+ const agents = [];
306
+ for (const raw of data.agents ?? []) {
307
+ if (typeof raw.url !== "string" ||
308
+ !raw.url ||
309
+ typeof raw.type !== "number" ||
310
+ typeof raw.status !== "string") {
311
+ continue;
312
+ }
313
+ agents.push({
314
+ status: raw.status,
315
+ tags: raw.tags ?? {},
316
+ type: raw.type,
317
+ url: raw.url,
318
+ });
319
+ }
320
+ return {
321
+ agentType: organization.agentType,
322
+ agents,
323
+ };
324
+ }
325
+ async createApplication(input) {
326
+ return this.requestData("/api/v2/applications/", {
327
+ apiKey: input.apiKey,
328
+ // Live edit only attaches to code-mode apps; without this the server
329
+ // builds a legacy DSL app and `connect` rejects it.
330
+ body: JSON.stringify({
331
+ createUsingReact: true,
332
+ name: input.name,
333
+ organizationId: input.organizationId,
334
+ ...(input.templateName ? { templateName: input.templateName } : {}),
335
+ }),
336
+ method: "POST",
337
+ ...(input.signal ? { signal: input.signal } : {}),
338
+ ...(input.timeoutMs === undefined
339
+ ? {}
340
+ : { timeoutMs: input.timeoutMs }),
341
+ });
342
+ }
343
+ async uploadAttachment(input) {
344
+ const mimeType = input.mimeType ?? "application/zip";
345
+ // Multipart must not go through {@link send}: that helper forces
346
+ // application/json and would break the boundary.
347
+ const response = await uploadAttachmentToServerApi({
348
+ applicationId: input.applicationId,
349
+ authorization: `Bearer ${input.apiKey}`,
350
+ baseUrl: this.baseUrl(),
351
+ descriptor: {
352
+ blob: new Blob([Buffer.from(input.bytes)], { type: mimeType }),
353
+ fileName: input.fileName,
354
+ mimeType,
355
+ },
356
+ prefix: "message-attachment",
357
+ purpose: "attachment",
358
+ scopeType: input.applicationId?.trim() ? "app" : "org",
359
+ });
360
+ return {
361
+ applicationId: response.applicationId,
362
+ attachmentId: response.attachmentId,
363
+ contentUrl: response.contentUrl,
364
+ fileName: response.fileName,
365
+ mimeType: response.mimeType,
366
+ signedUrl: response.signedUrl,
367
+ signedUrlExpiresAt: response.signedUrlExpiresAt,
368
+ storageKey: response.storageKey,
369
+ };
370
+ }
371
+ async connectLiveEdit(input) {
372
+ const deadlineAtMs = Date.now() + this.timeoutMs;
373
+ const branchSegment = input.branch
374
+ ? `/branches/${encodeURIComponent(input.branch)}`
375
+ : "";
376
+ const path = `/api/v3/applications/${input.applicationId}${branchSegment}/connect`;
377
+ let liveEditId = "session";
378
+ const deadlineError = () => new Error(`Live edit ${liveEditId} did not become active within ${this.timeoutMs}ms`);
379
+ const requestBeforeDeadline = async (query) => {
380
+ if (Date.now() >= deadlineAtMs) {
381
+ throw deadlineError();
382
+ }
383
+ try {
384
+ return await this.requestData(`${path}${query}`, {
385
+ apiKey: input.apiKey,
386
+ method: "GET",
387
+ retry: false,
388
+ timeoutMs: Math.max(1, deadlineAtMs - Date.now()),
389
+ });
390
+ }
391
+ catch (error) {
392
+ if (Date.now() >= deadlineAtMs) {
393
+ throw deadlineError();
394
+ }
395
+ throw error;
396
+ }
397
+ };
398
+ let data = await requestBeforeDeadline("?returnBeforeReady=true");
399
+ let polled = false;
400
+ for (;;) {
401
+ const session = data.sandboxStartData;
402
+ if (!session) {
403
+ // Absent for a LOCAL lock — there is no remote sandbox to drive.
404
+ throw new Error("Server returned no live edit session for this application");
405
+ }
406
+ liveEditId = session.sandboxId || liveEditId;
407
+ const status = liveEditStatus(session.status);
408
+ if (session.pitcherURL &&
409
+ (session.status === undefined || status === "active")) {
410
+ return {
411
+ liveEditId: session.sandboxId,
412
+ liveEditUrl: session.pitcherURL,
413
+ pitcherToken: session.pitcherToken,
414
+ pitcherURL: session.pitcherURL,
415
+ };
416
+ }
417
+ if (status !== "starting") {
418
+ throw new Error(`Live edit ${session.sandboxId || "session"} is not connectable (${status})`);
419
+ }
420
+ if (polled) {
421
+ await delay(Math.min(1_000, Math.max(0, deadlineAtMs - Date.now())));
422
+ }
423
+ if (Date.now() >= deadlineAtMs) {
424
+ throw deadlineError();
425
+ }
426
+ polled = true;
427
+ data = await requestBeforeDeadline(`?liveEditId=${encodeURIComponent(session.sandboxId)}`);
428
+ }
429
+ }
430
+ async getLiveEditStatus(input) {
431
+ const branchSegment = input.branch
432
+ ? `/branches/${encodeURIComponent(input.branch)}`
433
+ : "";
434
+ const path = `/api/v3/applications/${input.applicationId}${branchSegment}/connect?liveEditId=${encodeURIComponent(input.liveEditId)}`;
435
+ try {
436
+ const data = await this.requestData(path, {
437
+ apiKey: input.apiKey,
438
+ method: "GET",
439
+ retry: false,
440
+ signal: input.signal,
441
+ });
442
+ if (data.sandboxStartData?.sandboxId !== input.liveEditId) {
443
+ return "unknown";
444
+ }
445
+ return liveEditStatus(data.sandboxStartData.status);
446
+ }
447
+ catch (error) {
448
+ if (error instanceof ServerHttpError && error.status === 404) {
449
+ // This route also returns 404 for an expired or mismatched editor lock
450
+ // before it queries SABS, so 404 does not prove the live edit is gone.
451
+ return "unknown";
452
+ }
453
+ throw error;
454
+ }
455
+ }
456
+ /**
457
+ * Mints an application-scoped credential. Connect's `pitcherToken` is a
458
+ * placeholder in the local sandbox path, so live-edit callers mint their
459
+ * own token here too.
460
+ */
461
+ async issueScopedToken(input) {
462
+ // Alone among the routes here, this one answers with a bare OAuth-shaped
463
+ // body rather than the `{ responseMeta, data }` envelope.
464
+ const scope = input.scope ?? "apps:update";
465
+ const body = { scope };
466
+ if (scope === "integrations:build") {
467
+ if (!input.integrationId?.trim()) {
468
+ throw new Error("integrations:build scoped tokens require an integrationId; the token authorizes exactly one integration");
469
+ }
470
+ body.integrationId = input.integrationId;
471
+ }
472
+ else {
473
+ if (!input.applicationId?.trim()) {
474
+ throw new Error(`${scope} scoped tokens require an applicationId`);
475
+ }
476
+ body.applicationId = input.applicationId;
477
+ }
478
+ if (scope === "apps:preview") {
479
+ if (!input.commitId?.trim()) {
480
+ throw new Error("apps:preview scoped tokens require a commitId so the viewer can load that commit");
481
+ }
482
+ body.commitId = input.commitId;
483
+ }
484
+ const data = await this.request("/api/v1/token/scoped", {
485
+ apiKey: input.apiKey,
486
+ body: JSON.stringify(body),
487
+ method: "POST",
488
+ });
489
+ if (!data?.access_token) {
490
+ throw new Error("Server /api/v1/token/scoped returned no access_token; the live-edit session has nothing to verify");
491
+ }
492
+ return data.access_token;
493
+ }
494
+ async getSupersetIntegrations(input) {
495
+ const params = new URLSearchParams({
496
+ // The endpoint rejects a request without `kind` (400 "Kind must be
497
+ // provided."); PLUGIN is what the editor asks for when it builds the
498
+ // integration list Clark sees.
499
+ kind: "PLUGIN",
500
+ organizationId: input.organizationId,
501
+ });
502
+ if (input.applicationId) {
503
+ params.set("applicationId", input.applicationId);
504
+ }
505
+ const data = await this.requestData(`/api/v1/integrations/superset?${params.toString()}`, { apiKey: input.apiKey, method: "GET" });
506
+ return Array.isArray(data) ? data : [];
507
+ }
508
+ async getIntegration(input) {
509
+ try {
510
+ return await this.requestData(`/api/v1/integrations/${encodeURIComponent(input.integrationId)}`, { apiKey: input.apiKey, method: "GET" });
511
+ }
512
+ catch (error) {
513
+ if (error instanceof ServerHttpError && error.status === 404) {
514
+ return undefined;
515
+ }
516
+ throw error;
517
+ }
518
+ }
519
+ async getOrganizationProfiles(input) {
520
+ const data = await this.requestData(`/api/v1/organizations/${encodeURIComponent(input.organizationId)}/profiles`, { apiKey: input.apiKey, method: "GET" });
521
+ if (!Array.isArray(data)) {
522
+ return [];
523
+ }
524
+ return data
525
+ .filter((row) => typeof row?.key === "string" && row.key.length > 0)
526
+ .map((row) => ({
527
+ ...(row.allowedInEditor !== undefined
528
+ ? { allowedInEditor: row.allowedInEditor }
529
+ : {}),
530
+ ...(row.displayName ? { displayName: row.displayName } : {}),
531
+ ...(row.id ? { id: row.id } : {}),
532
+ key: row.key,
533
+ }));
534
+ }
535
+ /**
536
+ * Snapshots the application's current live-edit state as a commit.
537
+ *
538
+ * Deploys address a commit, never a branch head, so anything publishing work
539
+ * that has not been committed yet has to make one first — the same thing the
540
+ * editor's publish flow does.
541
+ */
542
+ async createCommit(input) {
543
+ const branchSegment = input.branch
544
+ ? `/branches/${encodeURIComponent(input.branch)}`
545
+ : "";
546
+ const data = await this.requestData(`/api/v2/applications/${input.applicationId}${branchSegment}/commit`, {
547
+ apiKey: input.apiKey,
548
+ body: JSON.stringify({
549
+ commitMessage: input.message,
550
+ // The server compares this against the last live-edit write to catch
551
+ // a commit racing an editor that has moved on since.
552
+ lastSuccessfulWrite: Date.now(),
553
+ }),
554
+ method: "POST",
555
+ ...(input.signal ? { signal: input.signal } : {}),
556
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
557
+ });
558
+ return {
559
+ commitId: data.commitId,
560
+ ...(data.directoryContentsHash
561
+ ? { directoryHash: data.directoryContentsHash }
562
+ : {}),
563
+ };
564
+ }
565
+ /**
566
+ * Starts a SABS build of a commit's content, the same call the editor makes
567
+ * behind Preview and behind a publish.
568
+ *
569
+ * The response carries a build id this caller has no use for: progress is
570
+ * observed by content hash, which is also how a preview and a publish of the
571
+ * same content end up watching one build instead of racing two.
572
+ */
573
+ async createBuild(input) {
574
+ await this.request(`/api/v3/applications/${input.applicationId}/${encodeURIComponent(input.directoryHash)}/builds`, {
575
+ apiKey: input.apiKey,
576
+ body: JSON.stringify({ commitId: input.commitId }),
577
+ method: "POST",
578
+ });
579
+ }
580
+ async getBuildProgress(input) {
581
+ const path = `/api/v3/applications/${input.applicationId}/build-bulk-status`;
582
+ // POST because the hashes go in the body, but it is a read: the server
583
+ // asks SABS for status and changes nothing.
584
+ const data = await this.requestData(path, {
585
+ apiKey: input.apiKey,
586
+ body: JSON.stringify({ directoryHashes: [input.directoryHash] }),
587
+ method: "POST",
588
+ });
589
+ // Absent for content SABS has never built. Same meaning as UNSPECIFIED:
590
+ // there is no build to wait for.
591
+ return buildProgress(data.directoryHashToStatus?.[input.directoryHash]?.status);
592
+ }
593
+ async deployApplication(input) {
594
+ const path = `/api/v2/applications/${input.applicationId}/deployments`;
595
+ const body = await this.request(path, {
596
+ apiKey: input.apiKey,
597
+ // PostApplicationDeployRequestBody rejects unknown properties, so this
598
+ // body carries the commit and nothing else — the branch is already
599
+ // fixed by the commit being deployed.
600
+ body: JSON.stringify({ commitId: input.commitId }),
601
+ method: "POST",
602
+ ...(input.signal ? { signal: input.signal } : {}),
603
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
604
+ });
605
+ if (body?.data?.deploymentId) {
606
+ return { deploymentId: body.data.deploymentId, status: "deployed" };
607
+ }
608
+ if (isDeployStatus(body?.status)) {
609
+ return { message: body?.message, status: body.status };
610
+ }
611
+ // Neither shape. Guessing "it worked" here would report a publish that
612
+ // never happened as a success.
613
+ throw new Error(`Server ${path} returned neither a deployment nor a deploy status`);
614
+ }
615
+ /**
616
+ * Polls the deploy queue for a commit the UI watches after a queued publish.
617
+ */
618
+ async getDeployStatus(input) {
619
+ const path = `/api/v2/applications/${input.applicationId}/deploy-status/${encodeURIComponent(input.commitId)}`;
620
+ const body = await this.request(path, {
621
+ apiKey: input.apiKey,
622
+ method: "GET",
623
+ retry: false,
624
+ ...(input.signal ? { signal: input.signal } : {}),
625
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
626
+ });
627
+ const status = body?.status;
628
+ if (status === "none" ||
629
+ status === "QUEUED" ||
630
+ status === "DEPLOYING" ||
631
+ status === "DEPLOYED" ||
632
+ status === "FAILED" ||
633
+ status === "AWAITING_ACK") {
634
+ return {
635
+ error: typeof body?.error === "string" ? body.error : undefined,
636
+ isLive: typeof body?.isLive === "boolean" ? body.isLive : undefined,
637
+ status,
638
+ };
639
+ }
640
+ throw new Error(`Server ${path} returned an unknown deploy status`);
641
+ }
642
+ /**
643
+ * Asks Clark for an app name, the same way the editor header does after a
644
+ * prompt-created app loads.
645
+ */
646
+ async generateAppName(input) {
647
+ // Another bare body rather than the `{ responseMeta, data }` envelope.
648
+ const data = await this.request("/api/v1/ai/generate-app-name", {
649
+ apiKey: input.apiKey,
650
+ body: JSON.stringify({
651
+ applicationId: input.applicationId,
652
+ prompt: input.prompt,
653
+ }),
654
+ method: "POST",
655
+ });
656
+ if (!data?.name?.trim()) {
657
+ throw new Error("Server /api/v1/ai/generate-app-name returned no name");
658
+ }
659
+ return data.name.trim();
660
+ }
661
+ /**
662
+ * Looks a name up in the org-wide application listing.
663
+ *
664
+ * The API exposes no single-application GET that carries a name — only this
665
+ * listing and the metadata PUT — so one label costs a list read. That is
666
+ * affordable where it is used (alongside a build and a screenshot) and is
667
+ * why nothing on a hot path should call it.
668
+ */
669
+ async getApplicationName(input) {
670
+ const body = await this.request("/api/v2/applications", { apiKey: input.apiKey, method: "GET" });
671
+ const found = body?.data?.applications?.find((application) => application?.id === input.applicationId);
672
+ return found?.name?.trim() || undefined;
673
+ }
674
+ async renameApplication(input) {
675
+ await this.request(`/api/v2/applications/${input.applicationId}/metadata`, {
676
+ apiKey: input.apiKey,
677
+ // PutApplicationMetadataUpdateRequestBody accepts exactly one of name,
678
+ // folderId, or isPublic — sending more than one fails validation.
679
+ body: JSON.stringify({ name: input.name }),
680
+ method: "PUT",
681
+ });
682
+ }
683
+ }
684
+ //# sourceMappingURL=client.js.map