@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,715 @@
1
+ import { getBasePluginId, } from "@superblocksteam/shared";
2
+ import { listIntegrationsForClark, toPromptContextIntegration, } from "../integrations/map.js";
3
+ import { sanitizeAgentFacingText, sanitizeAgentFacingValue, } from "./agent-facing-text.js";
4
+ const ADMIN_MCP_SETUP_MESSAGE = "Integrations CRUD is available on this Superblocks MCP via create_integration, update_integration, and list_integrations (same connector as start_app). Use those Admin tools, then continue with edit_app. The headless editor session cannot open the Integrations UI.";
5
+ /**
6
+ * Clark tools that already use PROMPT access rules in the editor. The headless
7
+ * gateway has no human approval UI, so only these may be auto-approved; any
8
+ * other toolName is denied.
9
+ */
10
+ const HEADLESS_AUTO_APPROVED_TOOLS = new Set([
11
+ "build_deleteFile",
12
+ "build_installPackages",
13
+ "executeRequestToIntegration",
14
+ "runMigrationVerification",
15
+ "testApi",
16
+ "updateMigrationWritePolicy",
17
+ ]);
18
+ function taskLabel(summary) {
19
+ const parts = [
20
+ summary.label || summary.taskType,
21
+ summary.state,
22
+ summary.message,
23
+ ].filter((part) => Boolean(part && part.trim()));
24
+ return parts.join(": ").slice(0, 160);
25
+ }
26
+ function changeLabel(info) {
27
+ const parts = [info.action || info.status, info.type, info.name].filter((part) => Boolean(part && String(part).trim()));
28
+ return parts.join(" ").slice(0, 160);
29
+ }
30
+ function missingSessionError(action) {
31
+ return new Error(`the headless gateway session cannot ${action} without an active prompt session`);
32
+ }
33
+ function formatIntegrationResult(data) {
34
+ if (data === undefined) {
35
+ return { response: "" };
36
+ }
37
+ if (data === null) {
38
+ return { response: "null" };
39
+ }
40
+ if (typeof data === "string") {
41
+ return { response: sanitizeAgentFacingText(data) };
42
+ }
43
+ try {
44
+ // Sanitized as a value first, then as text. Serializing first leaves only
45
+ // the text patterns to work with, and those cannot see field names: a row
46
+ // with a short `password` column reads as ordinary JSON to them.
47
+ const serialized = JSON.stringify(sanitizeAgentFacingValue(data), null, 2);
48
+ if (serialized) {
49
+ return { response: sanitizeAgentFacingText(serialized) };
50
+ }
51
+ }
52
+ catch (error) {
53
+ return {
54
+ error: sanitizeAgentFacingText(`Failed to serialize integration result: ${error instanceof Error ? error.message : String(error)}`),
55
+ };
56
+ }
57
+ return { response: sanitizeAgentFacingText(String(data)) };
58
+ }
59
+ function extractIntegrationResult(executionResult) {
60
+ if (!executionResult || typeof executionResult !== "object") {
61
+ return executionResult;
62
+ }
63
+ const result = executionResult;
64
+ if (result.output?.result !== undefined) {
65
+ return result.output.result;
66
+ }
67
+ for (const event of result.events ?? []) {
68
+ const eventResult = event.result?.event?.end?.output?.result;
69
+ if (eventResult !== undefined) {
70
+ return eventResult;
71
+ }
72
+ }
73
+ return executionResult;
74
+ }
75
+ /**
76
+ * True only for interactive IdP / OAuth style failures — not generic HTTP 401s
77
+ * from control-plane hydration (e.g. missing JWT on integration fetch) and not
78
+ * SQL/object permission wording like Snowflake's "does not exist or not authorized".
79
+ */
80
+ function looksLikeInteractiveAuthRequired(message) {
81
+ return /authentication is required|please authenticate|oauth(?:\s+token)?|idp|login required|token (?:expired|missing)|interactive auth/i.test(message);
82
+ }
83
+ /** Snowflake object RBAC / wrong FQN — not Superblocks or IdP auth. */
84
+ function looksLikeSnowflakeObjectPermissionError(message) {
85
+ return /does not exist or not authorized/i.test(message);
86
+ }
87
+ function looksLikeOrchestratorJwtGate401(message) {
88
+ return /orchestrator.*\b401\b/i.test(message);
89
+ }
90
+ const SNOWFLAKE_OBJECT_PERMISSION_HINT = " This is a Snowflake object permission or schema/name error for the integration's role — not Superblocks re-auth. Fix the fully-qualified name or grant access, then retry.";
91
+ const ORCHESTRATOR_JWT_GATE_HINT = " This is Superblocks/orchestrator auth (the scoped JWT or selected profile), not Snowflake username/password. Do not tell the user to re-enter credentials in the integrations panel. If the build still reaches ready, treat this as a mid-build API-check note rather than a user-facing blocker.";
92
+ function clarifySdkExecutionErrorMessage(message) {
93
+ let clarified = message;
94
+ if (looksLikeOrchestratorJwtGate401(message)) {
95
+ if (!message.includes(ORCHESTRATOR_JWT_GATE_HINT.trim())) {
96
+ clarified = `${message}${ORCHESTRATOR_JWT_GATE_HINT}`;
97
+ }
98
+ }
99
+ else if (looksLikeSnowflakeObjectPermissionError(message)) {
100
+ if (!message.includes(SNOWFLAKE_OBJECT_PERMISSION_HINT.trim())) {
101
+ clarified = `${message}${SNOWFLAKE_OBJECT_PERMISSION_HINT}`;
102
+ }
103
+ }
104
+ return sanitizeAgentFacingText(clarified);
105
+ }
106
+ /**
107
+ * Runs Clark's executeRequestToIntegration the same way the browser editor
108
+ * does: one inline V2 API step against the orchestrator. Username/password and
109
+ * other stored-credential integrations execute here; interactive IdP auth
110
+ * surfaces as an error asking the user to authenticate in the editor.
111
+ */
112
+ export async function executeRequestToIntegrationViaOrchestrator(context, payload) {
113
+ let pluginId = payload.integrationHint?.type;
114
+ let integrationName = payload.integrationHint?.displayName ??
115
+ payload.integrationHint?.name ??
116
+ "Integration";
117
+ if (!pluginId) {
118
+ let row;
119
+ try {
120
+ row = await context.server.getIntegration({
121
+ apiKey: context.apiKey,
122
+ integrationId: payload.integrationId,
123
+ });
124
+ if (!row) {
125
+ const rows = await context.server.getSupersetIntegrations({
126
+ apiKey: context.apiKey,
127
+ applicationId: context.applicationId,
128
+ organizationId: context.organizationId,
129
+ });
130
+ row = rows.find((candidate) => candidate.id === payload.integrationId);
131
+ }
132
+ }
133
+ catch (error) {
134
+ console.warn("gateway failed to resolve integration for execute", {
135
+ applicationId: context.applicationId,
136
+ integrationId: payload.integrationId,
137
+ message: error instanceof Error ? error.message : String(error),
138
+ });
139
+ }
140
+ if (!row?.pluginId) {
141
+ return {
142
+ error: `Integration ${payload.integrationId} not found or not accessible`,
143
+ };
144
+ }
145
+ pluginId = row.pluginId;
146
+ integrationName = row.name || integrationName;
147
+ }
148
+ const pluginIdForStep = getBasePluginId(pluginId);
149
+ const now = new Date().toISOString();
150
+ const inlineApiId = `ai-inline-${payload.integrationId}-${Date.now()}`;
151
+ const definition = {
152
+ blocks: [
153
+ {
154
+ name: "InlineQuery",
155
+ step: {
156
+ integration: payload.integrationId,
157
+ [pluginIdForStep]: { ...payload.request },
158
+ },
159
+ },
160
+ ],
161
+ metadata: {
162
+ id: inlineApiId,
163
+ name: `${integrationName} AI Query`,
164
+ organization: context.organizationId,
165
+ timestamps: { created: now, updated: now },
166
+ },
167
+ trigger: { application: {} },
168
+ };
169
+ try {
170
+ const result = await context.orchestrator.executeV2Api({
171
+ apiKey: context.apiKey,
172
+ body: {
173
+ definition: { api: definition },
174
+ inputs: {},
175
+ options: {
176
+ includeEventOutputs: true,
177
+ includeEvents: true,
178
+ includeResolved: true,
179
+ isAiTriggered: true,
180
+ suppressTracking: true,
181
+ },
182
+ ...(context.profileKey
183
+ ? { profile: { name: context.profileKey } }
184
+ : {}),
185
+ },
186
+ profileKey: context.profileKey,
187
+ scopedToken: context.sessionToken,
188
+ });
189
+ if (result && typeof result === "object" && "systemError" in result) {
190
+ const systemError = String(result.systemError ??
191
+ "Integration execution failed");
192
+ if (looksLikeInteractiveAuthRequired(systemError)) {
193
+ return {
194
+ error: sanitizeAgentFacingText(`${systemError} Authenticate this integration in the Superblocks editor (open the app edit URL), then ask Superblocks to retry the query.`),
195
+ };
196
+ }
197
+ return { error: clarifySdkExecutionErrorMessage(systemError) };
198
+ }
199
+ if (result === undefined) {
200
+ return { error: "No response received from integration" };
201
+ }
202
+ const errors = result.errors;
203
+ const unhandled = errors
204
+ ?.filter((entry) => !entry.handled)
205
+ .map((entry) => entry.message);
206
+ if (unhandled && unhandled.length > 0) {
207
+ const message = JSON.stringify(unhandled);
208
+ if (looksLikeInteractiveAuthRequired(message)) {
209
+ return {
210
+ error: sanitizeAgentFacingText(`${message} Authenticate this integration in the Superblocks editor (open the app edit URL), then ask Superblocks to retry the query.`),
211
+ };
212
+ }
213
+ return { error: clarifySdkExecutionErrorMessage(message) };
214
+ }
215
+ return formatIntegrationResult(extractIntegrationResult(result));
216
+ }
217
+ catch (error) {
218
+ const message = error instanceof Error
219
+ ? error.message
220
+ : "Failed to execute integration query";
221
+ if (looksLikeInteractiveAuthRequired(message)) {
222
+ return {
223
+ error: sanitizeAgentFacingText(`${message} Authenticate this integration in the Superblocks editor (open the app edit URL), then ask Superblocks to retry the query.`),
224
+ };
225
+ }
226
+ return { error: clarifySdkExecutionErrorMessage(message) };
227
+ }
228
+ }
229
+ /**
230
+ * Prefills Clark's promptContext.integrations the same way the editor does.
231
+ */
232
+ export async function loadIntegrationsForPrompt(context) {
233
+ const rows = await context.server.getSupersetIntegrations({
234
+ apiKey: context.apiKey,
235
+ applicationId: context.applicationId,
236
+ organizationId: context.organizationId,
237
+ });
238
+ return listIntegrationsForClark(rows);
239
+ }
240
+ export function mapSdkExecutionResponse(response) {
241
+ if (response && typeof response === "object" && "systemError" in response) {
242
+ const systemError = response.systemError;
243
+ return {
244
+ success: false,
245
+ error: {
246
+ code: "SYSTEM_ERROR",
247
+ message: clarifySdkExecutionErrorMessage(typeof systemError === "string"
248
+ ? systemError
249
+ : "Orchestrator returned a system error"),
250
+ },
251
+ };
252
+ }
253
+ if (response &&
254
+ typeof response === "object" &&
255
+ "errors" in response &&
256
+ Array.isArray(response.errors) &&
257
+ response.errors.length > 0) {
258
+ const first = response
259
+ .errors[0];
260
+ return {
261
+ success: false,
262
+ error: {
263
+ code: "EXECUTION_ERROR",
264
+ message: clarifySdkExecutionErrorMessage(first?.message ?? "API execution failed"),
265
+ },
266
+ };
267
+ }
268
+ if (response && typeof response === "object") {
269
+ const output = response.output
270
+ ?.result;
271
+ return {
272
+ success: true,
273
+ output: sanitizeAgentFacingValue(output),
274
+ };
275
+ }
276
+ return {
277
+ success: false,
278
+ error: {
279
+ code: "EMPTY_RESPONSE",
280
+ message: "No response from orchestrator",
281
+ },
282
+ };
283
+ }
284
+ /**
285
+ * Server -> editor calls the dev server makes into the peer driving a turn,
286
+ * whether that peer negotiated protocol "editor" or "gateway".
287
+ *
288
+ * Leaving these unimplemented is not inert. ISocket answers an unknown method
289
+ * with `{ code: 2, message: "unknown method <name>" }`, and the dev server does
290
+ * not catch every one of those rejections — `resetScope` in particular escalates
291
+ * to `unhandledRejection`, which exits the sandbox with "Dev server fatal exit"
292
+ * and takes the in-flight Clark build down with it. A browser editor answers
293
+ * all of them, so a held gateway socket has to as well.
294
+ *
295
+ * Keyed on `EditorClientMethods` rather than a hand-kept list so that a method
296
+ * added to the protocol upstream fails this package's typecheck instead of
297
+ * reaching a sandbox and crashing it. Same handler surface as
298
+ * `createEditorHandlers` in vite-plugin-file-sync's dev-server-ws-smoke-client,
299
+ * the other non-browser peer that speaks this protocol; it is not imported from
300
+ * there because the gateway does not depend on the vite plugin.
301
+ *
302
+ * When a listener is provided, chat, generation-state, task/change, and
303
+ * preview-ready pushes are forwarded so the gateway can wait for Clark's turn
304
+ * and surface progress over MCP.
305
+ */
306
+ export function createEditorClientMethods(getListener, onInbound, deps) {
307
+ // Notifications and state pushes. The gateway keeps no editor state unless a
308
+ // turn wait is armed, so acknowledging is the whole contract — the dev
309
+ // server only needs to know the peer is alive and listening.
310
+ const ack = [async () => undefined];
311
+ const setupUnavailable = [
312
+ async () => ({
313
+ error: ADMIN_MCP_SETUP_MESSAGE,
314
+ success: false,
315
+ }),
316
+ ];
317
+ const handlers = {
318
+ aiApplyApplicationEnvironmentSettings: ack,
319
+ aiClearToolPermission: ack,
320
+ aiPushApiUsageSummary: ack,
321
+ aiPushChangeInfo: [
322
+ async (info) => {
323
+ const label = changeLabel(info);
324
+ if (!label) {
325
+ return;
326
+ }
327
+ getListener?.()?.({ kind: "change_info", label });
328
+ },
329
+ ],
330
+ aiPushContextUsed: ack,
331
+ aiPushErrorSummary: ack,
332
+ aiPushMessage: [
333
+ async (message) => {
334
+ const listener = getListener?.();
335
+ listener?.({ kind: "message", message });
336
+ },
337
+ ],
338
+ aiPushPolicyAgentStep: ack,
339
+ aiPushPromptInterpretation: ack,
340
+ aiPushTaskStatus: [
341
+ async (summary) => {
342
+ const label = taskLabel(summary);
343
+ if (!label) {
344
+ return;
345
+ }
346
+ getListener?.()?.({ kind: "task_status", label });
347
+ },
348
+ ],
349
+ aiPushTokenUsage: ack,
350
+ aiRequestToolPermission: [
351
+ async (payload) => {
352
+ // Headless gateway has no editor approval UI. Auto-approve only the
353
+ // Clark agent tools that already have PROMPT access rules and must run
354
+ // unattended for local builds (testApi WRITE, migration checks, etc.).
355
+ // Unknown tools are denied by default.
356
+ if (!deps) {
357
+ return;
358
+ }
359
+ const toolName = payload.toolName?.trim() ?? "";
360
+ const approved = HEADLESS_AUTO_APPROVED_TOOLS.has(toolName);
361
+ await deps.respondToToolPermission({
362
+ approved,
363
+ messageId: payload.messageId,
364
+ requestId: payload.requestId ?? payload.messageId,
365
+ });
366
+ },
367
+ ],
368
+ // Says whether Clark left uncommitted work behind. The gateway needs it
369
+ // for the same reason the browser editor does: a pending draft has to be
370
+ // accepted before the app the edit URL serves matches what Clark built.
371
+ aiSetDraftState: [
372
+ async (payload) => {
373
+ getListener?.()?.({
374
+ kind: "draft_state",
375
+ hasDraft: Boolean(payload?.hasDraft),
376
+ });
377
+ },
378
+ ],
379
+ aiSetGenerationState: [
380
+ async (snapshot) => {
381
+ if (typeof snapshot?.state === "string") {
382
+ getListener?.()?.({
383
+ hasError: snapshot.hasError,
384
+ hasSuggestions: snapshot.hasSuggestions,
385
+ kind: "generation_state",
386
+ state: snapshot.state,
387
+ });
388
+ }
389
+ },
390
+ ],
391
+ appFactsChanged: ack,
392
+ clarkLog: ack,
393
+ closedByServer: ack,
394
+ error: ack,
395
+ manualDeleteApi: ack,
396
+ manualUpdateApi: ack,
397
+ notifyDeploySuccess: ack,
398
+ reloadIframe: [
399
+ async () => {
400
+ getListener?.()?.({ kind: "reload_iframe" });
401
+ },
402
+ ],
403
+ releasingLock: ack,
404
+ requestGitRefresh: ack,
405
+ resetScope: ack,
406
+ setApiDependencies: ack,
407
+ setDegradedMode: ack,
408
+ syncStatus: [
409
+ async (payload) => {
410
+ getListener?.()?.({
411
+ kind: "sync_status",
412
+ status: typeof payload?.status === "string" ? payload.status : undefined,
413
+ });
414
+ },
415
+ ],
416
+ updateApiInputs: ack,
417
+ updateScope: ack,
418
+ // No browser tab means no viewport to photograph. The flag matters more
419
+ // than the message: it tells the ai-service this was a refusal, not a
420
+ // failed capture, so Clark can fall back to the sandbox's own browser
421
+ // instead of reporting that it cannot see the app it just built.
422
+ aiCaptureScreenshot: [
423
+ async () => ({
424
+ error: "the headless gateway session has no browser viewport",
425
+ success: false,
426
+ viewportUnavailable: true,
427
+ }),
428
+ ],
429
+ aiCloseIntegrationSetup: [async () => ({ success: true })],
430
+ aiDeleteIntegration: setupUnavailable,
431
+ aiExecuteRequestToIntegration: [
432
+ async (payload) => {
433
+ const context = deps?.getContext();
434
+ if (!context) {
435
+ return {
436
+ error: "the headless gateway session cannot execute integration requests without an active prompt session",
437
+ };
438
+ }
439
+ const result = await executeRequestToIntegrationViaOrchestrator(context, payload);
440
+ const name = payload.integrationHint?.name ||
441
+ payload.integrationId ||
442
+ "integration";
443
+ deps?.onToolRun?.({
444
+ detail: result,
445
+ kind: "test_integration",
446
+ name,
447
+ success: !("error" in result && result.error),
448
+ });
449
+ return result;
450
+ },
451
+ ],
452
+ aiExecuteSdkApi: [
453
+ async (payload) => {
454
+ const context = deps?.getContext();
455
+ if (!context) {
456
+ throw missingSessionError("execute SDK APIs");
457
+ }
458
+ if (!payload.entryPoint) {
459
+ const missing = {
460
+ success: false,
461
+ error: {
462
+ code: "UNKNOWN_API",
463
+ message: `No entryPoint registered for API "${payload.apiName}". Was it discovered?`,
464
+ },
465
+ };
466
+ deps?.onToolRun?.({
467
+ detail: missing,
468
+ kind: "test_api",
469
+ name: payload.apiName,
470
+ success: false,
471
+ });
472
+ return missing;
473
+ }
474
+ try {
475
+ const inSync = await context.waitUntilFilesSynced();
476
+ if (!inSync) {
477
+ const syncing = {
478
+ success: false,
479
+ error: {
480
+ code: "SYSTEM_ERROR",
481
+ message: "The API files are still syncing. Wait for the current edit to finish saving, then retry testApi.",
482
+ },
483
+ };
484
+ deps?.onToolRun?.({
485
+ detail: syncing,
486
+ kind: "test_api",
487
+ name: payload.apiName,
488
+ success: false,
489
+ });
490
+ return syncing;
491
+ }
492
+ const response = await context.orchestrator.executeSdkApiV3({
493
+ apiKey: context.apiKey,
494
+ applicationId: context.applicationId,
495
+ branchName: context.branchName,
496
+ entryPoint: payload.entryPoint,
497
+ exportName: payload.exportName,
498
+ inputs: payload.input ?? {},
499
+ profileKey: context.profileKey,
500
+ scopedToken: context.sessionToken,
501
+ });
502
+ const mapped = mapSdkExecutionResponse(response);
503
+ deps?.onToolRun?.({
504
+ detail: mapped,
505
+ kind: "test_api",
506
+ name: payload.apiName,
507
+ success: mapped.success,
508
+ });
509
+ return mapped;
510
+ }
511
+ catch (error) {
512
+ const failed = {
513
+ success: false,
514
+ error: {
515
+ code: "SYSTEM_ERROR",
516
+ message: error instanceof Error
517
+ ? clarifySdkExecutionErrorMessage(error.message)
518
+ : "Failed to execute SDK API",
519
+ },
520
+ };
521
+ deps?.onToolRun?.({
522
+ detail: failed,
523
+ kind: "test_api",
524
+ name: payload.apiName,
525
+ success: false,
526
+ });
527
+ return failed;
528
+ }
529
+ },
530
+ ],
531
+ aiExecuteV2Api: [
532
+ async (payload) => {
533
+ const context = deps?.getContext();
534
+ if (!context) {
535
+ throw missingSessionError("execute V2 APIs");
536
+ }
537
+ if (payload.api.type === "fetch") {
538
+ return {
539
+ systemError: `V2 API fetch for '${payload.api.apiName}' requires the browser editor; use SDK APIs (aiExecuteSdkApi) from Gateway, or open the app in the UI.`,
540
+ };
541
+ }
542
+ const now = new Date().toISOString();
543
+ const definition = {
544
+ ...payload.api.definition,
545
+ metadata: {
546
+ ...(payload.api.definition.metadata ?? {}),
547
+ organization: context.organizationId,
548
+ timestamps: { created: now, updated: now },
549
+ },
550
+ trigger: { application: {} },
551
+ };
552
+ try {
553
+ const result = await context.orchestrator.executeV2Api({
554
+ apiKey: context.apiKey,
555
+ body: {
556
+ definition: { api: definition },
557
+ inputs: payload.inputs ?? {},
558
+ options: {
559
+ includeEventOutputs: true,
560
+ includeEvents: true,
561
+ includeResolved: true,
562
+ isAiTriggered: true,
563
+ suppressTracking: true,
564
+ },
565
+ ...(context.profileKey
566
+ ? { profile: { name: context.profileKey } }
567
+ : {}),
568
+ },
569
+ profileKey: context.profileKey,
570
+ scopedToken: context.sessionToken,
571
+ });
572
+ if (result && typeof result === "object" && "systemError" in result) {
573
+ return result;
574
+ }
575
+ if (result === undefined) {
576
+ return { systemError: "No response received from API execution" };
577
+ }
578
+ const outputs = result.output?.result ?? {};
579
+ return {
580
+ errors: result.errors,
581
+ outputs,
582
+ };
583
+ }
584
+ catch (error) {
585
+ return {
586
+ systemError: error instanceof Error
587
+ ? error.message
588
+ : "Failed to execute V2 API",
589
+ };
590
+ }
591
+ },
592
+ ],
593
+ aiGetIntegration: [
594
+ async (payload) => {
595
+ const context = deps?.getContext();
596
+ if (!context) {
597
+ throw missingSessionError("provide integration metadata");
598
+ }
599
+ // A rejection here cancels Clark's turn, so every lookup failure
600
+ // degrades to the unknown-integration placeholder below.
601
+ let row;
602
+ try {
603
+ row = await context.server.getIntegration({
604
+ apiKey: context.apiKey,
605
+ integrationId: payload.integrationId,
606
+ });
607
+ if (!row) {
608
+ const rows = await context.server.getSupersetIntegrations({
609
+ apiKey: context.apiKey,
610
+ applicationId: context.applicationId,
611
+ organizationId: context.organizationId,
612
+ });
613
+ row = rows.find((candidate) => candidate.id === payload.integrationId);
614
+ }
615
+ }
616
+ catch (error) {
617
+ console.warn("gateway failed to load integration for Clark", {
618
+ applicationId: context.applicationId,
619
+ integrationId: payload.integrationId,
620
+ message: error instanceof Error ? error.message : String(error),
621
+ });
622
+ }
623
+ if (!row) {
624
+ return {
625
+ displayName: payload.integrationId,
626
+ id: payload.integrationId,
627
+ metadata: {},
628
+ name: payload.integrationId,
629
+ type: "unknown",
630
+ };
631
+ }
632
+ let metadata = {};
633
+ try {
634
+ metadata = await context.orchestrator.getMetadata({
635
+ apiKey: context.apiKey,
636
+ integrationId: payload.integrationId,
637
+ profileKey: context.profileKey,
638
+ scopedToken: context.sessionToken,
639
+ });
640
+ }
641
+ catch {
642
+ metadata = {};
643
+ }
644
+ return toPromptContextIntegration(row, metadata);
645
+ },
646
+ ],
647
+ aiGetIntegrationDependents: [
648
+ async () => ({
649
+ error: "the headless gateway session cannot inspect integration dependents",
650
+ success: false,
651
+ }),
652
+ ],
653
+ aiGetIntegrationFormSchema: [
654
+ async () => ({
655
+ error: ADMIN_MCP_SETUP_MESSAGE,
656
+ success: false,
657
+ }),
658
+ ],
659
+ aiGetIntegrations: [
660
+ async () => {
661
+ const context = deps?.getContext();
662
+ if (!context) {
663
+ return [];
664
+ }
665
+ try {
666
+ return await loadIntegrationsForPrompt(context);
667
+ }
668
+ catch (error) {
669
+ // Clark cancels the whole turn if this rejects, so a failed lookup
670
+ // degrades to "no integrations" instead of killing the build.
671
+ console.warn("gateway failed to list integrations for Clark", {
672
+ applicationId: context.applicationId,
673
+ message: error instanceof Error ? error.message : String(error),
674
+ });
675
+ return [];
676
+ }
677
+ },
678
+ ],
679
+ aiGetOpenApiSpec: [async () => undefined],
680
+ aiNavigatePreviewRoute: [
681
+ async () => ({
682
+ error: "the headless gateway session has no browser route",
683
+ success: false,
684
+ }),
685
+ ],
686
+ aiOpenExistingIntegration: setupUnavailable,
687
+ aiOpenIntegrationSetup: setupUnavailable,
688
+ aiSaveIntegration: setupUnavailable,
689
+ aiTestIntegrationConnection: [
690
+ async () => ({
691
+ error: {
692
+ details: "",
693
+ message: ADMIN_MCP_SETUP_MESSAGE,
694
+ },
695
+ success: false,
696
+ }),
697
+ ],
698
+ aiUpdateIntegrationFields: setupUnavailable,
699
+ };
700
+ const observed = handlers;
701
+ if (onInbound) {
702
+ for (const key of Object.keys(observed)) {
703
+ const chain = observed[key];
704
+ observed[key] = chain.map((handler) => async (...args) => {
705
+ onInbound(key);
706
+ return handler(...args);
707
+ });
708
+ }
709
+ }
710
+ // ISocket's MethodHandlers is keyed on the socket's own generic parameters,
711
+ // which the gateway leaves empty because it calls only one server method.
712
+ // Exhaustiveness is already enforced above, on the protocol type.
713
+ return observed;
714
+ }
715
+ //# sourceMappingURL=editor-client-methods.js.map