@agent-native/core 0.15.3 → 0.15.4

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 (149) hide show
  1. package/dist/client/dev-mode.d.ts +14 -0
  2. package/dist/client/dev-mode.d.ts.map +1 -0
  3. package/dist/client/dev-mode.js +14 -0
  4. package/dist/client/dev-mode.js.map +1 -0
  5. package/dist/client/extensions/EmbeddedTool.d.ts +20 -0
  6. package/dist/client/extensions/EmbeddedTool.d.ts.map +1 -0
  7. package/dist/client/extensions/EmbeddedTool.js +199 -0
  8. package/dist/client/extensions/EmbeddedTool.js.map +1 -0
  9. package/dist/client/extensions/ToolEditor.d.ts +5 -0
  10. package/dist/client/extensions/ToolEditor.d.ts.map +1 -0
  11. package/dist/client/extensions/ToolEditor.js +129 -0
  12. package/dist/client/extensions/ToolEditor.js.map +1 -0
  13. package/dist/client/extensions/ToolViewer.d.ts +5 -0
  14. package/dist/client/extensions/ToolViewer.d.ts.map +1 -0
  15. package/dist/client/extensions/ToolViewer.js +400 -0
  16. package/dist/client/extensions/ToolViewer.js.map +1 -0
  17. package/dist/client/extensions/ToolViewerPage.d.ts +2 -0
  18. package/dist/client/extensions/ToolViewerPage.d.ts.map +1 -0
  19. package/dist/client/extensions/ToolViewerPage.js +24 -0
  20. package/dist/client/extensions/ToolViewerPage.js.map +1 -0
  21. package/dist/client/extensions/ToolsListPage.d.ts +2 -0
  22. package/dist/client/extensions/ToolsListPage.d.ts.map +1 -0
  23. package/dist/client/extensions/ToolsListPage.js +67 -0
  24. package/dist/client/extensions/ToolsListPage.js.map +1 -0
  25. package/dist/client/extensions/ToolsSidebarSection.d.ts +2 -0
  26. package/dist/client/extensions/ToolsSidebarSection.d.ts.map +1 -0
  27. package/dist/client/extensions/ToolsSidebarSection.js +236 -0
  28. package/dist/client/extensions/ToolsSidebarSection.js.map +1 -0
  29. package/dist/client/extensions/tool-order.d.ts +7 -0
  30. package/dist/client/extensions/tool-order.d.ts.map +1 -0
  31. package/dist/client/extensions/tool-order.js +47 -0
  32. package/dist/client/extensions/tool-order.js.map +1 -0
  33. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  34. package/dist/client/settings/useBuilderStatus.js +137 -20
  35. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  36. package/dist/client/settings/useBuilderStatus.spec.js +28 -0
  37. package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
  38. package/dist/client/tools/EmbeddedTool.d.ts +20 -0
  39. package/dist/client/tools/EmbeddedTool.d.ts.map +1 -0
  40. package/dist/client/tools/EmbeddedTool.js +199 -0
  41. package/dist/client/tools/EmbeddedTool.js.map +1 -0
  42. package/dist/client/tools/ExtensionSlot.d.ts +27 -0
  43. package/dist/client/tools/ExtensionSlot.d.ts.map +1 -0
  44. package/dist/client/tools/ExtensionSlot.js +96 -0
  45. package/dist/client/tools/ExtensionSlot.js.map +1 -0
  46. package/dist/client/tools/ToolEditor.d.ts +5 -0
  47. package/dist/client/tools/ToolEditor.d.ts.map +1 -0
  48. package/dist/client/tools/ToolEditor.js +129 -0
  49. package/dist/client/tools/ToolEditor.js.map +1 -0
  50. package/dist/client/tools/ToolViewer.d.ts +5 -0
  51. package/dist/client/tools/ToolViewer.d.ts.map +1 -0
  52. package/dist/client/tools/ToolViewer.js +400 -0
  53. package/dist/client/tools/ToolViewer.js.map +1 -0
  54. package/dist/client/tools/ToolViewerPage.d.ts +2 -0
  55. package/dist/client/tools/ToolViewerPage.d.ts.map +1 -0
  56. package/dist/client/tools/ToolViewerPage.js +24 -0
  57. package/dist/client/tools/ToolViewerPage.js.map +1 -0
  58. package/dist/client/tools/ToolsListPage.d.ts +2 -0
  59. package/dist/client/tools/ToolsListPage.d.ts.map +1 -0
  60. package/dist/client/tools/ToolsListPage.js +67 -0
  61. package/dist/client/tools/ToolsListPage.js.map +1 -0
  62. package/dist/client/tools/ToolsSidebarSection.d.ts +2 -0
  63. package/dist/client/tools/ToolsSidebarSection.d.ts.map +1 -0
  64. package/dist/client/tools/ToolsSidebarSection.js +236 -0
  65. package/dist/client/tools/ToolsSidebarSection.js.map +1 -0
  66. package/dist/client/tools/iframe-bridge.d.ts +38 -0
  67. package/dist/client/tools/iframe-bridge.d.ts.map +1 -0
  68. package/dist/client/tools/iframe-bridge.js +207 -0
  69. package/dist/client/tools/iframe-bridge.js.map +1 -0
  70. package/dist/client/tools/index.d.ts +8 -0
  71. package/dist/client/tools/index.d.ts.map +1 -0
  72. package/dist/client/tools/index.js +8 -0
  73. package/dist/client/tools/index.js.map +1 -0
  74. package/dist/client/tools/tool-order.d.ts +7 -0
  75. package/dist/client/tools/tool-order.d.ts.map +1 -0
  76. package/dist/client/tools/tool-order.js +47 -0
  77. package/dist/client/tools/tool-order.js.map +1 -0
  78. package/dist/server/auth.d.ts.map +1 -1
  79. package/dist/server/auth.js +20 -0
  80. package/dist/server/auth.js.map +1 -1
  81. package/dist/server/builder-browser.d.ts +7 -1
  82. package/dist/server/builder-browser.d.ts.map +1 -1
  83. package/dist/server/builder-browser.js +45 -4
  84. package/dist/server/builder-browser.js.map +1 -1
  85. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  86. package/dist/server/core-routes-plugin.js +67 -4
  87. package/dist/server/core-routes-plugin.js.map +1 -1
  88. package/dist/server/google-auth-mode.d.ts +3 -3
  89. package/dist/server/google-auth-mode.js.map +1 -1
  90. package/dist/server/google-auth-plugin.d.ts +3 -2
  91. package/dist/server/google-auth-plugin.d.ts.map +1 -1
  92. package/dist/server/google-auth-plugin.js +9 -2
  93. package/dist/server/google-auth-plugin.js.map +1 -1
  94. package/dist/server/local-migration.d.ts +41 -0
  95. package/dist/server/local-migration.d.ts.map +1 -0
  96. package/dist/server/local-migration.js +235 -0
  97. package/dist/server/local-migration.js.map +1 -0
  98. package/dist/server/onboarding-html.d.ts.map +1 -1
  99. package/dist/server/onboarding-html.js +9 -2
  100. package/dist/server/onboarding-html.js.map +1 -1
  101. package/dist/tools/actions.d.ts +3 -0
  102. package/dist/tools/actions.d.ts.map +1 -0
  103. package/dist/tools/actions.js +272 -0
  104. package/dist/tools/actions.js.map +1 -0
  105. package/dist/tools/fetch-tool.d.ts +23 -0
  106. package/dist/tools/fetch-tool.d.ts.map +1 -0
  107. package/dist/tools/fetch-tool.js +178 -0
  108. package/dist/tools/fetch-tool.js.map +1 -0
  109. package/dist/tools/html-shell.d.ts +45 -0
  110. package/dist/tools/html-shell.d.ts.map +1 -0
  111. package/dist/tools/html-shell.js +514 -0
  112. package/dist/tools/html-shell.js.map +1 -0
  113. package/dist/tools/proxy-security.d.ts +12 -0
  114. package/dist/tools/proxy-security.d.ts.map +1 -0
  115. package/dist/tools/proxy-security.js +158 -0
  116. package/dist/tools/proxy-security.js.map +1 -0
  117. package/dist/tools/routes.d.ts +2 -0
  118. package/dist/tools/routes.d.ts.map +1 -0
  119. package/dist/tools/routes.js +627 -0
  120. package/dist/tools/routes.js.map +1 -0
  121. package/dist/tools/schema.d.ts +664 -0
  122. package/dist/tools/schema.d.ts.map +1 -0
  123. package/dist/tools/schema.js +146 -0
  124. package/dist/tools/schema.js.map +1 -0
  125. package/dist/tools/slots/routes.d.ts +15 -0
  126. package/dist/tools/slots/routes.d.ts.map +1 -0
  127. package/dist/tools/slots/routes.js +94 -0
  128. package/dist/tools/slots/routes.js.map +1 -0
  129. package/dist/tools/slots/schema.d.ts +303 -0
  130. package/dist/tools/slots/schema.d.ts.map +1 -0
  131. package/dist/tools/slots/schema.js +76 -0
  132. package/dist/tools/slots/schema.js.map +1 -0
  133. package/dist/tools/slots/store.d.ts +66 -0
  134. package/dist/tools/slots/store.d.ts.map +1 -0
  135. package/dist/tools/slots/store.js +227 -0
  136. package/dist/tools/slots/store.js.map +1 -0
  137. package/dist/tools/store.d.ts +40 -0
  138. package/dist/tools/store.d.ts.map +1 -0
  139. package/dist/tools/store.js +193 -0
  140. package/dist/tools/store.js.map +1 -0
  141. package/dist/tools/theme.d.ts +2 -0
  142. package/dist/tools/theme.d.ts.map +1 -0
  143. package/dist/tools/theme.js +67 -0
  144. package/dist/tools/theme.js.map +1 -0
  145. package/dist/tools/url-safety.d.ts +24 -0
  146. package/dist/tools/url-safety.d.ts.map +1 -0
  147. package/dist/tools/url-safety.js +224 -0
  148. package/dist/tools/url-safety.js.map +1 -0
  149. package/package.json +1 -1
@@ -0,0 +1,514 @@
1
+ export const TOOL_IFRAME_CSP = "default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self';";
2
+ export const TOOL_IFRAME_META_CSP = TOOL_IFRAME_CSP.replace(/\s*frame-ancestors 'self';?$/, "");
3
+ export function buildToolHtml(content, themeVars, isDark, toolId, binding) {
4
+ const toolIdJson = JSON.stringify(toolId ?? "");
5
+ const toolIdAttr = escapeHtmlAttribute(toolId ?? "");
6
+ const bindingJson = JSON.stringify(binding ?? {
7
+ authorEmail: "",
8
+ viewerEmail: "",
9
+ isAuthor: true,
10
+ role: "owner",
11
+ });
12
+ return `<!DOCTYPE html>
13
+ <html lang="en"${isDark ? ' class="dark"' : ""}>
14
+ <head>
15
+ <meta charset="utf-8" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
17
+ <meta http-equiv="Content-Security-Policy" content="${TOOL_IFRAME_META_CSP}" />
18
+ ${binding && !binding.isAuthor ? `<meta name="agent-native-tool-author" content="${escapeHtmlAttribute(binding.authorEmail)}" />` : ""}
19
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
20
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
21
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap" rel="stylesheet" />
22
+ <script>
23
+ var _toolErrors = [];
24
+ var _toolErrorDetails = [];
25
+ var _consoleLogs = [];
26
+ var _networkLogs = [];
27
+
28
+ var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };
29
+ function _wrapConsole(level, orig) {
30
+ return function() {
31
+ var args = Array.prototype.slice.call(arguments);
32
+ var msg = args.map(function(a) {
33
+ try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }
34
+ catch(e) { return String(a); }
35
+ }).join(' ');
36
+ if (_consoleLogs.length >= 50) _consoleLogs.shift();
37
+ _consoleLogs.push({ level: level, message: msg });
38
+ orig.apply(console, arguments);
39
+ };
40
+ }
41
+ console.log = _wrapConsole('log', _origConsole.log);
42
+ console.warn = _wrapConsole('warn', _origConsole.warn);
43
+ console.error = _wrapConsole('error', _origConsole.error);
44
+ console.info = _wrapConsole('info', _origConsole.info);
45
+
46
+ function _collectError(message, stack) {
47
+ if (!message) return;
48
+ if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';
49
+ if (_toolErrors.indexOf(message) !== -1) return;
50
+ _toolErrors.push(message);
51
+ _toolErrorDetails.push({ message: message, stack: stack || '' });
52
+ var toast = document.getElementById('__tool-error-toast');
53
+ if (!toast) return;
54
+ var msg = document.getElementById('__tool-error-msg');
55
+ if (_toolErrors.length === 1) {
56
+ msg.textContent = _toolErrors[0];
57
+ } else {
58
+ msg.textContent = _toolErrors.length + ' errors — ' + _toolErrors[_toolErrors.length - 1];
59
+ }
60
+ toast.style.display = 'block';
61
+ }
62
+
63
+ window.addEventListener('error', function(event) {
64
+ var msg = event.message || '';
65
+ if (msg.indexOf('Alpine Expression Error') === 0) return;
66
+ var stack = event.error && event.error.stack ? event.error.stack : '';
67
+ _collectError(msg, stack);
68
+ });
69
+
70
+ window.addEventListener('unhandledrejection', function(event) {
71
+ var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);
72
+ var stack = event.reason && event.reason.stack ? event.reason.stack : '';
73
+ _collectError(msg, stack);
74
+ });
75
+ </script>
76
+ <!--
77
+ SECURITY: pinned to exact patch versions + SRI integrity hashes. A
78
+ malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would
79
+ otherwise inject code into every tool. To bump these versions:
80
+ 1. npm view @tailwindcss/browser version (or alpinejs)
81
+ 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \
82
+ | openssl dgst -sha384 -binary | openssl base64 -A
83
+ 3. Update the URL + integrity hash below in lockstep.
84
+ -->
85
+ <script
86
+ src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4"
87
+ integrity="sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml"
88
+ crossorigin="anonymous"
89
+ ></script>
90
+ <script
91
+ defer
92
+ src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js"
93
+ integrity="sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be"
94
+ crossorigin="anonymous"
95
+ ></script>
96
+ <style>${themeVars}</style>
97
+ <style type="text/tailwindcss">
98
+ @custom-variant dark (&:where(.dark, .dark *));
99
+ @theme {
100
+ --color-border: hsl(var(--border));
101
+ --color-input: hsl(var(--input));
102
+ --color-ring: hsl(var(--ring));
103
+ --color-background: hsl(var(--background));
104
+ --color-foreground: hsl(var(--foreground));
105
+ --color-primary: hsl(var(--primary));
106
+ --color-primary-foreground: hsl(var(--primary-foreground));
107
+ --color-secondary: hsl(var(--secondary));
108
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
109
+ --color-destructive: hsl(var(--destructive));
110
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
111
+ --color-muted: hsl(var(--muted));
112
+ --color-muted-foreground: hsl(var(--muted-foreground));
113
+ --color-accent: hsl(var(--accent));
114
+ --color-accent-foreground: hsl(var(--accent-foreground));
115
+ --color-popover: hsl(var(--popover));
116
+ --color-popover-foreground: hsl(var(--popover-foreground));
117
+ --color-card: hsl(var(--card));
118
+ --color-card-foreground: hsl(var(--card-foreground));
119
+ --color-sidebar: hsl(var(--sidebar-background));
120
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
121
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
122
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
123
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
124
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
125
+ --color-sidebar-border: hsl(var(--sidebar-border));
126
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
127
+ --radius-lg: var(--radius);
128
+ --radius-md: calc(var(--radius) - 2px);
129
+ --radius-sm: calc(var(--radius) - 4px);
130
+ }
131
+ </style>
132
+ <style>
133
+ *, *::before, *::after { border-color: hsl(var(--border)); }
134
+ body {
135
+ --agent-native-tool-padding: clamp(16px, 2vw, 24px);
136
+ box-sizing: border-box;
137
+ font-family: 'Inter', sans-serif;
138
+ margin: 0;
139
+ min-height: 100vh;
140
+ padding: var(--agent-native-tool-padding);
141
+ }
142
+ body:has(> [data-tool-layout="full-bleed"]),
143
+ body:has(> [data-tool-padding="none"]),
144
+ body:has(> .agent-native-tool-bleed) {
145
+ padding: 0;
146
+ }
147
+ </style>
148
+ <script>
149
+ var _toolRequestSeq = 0;
150
+ var _toolPendingRequests = {};
151
+
152
+ window.addEventListener('message', function(event) {
153
+ if (event.source !== window.parent) return;
154
+ var message = event.data || {};
155
+ if (message.type !== 'agent-native-tool-response') return;
156
+ var pending = _toolPendingRequests[message.requestId];
157
+ if (!pending) return;
158
+ delete _toolPendingRequests[message.requestId];
159
+ if (message.error) {
160
+ pending.reject(new Error(message.error));
161
+ } else {
162
+ pending.resolve(message.response);
163
+ }
164
+ });
165
+
166
+ function hostRequest(path, options) {
167
+ options = options || {};
168
+ return new Promise(function(resolve, reject) {
169
+ var requestId = 'tool-req-' + (++_toolRequestSeq);
170
+ _toolPendingRequests[requestId] = { resolve: resolve, reject: reject };
171
+ window.parent.postMessage({
172
+ type: 'agent-native-tool-request',
173
+ requestId: requestId,
174
+ path: path,
175
+ options: {
176
+ method: options.method || 'GET',
177
+ headers: options.headers || {},
178
+ body: options.body,
179
+ },
180
+ }, '*');
181
+ setTimeout(function() {
182
+ var pending = _toolPendingRequests[requestId];
183
+ if (!pending) return;
184
+ delete _toolPendingRequests[requestId];
185
+ pending.reject(new Error('Tool host request timed out'));
186
+ }, 30000);
187
+ });
188
+ }
189
+
190
+ var _origHostRequest = hostRequest;
191
+ hostRequest = function(path, options) {
192
+ var entry = { path: path, method: (options && options.method) || 'GET' };
193
+ return _origHostRequest(path, options).then(function(res) {
194
+ entry.ok = res.ok;
195
+ entry.status = res.status;
196
+ if (!res.ok && res.body) {
197
+ try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}
198
+ }
199
+ if (_networkLogs.length >= 20) _networkLogs.shift();
200
+ _networkLogs.push(entry);
201
+ return res;
202
+ }, function(err) {
203
+ entry.ok = false;
204
+ entry.error = err.message;
205
+ if (_networkLogs.length >= 20) _networkLogs.shift();
206
+ _networkLogs.push(entry);
207
+ throw err;
208
+ });
209
+ };
210
+
211
+ function toolFetch(url, options) {
212
+ var opts = options || {};
213
+ return hostRequest('/_agent-native/tools/proxy', {
214
+ method: 'POST',
215
+ headers: { 'Content-Type': 'application/json' },
216
+ body: JSON.stringify({
217
+ url: url,
218
+ method: opts.method || 'GET',
219
+ headers: opts.headers,
220
+ body: opts.body,
221
+ }),
222
+ }).then(function(res) {
223
+ var data = res.body;
224
+ if (data.error && data.status === undefined) {
225
+ throw new Error(data.error);
226
+ }
227
+ return {
228
+ ok: data.status >= 200 && data.status < 300,
229
+ status: data.status,
230
+ json: function() { return Promise.resolve(data.body); },
231
+ text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },
232
+ };
233
+ });
234
+ }
235
+
236
+ async function appAction(name, params) {
237
+ params = params || {};
238
+ var res = await hostRequest('/_agent-native/actions/' + encodeURIComponent(name), {
239
+ method: 'POST',
240
+ headers: { 'Content-Type': 'application/json' },
241
+ body: JSON.stringify(params),
242
+ });
243
+ if (!res.ok) {
244
+ var err = res.body || { error: res.statusText };
245
+ throw new Error(err.error || 'Action failed: ' + res.status);
246
+ }
247
+ return res.body;
248
+ }
249
+
250
+ async function appFetch(path, options) {
251
+ options = options || {};
252
+ var res = await hostRequest(path, {
253
+ ...options,
254
+ headers: {
255
+ 'Content-Type': 'application/json',
256
+ ...(options.headers || {}),
257
+ },
258
+ });
259
+ if (!res.ok) {
260
+ var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };
261
+ throw new Error(err.error || 'Request failed: ' + res.status);
262
+ }
263
+ return res.body;
264
+ }
265
+
266
+ async function dbQuery(sql, args) {
267
+ var body = { sql: sql };
268
+ if (args) body.args = args;
269
+ return appFetch('/_agent-native/tools/sql/query', {
270
+ method: 'POST',
271
+ body: JSON.stringify(body),
272
+ });
273
+ }
274
+
275
+ async function dbExec(sql, args) {
276
+ var body = { sql: sql };
277
+ if (args) body.args = args;
278
+ return appFetch('/_agent-native/tools/sql/exec', {
279
+ method: 'POST',
280
+ body: JSON.stringify(body),
281
+ });
282
+ }
283
+
284
+ var _toolId = ${toolIdJson};
285
+ var _toolBinding = ${bindingJson};
286
+ window.toolBinding = _toolBinding;
287
+ // SECURITY (audit H4): announce the resolved binding to the parent so the
288
+ // host bridge can gate dangerous helpers based on viewer role. Sent
289
+ // BEFORE the user-authored content has a chance to run, so a malicious
290
+ // tool body cannot suppress or rewrite the announcement. The parent
291
+ // ignores subsequent announcements for the same iframe; see
292
+ // ToolViewer.tsx / EmbeddedTool.tsx.
293
+ try {
294
+ window.parent.postMessage(
295
+ {
296
+ type: 'agent-native-tool-binding',
297
+ toolId: _toolId,
298
+ binding: _toolBinding,
299
+ },
300
+ '*',
301
+ );
302
+ } catch (_) {}
303
+ // SECURITY: when the viewer is not the author of this tool, emit a clear
304
+ // console warning. The bridge currently runs every helper with the
305
+ // viewer's session — a malicious shared tool can call any action, read
306
+ // any owned table row in scope, and resolve any user-scope secret. A
307
+ // full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.
308
+ if (_toolBinding && !_toolBinding.isAuthor) {
309
+ try {
310
+ console.warn(
311
+ '[agent-native] Shared tool — running with viewer\\'s session. ' +
312
+ 'Author: ' + (_toolBinding.authorEmail || '<unknown>') + '. ' +
313
+ 'Bridge calls (appAction, dbExec, toolFetch) execute under ' +
314
+ 'your account; they are gated by your permissions, not the ' +
315
+ 'author\\'s. Do not run untrusted shared tools.',
316
+ );
317
+ } catch (_) {}
318
+ }
319
+
320
+ var toolData = {
321
+ async list(collection, opts) {
322
+ var limit = (opts && opts.limit) || 100;
323
+ var scope = (opts && opts.scope) || 'user';
324
+ var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);
325
+ if (!res.ok) throw new Error('Failed to list tool data');
326
+ return res.body;
327
+ },
328
+ async get(collection, id, opts) {
329
+ var scope = (opts && opts.scope) || 'user';
330
+ var items = await this.list(collection, { scope: scope });
331
+ return (items || []).find(function(item) { return item.id === id; }) || null;
332
+ },
333
+ async set(collection, id, data, opts) {
334
+ var scope = (opts && opts.scope) || 'user';
335
+ var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection), {
336
+ method: 'POST',
337
+ headers: { 'Content-Type': 'application/json' },
338
+ body: JSON.stringify({ id: id, data: data, scope: scope }),
339
+ });
340
+ if (!res.ok) throw new Error('Failed to save tool data');
341
+ return res.body;
342
+ },
343
+ async remove(collection, id, opts) {
344
+ var scope = (opts && opts.scope) || 'user';
345
+ var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {
346
+ method: 'DELETE',
347
+ });
348
+ if (!res.ok) throw new Error('Failed to delete tool data');
349
+ return res.body;
350
+ },
351
+ };
352
+ </script>
353
+ <style>
354
+ #__tool-error-toast {
355
+ display: none;
356
+ position: fixed;
357
+ bottom: 16px;
358
+ right: 16px;
359
+ max-width: 420px;
360
+ background: hsl(var(--destructive));
361
+ color: hsl(var(--destructive-foreground));
362
+ border: 1px solid hsl(var(--destructive) / .6);
363
+ border-radius: calc(var(--radius, .5rem) + 2px);
364
+ padding: 12px 16px;
365
+ font-size: 13px;
366
+ line-height: 1.4;
367
+ font-family: 'Inter', sans-serif;
368
+ z-index: 9999;
369
+ box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);
370
+ animation: __toast-in 0.2s ease-out;
371
+ }
372
+ @keyframes __toast-in {
373
+ from { opacity: 0; transform: translateY(8px); }
374
+ to { opacity: 1; transform: translateY(0); }
375
+ }
376
+ </style>
377
+ <script>
378
+ // Extension-point slot context: when a tool is rendered embedded inside an
379
+ // ExtensionSlot, the host pushes a context object via postMessage. Tools
380
+ // read it synchronously via window.slotContext or subscribe to changes
381
+ // via window.onSlotContext(fn). When rendered full-page (no ?slot= param),
382
+ // slotContext stays null and tools branch on that.
383
+ window.slotContext = null;
384
+ var _slotContextSubscribers = [];
385
+ window.onSlotContext = function(fn) {
386
+ _slotContextSubscribers.push(fn);
387
+ if (window.slotContext !== null) {
388
+ try { fn(window.slotContext); } catch(_) {}
389
+ }
390
+ return function() {
391
+ _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });
392
+ };
393
+ };
394
+ window.addEventListener('message', function(event) {
395
+ if (event.source !== window.parent) return;
396
+ var msg = event.data;
397
+ if (!msg || msg.type !== 'agent-native-slot-context') return;
398
+ window.slotContext = msg.context || {};
399
+ _slotContextSubscribers.forEach(function(fn) {
400
+ try { fn(window.slotContext); } catch(_) {}
401
+ });
402
+ });
403
+
404
+ // Auto-resize the iframe to its content when running in slot mode. The
405
+ // host listens for agent-native-tool-resize and adjusts the iframe height.
406
+ if (new URLSearchParams(location.search).get('slot')) {
407
+ var _lastH = 0;
408
+ var _reportHeight = function() {
409
+ var h = Math.max(
410
+ document.documentElement.scrollHeight,
411
+ document.body ? document.body.scrollHeight : 0,
412
+ );
413
+ if (h !== _lastH) {
414
+ _lastH = h;
415
+ window.parent.postMessage({ type: 'agent-native-tool-resize', height: h }, '*');
416
+ }
417
+ };
418
+ if (typeof ResizeObserver !== 'undefined') {
419
+ var _ro = new ResizeObserver(_reportHeight);
420
+ document.addEventListener('DOMContentLoaded', function() {
421
+ _ro.observe(document.documentElement);
422
+ if (document.body) _ro.observe(document.body);
423
+ });
424
+ }
425
+ // Initial reports — Alpine takes a tick to render after DOMContentLoaded.
426
+ setTimeout(_reportHeight, 50);
427
+ setTimeout(_reportHeight, 250);
428
+ }
429
+
430
+ window.addEventListener('message', function(event) {
431
+ if (event.source !== window.parent) return;
432
+ var msg = event.data;
433
+ if (!msg || msg.type !== 'agent-native-theme-update') return;
434
+ var root = document.documentElement;
435
+ if (msg.isDark !== undefined) {
436
+ if (msg.isDark) root.classList.add('dark');
437
+ else root.classList.remove('dark');
438
+ }
439
+ var vars = msg.vars || {};
440
+ for (var key in vars) {
441
+ if (vars.hasOwnProperty(key)) {
442
+ root.style.setProperty(key, vars[key]);
443
+ }
444
+ }
445
+ });
446
+
447
+ document.addEventListener('keydown', function(e) {
448
+ if ((e.metaKey || e.ctrlKey) && !e.altKey) {
449
+ var key = e.key.toLowerCase();
450
+ if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;
451
+ e.preventDefault();
452
+ e.stopPropagation();
453
+ window.parent.postMessage({
454
+ type: 'agent-native-tool-keydown',
455
+ key: e.key, code: e.code,
456
+ metaKey: e.metaKey, ctrlKey: e.ctrlKey,
457
+ shiftKey: e.shiftKey, altKey: e.altKey,
458
+ }, '*');
459
+ return;
460
+ }
461
+ if (e.key === 'Escape') {
462
+ window.parent.postMessage({
463
+ type: 'agent-native-tool-keydown',
464
+ key: e.key, code: e.code,
465
+ metaKey: false, ctrlKey: false,
466
+ shiftKey: false, altKey: false,
467
+ }, '*');
468
+ }
469
+ });
470
+
471
+ document.addEventListener('DOMContentLoaded', function() {
472
+ var fixBtn = document.getElementById('__tool-error-fix');
473
+ if (fixBtn) {
474
+ fixBtn.addEventListener('click', function() {
475
+ window.parent.postMessage({
476
+ type: 'agent-native-tool-error-fix',
477
+ errors: _toolErrors,
478
+ errorDetails: _toolErrorDetails,
479
+ consoleLogs: _consoleLogs.slice(-30),
480
+ networkLogs: _networkLogs.slice(-15)
481
+ }, '*');
482
+ document.getElementById('__tool-error-toast').style.display = 'none';
483
+ });
484
+ }
485
+ var dismissBtn = document.getElementById('__tool-error-dismiss');
486
+ if (dismissBtn) {
487
+ dismissBtn.addEventListener('click', function() {
488
+ document.getElementById('__tool-error-toast').style.display = 'none';
489
+ });
490
+ }
491
+ });
492
+ </script>
493
+ </head>
494
+ <body${toolId ? ` data-tool-id="${toolIdAttr}"` : ""} class="bg-background text-foreground">
495
+ ${content}
496
+ <div id="__tool-error-toast">
497
+ <div style="display:flex;align-items:flex-start;gap:8px;">
498
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px;"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
499
+ <span id="__tool-error-msg" style="flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;"></span>
500
+ <button id="__tool-error-fix" style="cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;">Fix</button>
501
+ <button id="__tool-error-dismiss" style="cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;">&#215;</button>
502
+ </div>
503
+ </div>
504
+ </body>
505
+ </html>`;
506
+ }
507
+ function escapeHtmlAttribute(value) {
508
+ return value
509
+ .replace(/&/g, "&amp;")
510
+ .replace(/"/g, "&quot;")
511
+ .replace(/</g, "&lt;")
512
+ .replace(/>/g, "&gt;");
513
+ }
514
+ //# sourceMappingURL=html-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-shell.js","sourceRoot":"","sources":["../../src/tools/html-shell.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAC1B,2YAA2Y,CAAC;AAE9Y,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC,OAAO,CACzD,8BAA8B,EAC9B,EAAE,CACH,CAAC;AA6CF,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,SAAiB,EACjB,MAAe,EACf,MAAe,EACf,OAA2B;IAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAChC,OAAO,IAAI;QACT,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;KACd,CACF,CAAC;IAEF,OAAO;iBACQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;;;;wDAIU,oBAAoB;IACxE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,kDAAkD,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA8E7H,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA4LA,UAAU;yBACL,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiN5B,MAAM,CAAC,CAAC,CAAC,kBAAkB,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE;GAClD,OAAO;;;;;;;;;;SAUD,CAAC;AACV,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["export const TOOL_IFRAME_CSP =\n \"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self';\";\n\nexport const TOOL_IFRAME_META_CSP = TOOL_IFRAME_CSP.replace(\n /\\s*frame-ancestors 'self';?$/,\n \"\",\n);\n\n/**\n * SECURITY — TOOL CONTENT IS UNTRUSTED.\n *\n * `${content}` (line ~Body) interpolates raw HTML/JS authored by a user. This\n * file is the boundary between framework-controlled HTML and user-controlled\n * HTML. Two non-negotiable invariants for every change here:\n *\n * 1. The iframe MUST be rendered with a `sandbox` attribute that does NOT\n * include `allow-same-origin`. The viewer (`ToolViewer.tsx`,\n * `EmbeddedTool.tsx`) sets `sandbox=\"allow-scripts allow-forms\"` — and\n * that is the only acceptable shape. Adding `allow-same-origin` would\n * give the tool full DOM access to the parent window via cross-frame\n * script.\n *\n * 2. Every reachable parent action must treat the postMessage payload as\n * hostile. The bridge in `iframe-bridge.ts` enforces a path allowlist,\n * header sanitization, and method allowlist; do not relax those gates\n * for \"convenience\" in this file or any caller.\n *\n * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the\n * `tools` skill. When in doubt, fail closed.\n */\n\nexport interface ToolRenderBinding {\n /** Email of the user who authored / owns the tool. */\n authorEmail: string;\n /** Email of the user currently viewing/running the tool. */\n viewerEmail: string;\n /** True when viewer === author. */\n isAuthor: boolean;\n /**\n * Resolved role for the viewer (\"owner\" | \"admin\" | \"editor\" | \"viewer\").\n *\n * TODO(security, audit H4): the host-side bridge does not yet gate any\n * helper based on this value — every viewer gets the same powers as the\n * author. The role is plumbed through so a follow-up PR can constrain\n * `appAction` / `dbExec` / `toolFetch` for non-author viewers (and\n * eventually require an explicit consent step before running a shared\n * tool, audit C1). For now this is metadata only.\n */\n role: \"owner\" | \"admin\" | \"editor\" | \"viewer\";\n}\n\nexport function buildToolHtml(\n content: string,\n themeVars: string,\n isDark: boolean,\n toolId?: string,\n binding?: ToolRenderBinding,\n): string {\n const toolIdJson = JSON.stringify(toolId ?? \"\");\n const toolIdAttr = escapeHtmlAttribute(toolId ?? \"\");\n const bindingJson = JSON.stringify(\n binding ?? {\n authorEmail: \"\",\n viewerEmail: \"\",\n isAuthor: true,\n role: \"owner\",\n },\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\"${isDark ? ' class=\"dark\"' : \"\"}>\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"${TOOL_IFRAME_META_CSP}\" />\n ${binding && !binding.isAuthor ? `<meta name=\"agent-native-tool-author\" content=\"${escapeHtmlAttribute(binding.authorEmail)}\" />` : \"\"}\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap\" rel=\"stylesheet\" />\n <script>\n var _toolErrors = [];\n var _toolErrorDetails = [];\n var _consoleLogs = [];\n var _networkLogs = [];\n\n var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };\n function _wrapConsole(level, orig) {\n return function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }\n catch(e) { return String(a); }\n }).join(' ');\n if (_consoleLogs.length >= 50) _consoleLogs.shift();\n _consoleLogs.push({ level: level, message: msg });\n orig.apply(console, arguments);\n };\n }\n console.log = _wrapConsole('log', _origConsole.log);\n console.warn = _wrapConsole('warn', _origConsole.warn);\n console.error = _wrapConsole('error', _origConsole.error);\n console.info = _wrapConsole('info', _origConsole.info);\n\n function _collectError(message, stack) {\n if (!message) return;\n if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';\n if (_toolErrors.indexOf(message) !== -1) return;\n _toolErrors.push(message);\n _toolErrorDetails.push({ message: message, stack: stack || '' });\n var toast = document.getElementById('__tool-error-toast');\n if (!toast) return;\n var msg = document.getElementById('__tool-error-msg');\n if (_toolErrors.length === 1) {\n msg.textContent = _toolErrors[0];\n } else {\n msg.textContent = _toolErrors.length + ' errors — ' + _toolErrors[_toolErrors.length - 1];\n }\n toast.style.display = 'block';\n }\n\n window.addEventListener('error', function(event) {\n var msg = event.message || '';\n if (msg.indexOf('Alpine Expression Error') === 0) return;\n var stack = event.error && event.error.stack ? event.error.stack : '';\n _collectError(msg, stack);\n });\n\n window.addEventListener('unhandledrejection', function(event) {\n var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);\n var stack = event.reason && event.reason.stack ? event.reason.stack : '';\n _collectError(msg, stack);\n });\n </script>\n <!--\n SECURITY: pinned to exact patch versions + SRI integrity hashes. A\n malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would\n otherwise inject code into every tool. To bump these versions:\n 1. npm view @tailwindcss/browser version (or alpinejs)\n 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \\\n | openssl dgst -sha384 -binary | openssl base64 -A\n 3. Update the URL + integrity hash below in lockstep.\n -->\n <script\n src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4\"\n integrity=\"sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n defer\n src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js\"\n integrity=\"sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be\"\n crossorigin=\"anonymous\"\n ></script>\n <style>${themeVars}</style>\n <style type=\"text/tailwindcss\">\n @custom-variant dark (&:where(.dark, .dark *));\n @theme {\n --color-border: hsl(var(--border));\n --color-input: hsl(var(--input));\n --color-ring: hsl(var(--ring));\n --color-background: hsl(var(--background));\n --color-foreground: hsl(var(--foreground));\n --color-primary: hsl(var(--primary));\n --color-primary-foreground: hsl(var(--primary-foreground));\n --color-secondary: hsl(var(--secondary));\n --color-secondary-foreground: hsl(var(--secondary-foreground));\n --color-destructive: hsl(var(--destructive));\n --color-destructive-foreground: hsl(var(--destructive-foreground));\n --color-muted: hsl(var(--muted));\n --color-muted-foreground: hsl(var(--muted-foreground));\n --color-accent: hsl(var(--accent));\n --color-accent-foreground: hsl(var(--accent-foreground));\n --color-popover: hsl(var(--popover));\n --color-popover-foreground: hsl(var(--popover-foreground));\n --color-card: hsl(var(--card));\n --color-card-foreground: hsl(var(--card-foreground));\n --color-sidebar: hsl(var(--sidebar-background));\n --color-sidebar-foreground: hsl(var(--sidebar-foreground));\n --color-sidebar-primary: hsl(var(--sidebar-primary));\n --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));\n --color-sidebar-accent: hsl(var(--sidebar-accent));\n --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));\n --color-sidebar-border: hsl(var(--sidebar-border));\n --color-sidebar-ring: hsl(var(--sidebar-ring));\n --radius-lg: var(--radius);\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n }\n </style>\n\t <style>\n\t *, *::before, *::after { border-color: hsl(var(--border)); }\n\t body {\n\t --agent-native-tool-padding: clamp(16px, 2vw, 24px);\n\t box-sizing: border-box;\n\t font-family: 'Inter', sans-serif;\n\t margin: 0;\n\t min-height: 100vh;\n\t padding: var(--agent-native-tool-padding);\n\t }\n\t body:has(> [data-tool-layout=\"full-bleed\"]),\n\t body:has(> [data-tool-padding=\"none\"]),\n\t body:has(> .agent-native-tool-bleed) {\n\t padding: 0;\n\t }\n\t </style>\n\t <script>\n\t var _toolRequestSeq = 0;\n\t var _toolPendingRequests = {};\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var message = event.data || {};\n\t if (message.type !== 'agent-native-tool-response') return;\n\t var pending = _toolPendingRequests[message.requestId];\n\t if (!pending) return;\n\t delete _toolPendingRequests[message.requestId];\n\t if (message.error) {\n\t pending.reject(new Error(message.error));\n\t } else {\n\t pending.resolve(message.response);\n\t }\n\t });\n\n\t function hostRequest(path, options) {\n\t options = options || {};\n\t return new Promise(function(resolve, reject) {\n\t var requestId = 'tool-req-' + (++_toolRequestSeq);\n\t _toolPendingRequests[requestId] = { resolve: resolve, reject: reject };\n\t window.parent.postMessage({\n\t type: 'agent-native-tool-request',\n\t requestId: requestId,\n\t path: path,\n\t options: {\n\t method: options.method || 'GET',\n\t headers: options.headers || {},\n\t body: options.body,\n\t },\n\t }, '*');\n\t setTimeout(function() {\n\t var pending = _toolPendingRequests[requestId];\n\t if (!pending) return;\n\t delete _toolPendingRequests[requestId];\n\t pending.reject(new Error('Tool host request timed out'));\n\t }, 30000);\n\t });\n\t }\n\n\t var _origHostRequest = hostRequest;\n\t hostRequest = function(path, options) {\n\t var entry = { path: path, method: (options && options.method) || 'GET' };\n\t return _origHostRequest(path, options).then(function(res) {\n\t entry.ok = res.ok;\n\t entry.status = res.status;\n\t if (!res.ok && res.body) {\n\t try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}\n\t }\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t return res;\n\t }, function(err) {\n\t entry.ok = false;\n\t entry.error = err.message;\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t throw err;\n\t });\n\t };\n\n\t function toolFetch(url, options) {\n\t var opts = options || {};\n\t return hostRequest('/_agent-native/tools/proxy', {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({\n\t url: url,\n method: opts.method || 'GET',\n headers: opts.headers,\n body: opts.body,\n }),\n\t }).then(function(res) {\n\t var data = res.body;\n\t if (data.error && data.status === undefined) {\n\t throw new Error(data.error);\n\t }\n return {\n ok: data.status >= 200 && data.status < 300,\n status: data.status,\n\t json: function() { return Promise.resolve(data.body); },\n\t text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },\n\t };\n\t });\n\t }\n\n\t async function appAction(name, params) {\n\t params = params || {};\n\t var res = await hostRequest('/_agent-native/actions/' + encodeURIComponent(name), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\t if (!res.ok) {\n\t var err = res.body || { error: res.statusText };\n\t throw new Error(err.error || 'Action failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t async function appFetch(path, options) {\n\t options = options || {};\n\t var res = await hostRequest(path, {\n\t ...options,\n\t headers: {\n\t 'Content-Type': 'application/json',\n\t ...(options.headers || {}),\n\t },\n\t });\n\t if (!res.ok) {\n\t var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };\n\t throw new Error(err.error || 'Request failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n async function dbQuery(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/tools/sql/query', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n async function dbExec(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/tools/sql/exec', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n var _toolId = ${toolIdJson};\n var _toolBinding = ${bindingJson};\n window.toolBinding = _toolBinding;\n // SECURITY (audit H4): announce the resolved binding to the parent so the\n // host bridge can gate dangerous helpers based on viewer role. Sent\n // BEFORE the user-authored content has a chance to run, so a malicious\n // tool body cannot suppress or rewrite the announcement. The parent\n // ignores subsequent announcements for the same iframe; see\n // ToolViewer.tsx / EmbeddedTool.tsx.\n try {\n window.parent.postMessage(\n {\n type: 'agent-native-tool-binding',\n toolId: _toolId,\n binding: _toolBinding,\n },\n '*',\n );\n } catch (_) {}\n // SECURITY: when the viewer is not the author of this tool, emit a clear\n // console warning. The bridge currently runs every helper with the\n // viewer's session — a malicious shared tool can call any action, read\n // any owned table row in scope, and resolve any user-scope secret. A\n // full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.\n if (_toolBinding && !_toolBinding.isAuthor) {\n try {\n console.warn(\n '[agent-native] Shared tool — running with viewer\\\\'s session. ' +\n 'Author: ' + (_toolBinding.authorEmail || '<unknown>') + '. ' +\n 'Bridge calls (appAction, dbExec, toolFetch) execute under ' +\n 'your account; they are gated by your permissions, not the ' +\n 'author\\\\'s. Do not run untrusted shared tools.',\n );\n } catch (_) {}\n }\n\n var toolData = {\n\t async list(collection, opts) {\n\t var limit = (opts && opts.limit) || 100;\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);\n\t if (!res.ok) throw new Error('Failed to list tool data');\n\t return res.body;\n\t },\n async get(collection, id, opts) {\n var scope = (opts && opts.scope) || 'user';\n var items = await this.list(collection, { scope: scope });\n return (items || []).find(function(item) { return item.id === id; }) || null;\n },\n async set(collection, id, data, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({ id: id, data: data, scope: scope }),\n\t });\n\t if (!res.ok) throw new Error('Failed to save tool data');\n\t return res.body;\n\t },\n\t async remove(collection, id, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/tools/data/' + _toolId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {\n\t method: 'DELETE',\n\t });\n\t if (!res.ok) throw new Error('Failed to delete tool data');\n\t return res.body;\n\t },\n\t };\n\t </script>\n\t <style>\n\t #__tool-error-toast {\n\t display: none;\n\t position: fixed;\n\t bottom: 16px;\n\t right: 16px;\n\t max-width: 420px;\n\t background: hsl(var(--destructive));\n\t color: hsl(var(--destructive-foreground));\n\t border: 1px solid hsl(var(--destructive) / .6);\n\t border-radius: calc(var(--radius, .5rem) + 2px);\n\t padding: 12px 16px;\n\t font-size: 13px;\n\t line-height: 1.4;\n\t font-family: 'Inter', sans-serif;\n\t z-index: 9999;\n\t box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);\n\t animation: __toast-in 0.2s ease-out;\n\t }\n\t @keyframes __toast-in {\n\t from { opacity: 0; transform: translateY(8px); }\n\t to { opacity: 1; transform: translateY(0); }\n\t }\n\t </style>\n\t <script>\n\t // Extension-point slot context: when a tool is rendered embedded inside an\n\t // ExtensionSlot, the host pushes a context object via postMessage. Tools\n\t // read it synchronously via window.slotContext or subscribe to changes\n\t // via window.onSlotContext(fn). When rendered full-page (no ?slot= param),\n\t // slotContext stays null and tools branch on that.\n\t window.slotContext = null;\n\t var _slotContextSubscribers = [];\n\t window.onSlotContext = function(fn) {\n\t _slotContextSubscribers.push(fn);\n\t if (window.slotContext !== null) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t }\n\t return function() {\n\t _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });\n\t };\n\t };\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-slot-context') return;\n\t window.slotContext = msg.context || {};\n\t _slotContextSubscribers.forEach(function(fn) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t });\n\t });\n\n\t // Auto-resize the iframe to its content when running in slot mode. The\n\t // host listens for agent-native-tool-resize and adjusts the iframe height.\n\t if (new URLSearchParams(location.search).get('slot')) {\n\t var _lastH = 0;\n\t var _reportHeight = function() {\n\t var h = Math.max(\n\t document.documentElement.scrollHeight,\n\t document.body ? document.body.scrollHeight : 0,\n\t );\n\t if (h !== _lastH) {\n\t _lastH = h;\n\t window.parent.postMessage({ type: 'agent-native-tool-resize', height: h }, '*');\n\t }\n\t };\n\t if (typeof ResizeObserver !== 'undefined') {\n\t var _ro = new ResizeObserver(_reportHeight);\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _ro.observe(document.documentElement);\n\t if (document.body) _ro.observe(document.body);\n\t });\n\t }\n\t // Initial reports — Alpine takes a tick to render after DOMContentLoaded.\n\t setTimeout(_reportHeight, 50);\n\t setTimeout(_reportHeight, 250);\n\t }\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-theme-update') return;\n\t var root = document.documentElement;\n\t if (msg.isDark !== undefined) {\n\t if (msg.isDark) root.classList.add('dark');\n\t else root.classList.remove('dark');\n\t }\n\t var vars = msg.vars || {};\n\t for (var key in vars) {\n\t if (vars.hasOwnProperty(key)) {\n\t root.style.setProperty(key, vars[key]);\n\t }\n\t }\n\t });\n\n\t document.addEventListener('keydown', function(e) {\n\t if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n\t var key = e.key.toLowerCase();\n\t if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;\n\t e.preventDefault();\n\t e.stopPropagation();\n\t window.parent.postMessage({\n\t type: 'agent-native-tool-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: e.metaKey, ctrlKey: e.ctrlKey,\n\t shiftKey: e.shiftKey, altKey: e.altKey,\n\t }, '*');\n\t return;\n\t }\n\t if (e.key === 'Escape') {\n\t window.parent.postMessage({\n\t type: 'agent-native-tool-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: false, ctrlKey: false,\n\t shiftKey: false, altKey: false,\n\t }, '*');\n\t }\n\t });\n\n\t document.addEventListener('DOMContentLoaded', function() {\n\t var fixBtn = document.getElementById('__tool-error-fix');\n\t if (fixBtn) {\n\t fixBtn.addEventListener('click', function() {\n\t window.parent.postMessage({\n\t type: 'agent-native-tool-error-fix',\n\t errors: _toolErrors,\n\t errorDetails: _toolErrorDetails,\n\t consoleLogs: _consoleLogs.slice(-30),\n\t networkLogs: _networkLogs.slice(-15)\n\t }, '*');\n\t document.getElementById('__tool-error-toast').style.display = 'none';\n\t });\n\t }\n\t var dismissBtn = document.getElementById('__tool-error-dismiss');\n\t if (dismissBtn) {\n\t dismissBtn.addEventListener('click', function() {\n\t document.getElementById('__tool-error-toast').style.display = 'none';\n\t });\n\t }\n\t });\n\t </script>\n\t</head>\n\t<body${toolId ? ` data-tool-id=\"${toolIdAttr}\"` : \"\"} class=\"bg-background text-foreground\">\n\t${content}\n\t<div id=\"__tool-error-toast\">\n\t <div style=\"display:flex;align-items:flex-start;gap:8px;\">\n\t <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;margin-top:1px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>\n\t <span id=\"__tool-error-msg\" style=\"flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;\"></span>\n\t <button id=\"__tool-error-fix\" style=\"cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;\">Fix</button>\n\t <button id=\"__tool-error-dismiss\" style=\"cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;\">&#215;</button>\n\t </div>\n\t</div>\n\t</body>\n\t</html>`;\n}\n\nfunction escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n"]}
@@ -0,0 +1,12 @@
1
+ export declare const MAX_TOOL_PROXY_RESPONSE_SIZE: number;
2
+ export declare function normalizeToolProxyMethod(value: unknown): string | null;
3
+ export declare function sanitizeOutboundHeaders(value: unknown): Record<string, string>;
4
+ export declare function collectSecretValues(...groups: Array<Array<string> | undefined>): string[];
5
+ export declare function redactSecrets<T>(value: T, secretValues: string[]): T;
6
+ export declare function redactString(text: string, secretValues: string[]): string;
7
+ export declare function readResponseTextWithLimit(response: Response, maxBytes?: number): Promise<{
8
+ text: string;
9
+ truncated: boolean;
10
+ size: number;
11
+ }>;
12
+ //# sourceMappingURL=proxy-security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-security.d.ts","sourceRoot":"","sources":["../../src/tools/proxy-security.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,4BAA4B,QAAc,CAAC;AAWxD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAexB;AAED,wBAAgB,mBAAmB,CACjC,GAAG,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,GAC1C,MAAM,EAAE,CAQV;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,CAiBpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAQzE;AAaD,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,QAAQ,EAClB,QAAQ,SAA+B,GACtC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAyD7D"}