@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.
- package/dist/chrome/ProfileSwitcher.js +236 -213
- package/dist/credentials/SchemaForm.js +2 -0
- package/dist/shared/agent-tools/AgentToolsProvider.d.ts +33 -0
- package/dist/shared/agent-tools/AgentToolsProvider.js +48 -0
- package/dist/shared/agent-tools/adapters/assistant.d.ts +85 -0
- package/dist/shared/agent-tools/adapters/assistant.js +110 -0
- package/dist/shared/agent-tools/adapters/webmcp.d.ts +69 -0
- package/dist/shared/agent-tools/adapters/webmcp.js +81 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.d.ts +12 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.js +34 -0
- package/dist/shared/agent-tools/confirm/confirmStore.d.ts +37 -0
- package/dist/shared/agent-tools/confirm/confirmStore.js +58 -0
- package/dist/shared/agent-tools/defineAgentTool.d.ts +13 -0
- package/dist/shared/agent-tools/defineAgentTool.js +27 -0
- package/dist/shared/agent-tools/errors.d.ts +33 -0
- package/dist/shared/agent-tools/errors.js +105 -0
- package/dist/shared/agent-tools/index.d.ts +29 -0
- package/dist/shared/agent-tools/limits.d.ts +20 -0
- package/dist/shared/agent-tools/limits.js +4 -0
- package/dist/shared/agent-tools/log.d.ts +15 -0
- package/dist/shared/agent-tools/log.js +11 -0
- package/dist/shared/agent-tools/outputBudget.d.ts +21 -0
- package/dist/shared/agent-tools/outputBudget.js +51 -0
- package/dist/shared/agent-tools/registry.d.ts +13 -0
- package/dist/shared/agent-tools/registry.js +53 -0
- package/dist/shared/agent-tools/runAgentTool.d.ts +21 -0
- package/dist/shared/agent-tools/runAgentTool.js +57 -0
- package/dist/shared/agent-tools/shell/shellTools.d.ts +44 -0
- package/dist/shared/agent-tools/shell/shellTools.js +145 -0
- package/dist/shared/agent-tools/testSupport/installDom.d.ts +6 -0
- package/dist/shared/agent-tools/types.d.ts +106 -0
- package/dist/shared/agent-tools/ui/dom.d.ts +43 -0
- package/dist/shared/agent-tools/ui/dom.js +178 -0
- package/dist/shared/agent-tools/ui/refs.d.ts +8 -0
- package/dist/shared/agent-tools/ui/refs.js +20 -0
- package/dist/shared/agent-tools/ui/uiTools.d.ts +40 -0
- package/dist/shared/agent-tools/ui/uiTools.js +277 -0
- package/dist/shared/agent-tools/useAgentTools.d.ts +14 -0
- package/dist/shared/agent-tools/useAgentTools.js +14 -0
- package/dist/shared/assistant/api.d.ts +18 -0
- package/dist/shared/assistant/api.js +145 -131
- package/dist/shared/assistant/ui/AssistantChatArea.js +1158 -1148
- package/dist/shared/assistant/writeProposals.d.ts +6 -0
- package/dist/shared/providers/shell/ActiveContextProvider.d.ts +5 -0
- package/dist/shared/providers/shell/ActiveContextProvider.js +4 -1
- package/dist/shared/providers/shell/index.d.ts +1 -1
- package/dist/shared-agent-tools.d.ts +2 -0
- package/dist/shared-agent-tools.js +16 -0
- package/dist/shared-assistant.js +285 -285
- package/dist/shared-providers-shell.js +20 -20
- package/dist/ui/alert-dialog.js +2 -0
- 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.
|