@tutti-os/agent-gui 0.0.133 → 0.0.135

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 (77) hide show
  1. package/README.md +19 -5
  2. package/dist/{AgentRichTextReadonly-PEKCMRPE.js → AgentRichTextReadonly-HCKTNAYO.js} +7 -6
  3. package/dist/agent-conversation/index.d.ts +2 -2
  4. package/dist/agent-conversation/index.js +9 -9
  5. package/dist/agent-gui.d.ts +34 -6
  6. package/dist/agent-gui.js +17 -17
  7. package/dist/agent-message-center/index.js +7 -7
  8. package/dist/{agentFileMentionContracts-CWAeuL7S.d.ts → agentFileMentionContracts-B0yQizXB.d.ts} +5 -0
  9. package/dist/{agentGuiNodeTypes-B57MfwVo.d.ts → agentGuiNodeTypes-DV7KvZRe.d.ts} +6 -5
  10. package/dist/agents.d.ts +1 -1
  11. package/dist/agents.js +1 -1
  12. package/dist/app/renderer/agentactivity.css +6 -0
  13. package/dist/{chunk-GBBNYGL7.js → chunk-25VMCMAQ.js} +4 -4
  14. package/dist/{chunk-VS5BRBY6.js → chunk-5WOCGIXJ.js} +4 -1
  15. package/dist/chunk-5WOCGIXJ.js.map +1 -0
  16. package/dist/chunk-DLCZJKCA.js +1 -0
  17. package/dist/chunk-DLCZJKCA.js.map +1 -0
  18. package/dist/{chunk-QQRTUHCH.js → chunk-EU7DCT4F.js} +2 -2
  19. package/dist/{chunk-SZOTLVH6.js → chunk-GIYGO2PB.js} +2 -2
  20. package/dist/{chunk-HCRROERX.js → chunk-IN3HXYOO.js} +4 -4
  21. package/dist/{chunk-N62NVPHT.js → chunk-IYZ4GOSN.js} +4 -4
  22. package/dist/{chunk-A6NSHCRP.js → chunk-JI5KND3K.js} +54 -51
  23. package/dist/chunk-JI5KND3K.js.map +1 -0
  24. package/dist/{chunk-J4TTEMRW.js → chunk-KWGMK4IZ.js} +4 -4
  25. package/dist/{chunk-NE6H7S5Z.js → chunk-OFJNIQWR.js} +2 -2
  26. package/dist/{chunk-NE6H7S5Z.js.map → chunk-OFJNIQWR.js.map} +1 -1
  27. package/dist/{chunk-UARZZJJB.js → chunk-OI6BYC7P.js} +4 -4
  28. package/dist/{chunk-OBWM2RFE.js → chunk-QPEO4OQS.js} +308 -8
  29. package/dist/chunk-QPEO4OQS.js.map +1 -0
  30. package/dist/{chunk-XNE2ROCP.js → chunk-UA75CGAG.js} +3 -3
  31. package/dist/{chunk-PVTAFUDS.js → chunk-VGS7S7YT.js} +2 -2
  32. package/dist/{chunk-OQR3HXPF.js → chunk-VUNVGEQG.js} +7 -1
  33. package/dist/chunk-VUNVGEQG.js.map +1 -0
  34. package/dist/{chunk-IZ626FDO.js → chunk-Y5HD6XHP.js} +485 -361
  35. package/dist/chunk-Y5HD6XHP.js.map +1 -0
  36. package/dist/{chunk-KPMQGPYU.js → chunk-ZVYUTXQJ.js} +1 -1
  37. package/dist/chunk-ZVYUTXQJ.js.map +1 -0
  38. package/dist/context-mention-palette/index.d.ts +1 -1
  39. package/dist/context-mention-palette/index.js +8 -7
  40. package/dist/{contribution-BbW-kkpi.d.ts → contribution-CDdwTppn.d.ts} +2 -0
  41. package/dist/custom-mention.js +1 -0
  42. package/dist/i18n/index.d.ts +6 -0
  43. package/dist/i18n/index.js +2 -2
  44. package/dist/index.d.ts +53 -9
  45. package/dist/index.js +20 -20
  46. package/dist/index.js.map +1 -1
  47. package/dist/mention-search.d.ts +1 -1
  48. package/dist/mention-search.js +6 -5
  49. package/dist/{types-BeAVr4of.d.ts → types-ee3zJ6Cc.d.ts} +3 -0
  50. package/dist/workbench/browser-element-context/index.d.ts +37 -0
  51. package/dist/workbench/browser-element-context/index.js +750 -0
  52. package/dist/workbench/browser-element-context/index.js.map +1 -0
  53. package/dist/workbench/contribution.d.ts +1 -1
  54. package/dist/workbench/contribution.js +4 -4
  55. package/dist/workbench/index.d.ts +1 -1
  56. package/dist/workbench/index.js +4 -4
  57. package/dist/workbench/sessionTitle.js +2 -2
  58. package/dist/workbench/tool-sidebar/index.d.ts +189 -0
  59. package/dist/workbench/tool-sidebar/index.js +1492 -0
  60. package/dist/workbench/tool-sidebar/index.js.map +1 -0
  61. package/package.json +27 -12
  62. package/dist/chunk-A6NSHCRP.js.map +0 -1
  63. package/dist/chunk-IZ626FDO.js.map +0 -1
  64. package/dist/chunk-KPMQGPYU.js.map +0 -1
  65. package/dist/chunk-OBWM2RFE.js.map +0 -1
  66. package/dist/chunk-OQR3HXPF.js.map +0 -1
  67. package/dist/chunk-VS5BRBY6.js.map +0 -1
  68. /package/dist/{AgentRichTextReadonly-PEKCMRPE.js.map → AgentRichTextReadonly-HCKTNAYO.js.map} +0 -0
  69. /package/dist/{chunk-GBBNYGL7.js.map → chunk-25VMCMAQ.js.map} +0 -0
  70. /package/dist/{chunk-QQRTUHCH.js.map → chunk-EU7DCT4F.js.map} +0 -0
  71. /package/dist/{chunk-SZOTLVH6.js.map → chunk-GIYGO2PB.js.map} +0 -0
  72. /package/dist/{chunk-HCRROERX.js.map → chunk-IN3HXYOO.js.map} +0 -0
  73. /package/dist/{chunk-N62NVPHT.js.map → chunk-IYZ4GOSN.js.map} +0 -0
  74. /package/dist/{chunk-J4TTEMRW.js.map → chunk-KWGMK4IZ.js.map} +0 -0
  75. /package/dist/{chunk-UARZZJJB.js.map → chunk-OI6BYC7P.js.map} +0 -0
  76. /package/dist/{chunk-XNE2ROCP.js.map → chunk-UA75CGAG.js.map} +0 -0
  77. /package/dist/{chunk-PVTAFUDS.js.map → chunk-VGS7S7YT.js.map} +0 -0
@@ -0,0 +1,750 @@
1
+ import "../../chunk-DLCZJKCA.js";
2
+ import "../../chunk-FGLQZ6I4.js";
3
+ import {
4
+ useEngineSelector
5
+ } from "../../chunk-2RQM7PJN.js";
6
+ import {
7
+ registerAgentCustomMentionKind
8
+ } from "../../chunk-D2JUDUYY.js";
9
+
10
+ // workbench/browser-element-context/BrowserElementContextAction.tsx
11
+ import { useCallback, useState } from "react";
12
+ import {
13
+ Button,
14
+ InspectIcon,
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipTrigger,
18
+ cn
19
+ } from "@tutti-os/ui-system";
20
+ import { useActiveBrowserNodeWebview } from "@tutti-os/browser-node/react";
21
+
22
+ // workbench/browser-element-context/browserElementSnapshot.ts
23
+ var browserElementSnapshotFormat = "tutti.browser-element.v1";
24
+ var browserElementSnapshotMaxHtmlChars = 32e3;
25
+ var browserElementSnapshotMaxTextChars = 4e3;
26
+ function normalizeBrowserElementSelectionResult(value) {
27
+ if (!isRecord(value)) return null;
28
+ if (value.status === "cancelled") return { status: "cancelled" };
29
+ if (value.status !== "selected" || !isRecord(value.snapshot)) return null;
30
+ const snapshot = value.snapshot;
31
+ const page = isRecord(snapshot.page) ? snapshot.page : {};
32
+ const element = isRecord(snapshot.element) ? snapshot.element : {};
33
+ const bounds = isRecord(element.bounds) ? element.bounds : {};
34
+ const viewport = isRecord(snapshot.viewport) ? snapshot.viewport : {};
35
+ const normalized = {
36
+ format: browserElementSnapshotFormat,
37
+ capturedAt: boundedString(snapshot.capturedAt, 64) || (/* @__PURE__ */ new Date()).toISOString(),
38
+ page: {
39
+ title: boundedString(page.title, 500),
40
+ url: sanitizeSnapshotUrl(boundedString(page.url, 4e3))
41
+ },
42
+ element: {
43
+ ariaLabel: nullableBoundedString(element.ariaLabel, 1e3),
44
+ attributes: boundedStringRecord(element.attributes, 40, 200, 2e3),
45
+ bounds: {
46
+ height: finiteNumber(bounds.height),
47
+ width: finiteNumber(bounds.width),
48
+ x: finiteNumber(bounds.x),
49
+ y: finiteNumber(bounds.y)
50
+ },
51
+ classes: boundedStringArray(element.classes, 24, 200),
52
+ domPath: boundedString(element.domPath, 4e3) || boundedString(element.selector, 2e3),
53
+ html: boundedString(element.html, browserElementSnapshotMaxHtmlChars),
54
+ htmlTruncated: element.htmlTruncated === true || typeof element.html === "string" && element.html.length > browserElementSnapshotMaxHtmlChars,
55
+ id: nullableBoundedString(element.id, 500),
56
+ role: nullableBoundedString(element.role, 200),
57
+ selector: boundedString(element.selector, 2e3),
58
+ styles: boundedStringRecord(element.styles, 32, 100, 500),
59
+ tagName: boundedString(element.tagName, 100).toLowerCase(),
60
+ text: boundedString(element.text, browserElementSnapshotMaxTextChars),
61
+ textTruncated: element.textTruncated === true || typeof element.text === "string" && element.text.length > browserElementSnapshotMaxTextChars
62
+ },
63
+ ancestors: Array.isArray(snapshot.ancestors) ? snapshot.ancestors.slice(0, 8).flatMap(
64
+ (item) => isRecord(item) ? [
65
+ {
66
+ id: nullableBoundedString(item.id, 500),
67
+ role: nullableBoundedString(item.role, 200),
68
+ selector: boundedString(item.selector, 2e3),
69
+ tagName: boundedString(item.tagName, 100).toLowerCase()
70
+ }
71
+ ] : []
72
+ ) : [],
73
+ viewport: {
74
+ devicePixelRatio: finiteNumber(viewport.devicePixelRatio, 1),
75
+ height: finiteNumber(viewport.height),
76
+ scrollX: finiteNumber(viewport.scrollX),
77
+ scrollY: finiteNumber(viewport.scrollY),
78
+ width: finiteNumber(viewport.width)
79
+ }
80
+ };
81
+ if (!normalized.element.tagName || !normalized.element.domPath) return null;
82
+ return { snapshot: normalized, status: "selected" };
83
+ }
84
+ function serializeBrowserElementSnapshot(snapshot) {
85
+ const { bounds } = snapshot.element;
86
+ return [
87
+ `DOM Path: ${snapshot.element.domPath}`,
88
+ `Position: top=${formatPixel(bounds.y)}, left=${formatPixel(bounds.x)}, width=${formatPixel(bounds.width)}, height=${formatPixel(bounds.height)}`,
89
+ `HTML Element: ${compactHtmlFragment(snapshot.element.html)}`
90
+ ].join("\n");
91
+ }
92
+ function formatPixel(value) {
93
+ const rounded = Math.round(value * 100) / 100;
94
+ return `${Object.is(rounded, -0) ? 0 : rounded}px`;
95
+ }
96
+ function compactHtmlFragment(value) {
97
+ return value.replace(/\r?\n/gu, " ").replace(/\s{2,}/gu, " ").trim();
98
+ }
99
+ function sanitizeSnapshotUrl(value) {
100
+ try {
101
+ const url = new URL(value);
102
+ url.username = "";
103
+ url.password = "";
104
+ url.hash = "";
105
+ for (const key of [...url.searchParams.keys()]) {
106
+ if (/(?:token|auth|password|secret|session|cookie|key|signature)/iu.test(
107
+ key
108
+ )) {
109
+ url.searchParams.set(key, "[redacted]");
110
+ }
111
+ }
112
+ return url.toString();
113
+ } catch {
114
+ return boundedString(value, 4e3);
115
+ }
116
+ }
117
+ function boundedString(value, maxChars) {
118
+ return typeof value === "string" ? value.slice(0, maxChars) : "";
119
+ }
120
+ function nullableBoundedString(value, maxChars) {
121
+ const normalized = boundedString(value, maxChars).trim();
122
+ return normalized || null;
123
+ }
124
+ function boundedStringArray(value, maxItems, maxChars) {
125
+ return Array.isArray(value) ? value.slice(0, maxItems).filter((item) => typeof item === "string").map((item) => item.slice(0, maxChars)) : [];
126
+ }
127
+ function boundedStringRecord(value, maxItems, maxKeyChars, maxValueChars) {
128
+ if (!isRecord(value)) return {};
129
+ return Object.fromEntries(
130
+ Object.entries(value).slice(0, maxItems).flatMap(
131
+ ([key, item]) => typeof item === "string" ? [[key.slice(0, maxKeyChars), item.slice(0, maxValueChars)]] : []
132
+ )
133
+ );
134
+ }
135
+ function finiteNumber(value, fallback = 0) {
136
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
137
+ }
138
+ function isRecord(value) {
139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
140
+ }
141
+
142
+ // workbench/browser-element-context/browserElementSelectorScript.ts
143
+ function runTuttiBrowserElementSelector() {
144
+ const selectorGlobal = globalThis;
145
+ selectorGlobal.__tuttiBrowserElementSelector?.cancel();
146
+ return new Promise((resolve) => {
147
+ const overlay = document.createElement("div");
148
+ overlay.setAttribute("data-tutti-browser-element-highlight", "true");
149
+ Object.assign(overlay.style, {
150
+ background: "rgba(86, 156, 214, 0.12)",
151
+ border: "2px solid rgb(86, 156, 214)",
152
+ borderRadius: "3px",
153
+ boxSizing: "border-box",
154
+ display: "none",
155
+ left: "0",
156
+ pointerEvents: "none",
157
+ position: "fixed",
158
+ top: "0",
159
+ zIndex: "2147483647"
160
+ });
161
+ document.documentElement.append(overlay);
162
+ const previousCursor = document.documentElement.style.cursor;
163
+ document.documentElement.style.cursor = "crosshair";
164
+ const finish = (value) => {
165
+ document.removeEventListener("mousemove", handleMouseMove, true);
166
+ document.removeEventListener("click", handleClick, true);
167
+ document.removeEventListener("keydown", handleKeyDown, true);
168
+ document.documentElement.style.cursor = previousCursor;
169
+ overlay.remove();
170
+ delete selectorGlobal.__tuttiBrowserElementSelector;
171
+ resolve(value);
172
+ };
173
+ const cancel = () => finish({ status: "cancelled" });
174
+ selectorGlobal.__tuttiBrowserElementSelector = { cancel };
175
+ function handleMouseMove(event) {
176
+ const target = event.target;
177
+ if (!(target instanceof Element) || target === overlay) return;
178
+ const rect = target.getBoundingClientRect();
179
+ Object.assign(overlay.style, {
180
+ display: "block",
181
+ height: `${Math.max(0, rect.height)}px`,
182
+ transform: `translate(${rect.left}px, ${rect.top}px)`,
183
+ width: `${Math.max(0, rect.width)}px`
184
+ });
185
+ }
186
+ function handleClick(event) {
187
+ const target = event.target;
188
+ if (!(target instanceof Element) || target === overlay) return;
189
+ event.preventDefault();
190
+ event.stopImmediatePropagation();
191
+ finish({ snapshot: captureSnapshot(target), status: "selected" });
192
+ }
193
+ function handleKeyDown(event) {
194
+ if (event.key !== "Escape") return;
195
+ event.preventDefault();
196
+ event.stopImmediatePropagation();
197
+ cancel();
198
+ }
199
+ function captureSnapshot(target) {
200
+ const rect = target.getBoundingClientRect();
201
+ const rawText = isFormControl(target) ? "" : (target.textContent ?? "").replace(/\s+/gu, " ").trim();
202
+ const clone = target.cloneNode(true);
203
+ clone.querySelectorAll("script,style,noscript,template").forEach((item) => item.remove());
204
+ sanitizeElement(clone);
205
+ clone.querySelectorAll("*").forEach(sanitizeElement);
206
+ const rawHtml = clone.outerHTML;
207
+ const computed = getComputedStyle(target);
208
+ const styleNames = [
209
+ "display",
210
+ "position",
211
+ "color",
212
+ "background-color",
213
+ "font-family",
214
+ "font-size",
215
+ "font-weight",
216
+ "line-height",
217
+ "margin",
218
+ "padding",
219
+ "border",
220
+ "border-radius",
221
+ "width",
222
+ "height",
223
+ "flex",
224
+ "grid-template-columns",
225
+ "align-items",
226
+ "justify-content",
227
+ "overflow",
228
+ "opacity",
229
+ "visibility"
230
+ ];
231
+ return {
232
+ format: "tutti.browser-element.v1",
233
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
234
+ page: {
235
+ title: document.title,
236
+ url: sanitizeUrl(location.href)
237
+ },
238
+ element: {
239
+ ariaLabel: target.getAttribute("aria-label"),
240
+ attributes: readAttributes(target),
241
+ bounds: {
242
+ height: rect.height,
243
+ width: rect.width,
244
+ x: rect.x,
245
+ y: rect.y
246
+ },
247
+ classes: [...target.classList].slice(0, 24),
248
+ domPath: domPathFor(target),
249
+ html: rawHtml.slice(0, 32e3),
250
+ htmlTruncated: rawHtml.length > 32e3,
251
+ id: target.id || null,
252
+ role: target.getAttribute("role"),
253
+ selector: selectorFor(target),
254
+ styles: Object.fromEntries(
255
+ styleNames.map((name) => [name, computed.getPropertyValue(name)])
256
+ ),
257
+ tagName: target.tagName.toLowerCase(),
258
+ text: rawText.slice(0, 4e3),
259
+ textTruncated: rawText.length > 4e3
260
+ },
261
+ ancestors: readAncestors(target),
262
+ viewport: {
263
+ devicePixelRatio: globalThis.devicePixelRatio,
264
+ height: globalThis.innerHeight,
265
+ scrollX: globalThis.scrollX,
266
+ scrollY: globalThis.scrollY,
267
+ width: globalThis.innerWidth
268
+ }
269
+ };
270
+ }
271
+ function sanitizeElement(element) {
272
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLOptionElement) {
273
+ element.removeAttribute("value");
274
+ if ("value" in element) element.value = "";
275
+ if (element instanceof HTMLTextAreaElement) element.textContent = "";
276
+ }
277
+ for (const attribute of [...element.attributes]) {
278
+ const name = attribute.name.toLowerCase();
279
+ if (name.startsWith("on") || name === "nonce" || name === "integrity" || name === "srcdoc" || /(?:token|auth|password|secret|session|cookie|api[-_]?key)/iu.test(
280
+ name
281
+ )) {
282
+ element.setAttribute(attribute.name, "[redacted]");
283
+ continue;
284
+ }
285
+ if (name === "href" || name === "src" || name === "action") {
286
+ element.setAttribute(attribute.name, sanitizeUrl(attribute.value));
287
+ }
288
+ }
289
+ }
290
+ function isFormControl(element) {
291
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
292
+ }
293
+ function readAttributes(element) {
294
+ const result = {};
295
+ for (const attribute of [...element.attributes].slice(0, 40)) {
296
+ const name = attribute.name.toLowerCase();
297
+ if (name.startsWith("on") || name === "value" || name === "srcdoc") {
298
+ continue;
299
+ }
300
+ result[attribute.name] = /(?:token|auth|password|secret|session|cookie|api[-_]?key)/iu.test(
301
+ name
302
+ ) ? "[redacted]" : name === "href" || name === "src" || name === "action" ? sanitizeUrl(attribute.value) : attribute.value.slice(0, 2e3);
303
+ }
304
+ return result;
305
+ }
306
+ function sanitizeUrl(value) {
307
+ try {
308
+ const url = new URL(value, location.href);
309
+ url.username = "";
310
+ url.password = "";
311
+ url.hash = "";
312
+ for (const key of [...url.searchParams.keys()]) {
313
+ if (/(?:token|auth|password|secret|session|cookie|key|signature)/iu.test(
314
+ key
315
+ )) {
316
+ url.searchParams.set(key, "[redacted]");
317
+ }
318
+ }
319
+ return url.toString();
320
+ } catch {
321
+ return value.slice(0, 4e3);
322
+ }
323
+ }
324
+ function selectorFor(element) {
325
+ if (element.id) return `#${escapeCss(element.id)}`;
326
+ const segments = [];
327
+ let current = element;
328
+ while (current && segments.length < 8) {
329
+ let segment = current.tagName.toLowerCase();
330
+ const parentElement = current.parentElement;
331
+ if (parentElement) {
332
+ const currentTagName = current.tagName;
333
+ const siblings = [...parentElement.children].filter(
334
+ (item) => item.tagName === currentTagName
335
+ );
336
+ if (siblings.length > 1) {
337
+ segment += `:nth-of-type(${siblings.indexOf(current) + 1})`;
338
+ }
339
+ }
340
+ segments.unshift(segment);
341
+ current = parentElement;
342
+ }
343
+ return segments.join(" > ");
344
+ }
345
+ function domPathFor(element) {
346
+ const appRoot = document.querySelector("#app");
347
+ const root = appRoot?.contains(element) === true ? appRoot : document.body ?? document.documentElement;
348
+ const segments = [];
349
+ let current = element;
350
+ while (current) {
351
+ segments.unshift(domPathSegment(current, current === root));
352
+ if (current === root) break;
353
+ current = current.parentElement;
354
+ }
355
+ return segments.join(" > ").slice(0, 4e3);
356
+ }
357
+ function domPathSegment(element, isRoot) {
358
+ if (isRoot && element.id === "app") return "#app";
359
+ const tagName = element.tagName.toLowerCase();
360
+ const classNames = [...element.classList].filter(Boolean).slice(0, 6).map((name) => `.${name}`).join("");
361
+ if (classNames) return `${tagName}${classNames}`;
362
+ if (element.id) return `${tagName}#${element.id}`;
363
+ return tagName;
364
+ }
365
+ function escapeCss(value) {
366
+ return globalThis.CSS?.escape ? globalThis.CSS.escape(value) : value.replace(/[^a-zA-Z0-9_-]/gu, (character) => `\\${character}`);
367
+ }
368
+ function readAncestors(element) {
369
+ const result = [];
370
+ let current = element.parentElement;
371
+ while (current && result.length < 8) {
372
+ result.push({
373
+ id: current.id || null,
374
+ role: current.getAttribute("role"),
375
+ selector: selectorFor(current),
376
+ tagName: current.tagName.toLowerCase()
377
+ });
378
+ current = current.parentElement;
379
+ }
380
+ return result;
381
+ }
382
+ document.addEventListener("mousemove", handleMouseMove, true);
383
+ document.addEventListener("click", handleClick, true);
384
+ document.addEventListener("keydown", handleKeyDown, true);
385
+ });
386
+ }
387
+ var browserElementSelectorScript = `(${runTuttiBrowserElementSelector.toString()})()`;
388
+ var cancelBrowserElementSelectorScript = "globalThis.__tuttiBrowserElementSelector?.cancel();";
389
+
390
+ // workbench/browser-element-context/browserElementWebview.ts
391
+ var browserElementWebviewReadyTimeoutMs = 2e3;
392
+ function isBrowserElementWebviewReady(webview) {
393
+ if (!webview.isConnected) return false;
394
+ try {
395
+ const webContentsId = webview.getWebContentsId?.();
396
+ return typeof webContentsId === "number" && Number.isFinite(webContentsId);
397
+ } catch {
398
+ return false;
399
+ }
400
+ }
401
+ async function waitForBrowserElementWebviewReady(webview, timeoutMs = browserElementWebviewReadyTimeoutMs) {
402
+ if (isBrowserElementWebviewReady(webview)) return true;
403
+ if (!webview.isConnected) return false;
404
+ return new Promise((resolve) => {
405
+ let settled = false;
406
+ let timeoutSignal = null;
407
+ const finish = (ready) => {
408
+ if (settled) return;
409
+ settled = true;
410
+ timeoutSignal?.removeEventListener("abort", handleTimeout);
411
+ webview.removeEventListener("dom-ready", handleDomReady);
412
+ resolve(ready);
413
+ };
414
+ const handleDomReady = () => {
415
+ finish(isBrowserElementWebviewReady(webview));
416
+ };
417
+ timeoutSignal = AbortSignal.timeout(timeoutMs);
418
+ const handleTimeout = () => finish(false);
419
+ timeoutSignal.addEventListener("abort", handleTimeout, { once: true });
420
+ webview.addEventListener("dom-ready", handleDomReady, { once: true });
421
+ if (isBrowserElementWebviewReady(webview)) finish(true);
422
+ });
423
+ }
424
+ async function executeBrowserElementWebviewScript(webview, script, userGesture = false) {
425
+ if (!webview.executeJavaScript) {
426
+ throw new Error("Browser webview JavaScript execution is unavailable");
427
+ }
428
+ if (!await waitForBrowserElementWebviewReady(webview)) {
429
+ throw new Error("Browser webview is not ready");
430
+ }
431
+ return webview.executeJavaScript(script, userGesture);
432
+ }
433
+ async function cancelBrowserElementWebviewSelection(webview, script, onError) {
434
+ if (!webview?.executeJavaScript || !webview.isConnected) return;
435
+ try {
436
+ await webview.executeJavaScript(script);
437
+ } catch (error) {
438
+ if (onError) {
439
+ onError(error);
440
+ } else {
441
+ console.warn(
442
+ "[agent-gui] browser element selection cleanup failed",
443
+ error
444
+ );
445
+ }
446
+ }
447
+ }
448
+
449
+ // workbench/browser-element-context/browserElementMention.ts
450
+ import { createRichTextMentionMarkdown } from "@tutti-os/ui-rich-text/core";
451
+ var browserElementMentionKind = "browser-element";
452
+ function createBrowserElementMentionMarkdown(reference) {
453
+ const tagName = normalizeBrowserElementTagName(reference.tagName);
454
+ const context = reference.context.trim();
455
+ const workspaceId = reference.workspaceId.trim();
456
+ if (!tagName || !context || !workspaceId) {
457
+ return "";
458
+ }
459
+ return createRichTextMentionMarkdown({
460
+ providerId: browserElementMentionKind,
461
+ entityId: reference.id,
462
+ label: tagName,
463
+ scope: { context, tag: tagName, workspaceId }
464
+ });
465
+ }
466
+ function browserElementMentionLabel(tagName) {
467
+ const normalized = normalizeBrowserElementTagName(tagName);
468
+ return normalized ? `<${normalized}>` : "";
469
+ }
470
+ function presentBrowserElementMention(mention) {
471
+ const label = browserElementMentionLabel(
472
+ mention.scope?.tag?.trim() || mention.label
473
+ );
474
+ if (!label) {
475
+ return null;
476
+ }
477
+ const workspaceId = mention.scope?.workspaceId?.trim() ?? "";
478
+ return {
479
+ name: label,
480
+ ...workspaceId ? { workspaceId } : {}
481
+ };
482
+ }
483
+ function normalizeBrowserElementTagName(value) {
484
+ return value.trim().toLowerCase().replace(/[^a-z0-9:-]/gu, "").slice(0, 100);
485
+ }
486
+
487
+ // workbench/browser-element-context/browserElementContextSelectionController.ts
488
+ var BrowserElementContextSelectionController = class {
489
+ activeWebview = null;
490
+ detachFrame = null;
491
+ input;
492
+ listeners = /* @__PURE__ */ new Set();
493
+ session = null;
494
+ snapshot = { state: "idle" };
495
+ subscribedWebview = null;
496
+ constructor(input) {
497
+ this.input = input;
498
+ }
499
+ getSnapshot = () => this.snapshot;
500
+ subscribe = (listener) => {
501
+ this.listeners.add(listener);
502
+ return () => this.listeners.delete(listener);
503
+ };
504
+ configure(input) {
505
+ this.input = input;
506
+ }
507
+ bindAction(node, activeWebview) {
508
+ if (node) {
509
+ if (this.detachFrame !== null) {
510
+ cancelAnimationFrame(this.detachFrame);
511
+ this.detachFrame = null;
512
+ }
513
+ this.moveToActiveWebview(activeWebview);
514
+ return;
515
+ }
516
+ if (this.detachFrame !== null) cancelAnimationFrame(this.detachFrame);
517
+ this.detachFrame = requestAnimationFrame(() => {
518
+ this.detachFrame = null;
519
+ this.dispose();
520
+ });
521
+ }
522
+ toggle(activeWebview) {
523
+ if (this.session) {
524
+ this.endSelection(this.session);
525
+ return;
526
+ }
527
+ if (!activeWebview?.executeJavaScript) {
528
+ this.input.onError(this.input.failedCopy);
529
+ return;
530
+ }
531
+ const session = {
532
+ attempt: 0,
533
+ navigationPending: false,
534
+ webview: null
535
+ };
536
+ this.session = session;
537
+ this.setState("selecting");
538
+ this.moveToActiveWebview(activeWebview);
539
+ }
540
+ handleStartLoading = () => {
541
+ if (this.session) this.session.navigationPending = true;
542
+ };
543
+ handleDomReady = () => {
544
+ const session = this.session;
545
+ const webview = this.activeWebview;
546
+ if (!session || !webview) return;
547
+ session.navigationPending = false;
548
+ void this.moveSelectionToWebview(session, webview, true);
549
+ };
550
+ moveToActiveWebview(webview) {
551
+ this.activeWebview = webview;
552
+ const session = this.session;
553
+ if (!session) return;
554
+ if (!webview) {
555
+ const previousWebview = session.webview;
556
+ session.webview = null;
557
+ session.attempt += 1;
558
+ this.unsubscribeFromWebview();
559
+ void cancelBrowserElementWebviewSelection(
560
+ previousWebview,
561
+ cancelBrowserElementSelectorScript
562
+ );
563
+ return;
564
+ }
565
+ this.subscribeToWebview(webview);
566
+ void this.moveSelectionToWebview(session, webview);
567
+ }
568
+ subscribeToWebview(webview) {
569
+ if (this.subscribedWebview === webview) return;
570
+ this.unsubscribeFromWebview();
571
+ this.subscribedWebview = webview;
572
+ webview.addEventListener("did-start-loading", this.handleStartLoading);
573
+ webview.addEventListener("dom-ready", this.handleDomReady);
574
+ }
575
+ unsubscribeFromWebview() {
576
+ this.subscribedWebview?.removeEventListener(
577
+ "did-start-loading",
578
+ this.handleStartLoading
579
+ );
580
+ this.subscribedWebview?.removeEventListener(
581
+ "dom-ready",
582
+ this.handleDomReady
583
+ );
584
+ this.subscribedWebview = null;
585
+ }
586
+ async moveSelectionToWebview(session, webview, force = false) {
587
+ if (this.session !== session) return;
588
+ if (!force && session.webview === webview && session.attempt > 0) return;
589
+ const previousWebview = session.webview;
590
+ session.webview = webview;
591
+ session.navigationPending = false;
592
+ const attempt = ++session.attempt;
593
+ await cancelBrowserElementWebviewSelection(
594
+ previousWebview && (force || previousWebview !== webview) ? previousWebview : null,
595
+ cancelBrowserElementSelectorScript
596
+ );
597
+ if (!this.isCurrentSelectionAttempt(session, attempt)) return;
598
+ void this.runSelectionAttempt(session, webview, attempt);
599
+ }
600
+ async runSelectionAttempt(session, webview, attempt) {
601
+ try {
602
+ const rawResult = await executeBrowserElementWebviewScript(
603
+ webview,
604
+ browserElementSelectorScript,
605
+ true
606
+ );
607
+ if (!this.isCurrentSelectionAttempt(session, attempt)) return;
608
+ const result = normalizeBrowserElementSelectionResult(rawResult);
609
+ if (!result || result.status === "cancelled") {
610
+ this.endSelection(session);
611
+ return;
612
+ }
613
+ const content = serializeBrowserElementSnapshot(result.snapshot);
614
+ const mention = createBrowserElementMentionMarkdown({
615
+ context: content,
616
+ id: createBrowserElementReferenceId(),
617
+ tagName: result.snapshot.element.tagName,
618
+ workspaceId: this.input.workspaceId
619
+ });
620
+ if (!mention)
621
+ throw new Error("Browser element mention could not be created");
622
+ this.input.onAppendMention(mention);
623
+ this.endSelection(session);
624
+ } catch {
625
+ if (!this.isCurrentSelectionAttempt(session, attempt)) return;
626
+ if (session.navigationPending) return;
627
+ this.input.onError(this.input.failedCopy);
628
+ this.endSelection(session);
629
+ }
630
+ }
631
+ isCurrentSelectionAttempt(session, attempt) {
632
+ return this.session === session && session.attempt === attempt;
633
+ }
634
+ endSelection(session) {
635
+ if (this.session !== session) return;
636
+ this.session = null;
637
+ session.attempt += 1;
638
+ const webview = session.webview;
639
+ session.webview = null;
640
+ this.unsubscribeFromWebview();
641
+ void cancelBrowserElementWebviewSelection(
642
+ webview,
643
+ cancelBrowserElementSelectorScript
644
+ );
645
+ this.setState("idle");
646
+ }
647
+ setState(state) {
648
+ if (this.snapshot.state === state) return;
649
+ this.snapshot = { state };
650
+ for (const listener of this.listeners) listener(this.snapshot);
651
+ }
652
+ dispose() {
653
+ const session = this.session;
654
+ if (session) this.endSelection(session);
655
+ this.unsubscribeFromWebview();
656
+ this.activeWebview = null;
657
+ }
658
+ };
659
+ function createBrowserElementReferenceId() {
660
+ return `browser-element:${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
661
+ }
662
+
663
+ // workbench/browser-element-context/BrowserElementContextAction.tsx
664
+ import { jsx, jsxs } from "react/jsx-runtime";
665
+ function BrowserElementContextAction({
666
+ copy,
667
+ onAppendMention,
668
+ onError,
669
+ workspaceId
670
+ }) {
671
+ const activeWebview = useActiveBrowserNodeWebview();
672
+ const [controller] = useState(
673
+ () => new BrowserElementContextSelectionController({
674
+ failedCopy: copy.failed,
675
+ onAppendMention,
676
+ onError,
677
+ workspaceId
678
+ })
679
+ );
680
+ controller.configure({
681
+ failedCopy: copy.failed,
682
+ onAppendMention,
683
+ onError,
684
+ workspaceId
685
+ });
686
+ const state = useEngineSelector(controller, (snapshot) => snapshot.state);
687
+ const bindAction = useCallback(
688
+ (node) => controller.bindAction(node, activeWebview),
689
+ [activeWebview, controller]
690
+ );
691
+ const label = state === "selecting" ? copy.cancel : copy.select;
692
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
693
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
694
+ Button,
695
+ {
696
+ "aria-label": label,
697
+ "aria-pressed": state === "selecting",
698
+ className: cn(
699
+ "nodrag shrink-0 rounded-md",
700
+ state === "selecting" && "bg-[var(--transparency-block)] text-[var(--text-primary)]"
701
+ ),
702
+ size: "icon-sm",
703
+ ref: bindAction,
704
+ type: "button",
705
+ variant: "chrome",
706
+ onClick: () => controller.toggle(activeWebview),
707
+ children: /* @__PURE__ */ jsx(InspectIcon, { className: "size-[15px]" })
708
+ }
709
+ ) }),
710
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: label })
711
+ ] });
712
+ }
713
+
714
+ // workbench/browser-element-context/registerBrowserElementMention.tsx
715
+ import { Badge } from "@tutti-os/ui-system";
716
+ import { InspectIcon as InspectIcon2 } from "@tutti-os/ui-system/icons";
717
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
718
+ function registerBrowserElementMention() {
719
+ registerAgentCustomMentionKind({
720
+ kind: browserElementMentionKind,
721
+ materializePromptText: (mention) => {
722
+ const context = mention.scope?.context?.trim();
723
+ return context ? `
724
+ ${context}
725
+ ` : null;
726
+ },
727
+ present: presentBrowserElementMention,
728
+ renderChip: ({ name }) => /* @__PURE__ */ jsxs2(
729
+ Badge,
730
+ {
731
+ className: "h-5 max-w-full gap-1.5 rounded-[6px] px-2 py-0 font-[var(--font-mono)] text-[13px] font-semibold leading-5",
732
+ "data-agent-browser-element-chip": "true",
733
+ variant: "accent",
734
+ children: [
735
+ /* @__PURE__ */ jsx2(InspectIcon2, { "aria-hidden": "true", className: "size-3.5 shrink-0" }),
736
+ /* @__PURE__ */ jsx2("span", { className: "truncate", children: name })
737
+ ]
738
+ }
739
+ )
740
+ });
741
+ }
742
+ export {
743
+ BrowserElementContextAction,
744
+ browserElementMentionKind,
745
+ browserElementMentionLabel,
746
+ createBrowserElementMentionMarkdown,
747
+ presentBrowserElementMention,
748
+ registerBrowserElementMention
749
+ };
750
+ //# sourceMappingURL=index.js.map