@burdenoff/fe-libs 2026.1024.4 → 2026.1027.1

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 (62) hide show
  1. package/dist/credentials/SchemaForm.js +2 -0
  2. package/dist/shared/agent-tools/AgentToolsProvider.d.ts +33 -0
  3. package/dist/shared/agent-tools/AgentToolsProvider.js +48 -0
  4. package/dist/shared/agent-tools/adapters/assistant.d.ts +85 -0
  5. package/dist/shared/agent-tools/adapters/assistant.js +110 -0
  6. package/dist/shared/agent-tools/adapters/webmcp.d.ts +69 -0
  7. package/dist/shared/agent-tools/adapters/webmcp.js +81 -0
  8. package/dist/shared/agent-tools/confirm/AgentConfirmHost.d.ts +12 -0
  9. package/dist/shared/agent-tools/confirm/AgentConfirmHost.js +34 -0
  10. package/dist/shared/agent-tools/confirm/confirmStore.d.ts +37 -0
  11. package/dist/shared/agent-tools/confirm/confirmStore.js +58 -0
  12. package/dist/shared/agent-tools/defineAgentTool.d.ts +13 -0
  13. package/dist/shared/agent-tools/defineAgentTool.js +27 -0
  14. package/dist/shared/agent-tools/errors.d.ts +33 -0
  15. package/dist/shared/agent-tools/errors.js +105 -0
  16. package/dist/shared/agent-tools/index.d.ts +29 -0
  17. package/dist/shared/agent-tools/limits.d.ts +20 -0
  18. package/dist/shared/agent-tools/limits.js +4 -0
  19. package/dist/shared/agent-tools/log.d.ts +15 -0
  20. package/dist/shared/agent-tools/log.js +11 -0
  21. package/dist/shared/agent-tools/outputBudget.d.ts +21 -0
  22. package/dist/shared/agent-tools/outputBudget.js +51 -0
  23. package/dist/shared/agent-tools/registry.d.ts +13 -0
  24. package/dist/shared/agent-tools/registry.js +53 -0
  25. package/dist/shared/agent-tools/runAgentTool.d.ts +21 -0
  26. package/dist/shared/agent-tools/runAgentTool.js +57 -0
  27. package/dist/shared/agent-tools/shell/shellTools.d.ts +44 -0
  28. package/dist/shared/agent-tools/shell/shellTools.js +145 -0
  29. package/dist/shared/agent-tools/testSupport/installDom.d.ts +6 -0
  30. package/dist/shared/agent-tools/types.d.ts +106 -0
  31. package/dist/shared/agent-tools/ui/dom.d.ts +43 -0
  32. package/dist/shared/agent-tools/ui/dom.js +178 -0
  33. package/dist/shared/agent-tools/ui/refs.d.ts +8 -0
  34. package/dist/shared/agent-tools/ui/refs.js +20 -0
  35. package/dist/shared/agent-tools/ui/uiTools.d.ts +40 -0
  36. package/dist/shared/agent-tools/ui/uiTools.js +277 -0
  37. package/dist/shared/agent-tools/useAgentTools.d.ts +14 -0
  38. package/dist/shared/agent-tools/useAgentTools.js +14 -0
  39. package/dist/shared/assistant/api.d.ts +18 -0
  40. package/dist/shared/assistant/api.js +145 -131
  41. package/dist/shared/assistant/avatar/live/LiveAvatarStage.js +2 -3
  42. package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +2 -3
  43. package/dist/shared/assistant/avatar/live/avatarPorts.d.ts +9 -0
  44. package/dist/shared/assistant/avatar/live/avatarTransport.d.ts +25 -3
  45. package/dist/shared/assistant/avatar/live/avatarTransport.js +29 -2
  46. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +22 -4
  47. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +113 -81
  48. package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +15 -1
  49. package/dist/shared/assistant/avatar/live/liveAvatarController.js +204 -186
  50. package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.js +6 -1
  51. package/dist/shared/assistant/ui/AssistantChatArea.js +1158 -1148
  52. package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +1 -1
  53. package/dist/shared/assistant/writeProposals.d.ts +6 -0
  54. package/dist/shared/providers/shell/ActiveContextProvider.d.ts +5 -0
  55. package/dist/shared/providers/shell/ActiveContextProvider.js +4 -1
  56. package/dist/shared/providers/shell/index.d.ts +1 -1
  57. package/dist/shared-agent-tools.d.ts +2 -0
  58. package/dist/shared-agent-tools.js +16 -0
  59. package/dist/shared-assistant.js +285 -285
  60. package/dist/shared-providers-shell.js +20 -20
  61. package/dist/ui/alert-dialog.js +2 -0
  62. package/package.json +7 -2
@@ -0,0 +1,106 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * ★ BOFF-9783: one definition of an action an AI agent may take in the page,
4
+ * exposed through every channel that can reach the page — the browser's own
5
+ * agent through W3C WebMCP (`document.modelContext`) and the product's in-app
6
+ * assistant through client tool calls.
7
+ *
8
+ * A tool is code the product chose to expose. It runs in the person's tab, with
9
+ * the person's session, through the same API calls the UI makes, so the
10
+ * gateway's `@rbac` and tenant checks apply exactly as they do to a click.
11
+ * Nothing here is a security boundary on its own: the hints are advisory, the
12
+ * confirmation is a courtesy to the person, the backend decides.
13
+ */
14
+ /** Who asked for the tool to run. Logged; never trusted for authorization. */
15
+ export type AgentToolInitiator = 'webmcp' | 'assistant';
16
+ /**
17
+ * Advisory hints, mapped one to one onto WebMCP's `ToolAnnotations`.
18
+ *
19
+ * `consequential` is ALSO enforced here: such a tool never runs until the
20
+ * person confirms it in the page with a real click (`requestAgentConfirmation`).
21
+ */
22
+ export interface AgentToolAnnotations {
23
+ /** The tool changes nothing. */
24
+ readOnly?: boolean;
25
+ /** The result carries text other people wrote (names, run output, errors). */
26
+ untrustedContent?: boolean;
27
+ /** The tool changes something that matters: it is confirmed before it runs. */
28
+ consequential?: boolean;
29
+ }
30
+ /** What the confirmation dialog says. Plain text — never markup. */
31
+ export interface AgentToolConfirmation {
32
+ title: string;
33
+ body: string;
34
+ /** The confirm button's words, e.g. "Cancel run". */
35
+ confirmLabel: string;
36
+ }
37
+ export interface AgentToolExecuteContext {
38
+ /** Aborted when the agent cancels the call or the tool is unregistered. */
39
+ signal: AbortSignal;
40
+ initiator: AgentToolInitiator;
41
+ }
42
+ /** The input type a tool's zod object describes. */
43
+ export type AgentToolInputSchema = z.ZodObject<z.ZodRawShape>;
44
+ export interface AgentToolDefinition<S extends AgentToolInputSchema = AgentToolInputSchema, O = unknown> {
45
+ /** `^[a-z][a-z0-9_]*$`, at most 30 characters, e.g. `fg_runs_list`. */
46
+ name: string;
47
+ /** Short human title, shown in the confirmation dialog and the assistant's status line. */
48
+ title: string;
49
+ /** What the tool does and when to use it. At most 500 characters. */
50
+ description: string;
51
+ /**
52
+ * A STRICT zod object. Unknown keys are rejected, so every parameter has to be
53
+ * declared — each with a `.describe()` of at most 150 characters.
54
+ */
55
+ input: S;
56
+ annotations?: AgentToolAnnotations;
57
+ /** Required for consequential tools: what the person is asked to confirm. */
58
+ confirm?: (input: z.output<S>) => AgentToolConfirmation;
59
+ /**
60
+ * Do the work. Return only the fields an agent needs: the result is capped
61
+ * (`AGENT_TOOL_OUTPUT_MAX_BYTES`). Throw `AgentToolError` for an expected
62
+ * failure; anything else is reported as `FAILED` without its message.
63
+ */
64
+ execute: (input: z.output<S>, context: AgentToolExecuteContext) => Promise<O> | O;
65
+ }
66
+ /** A definition after `defineAgentTool` checked it and derived its JSON Schema. */
67
+ export interface DefinedAgentTool<S extends AgentToolInputSchema = AgentToolInputSchema, O = unknown> extends AgentToolDefinition<S, O> {
68
+ /** JSON Schema (draft 2020-12) of the input, `additionalProperties: false`. */
69
+ readonly inputSchema: Record<string, unknown>;
70
+ }
71
+ /** Any defined tool, whatever its input and output types. */
72
+ export type AnyDefinedAgentTool = DefinedAgentTool<AgentToolInputSchema, unknown>;
73
+ export type AgentToolErrorCode =
74
+ /** The input failed validation; `message` says which field and why. */
75
+ 'INVALID_INPUT'
76
+ /** The person is signed out. */
77
+ | 'UNAUTHENTICATED'
78
+ /** The backend refused: the person may not do this here. */
79
+ | 'FORBIDDEN'
80
+ /** Nothing with that id in the person's current workspace. */
81
+ | 'NOT_FOUND'
82
+ /** The record is in a state that does not allow this. */
83
+ | 'CONFLICT'
84
+ /** Only the person can do this step (a file picker, a sign-in popup, a confirmation they declined to give here). */
85
+ | 'NEEDS_USER'
86
+ /** The person declined, or the call was cancelled. */
87
+ | 'CANCELLED'
88
+ /** Anything else. */
89
+ | 'FAILED';
90
+ export interface AgentToolSuccess<O = unknown> {
91
+ ok: true;
92
+ data: O;
93
+ /** Set when the result was cut to fit the output budget. */
94
+ truncated?: true;
95
+ }
96
+ export interface AgentToolFailure {
97
+ ok: false;
98
+ error: {
99
+ code: AgentToolErrorCode;
100
+ message: string;
101
+ /** True when the same call may succeed later or with corrected input. */
102
+ retryable: boolean;
103
+ };
104
+ }
105
+ /** What every channel returns to the agent. */
106
+ export type AgentToolResult<O = unknown> = AgentToolSuccess<O> | AgentToolFailure;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ★ BOFF-9783: the DOM rules the generic UI tools live by — what counts as a
3
+ * control, what a control is called, and which parts of the page an agent may
4
+ * never see or touch.
5
+ *
6
+ * These read the page the way assistive technology does (roles, accessible
7
+ * names, states), so the agent's view is the same one a screen-reader user
8
+ * gets, and a page that is accessible is also operable by an agent.
9
+ */
10
+ /** Elements an agent can operate. */
11
+ export declare const INTERACTIVE_SELECTOR: string;
12
+ /**
13
+ * ★★ Regions an agent never sees or operates:
14
+ * - anything a product marks `data-agent="deny"` (billing, secrets, vault…);
15
+ * - the assistant's own panel, overlay and voice session — an agent must not
16
+ * drive the conversation it is part of;
17
+ * - every confirmation dialog — the person's decision, never the agent's
18
+ * (the dialogs also ignore untrusted clicks; this is the second line).
19
+ */
20
+ export declare const DENY_SELECTOR: string;
21
+ export declare function isDenied(element: Element): boolean;
22
+ /** A credential, payment or one-time-code field, or one a product marked sensitive. */
23
+ export declare function isSensitiveField(element: Element): boolean;
24
+ /**
25
+ * Rendered and reachable: not hidden, not inert, not behind a modal (a modal
26
+ * dialog marks the rest of the page `aria-hidden`), not `display: none`.
27
+ */
28
+ export declare function isVisible(element: Element): boolean;
29
+ export declare function isDisabled(element: Element): boolean;
30
+ /** The ARIA role, explicit or implied by the element. */
31
+ export declare function roleOf(element: Element): string;
32
+ /**
33
+ * The accessible name, in the order assistive technology computes it:
34
+ * aria-labelledby → aria-label → associated <label> → title → own text (for
35
+ * buttons, links and the like) → placeholder.
36
+ */
37
+ export declare function accessibleName(element: Element): string;
38
+ /** Short state flags: "disabled", "checked", "expanded", … */
39
+ export declare function stateOf(element: Element): string[];
40
+ /** The current value of a field, unless it is sensitive. */
41
+ export declare function valueOf(element: Element): string | null;
42
+ /** The open dialog's title, if a (non-denied) dialog is open. */
43
+ export declare function openDialogTitle(doc: Document): string | null;
@@ -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 };