@agent-native/core 0.15.5 → 0.15.6

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 (166) hide show
  1. package/dist/client/AgentPanel.d.ts.map +1 -1
  2. package/dist/client/AgentPanel.js +3 -2
  3. package/dist/client/AgentPanel.js.map +1 -1
  4. package/dist/client/AssistantChat.d.ts.map +1 -1
  5. package/dist/client/AssistantChat.js +5 -1
  6. package/dist/client/AssistantChat.js.map +1 -1
  7. package/dist/client/components/CodeRequiredDialog.js +1 -1
  8. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  9. package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
  10. package/dist/client/settings/BackgroundAgentSection.js +2 -1
  11. package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
  12. package/dist/client/settings/BrowserSection.d.ts.map +1 -1
  13. package/dist/client/settings/BrowserSection.js +3 -2
  14. package/dist/client/settings/BrowserSection.js.map +1 -1
  15. package/dist/client/settings/SettingsPanel.js +1 -1
  16. package/dist/client/settings/SettingsPanel.js.map +1 -1
  17. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
  18. package/dist/client/settings/VoiceTranscriptionSection.js +1 -0
  19. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  20. package/dist/client/settings/useBuilderStatus.d.ts +1 -0
  21. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  22. package/dist/client/settings/useBuilderStatus.js +53 -12
  23. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  24. package/dist/client/settings/useBuilderStatus.spec.js +43 -7
  25. package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
  26. package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
  27. package/dist/client/transcription/BuilderTranscriptionCta.js +7 -2
  28. package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
  29. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  30. package/dist/server/agent-chat-plugin.js +3 -1
  31. package/dist/server/agent-chat-plugin.js.map +1 -1
  32. package/dist/server/auth.d.ts.map +1 -1
  33. package/dist/server/auth.js +19 -2
  34. package/dist/server/auth.js.map +1 -1
  35. package/dist/server/builder-browser.d.ts +8 -0
  36. package/dist/server/builder-browser.d.ts.map +1 -1
  37. package/dist/server/builder-browser.js +20 -0
  38. package/dist/server/builder-browser.js.map +1 -1
  39. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  40. package/dist/server/core-routes-plugin.js +55 -23
  41. package/dist/server/core-routes-plugin.js.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/client/dev-mode.d.ts +0 -14
  44. package/dist/client/dev-mode.d.ts.map +0 -1
  45. package/dist/client/dev-mode.js +0 -14
  46. package/dist/client/dev-mode.js.map +0 -1
  47. package/dist/client/extensions/EmbeddedTool.d.ts +0 -20
  48. package/dist/client/extensions/EmbeddedTool.d.ts.map +0 -1
  49. package/dist/client/extensions/EmbeddedTool.js +0 -199
  50. package/dist/client/extensions/EmbeddedTool.js.map +0 -1
  51. package/dist/client/extensions/ToolEditor.d.ts +0 -5
  52. package/dist/client/extensions/ToolEditor.d.ts.map +0 -1
  53. package/dist/client/extensions/ToolEditor.js +0 -129
  54. package/dist/client/extensions/ToolEditor.js.map +0 -1
  55. package/dist/client/extensions/ToolViewer.d.ts +0 -5
  56. package/dist/client/extensions/ToolViewer.d.ts.map +0 -1
  57. package/dist/client/extensions/ToolViewer.js +0 -400
  58. package/dist/client/extensions/ToolViewer.js.map +0 -1
  59. package/dist/client/extensions/ToolViewerPage.d.ts +0 -2
  60. package/dist/client/extensions/ToolViewerPage.d.ts.map +0 -1
  61. package/dist/client/extensions/ToolViewerPage.js +0 -24
  62. package/dist/client/extensions/ToolViewerPage.js.map +0 -1
  63. package/dist/client/extensions/ToolsListPage.d.ts +0 -2
  64. package/dist/client/extensions/ToolsListPage.d.ts.map +0 -1
  65. package/dist/client/extensions/ToolsListPage.js +0 -67
  66. package/dist/client/extensions/ToolsListPage.js.map +0 -1
  67. package/dist/client/extensions/ToolsSidebarSection.d.ts +0 -2
  68. package/dist/client/extensions/ToolsSidebarSection.d.ts.map +0 -1
  69. package/dist/client/extensions/ToolsSidebarSection.js +0 -236
  70. package/dist/client/extensions/ToolsSidebarSection.js.map +0 -1
  71. package/dist/client/extensions/tool-order.d.ts +0 -7
  72. package/dist/client/extensions/tool-order.d.ts.map +0 -1
  73. package/dist/client/extensions/tool-order.js +0 -47
  74. package/dist/client/extensions/tool-order.js.map +0 -1
  75. package/dist/client/tools/EmbeddedTool.d.ts +0 -20
  76. package/dist/client/tools/EmbeddedTool.d.ts.map +0 -1
  77. package/dist/client/tools/EmbeddedTool.js +0 -199
  78. package/dist/client/tools/EmbeddedTool.js.map +0 -1
  79. package/dist/client/tools/ExtensionSlot.d.ts +0 -27
  80. package/dist/client/tools/ExtensionSlot.d.ts.map +0 -1
  81. package/dist/client/tools/ExtensionSlot.js +0 -96
  82. package/dist/client/tools/ExtensionSlot.js.map +0 -1
  83. package/dist/client/tools/ToolEditor.d.ts +0 -5
  84. package/dist/client/tools/ToolEditor.d.ts.map +0 -1
  85. package/dist/client/tools/ToolEditor.js +0 -129
  86. package/dist/client/tools/ToolEditor.js.map +0 -1
  87. package/dist/client/tools/ToolViewer.d.ts +0 -5
  88. package/dist/client/tools/ToolViewer.d.ts.map +0 -1
  89. package/dist/client/tools/ToolViewer.js +0 -400
  90. package/dist/client/tools/ToolViewer.js.map +0 -1
  91. package/dist/client/tools/ToolViewerPage.d.ts +0 -2
  92. package/dist/client/tools/ToolViewerPage.d.ts.map +0 -1
  93. package/dist/client/tools/ToolViewerPage.js +0 -24
  94. package/dist/client/tools/ToolViewerPage.js.map +0 -1
  95. package/dist/client/tools/ToolsListPage.d.ts +0 -2
  96. package/dist/client/tools/ToolsListPage.d.ts.map +0 -1
  97. package/dist/client/tools/ToolsListPage.js +0 -67
  98. package/dist/client/tools/ToolsListPage.js.map +0 -1
  99. package/dist/client/tools/ToolsSidebarSection.d.ts +0 -2
  100. package/dist/client/tools/ToolsSidebarSection.d.ts.map +0 -1
  101. package/dist/client/tools/ToolsSidebarSection.js +0 -236
  102. package/dist/client/tools/ToolsSidebarSection.js.map +0 -1
  103. package/dist/client/tools/iframe-bridge.d.ts +0 -38
  104. package/dist/client/tools/iframe-bridge.d.ts.map +0 -1
  105. package/dist/client/tools/iframe-bridge.js +0 -207
  106. package/dist/client/tools/iframe-bridge.js.map +0 -1
  107. package/dist/client/tools/index.d.ts +0 -8
  108. package/dist/client/tools/index.d.ts.map +0 -1
  109. package/dist/client/tools/index.js +0 -8
  110. package/dist/client/tools/index.js.map +0 -1
  111. package/dist/client/tools/tool-order.d.ts +0 -7
  112. package/dist/client/tools/tool-order.d.ts.map +0 -1
  113. package/dist/client/tools/tool-order.js +0 -47
  114. package/dist/client/tools/tool-order.js.map +0 -1
  115. package/dist/server/local-migration.d.ts +0 -41
  116. package/dist/server/local-migration.d.ts.map +0 -1
  117. package/dist/server/local-migration.js +0 -235
  118. package/dist/server/local-migration.js.map +0 -1
  119. package/dist/tools/actions.d.ts +0 -3
  120. package/dist/tools/actions.d.ts.map +0 -1
  121. package/dist/tools/actions.js +0 -272
  122. package/dist/tools/actions.js.map +0 -1
  123. package/dist/tools/fetch-tool.d.ts +0 -23
  124. package/dist/tools/fetch-tool.d.ts.map +0 -1
  125. package/dist/tools/fetch-tool.js +0 -178
  126. package/dist/tools/fetch-tool.js.map +0 -1
  127. package/dist/tools/html-shell.d.ts +0 -45
  128. package/dist/tools/html-shell.d.ts.map +0 -1
  129. package/dist/tools/html-shell.js +0 -514
  130. package/dist/tools/html-shell.js.map +0 -1
  131. package/dist/tools/proxy-security.d.ts +0 -12
  132. package/dist/tools/proxy-security.d.ts.map +0 -1
  133. package/dist/tools/proxy-security.js +0 -158
  134. package/dist/tools/proxy-security.js.map +0 -1
  135. package/dist/tools/routes.d.ts +0 -2
  136. package/dist/tools/routes.d.ts.map +0 -1
  137. package/dist/tools/routes.js +0 -627
  138. package/dist/tools/routes.js.map +0 -1
  139. package/dist/tools/schema.d.ts +0 -664
  140. package/dist/tools/schema.d.ts.map +0 -1
  141. package/dist/tools/schema.js +0 -146
  142. package/dist/tools/schema.js.map +0 -1
  143. package/dist/tools/slots/routes.d.ts +0 -15
  144. package/dist/tools/slots/routes.d.ts.map +0 -1
  145. package/dist/tools/slots/routes.js +0 -94
  146. package/dist/tools/slots/routes.js.map +0 -1
  147. package/dist/tools/slots/schema.d.ts +0 -303
  148. package/dist/tools/slots/schema.d.ts.map +0 -1
  149. package/dist/tools/slots/schema.js +0 -76
  150. package/dist/tools/slots/schema.js.map +0 -1
  151. package/dist/tools/slots/store.d.ts +0 -66
  152. package/dist/tools/slots/store.d.ts.map +0 -1
  153. package/dist/tools/slots/store.js +0 -227
  154. package/dist/tools/slots/store.js.map +0 -1
  155. package/dist/tools/store.d.ts +0 -40
  156. package/dist/tools/store.d.ts.map +0 -1
  157. package/dist/tools/store.js +0 -193
  158. package/dist/tools/store.js.map +0 -1
  159. package/dist/tools/theme.d.ts +0 -2
  160. package/dist/tools/theme.d.ts.map +0 -1
  161. package/dist/tools/theme.js +0 -67
  162. package/dist/tools/theme.js.map +0 -1
  163. package/dist/tools/url-safety.d.ts +0 -24
  164. package/dist/tools/url-safety.d.ts.map +0 -1
  165. package/dist/tools/url-safety.js +0 -224
  166. package/dist/tools/url-safety.js.map +0 -1
@@ -1,627 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { defineEventHandler, getMethod, setResponseStatus, setResponseHeader, } from "h3";
3
- import { readBody } from "../server/h3-helpers.js";
4
- import { getSession } from "../server/auth.js";
5
- import { recordChange } from "../server/poll.js";
6
- import { runWithRequestContext, getRequestOrgId, } from "../server/request-context.js";
7
- import { getOrgContext } from "../org/context.js";
8
- import { getDbExec, isPostgres } from "../db/client.js";
9
- import { listTools, getTool, createTool, updateTool, updateToolContent, deleteTool, ensureToolsTables, } from "./store.js";
10
- import { buildToolHtml, TOOL_IFRAME_CSP } from "./html-shell.js";
11
- import { getThemeVars } from "./theme.js";
12
- import { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist, } from "../secrets/substitution.js";
13
- import { collectSecretValues, normalizeToolProxyMethod, readResponseTextWithLimit, redactSecrets, redactString, sanitizeOutboundHeaders, } from "./proxy-security.js";
14
- import { createSsrfSafeDispatcher, isBlockedToolUrlWithDns, } from "./url-safety.js";
15
- import { ForbiddenError, resolveAccess } from "../sharing/access.js";
16
- export function createToolsHandler() {
17
- return defineEventHandler(async (event) => {
18
- const method = getMethod(event);
19
- const pathname = (event.url?.pathname || "")
20
- .replace(/^\/+/, "")
21
- .replace(/\/+$/, "");
22
- const parts = pathname ? pathname.split("/") : [];
23
- const session = await getSession(event).catch(() => null);
24
- if (!session?.email) {
25
- setResponseStatus(event, 401);
26
- return { error: "Authentication required" };
27
- }
28
- const orgCtx = await getOrgContext(event).catch(() => null);
29
- const userEmail = session.email;
30
- const orgId = orgCtx?.orgId ?? undefined;
31
- try {
32
- return await runWithRequestContext({ userEmail, orgId }, () => dispatch(event, method, parts, userEmail));
33
- }
34
- catch (err) {
35
- if (err instanceof ForbiddenError) {
36
- setResponseStatus(event, 403);
37
- return { error: err.message };
38
- }
39
- throw err;
40
- }
41
- });
42
- }
43
- async function dispatch(event, method, parts, userEmail) {
44
- // POST /sql/query — read-only SQL for tool iframes
45
- if (method === "POST" &&
46
- parts.length === 2 &&
47
- parts[0] === "sql" &&
48
- parts[1] === "query") {
49
- return handleSqlQuery(event);
50
- }
51
- // POST /sql/exec — write SQL for tool iframes
52
- if (method === "POST" &&
53
- parts.length === 2 &&
54
- parts[0] === "sql" &&
55
- parts[1] === "exec") {
56
- return handleSqlExec(event);
57
- }
58
- // GET /data/:toolId/:collection — list items in a collection
59
- if (method === "GET" && parts.length === 3 && parts[0] === "data") {
60
- return handleToolDataList(event, parts[1], parts[2], userEmail);
61
- }
62
- // POST /data/:toolId/:collection — create/upsert an item
63
- if (method === "POST" && parts.length === 3 && parts[0] === "data") {
64
- return handleToolDataUpsert(event, parts[1], parts[2], userEmail);
65
- }
66
- // DELETE /data/:toolId/:collection/:itemId — delete an item
67
- if (method === "DELETE" && parts.length === 4 && parts[0] === "data") {
68
- return handleToolDataDelete(event, parts[1], parts[2], parts[3], userEmail);
69
- }
70
- // POST /proxy
71
- if (method === "POST" && parts.length === 1 && parts[0] === "proxy") {
72
- return handleProxy(event, userEmail);
73
- }
74
- // GET / — list
75
- if (method === "GET" && parts.length === 0) {
76
- return listTools();
77
- }
78
- // POST / — create
79
- if (method === "POST" && parts.length === 0) {
80
- const body = await readBody(event);
81
- if (!body.name) {
82
- setResponseStatus(event, 400);
83
- return { error: "name is required" };
84
- }
85
- const tool = await createTool(body);
86
- recordChange({ source: "action", type: "change" });
87
- setResponseStatus(event, 201);
88
- return tool;
89
- }
90
- // GET /:id/render
91
- if (method === "GET" && parts.length === 2 && parts[1] === "render") {
92
- const access = await resolveAccess("tool", parts[0]);
93
- const tool = access?.resource;
94
- if (!tool) {
95
- setResponseStatus(event, 404);
96
- return { error: "Tool not found" };
97
- }
98
- const search = event.url?.search || "";
99
- const isDark = search.includes("dark=1") || search.includes("dark=true");
100
- const themeVars = getThemeVars(isDark);
101
- // Compute viewer-vs-author binding so the iframe can warn when the
102
- // viewer is NOT the author. The role is plumbed through to gate
103
- // dangerous bridge helpers in iframe-bridge.ts (audit H4).
104
- const isAuthor = tool.ownerEmail === userEmail;
105
- const html = buildToolHtml(tool.content, themeVars, isDark, parts[0], {
106
- authorEmail: tool.ownerEmail,
107
- viewerEmail: userEmail,
108
- isAuthor,
109
- role: access.role,
110
- });
111
- // Security headers per render. We set these explicitly here (rather than
112
- // rely on the global security-headers middleware) because:
113
- // - The global middleware sets X-Frame-Options: DENY which would break
114
- // the legitimate iframe usage of this route inside the app.
115
- // - frame-ancestors in the CSP must be set as an HTTP header to be
116
- // enforced; meta-CSP can't set it per spec.
117
- setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
118
- setResponseHeader(event, "Content-Security-Policy", TOOL_IFRAME_CSP);
119
- setResponseHeader(event, "X-Frame-Options", "SAMEORIGIN");
120
- setResponseHeader(event, "X-Content-Type-Options", "nosniff");
121
- setResponseHeader(event, "Referrer-Policy", "no-referrer");
122
- return html;
123
- }
124
- // GET /:id
125
- if (method === "GET" && parts.length === 1) {
126
- const tool = await getTool(parts[0]);
127
- if (!tool) {
128
- setResponseStatus(event, 404);
129
- return { error: "Tool not found" };
130
- }
131
- return tool;
132
- }
133
- // PUT /:id
134
- if (method === "PUT" && parts.length === 1) {
135
- const body = await readBody(event);
136
- const hasContentUpdate = body.content !== undefined || body.patches !== undefined;
137
- const hasMetaUpdate = body.name !== undefined ||
138
- body.description !== undefined ||
139
- body.icon !== undefined ||
140
- body.visibility !== undefined;
141
- let result = null;
142
- if (hasContentUpdate) {
143
- result = await updateToolContent(parts[0], {
144
- content: body.content,
145
- patches: body.patches,
146
- });
147
- }
148
- if (hasMetaUpdate) {
149
- result = await updateTool(parts[0], body);
150
- }
151
- if (!hasContentUpdate && !hasMetaUpdate) {
152
- result = await getTool(parts[0]);
153
- }
154
- if (!result) {
155
- setResponseStatus(event, 404);
156
- return { error: "Tool not found" };
157
- }
158
- recordChange({ source: "action", type: "change" });
159
- return result;
160
- }
161
- // DELETE /:id
162
- if (method === "DELETE" && parts.length === 1) {
163
- const ok = await deleteTool(parts[0]);
164
- if (!ok) {
165
- setResponseStatus(event, 404);
166
- return { error: "Tool not found" };
167
- }
168
- recordChange({ source: "action", type: "change" });
169
- return { ok: true };
170
- }
171
- setResponseStatus(event, 404);
172
- return { error: "Not found" };
173
- }
174
- async function handleToolDataList(event, toolId, collection, userEmail) {
175
- await ensureToolsTables();
176
- const tool = await getTool(toolId);
177
- if (!tool) {
178
- setResponseStatus(event, 404);
179
- return { error: "Tool not found" };
180
- }
181
- const client = getDbExec();
182
- const url = event.url;
183
- const limitParam = url?.searchParams?.get("limit");
184
- const limit = limitParam
185
- ? Math.min(Math.max(1, Number(limitParam)), 1000)
186
- : 100;
187
- const scope = url?.searchParams?.get("scope") || "user";
188
- const orgId = getRequestOrgId();
189
- if (scope === "org") {
190
- if (!orgId) {
191
- setResponseStatus(event, 400);
192
- return { error: "Org context required for scope=org" };
193
- }
194
- const result = await client.execute({
195
- sql: `SELECT COALESCE(item_id, id) AS id, tool_id, collection, data, owner_email, scope, org_id, created_at, updated_at
196
- FROM tool_data
197
- WHERE tool_id = ? AND collection = ? AND scope = 'org' AND org_id = ?
198
- ORDER BY created_at DESC
199
- LIMIT ?`,
200
- args: [toolId, collection, orgId, limit],
201
- });
202
- return result.rows ?? [];
203
- }
204
- if (scope === "all") {
205
- const result = await client.execute({
206
- sql: `SELECT COALESCE(item_id, id) AS id, tool_id, collection, data, owner_email, scope, org_id, created_at, updated_at
207
- FROM tool_data
208
- WHERE tool_id = ? AND collection = ?
209
- AND ((scope = 'user' AND owner_email = ?) OR (scope = 'org' AND org_id = ?))
210
- ORDER BY created_at DESC
211
- LIMIT ?`,
212
- args: [toolId, collection, userEmail, orgId ?? "", limit],
213
- });
214
- return result.rows ?? [];
215
- }
216
- const result = await client.execute({
217
- sql: `SELECT COALESCE(item_id, id) AS id, tool_id, collection, data, owner_email, scope, org_id, created_at, updated_at
218
- FROM tool_data
219
- WHERE tool_id = ? AND collection = ? AND scope = 'user' AND owner_email = ?
220
- ORDER BY updated_at DESC
221
- LIMIT ?`,
222
- args: [toolId, collection, userEmail, limit],
223
- });
224
- return result.rows ?? [];
225
- }
226
- async function handleToolDataUpsert(event, toolId, collection, userEmail) {
227
- await ensureToolsTables();
228
- const tool = await getTool(toolId);
229
- if (!tool) {
230
- setResponseStatus(event, 404);
231
- return { error: "Tool not found" };
232
- }
233
- const body = await readBody(event);
234
- if (body.data === undefined) {
235
- setResponseStatus(event, 400);
236
- return { error: "data is required" };
237
- }
238
- const itemId = String(body.id || randomUUID());
239
- const data = typeof body.data === "string" ? body.data : JSON.stringify(body.data);
240
- const now = new Date().toISOString();
241
- const scope = body.scope === "org" ? "org" : "user";
242
- const orgId = getRequestOrgId();
243
- if (scope === "org" && !orgId) {
244
- setResponseStatus(event, 400);
245
- return { error: "Org context required for scope=org" };
246
- }
247
- const scopeKey = scope === "org" ? `org:${orgId}` : userEmail;
248
- const client = getDbExec();
249
- const pg = isPostgres();
250
- const conflictClause = pg
251
- ? `ON CONFLICT (tool_id, collection, scope_key, item_id)
252
- DO UPDATE SET data = EXCLUDED.data, updated_at = EXCLUDED.updated_at`
253
- : `ON CONFLICT (tool_id, collection, scope_key, item_id)
254
- DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`;
255
- await client.execute({
256
- sql: `INSERT INTO tool_data (id, tool_id, collection, item_id, data, owner_email, scope, org_id, scope_key, created_at, updated_at)
257
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
258
- ${conflictClause}`,
259
- args: [
260
- randomUUID(),
261
- toolId,
262
- collection,
263
- itemId,
264
- data,
265
- userEmail,
266
- scope,
267
- scope === "org" ? orgId : null,
268
- scopeKey,
269
- now,
270
- now,
271
- ],
272
- });
273
- return {
274
- id: itemId,
275
- toolId,
276
- collection,
277
- data,
278
- ownerEmail: userEmail,
279
- scope,
280
- orgId: scope === "org" ? orgId : null,
281
- createdAt: now,
282
- updatedAt: now,
283
- };
284
- }
285
- async function handleToolDataDelete(event, toolId, collection, itemId, userEmail) {
286
- await ensureToolsTables();
287
- const tool = await getTool(toolId);
288
- if (!tool) {
289
- setResponseStatus(event, 404);
290
- return { error: "Tool not found" };
291
- }
292
- const url = event.url;
293
- const scope = url?.searchParams?.get("scope") || "user";
294
- const orgId = getRequestOrgId();
295
- const client = getDbExec();
296
- if (scope === "org") {
297
- if (!orgId) {
298
- setResponseStatus(event, 400);
299
- return { error: "Org context required for scope=org" };
300
- }
301
- await client.execute({
302
- sql: `DELETE FROM tool_data WHERE COALESCE(item_id, id) = ? AND tool_id = ? AND collection = ? AND scope = 'org' AND org_id = ?`,
303
- args: [itemId, toolId, collection, orgId],
304
- });
305
- return { ok: true };
306
- }
307
- await client.execute({
308
- sql: `DELETE FROM tool_data WHERE COALESCE(item_id, id) = ? AND tool_id = ? AND collection = ? AND scope = 'user' AND owner_email = ?`,
309
- args: [itemId, toolId, collection, userEmail],
310
- });
311
- return { ok: true };
312
- }
313
- async function handleProxy(event, userEmail) {
314
- const body = await readBody(event);
315
- const rawUrl = body.url;
316
- if (!rawUrl || typeof rawUrl !== "string") {
317
- setResponseStatus(event, 400);
318
- return { error: "url is required" };
319
- }
320
- const method = normalizeToolProxyMethod(body.method || "GET");
321
- if (!method) {
322
- setResponseStatus(event, 405);
323
- return {
324
- error: "Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.",
325
- };
326
- }
327
- const rawHeaders = body.headers || {};
328
- const rawBody = body.body;
329
- let resolvedUrl = rawUrl;
330
- let resolvedHeaders = JSON.stringify(rawHeaders);
331
- let resolvedBody = rawBody;
332
- const allUsedKeys = [];
333
- const allSecretValues = [];
334
- try {
335
- const urlResult = await resolveKeyReferences(rawUrl, "user", userEmail);
336
- resolvedUrl = urlResult.resolved;
337
- allUsedKeys.push(...urlResult.usedKeys);
338
- allSecretValues.push(...urlResult.secretValues);
339
- const headerResult = await resolveKeyReferences(resolvedHeaders, "user", userEmail);
340
- resolvedHeaders = headerResult.resolved;
341
- allUsedKeys.push(...headerResult.usedKeys);
342
- allSecretValues.push(...headerResult.secretValues);
343
- if (rawBody) {
344
- const bodyResult = await resolveKeyReferences(typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody), "user", userEmail);
345
- resolvedBody = bodyResult.resolved;
346
- allUsedKeys.push(...bodyResult.usedKeys);
347
- allSecretValues.push(...bodyResult.secretValues);
348
- }
349
- }
350
- catch (err) {
351
- setResponseStatus(event, 400);
352
- return { error: `Key resolution failed: ${err?.message ?? err}` };
353
- }
354
- const secretValues = collectSecretValues(allSecretValues);
355
- if (await isBlockedToolUrlWithDns(resolvedUrl)) {
356
- setResponseStatus(event, 403);
357
- return { error: "Requests to private/internal addresses are not allowed" };
358
- }
359
- for (const keyName of new Set(allUsedKeys)) {
360
- const allowlist = await getKeyAllowlist(keyName, "user", userEmail);
361
- if (!validateUrlAllowlist(resolvedUrl, allowlist)) {
362
- setResponseStatus(event, 403);
363
- return {
364
- error: `Key "${keyName}" is not allowed for this URL origin`,
365
- };
366
- }
367
- }
368
- let headers;
369
- try {
370
- headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));
371
- }
372
- catch {
373
- headers = sanitizeOutboundHeaders(rawHeaders);
374
- }
375
- const controller = new AbortController();
376
- const timeout = setTimeout(() => controller.abort(), 15_000);
377
- // Best-effort connect-time SSRF guard. When undici is available (it ships
378
- // with Node 18+ but is not always exposed as an importable module), the
379
- // dispatcher re-checks the resolved IP at TCP-connect time, closing the
380
- // TOCTOU between the pre-flight `isBlockedToolUrlWithDns` lookup and the
381
- // actual fetch lookup. If undici is not importable, fall through to plain
382
- // fetch — the pre-flight remains the primary protection.
383
- const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;
384
- try {
385
- const fetchOpts = {
386
- method,
387
- headers,
388
- signal: controller.signal,
389
- redirect: "manual",
390
- };
391
- if (dispatcher)
392
- fetchOpts.dispatcher = dispatcher;
393
- if (resolvedBody && ["POST", "PUT", "PATCH"].includes(method)) {
394
- const isStringBody = typeof resolvedBody === "string";
395
- fetchOpts.body = isStringBody
396
- ? resolvedBody
397
- : JSON.stringify(resolvedBody);
398
- // Only inject Content-Type when (a) the caller didn't set one and
399
- // (b) the body is actually JSON-shaped (object or stringified JSON).
400
- // Otherwise leave it unset so the runtime fetch picks an appropriate
401
- // default and we don't misrepresent text/plain bodies as JSON.
402
- const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
403
- if (!hasContentType) {
404
- const isJsonShaped = !isStringBody ||
405
- (typeof resolvedBody === "string" &&
406
- /^\s*[{[]/.test(resolvedBody) &&
407
- isLikelyJson(resolvedBody));
408
- if (isJsonShaped)
409
- headers["Content-Type"] = "application/json";
410
- }
411
- }
412
- const response = await fetch(resolvedUrl, fetchOpts);
413
- if (response.status >= 300 && response.status < 400) {
414
- const location = response.headers.get("location");
415
- const redirectUrl = location ? new URL(location, resolvedUrl).href : null;
416
- if (redirectUrl && (await isBlockedToolUrlWithDns(redirectUrl))) {
417
- setResponseStatus(event, 403);
418
- return { error: "Redirect to private/internal address blocked" };
419
- }
420
- if (redirectUrl) {
421
- for (const keyName of new Set(allUsedKeys)) {
422
- const allowlist = await getKeyAllowlist(keyName, "user", userEmail);
423
- if (!validateUrlAllowlist(redirectUrl, allowlist)) {
424
- setResponseStatus(event, 403);
425
- return {
426
- error: `Redirect URL is not allowed for key "${keyName}"`,
427
- };
428
- }
429
- }
430
- }
431
- return {
432
- status: response.status,
433
- body: {
434
- redirect: redirectUrl
435
- ? redactString(redirectUrl, secretValues)
436
- : location,
437
- },
438
- };
439
- }
440
- const { text } = await readResponseTextWithLimit(response);
441
- let responseBody;
442
- try {
443
- responseBody = JSON.parse(text);
444
- }
445
- catch {
446
- responseBody = text;
447
- }
448
- return {
449
- status: response.status,
450
- body: redactSecrets(responseBody, secretValues),
451
- };
452
- }
453
- catch (err) {
454
- if (err?.name === "AbortError") {
455
- setResponseStatus(event, 504);
456
- return { error: "Upstream request timed out" };
457
- }
458
- setResponseStatus(event, 502);
459
- return {
460
- error: `Proxy request failed: ${redactSecrets(err?.message ?? String(err), secretValues)}`,
461
- };
462
- }
463
- finally {
464
- clearTimeout(timeout);
465
- }
466
- }
467
- /**
468
- * Capture console output from a CLI script that uses console.log for results.
469
- * Same technique as wrapCliScript in agent-chat-plugin.ts.
470
- */
471
- let captureCliOutputQueue = Promise.resolve();
472
- async function captureCliOutput(fn, args) {
473
- const previousCapture = captureCliOutputQueue;
474
- let releaseCapture;
475
- captureCliOutputQueue = new Promise((resolve) => {
476
- releaseCapture = resolve;
477
- });
478
- await previousCapture;
479
- const logs = [];
480
- const origLog = console.log;
481
- const origError = console.error;
482
- const origStdoutWrite = process.stdout.write;
483
- console.log = (...a) => {
484
- logs.push(a.map(String).join(" "));
485
- };
486
- console.error = (...a) => {
487
- logs.push(a.map(String).join(" "));
488
- };
489
- process.stdout.write = ((chunk) => {
490
- if (typeof chunk === "string")
491
- logs.push(chunk);
492
- else if (Buffer.isBuffer(chunk))
493
- logs.push(chunk.toString());
494
- return true;
495
- });
496
- try {
497
- await fn(args);
498
- }
499
- catch (err) {
500
- logs.push(`Error: ${err?.message ?? String(err)}`);
501
- }
502
- finally {
503
- console.log = origLog;
504
- console.error = origError;
505
- process.stdout.write = origStdoutWrite;
506
- releaseCapture();
507
- }
508
- return logs.join("\n") || "(no output)";
509
- }
510
- async function handleSqlQuery(event) {
511
- const body = await readBody(event);
512
- const sql = body.sql;
513
- if (!sql || typeof sql !== "string") {
514
- setResponseStatus(event, 400);
515
- return { error: "sql is required" };
516
- }
517
- const cleanSql = stripSqlComments(sql);
518
- if (!/^\s*(SELECT|WITH)\b/i.test(cleanSql)) {
519
- setResponseStatus(event, 403);
520
- return { error: "Only SELECT queries are allowed from tools" };
521
- }
522
- if (SENSITIVE_SQL_RE.test(cleanSql)) {
523
- setResponseStatus(event, 403);
524
- return { error: "Sensitive framework tables are not readable from tools" };
525
- }
526
- try {
527
- const mod = await import("../scripts/db/query.js");
528
- const args = ["--sql", sql, "--format", "json"];
529
- if (body.limit)
530
- args.push("--limit", String(body.limit));
531
- if (body.args !== undefined) {
532
- if (!Array.isArray(body.args)) {
533
- setResponseStatus(event, 400);
534
- return { error: "args must be an array" };
535
- }
536
- args.push("--args", JSON.stringify(body.args));
537
- }
538
- const output = await captureCliOutput(mod.default, args);
539
- try {
540
- return JSON.parse(output);
541
- }
542
- catch {
543
- return { output };
544
- }
545
- }
546
- catch (err) {
547
- setResponseStatus(event, 500);
548
- return { error: err?.message ?? "Query failed" };
549
- }
550
- }
551
- // TODO(security): replace this regex blocklist with a SQL parser + an explicit
552
- // allowlist of tables a tool may read/write (e.g. only `tool_data`, plus a
553
- // per-template list). The current blocklist is best-effort defense in depth
554
- // and is by design bypassable via SQL constructions that don't include the
555
- // blocklisted token literally (string concat, dynamic SQL, etc). The temp-
556
- // view scoping in scripts/db/scoping.ts is the actual ownership boundary.
557
- const DESTRUCTIVE_SQL_RE = /\b(CREATE\s+(?:(?:LOCAL|GLOBAL)\s+)?(?:TEMPORARY|TEMP)?\s*(TABLE|INDEX|VIEW|SCHEMA|DATABASE|TRIGGER|FUNCTION|EXTENSION|ROLE|TABLESPACE|PUBLICATION|SUBSCRIPTION)|DROP\s+(TABLE|INDEX|VIEW|SCHEMA|DATABASE|TRIGGER|FUNCTION|EXTENSION|ROLE)|TRUNCATE|DELETE\s+FROM\s+(?!tool_data\b)|ALTER\s+(TABLE|VIEW|SCHEMA|DATABASE|FUNCTION|ROLE|EXTENSION|PUBLICATION)\s+(?!tool_data\b)|ATTACH|DETACH|VACUUM|REINDEX|PRAGMA|GRANT|REVOKE|SET\s+ROLE|RESET\s+ROLE|COPY)\b/i;
558
- // Sensitive tables that tools must not touch directly. Includes Better Auth
559
- // identity tables, framework infrastructure (tracing, evals, automations,
560
- // integrations, notifications, scheduling, sharing/orgs), and Postgres
561
- // catalogs that would let a tool enumerate or read internals.
562
- const SENSITIVE_SQL_RE = /\b(app_secrets|user|users|session|sessions|account|accounts|verification|oauth_tokens|tools|tool_shares|tool_slots|tool_slot_installs|member|organization|invitation|jwks|agent_trace_spans|agent_trace_summaries|agent_feedback|agent_satisfaction_scores|agent_evals|agent_runs|agent_run_events|notifications|progress_runs|integration_configs|integration_pending_tasks|integration_thread_mappings|resources|org_members|org_invitations|bigquery_cache|dashboard_views|pg_catalog|information_schema|pg_class|pg_proc|pg_namespace|pg_user|pg_roles|pg_authid|pg_shadow)\b/i;
563
- // Refuses positional INSERTs (no column list). `INSERT INTO recordings VALUES
564
- // (...)` would let a tool stuff arbitrary owner_email values into a row.
565
- // `INSERT INTO recordings (col1, col2) VALUES (...)` is required so the
566
- // downstream injectOwnership helper can append owner_email.
567
- const POSITIONAL_INSERT_RE = /\bINSERT\s+INTO\s+["'`]?\w+["'`]?\s+VALUES\b/i;
568
- function stripSqlComments(sql) {
569
- return sql.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
570
- }
571
- function isLikelyJson(text) {
572
- try {
573
- const parsed = JSON.parse(text);
574
- return parsed !== null && typeof parsed === "object";
575
- }
576
- catch {
577
- return false;
578
- }
579
- }
580
- async function handleSqlExec(event) {
581
- const body = await readBody(event);
582
- const sql = body.sql;
583
- if (!sql || typeof sql !== "string") {
584
- setResponseStatus(event, 400);
585
- return { error: "sql is required" };
586
- }
587
- const cleanSql = stripSqlComments(sql);
588
- if (DESTRUCTIVE_SQL_RE.test(cleanSql)) {
589
- setResponseStatus(event, 403);
590
- return {
591
- error: "Schema changes and destructive SQL are not allowed from tools",
592
- };
593
- }
594
- if (SENSITIVE_SQL_RE.test(cleanSql)) {
595
- setResponseStatus(event, 403);
596
- return { error: "Sensitive framework tables are not writable from tools" };
597
- }
598
- if (POSITIONAL_INSERT_RE.test(cleanSql)) {
599
- setResponseStatus(event, 400);
600
- return {
601
- error: "INSERT must specify an explicit column list (e.g. INSERT INTO t (col1, col2) VALUES (?, ?)) so ownership can be injected.",
602
- };
603
- }
604
- try {
605
- const mod = await import("../scripts/db/exec.js");
606
- const args = ["--sql", sql, "--format", "json"];
607
- if (body.args !== undefined) {
608
- if (!Array.isArray(body.args)) {
609
- setResponseStatus(event, 400);
610
- return { error: "args must be an array" };
611
- }
612
- args.push("--args", JSON.stringify(body.args));
613
- }
614
- const output = await captureCliOutput(mod.default, args);
615
- try {
616
- return JSON.parse(output);
617
- }
618
- catch {
619
- return { output };
620
- }
621
- }
622
- catch (err) {
623
- setResponseStatus(event, 500);
624
- return { error: err?.message ?? "Exec failed" };
625
- }
626
- }
627
- //# sourceMappingURL=routes.js.map