@burdenoff/fe-libs 2026.1026.1 → 2026.1027.2

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 (52) hide show
  1. package/dist/chrome/ProfileSwitcher.js +236 -213
  2. package/dist/credentials/SchemaForm.js +2 -0
  3. package/dist/shared/agent-tools/AgentToolsProvider.d.ts +33 -0
  4. package/dist/shared/agent-tools/AgentToolsProvider.js +48 -0
  5. package/dist/shared/agent-tools/adapters/assistant.d.ts +85 -0
  6. package/dist/shared/agent-tools/adapters/assistant.js +110 -0
  7. package/dist/shared/agent-tools/adapters/webmcp.d.ts +69 -0
  8. package/dist/shared/agent-tools/adapters/webmcp.js +81 -0
  9. package/dist/shared/agent-tools/confirm/AgentConfirmHost.d.ts +12 -0
  10. package/dist/shared/agent-tools/confirm/AgentConfirmHost.js +34 -0
  11. package/dist/shared/agent-tools/confirm/confirmStore.d.ts +37 -0
  12. package/dist/shared/agent-tools/confirm/confirmStore.js +58 -0
  13. package/dist/shared/agent-tools/defineAgentTool.d.ts +13 -0
  14. package/dist/shared/agent-tools/defineAgentTool.js +27 -0
  15. package/dist/shared/agent-tools/errors.d.ts +33 -0
  16. package/dist/shared/agent-tools/errors.js +105 -0
  17. package/dist/shared/agent-tools/index.d.ts +29 -0
  18. package/dist/shared/agent-tools/limits.d.ts +20 -0
  19. package/dist/shared/agent-tools/limits.js +4 -0
  20. package/dist/shared/agent-tools/log.d.ts +15 -0
  21. package/dist/shared/agent-tools/log.js +11 -0
  22. package/dist/shared/agent-tools/outputBudget.d.ts +21 -0
  23. package/dist/shared/agent-tools/outputBudget.js +51 -0
  24. package/dist/shared/agent-tools/registry.d.ts +13 -0
  25. package/dist/shared/agent-tools/registry.js +53 -0
  26. package/dist/shared/agent-tools/runAgentTool.d.ts +21 -0
  27. package/dist/shared/agent-tools/runAgentTool.js +57 -0
  28. package/dist/shared/agent-tools/shell/shellTools.d.ts +44 -0
  29. package/dist/shared/agent-tools/shell/shellTools.js +145 -0
  30. package/dist/shared/agent-tools/testSupport/installDom.d.ts +6 -0
  31. package/dist/shared/agent-tools/types.d.ts +106 -0
  32. package/dist/shared/agent-tools/ui/dom.d.ts +43 -0
  33. package/dist/shared/agent-tools/ui/dom.js +178 -0
  34. package/dist/shared/agent-tools/ui/refs.d.ts +8 -0
  35. package/dist/shared/agent-tools/ui/refs.js +20 -0
  36. package/dist/shared/agent-tools/ui/uiTools.d.ts +40 -0
  37. package/dist/shared/agent-tools/ui/uiTools.js +277 -0
  38. package/dist/shared/agent-tools/useAgentTools.d.ts +14 -0
  39. package/dist/shared/agent-tools/useAgentTools.js +14 -0
  40. package/dist/shared/assistant/api.d.ts +18 -0
  41. package/dist/shared/assistant/api.js +145 -131
  42. package/dist/shared/assistant/ui/AssistantChatArea.js +1158 -1148
  43. package/dist/shared/assistant/writeProposals.d.ts +6 -0
  44. package/dist/shared/providers/shell/ActiveContextProvider.d.ts +5 -0
  45. package/dist/shared/providers/shell/ActiveContextProvider.js +4 -1
  46. package/dist/shared/providers/shell/index.d.ts +1 -1
  47. package/dist/shared-agent-tools.d.ts +2 -0
  48. package/dist/shared-agent-tools.js +16 -0
  49. package/dist/shared-assistant.js +285 -285
  50. package/dist/shared-providers-shell.js +20 -20
  51. package/dist/ui/alert-dialog.js +2 -0
  52. package/package.json +7 -2
@@ -0,0 +1,178 @@
1
+ //#region src/shared/agent-tools/ui/dom.ts
2
+ var e = [
3
+ "a[href]",
4
+ "button",
5
+ "input:not([type=\"hidden\"])",
6
+ "select",
7
+ "textarea",
8
+ "summary",
9
+ "[contenteditable=\"true\"]",
10
+ "[contenteditable=\"\"]",
11
+ ...[
12
+ "button",
13
+ "link",
14
+ "tab",
15
+ "menuitem",
16
+ "menuitemcheckbox",
17
+ "menuitemradio",
18
+ "option",
19
+ "checkbox",
20
+ "radio",
21
+ "switch",
22
+ "combobox",
23
+ "textbox",
24
+ "searchbox",
25
+ "slider",
26
+ "spinbutton",
27
+ "treeitem"
28
+ ].map((e) => `[role="${e}"]`)
29
+ ].join(", "), t = [
30
+ "[data-agent=\"deny\"]",
31
+ "[data-assistant-panel]",
32
+ "[data-assistant-overlay]",
33
+ "[data-assistant-voice-session]",
34
+ "[data-testid^=\"agent-tool-confirm\"]",
35
+ "[role=\"alertdialog\"][data-testid^=\"assistant-\"]",
36
+ "[role=\"dialog\"][data-testid^=\"assistant-\"]"
37
+ ].join(", "), n = /(^|\s)(current-password|new-password|one-time-code|cc-[a-z-]+|webauthn)(\s|$)/i, r = /pass(word|code|phrase)|passwd|pwd|secret|token|api[-_]?key|private[-_]?key|client[-_]?secret|cvv|cvc|card[-_]?number|iban|ssn/i;
38
+ function i(e) {
39
+ return e.closest(t) !== null;
40
+ }
41
+ function a(e) {
42
+ if (e.closest("[data-agent-sensitive]")) return !0;
43
+ let t = e.tagName.toLowerCase();
44
+ if (t !== "input" && t !== "textarea") return !1;
45
+ if ((e.getAttribute("type") ?? "").toLowerCase() === "password" || n.test(e.getAttribute("autocomplete") ?? "")) return !0;
46
+ let i = `${e.getAttribute("name") ?? ""} ${e.getAttribute("id") ?? ""}`;
47
+ return r.test(i);
48
+ }
49
+ function o(e) {
50
+ if (!e.isConnected || e.closest("[hidden], [inert], [aria-hidden=\"true\"]")) return !1;
51
+ let t = e;
52
+ if (typeof t.checkVisibility == "function") return t.checkVisibility({
53
+ checkOpacity: !1,
54
+ checkVisibilityCSS: !0
55
+ });
56
+ let n = e.ownerDocument.defaultView;
57
+ if (!n) return !0;
58
+ for (let t = e; t; t = t.parentElement) {
59
+ let e = n.getComputedStyle(t);
60
+ if (e.display === "none" || e.visibility === "hidden") return !1;
61
+ }
62
+ return !0;
63
+ }
64
+ function s(e) {
65
+ return e.getAttribute("aria-disabled") === "true" ? !0 : "disabled" in e && e.disabled === !0;
66
+ }
67
+ var c = {
68
+ checkbox: "checkbox",
69
+ radio: "radio",
70
+ range: "slider",
71
+ number: "spinbutton",
72
+ button: "button",
73
+ submit: "button",
74
+ reset: "button",
75
+ image: "button",
76
+ search: "searchbox",
77
+ file: "file"
78
+ };
79
+ function l(e) {
80
+ let t = e.getAttribute("role")?.trim().split(/\s+/)[0];
81
+ if (t) return t;
82
+ switch (e.tagName.toLowerCase()) {
83
+ case "a": return "link";
84
+ case "button":
85
+ case "summary": return "button";
86
+ case "select": return e.multiple ? "listbox" : "combobox";
87
+ case "textarea": return "textbox";
88
+ case "input": return c[(e.getAttribute("type") ?? "text").toLowerCase()] ?? "textbox";
89
+ default: return e.hasAttribute("contenteditable") ? "textbox" : "generic";
90
+ }
91
+ }
92
+ function u(e) {
93
+ return (e ?? "").replace(/\s+/g, " ").trim();
94
+ }
95
+ var d = 80;
96
+ function f(e) {
97
+ let t = e.ownerDocument, n = (e) => e.length > d ? `${e.slice(0, d - 1)}…` : e, r = e.getAttribute("aria-labelledby");
98
+ if (r) {
99
+ let e = u(r.split(/\s+/).map((e) => t.getElementById(e)?.textContent ?? "").join(" "));
100
+ if (e) return n(e);
101
+ }
102
+ let i = u(e.getAttribute("aria-label"));
103
+ if (i) return n(i);
104
+ let a = e.labels;
105
+ if (a && a.length > 0) {
106
+ let e = u(Array.from(a, (e) => e.textContent ?? "").join(" "));
107
+ if (e) return n(e);
108
+ }
109
+ let o = u(e.getAttribute("title"));
110
+ if (o) return n(o);
111
+ let s = e.tagName.toLowerCase();
112
+ if (!(s === "textarea" || s === "select" || s === "input" && ![
113
+ "button",
114
+ "submit",
115
+ "reset"
116
+ ].includes((e.getAttribute("type") ?? "").toLowerCase()))) {
117
+ let t = u(e.textContent);
118
+ if (t) return n(t);
119
+ let r = u(e.querySelector("img[alt]")?.getAttribute("alt"));
120
+ if (r) return n(r);
121
+ if (s === "input") {
122
+ let t = u(e.value);
123
+ if (t) return n(t);
124
+ }
125
+ }
126
+ return n(u(e.getAttribute("placeholder")));
127
+ }
128
+ function p(e) {
129
+ let t = [];
130
+ s(e) && t.push("disabled");
131
+ let n = e.tagName.toLowerCase(), r = (e.getAttribute("type") ?? "").toLowerCase();
132
+ n === "input" && (r === "checkbox" || r === "radio") ? e.checked && t.push("checked") : e.getAttribute("aria-checked") === "true" && t.push("checked");
133
+ for (let n of [
134
+ "expanded",
135
+ "selected",
136
+ "pressed"
137
+ ]) e.getAttribute(`aria-${n}`) === "true" && t.push(n);
138
+ return e.getAttribute("aria-current") && e.getAttribute("aria-current") !== "false" && t.push("current"), (e.hasAttribute("required") || e.getAttribute("aria-required") === "true") && t.push("required"), t;
139
+ }
140
+ var m = 60;
141
+ function h(e) {
142
+ if (a(e)) return null;
143
+ let t = e.tagName.toLowerCase(), n = null;
144
+ if (t === "select") {
145
+ let t = e;
146
+ n = t.selectedOptions[0]?.textContent ?? t.value;
147
+ } else if (t === "textarea") n = e.value;
148
+ else if (t === "input") {
149
+ let t = (e.getAttribute("type") ?? "text").toLowerCase();
150
+ if ([
151
+ "checkbox",
152
+ "radio",
153
+ "button",
154
+ "submit",
155
+ "reset",
156
+ "image",
157
+ "file"
158
+ ].includes(t)) return null;
159
+ n = e.value;
160
+ } else (e.getAttribute("role") === "combobox" || e.hasAttribute("aria-valuenow")) && (n = e.getAttribute("aria-valuetext") ?? e.getAttribute("aria-valuenow"));
161
+ let r = u(n);
162
+ return r ? r.length > m ? `${r.slice(0, m - 1)}…` : r : null;
163
+ }
164
+ function g(e) {
165
+ let t = Array.from(e.querySelectorAll("[role=\"dialog\"], [role=\"alertdialog\"], dialog[open]")).filter((e) => o(e) && !i(e)), n = t[t.length - 1];
166
+ return n ? _(n) || "Dialog" : null;
167
+ }
168
+ function _(e) {
169
+ let t = e.getAttribute("aria-labelledby");
170
+ if (t) {
171
+ let n = u(e.ownerDocument.getElementById(t)?.textContent);
172
+ if (n) return n.slice(0, d);
173
+ }
174
+ let n = u(e.getAttribute("aria-label"));
175
+ return n ? n.slice(0, d) : u(e.querySelector("h1, h2, h3")?.textContent).slice(0, d);
176
+ }
177
+ //#endregion
178
+ export { t as DENY_SELECTOR, e as INTERACTIVE_SELECTOR, f as accessibleName, i as isDenied, s as isDisabled, a as isSensitiveField, o as isVisible, g as openDialogTitle, l as roleOf, p as stateOf, h as valueOf };
@@ -0,0 +1,8 @@
1
+ export declare const REF_PATTERN: RegExp;
2
+ export declare function refFor(element: Element): string;
3
+ /** The element for `ref`, or null when it is gone from the page. */
4
+ export declare function elementFor(ref: string): Element | null;
5
+ /** Forget refs whose elements are gone. Called on every snapshot. */
6
+ export declare function pruneRefs(): void;
7
+ /** Test seam. */
8
+ export declare function resetRefs(): void;
@@ -0,0 +1,20 @@
1
+ //#region src/shared/agent-tools/ui/refs.ts
2
+ var e = /* @__PURE__ */ new WeakMap(), t = /* @__PURE__ */ new Map(), n = 1, r = /^e\d{1,7}$/;
3
+ function i(r) {
4
+ let i = e.get(r);
5
+ if (i) return t.has(i) || t.set(i, new WeakRef(r)), i;
6
+ let a = `e${n}`;
7
+ return n += 1, e.set(r, a), t.set(a, new WeakRef(r)), a;
8
+ }
9
+ function a(e) {
10
+ let n = t.get(e)?.deref();
11
+ return !n || !n.isConnected ? null : n;
12
+ }
13
+ function o() {
14
+ for (let [e, n] of t) {
15
+ let r = n.deref();
16
+ (!r || !r.isConnected) && t.delete(e);
17
+ }
18
+ }
19
+ //#endregion
20
+ export { r as REF_PATTERN, a as elementFor, o as pruneRefs, i as refFor };
@@ -0,0 +1,40 @@
1
+ import { AnyDefinedAgentTool } from '../types';
2
+ /**
3
+ * ★ BOFF-9783: generic UI tools — see the page, click, type, choose, press a
4
+ * key — so an agent can do in any product what the person can do with a mouse
5
+ * and keyboard, without each product writing a tool per button.
6
+ *
7
+ * Guards, in order of strength:
8
+ * - deny zones (`dom.ts`) are invisible and inoperable: products mark billing,
9
+ * secrets and the like `data-agent="deny"`; the assistant panel and every
10
+ * confirmation dialog are always denied;
11
+ * - sensitive fields (passwords, payment, one-time codes) are listed but never
12
+ * read or filled;
13
+ * - a click on something consequential ("Delete", "Cancel run", "Log out", or
14
+ * `data-agent="confirm"`) waits for the person to confirm with a real click;
15
+ * - what only a person can start (a file picker, a link out of the tab) is
16
+ * refused with `NEEDS_USER`.
17
+ *
18
+ * Every action is an ordinary page event, so the app's own handlers, validation
19
+ * and confirmation dialogs run as they would for the person — and those
20
+ * dialogs ignore the untrusted clicks scripts make, so an agent can open a
21
+ * "Delete?" dialog but never answer it.
22
+ */
23
+ export interface UiToolsOptions {
24
+ /** Where the agent may look. Default `document.body`. */
25
+ root?: () => Element | null;
26
+ }
27
+ /**
28
+ * Would a click on this deserve the person's confirmation?
29
+ *
30
+ * ★ Locale-independent signals come first, because a product's labels may be
31
+ * in any language ("Supprimer", "Eliminar"):
32
+ * - `data-agent="confirm"` on the control or a wrapper (product-declared);
33
+ * - a destructive button (`data-variant="destructive"`, the shared Button);
34
+ * - any control inside an alert dialog except its cancel button
35
+ * (`data-slot="alert-dialog-cancel"`): alert dialogs are where the app asks
36
+ * "are you sure?", so their action is the consequential step.
37
+ * The English verb list is an extra net for unmarked controls, not the rule.
38
+ */
39
+ export declare function isConsequentialControl(element: Element, name: string): boolean;
40
+ export declare function createUiTools(options?: UiToolsOptions): AnyDefinedAgentTool[];
@@ -0,0 +1,277 @@
1
+ import { AgentToolError as e } from "../errors.js";
2
+ import { requestAgentConfirmation as t } from "../confirm/confirmStore.js";
3
+ import { defineAgentTool as n } from "../defineAgentTool.js";
4
+ import { INTERACTIVE_SELECTOR as r, accessibleName as i, isDenied as a, isDisabled as o, isSensitiveField as s, isVisible as c, openDialogTitle as l, roleOf as u, stateOf as d, valueOf as f } from "./dom.js";
5
+ import { REF_PATTERN as p, elementFor as m, pruneRefs as h, refFor as g } from "./refs.js";
6
+ import { z as _ } from "zod";
7
+ //#region src/shared/agent-tools/ui/uiTools.ts
8
+ var v = 1100, y = new TextEncoder(), b = 250, x = 3e3, S = 1200, C = /\b(delete|remove|archive|revoke|deactivate|disable|cancel|terminate|stop|pay|purchase|buy|checkout|transfer|subscribe|unsubscribe|upgrade|downgrade|log ?out|sign ?out|reset|publish|send|invite|uninstall|disconnect|approve|reject)\b/i, w = /^(cancel|close|dismiss|back|no|not now|keep editing)$/i;
9
+ function T(e, t) {
10
+ if (e.closest("[data-agent=\"confirm\"]") || e.closest("[data-variant=\"destructive\"]")) return !0;
11
+ if (e.closest("[role=\"alertdialog\"]")) return e.closest("[data-slot=\"alert-dialog-cancel\"]") === null;
12
+ let n = t.trim();
13
+ return w.test(n) ? !1 : C.test(n);
14
+ }
15
+ var E = _.string().regex(p, "must be a ref from ui_snapshot, like \"e12\"").describe("The ref of the element, from ui_snapshot (e.g. \"e12\").");
16
+ function D() {
17
+ return typeof document > "u" ? null : document.body;
18
+ }
19
+ function O(t) {
20
+ let n = m(t);
21
+ if (!n) throw new e("NOT_FOUND", "That element is no longer on the page. Take a new ui_snapshot.", { retryable: !0 });
22
+ if (a(n)) throw new e("FORBIDDEN", "That part of the page is off-limits to agents.");
23
+ if (!c(n)) throw new e("CONFLICT", "That element is not visible right now. Take a new ui_snapshot.", { retryable: !0 });
24
+ return n;
25
+ }
26
+ function k(e) {
27
+ let { outline: t, outlineOffset: n } = e.style;
28
+ e.style.outline = "2px solid var(--ring)", e.style.outlineOffset = "2px", setTimeout(() => {
29
+ e.style.outline = t, e.style.outlineOffset = n;
30
+ }, S);
31
+ }
32
+ function A(e) {
33
+ e.scrollIntoView?.({
34
+ block: "center",
35
+ inline: "nearest"
36
+ }), k(e);
37
+ }
38
+ function j(e, t) {
39
+ return new Promise((n) => {
40
+ let r = e.defaultView;
41
+ if (!r || typeof r.MutationObserver != "function") {
42
+ n();
43
+ return;
44
+ }
45
+ let i, a = () => {
46
+ o.disconnect(), clearTimeout(i), clearTimeout(s), t.removeEventListener("abort", a), n();
47
+ }, o = new r.MutationObserver(() => {
48
+ clearTimeout(i), i = setTimeout(a, b);
49
+ });
50
+ o.observe(e.body, {
51
+ childList: !0,
52
+ subtree: !0,
53
+ attributes: !0,
54
+ characterData: !0
55
+ }), i = setTimeout(a, b);
56
+ let s = setTimeout(a, x);
57
+ t.addEventListener("abort", a, { once: !0 });
58
+ });
59
+ }
60
+ function M(e) {
61
+ let t = Array.from(e.querySelectorAll("[data-sonner-toast], [role=\"alert\"]")).filter((e) => c(e) && !a(e)).map((e) => (e.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 120)).filter(Boolean).slice(-3), n = l(e);
62
+ return {
63
+ path: e.defaultView?.location.pathname ?? null,
64
+ ...n ? { dialog: n } : {},
65
+ ...t.length > 0 ? { notices: t } : {}
66
+ };
67
+ }
68
+ function N(e, t) {
69
+ let n = {
70
+ bubbles: !0,
71
+ cancelable: !0,
72
+ composed: !0,
73
+ button: 0,
74
+ view: t
75
+ };
76
+ return typeof t.PointerEvent == "function" ? new t.PointerEvent(e, {
77
+ ...n,
78
+ pointerType: "mouse",
79
+ isPrimary: !0
80
+ }) : new t.MouseEvent(e, n);
81
+ }
82
+ function P(e) {
83
+ let t = e.ownerDocument.defaultView;
84
+ e.focus?.({ preventScroll: !0 }), t && (e.dispatchEvent(N("pointerdown", t)), e.dispatchEvent(new t.MouseEvent("mousedown", {
85
+ bubbles: !0,
86
+ cancelable: !0
87
+ })), e.dispatchEvent(N("pointerup", t)), e.dispatchEvent(new t.MouseEvent("mouseup", {
88
+ bubbles: !0,
89
+ cancelable: !0
90
+ }))), e.click();
91
+ }
92
+ function F(e, t) {
93
+ let n = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(e), "value")?.set;
94
+ n ? n.call(e, t) : e.value = t;
95
+ let r = e.ownerDocument.defaultView, i = r?.InputEvent ?? r?.Event;
96
+ i && e.dispatchEvent(new i("input", {
97
+ bubbles: !0,
98
+ cancelable: !1,
99
+ composed: !0
100
+ })), e.dispatchEvent(new Event("change", { bubbles: !0 }));
101
+ }
102
+ function I(e) {
103
+ let t = e.closest("a[href]");
104
+ if (!t) return !1;
105
+ if (t.target && t.target !== "_self") return !0;
106
+ let n = e.ownerDocument.defaultView?.location.origin;
107
+ try {
108
+ return !!n && new URL(t.href, n).origin !== n;
109
+ } catch {
110
+ return !0;
111
+ }
112
+ }
113
+ function L(p = {}) {
114
+ let m = p.root ?? D;
115
+ return [
116
+ n({
117
+ name: "ui_snapshot",
118
+ title: "Read the screen",
119
+ description: "Lists the controls visible on the page (links, buttons, fields, tabs, menu items) with a ref, role, name, current value and state. Use the refs with ui_click, ui_fill, ui_select and ui_press_key. Take a new snapshot after the page changes. Page through long lists with offset.",
120
+ input: _.strictObject({
121
+ filter: _.string().trim().max(60).optional().describe("Only list controls whose name or role contains this text."),
122
+ offset: _.number().int().min(0).max(5e3).default(0).describe("Index of the first control to list, from nextOffset of a previous call.")
123
+ }),
124
+ annotations: {
125
+ readOnly: !0,
126
+ untrustedContent: !0
127
+ },
128
+ execute: ({ filter: t, offset: n }) => {
129
+ let o = m();
130
+ if (!o) throw new e("NEEDS_USER", "The page is not available.");
131
+ h();
132
+ let p = t?.toLowerCase(), _ = Array.from(o.querySelectorAll(r)).filter((e) => c(e) && !a(e) && u(e) !== "generic"), b = [];
133
+ for (let e of _) {
134
+ let t = u(e), n = i(e);
135
+ if (p && !`${t} ${n}`.toLowerCase().includes(p)) continue;
136
+ let r = s(e), a = r ? null : f(e), o = d(e);
137
+ r && o.push("sensitive"), b.push(`${g(e)} ${t} ${JSON.stringify(n)}${a ? ` value=${JSON.stringify(a)}` : ""}${o.length > 0 ? ` [${o.join(",")}]` : ""}`);
138
+ }
139
+ let x = [], S = 0, C = n;
140
+ for (; C < b.length; C += 1) {
141
+ let e = b[C] ?? "", t = y.encode(e).length + 3;
142
+ if (x.length > 0 && S + t > v) break;
143
+ x.push(e), S += t;
144
+ }
145
+ let w = o.ownerDocument, T = w.querySelector("main h1, h1")?.textContent?.replace(/\s+/g, " ").trim(), E = l(w);
146
+ return {
147
+ path: w.defaultView?.location.pathname ?? null,
148
+ ...T ? { heading: T.slice(0, 120) } : {},
149
+ ...E ? { dialog: E } : {},
150
+ total: b.length,
151
+ items: x,
152
+ ...C < b.length ? { nextOffset: C } : {}
153
+ };
154
+ }
155
+ }),
156
+ n({
157
+ name: "ui_click",
158
+ title: "Click",
159
+ description: "Clicks a control from ui_snapshot (button, link, tab, menu item, checkbox). Returns the page path, any dialog that opened and new notices. Clicks that delete, cancel, pay or similar first ask the person to confirm.",
160
+ input: _.strictObject({ ref: E }),
161
+ execute: async ({ ref: n }, { signal: r }) => {
162
+ let a = O(n);
163
+ if (o(a)) throw new e("CONFLICT", "That control is disabled right now.");
164
+ if (s(a)) throw new e("NEEDS_USER", "That control belongs to a password, key or payment field: ask the person to use it.");
165
+ if (a instanceof HTMLInputElement && a.type === "file") throw new e("NEEDS_USER", "Choosing a file needs the person: ask them to pick it.");
166
+ if (I(a)) throw new e("NEEDS_USER", "That link opens outside this tab: ask the person to open it.");
167
+ let c = i(a), l = u(a);
168
+ if (T(a, c)) {
169
+ if (!await t("Click", {
170
+ title: "Allow this click?",
171
+ body: `An AI agent wants to click "${c || u(a)}" on this page.`,
172
+ confirmLabel: "Allow"
173
+ }, r)) throw new e("CANCELLED", "The person declined this click.");
174
+ let s = O(n);
175
+ if (s !== a || o(s) || i(s) !== c || u(s) !== l) throw new e("CONFLICT", "The page changed. Take a new ui_snapshot.", { retryable: !0 });
176
+ }
177
+ return A(a), P(a), await j(a.ownerDocument, r), {
178
+ clicked: c || u(a),
179
+ ...M(a.ownerDocument)
180
+ };
181
+ }
182
+ }),
183
+ n({
184
+ name: "ui_fill",
185
+ title: "Type into a field",
186
+ description: "Replaces the text of a text field, text area or search box from ui_snapshot. Does not submit; click the submit button or press Enter afterwards. Password, payment and code fields are refused.",
187
+ input: _.strictObject({
188
+ ref: E,
189
+ value: _.string().max(2e3).describe("The text the field should contain.")
190
+ }),
191
+ execute: async ({ ref: t, value: n }, { signal: r }) => {
192
+ let a = O(t);
193
+ if (s(a)) throw new e("NEEDS_USER", "That is a password, payment or code field: ask the person to fill it.");
194
+ if (o(a) || a.readOnly) throw new e("CONFLICT", "That field cannot be edited right now.");
195
+ if (A(a), a.focus?.({ preventScroll: !0 }), a instanceof HTMLInputElement || a instanceof HTMLTextAreaElement) {
196
+ let t = u(a);
197
+ if (![
198
+ "textbox",
199
+ "searchbox",
200
+ "combobox",
201
+ "spinbutton"
202
+ ].includes(t)) throw new e("INVALID_INPUT", `A ${t} is not a text field; use ui_click.`);
203
+ if (a.maxLength > 0 && n.length > a.maxLength) throw new e("INVALID_INPUT", `That field takes at most ${a.maxLength} characters.`);
204
+ F(a, n);
205
+ } else if (a.isContentEditable) a.textContent = n, a.dispatchEvent(new Event("input", { bubbles: !0 }));
206
+ else throw new e("INVALID_INPUT", "That element is not a text field. Use ui_click, or ui_select for a dropdown.");
207
+ return await j(a.ownerDocument, r), {
208
+ filled: i(a),
209
+ ...M(a.ownerDocument)
210
+ };
211
+ }
212
+ }),
213
+ n({
214
+ name: "ui_select",
215
+ title: "Choose an option",
216
+ description: "Chooses an option in a native dropdown (select) from ui_snapshot, by its visible text or value. For a custom dropdown, ui_click it, take a new ui_snapshot and ui_click the option.",
217
+ input: _.strictObject({
218
+ ref: E,
219
+ option: _.string().trim().min(1).max(200).describe("The option text (or value) to choose.")
220
+ }),
221
+ execute: async ({ ref: t, option: n }, { signal: r }) => {
222
+ let i = O(t);
223
+ if (!(i instanceof HTMLSelectElement)) throw new e("INVALID_INPUT", "That is not a native dropdown: ui_click it, take a new ui_snapshot, then ui_click the option.");
224
+ if (o(i)) throw new e("CONFLICT", "That dropdown is disabled.");
225
+ let a = n.toLowerCase(), s = Array.from(i.options).find((e) => !e.disabled && ((e.textContent ?? "").trim().toLowerCase() === a || e.value.toLowerCase() === a));
226
+ if (!s) {
227
+ let t = Array.from(i.options).filter((e) => !e.disabled).slice(0, 15).map((e) => (e.textContent ?? "").trim()).join(", ");
228
+ throw new e("INVALID_INPUT", `No option "${n.slice(0, 60)}". Options: ${t}`);
229
+ }
230
+ A(i);
231
+ let c = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value")?.set;
232
+ return c ? c.call(i, s.value) : i.value = s.value, i.dispatchEvent(new Event("input", { bubbles: !0 })), i.dispatchEvent(new Event("change", { bubbles: !0 })), await j(i.ownerDocument, r), {
233
+ selected: (s.textContent ?? "").trim(),
234
+ ...M(i.ownerDocument)
235
+ };
236
+ }
237
+ }),
238
+ n({
239
+ name: "ui_press_key",
240
+ title: "Press a key",
241
+ description: "Presses a key on a control from ui_snapshot, or on the focused element: Enter to submit or activate, Escape to close a menu or dialog, arrow keys to move in lists.",
242
+ input: _.strictObject({
243
+ key: _.enum([
244
+ "Enter",
245
+ "Escape",
246
+ "ArrowUp",
247
+ "ArrowDown",
248
+ "ArrowLeft",
249
+ "ArrowRight"
250
+ ]).describe("The key to press."),
251
+ ref: E.optional().describe("Element to press it on. Default: the focused element.")
252
+ }),
253
+ execute: async ({ key: t, ref: n }, { signal: r }) => {
254
+ let o = m()?.ownerDocument ?? (typeof document > "u" ? null : document);
255
+ if (!o) throw new e("NEEDS_USER", "The page is not available.");
256
+ let s = n ? O(n) : o.activeElement ?? o.body;
257
+ if (!s) throw new e("NEEDS_USER", "Nothing has focus on the page.");
258
+ if (a(s)) throw new e("FORBIDDEN", "Focus is in a part of the page that is off-limits to agents.");
259
+ if (t === "Enter" && T(s, i(s))) throw new e("NEEDS_USER", "Use ui_click on that control: it needs the person to confirm.");
260
+ s.focus?.({ preventScroll: !0 });
261
+ let c = o.defaultView?.KeyboardEvent ?? KeyboardEvent, l = {
262
+ key: t,
263
+ code: t,
264
+ bubbles: !0,
265
+ cancelable: !0,
266
+ composed: !0
267
+ };
268
+ return s.dispatchEvent(new c("keydown", l)), s.dispatchEvent(new c("keyup", l)), await j(o, r), {
269
+ pressed: t,
270
+ ...M(o)
271
+ };
272
+ }
273
+ })
274
+ ];
275
+ }
276
+ //#endregion
277
+ export { L as createUiTools, T as isConsequentialControl };
@@ -0,0 +1,14 @@
1
+ import { AnyDefinedAgentTool } from './types';
2
+ /**
3
+ * Offer `tools` to agents for as long as the calling component is mounted.
4
+ *
5
+ * Call it in the component that owns the state the tools act on — a route's
6
+ * page for page tools, the builder for canvas tools — so a tool exists exactly
7
+ * when it can work. Registration is synchronous and removal is by token
8
+ * (`registry.ts`), so React Strict Mode's mount → unmount → mount and a route
9
+ * transition's overlapping mount/unmount both end with one registration each.
10
+ *
11
+ * `tools` should be stable (module constants, or memoised): a new array
12
+ * re-registers every tool.
13
+ */
14
+ export declare function useAgentTools(tools: readonly AnyDefinedAgentTool[], enabled?: boolean): void;
@@ -0,0 +1,14 @@
1
+ import { registerAgentTool as e } from "./registry.js";
2
+ import { useEffect as t } from "react";
3
+ //#region src/shared/agent-tools/useAgentTools.ts
4
+ function n(n, r = !0) {
5
+ t(() => {
6
+ if (!r) return;
7
+ let t = n.map((t) => e(t));
8
+ return () => {
9
+ for (let e of t) e();
10
+ };
11
+ }, [n, r]);
12
+ }
13
+ //#endregion
14
+ export { n as useAgentTools };
@@ -1,4 +1,5 @@
1
1
  import { AssistantUiCatalogue } from './uiActions/uiCatalogue';
2
+ import { ClientToolManifestEntry } from '../agent-tools/adapters/assistant';
2
3
  import { AssistantMode, AssistantPromptPart, AssistantRawMessage, AssistantTokenUsage, AssistantSandboxAuthContext, AssistantArtifact, AssistantSessionInfo, DownloadedAssistantArtifact, StreamAccess } from './types';
3
4
  import { SandboxWriteCapabilities, WriteApprovalMode, WriteExecutionState } from './writeProposals';
4
5
  import { WriteReplyKind, WriteReplyOrigin } from './writeReplyBinder';
@@ -439,6 +440,8 @@ export declare function buildPromptRequestBody(args: {
439
440
  writeReply?: AssistantPromptWriteReply | null;
440
441
  /** ★ BOFF-8060 (N1b, dormant): the pages this person may be sent to. Absent unless a product opts in. */
441
442
  uiCatalogue?: AssistantUiCatalogue | null;
443
+ /** ★ BOFF-9786: the page's browser tools. Absent unless the sandbox runs them and the page registers some. */
444
+ clientTools?: readonly ClientToolManifestEntry[] | null;
442
445
  }): Record<string, unknown>;
443
446
  export interface AssistantPromptGatewayRequest {
444
447
  sandboxId: string;
@@ -455,6 +458,8 @@ export interface AssistantPromptGatewayRequest {
455
458
  writeReply?: AssistantPromptWriteReply | null;
456
459
  /** The navigable pages (BOFF-8060 N1b, dormant). */
457
460
  uiCatalogue?: AssistantUiCatalogue | null;
461
+ /** The page's browser tools (BOFF-9786). */
462
+ clientTools?: readonly ClientToolManifestEntry[] | null;
458
463
  }
459
464
  /** The actual unbatched GraphQL body, including context and forwarded-token overhead. */
460
465
  export declare function buildPromptGatewayRequest(args: AssistantPromptGatewayRequest): {
@@ -473,6 +478,7 @@ options?: {
473
478
  conversationId?: string | null;
474
479
  writeReply?: AssistantPromptWriteReply | null;
475
480
  uiCatalogue?: AssistantUiCatalogue | null;
481
+ clientTools?: readonly ClientToolManifestEntry[] | null;
476
482
  }): Promise<{
477
483
  messageID?: string;
478
484
  }>;
@@ -502,7 +508,19 @@ export declare function getAssistantMessages(sandboxId: string, workspaceId: str
502
508
  * break the read: an offer is a suggestion, the turn is not.
503
509
  */
504
510
  surfaceUiOffers?: (offers: unknown, serverNow: number) => void;
511
+ /**
512
+ * ★ BOFF-9786: hand the answer's `clientToolRequests` (unparsed) to the
513
+ * turn's browser-tool runner. Only the panel's turn poller passes it, and
514
+ * never allowed to break the read.
515
+ */
516
+ surfaceClientToolRequests?: (requests: unknown) => void;
505
517
  }): Promise<AssistantRawMessage[]>;
518
+ /**
519
+ * ★ BOFF-9786: answer one browser-tool call with the page's result. The
520
+ * sandbox accepts it only from the person the call is bound to; a call it has
521
+ * already ended answers 409, which is not an error worth retrying.
522
+ */
523
+ export declare function postClientToolResult(sandboxId: string, workspaceId: string, sessionId: string, callId: string, result: unknown, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise<void>;
506
524
  /**
507
525
  * Ask the svc to mint a signed SSE stream URL. Returns null when unavailable
508
526
  * (older backend / preview data plane off) → caller falls back to polling.