@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,29 @@
1
+ /**
2
+ * ★ BOFF-9783: agent tools — actions AI agents may take in the page, defined
3
+ * once and offered through WebMCP and the in-app assistant.
4
+ *
5
+ * ```tsx
6
+ * // once, in the app shell
7
+ * <AgentToolsProvider product="fluidgrids" gatherPageContext={gatherPageContext} />
8
+ *
9
+ * // in the component that owns the state a tool acts on
10
+ * const RUN_TOOLS = [defineAgentTool({ name: 'fg_run_get', … })];
11
+ * useAgentTools(RUN_TOOLS);
12
+ * ```
13
+ */
14
+ export * from './types';
15
+ export * from './limits';
16
+ export * from './errors';
17
+ export * from './defineAgentTool';
18
+ export * from './outputBudget';
19
+ export * from './registry';
20
+ export * from './runAgentTool';
21
+ export * from './useAgentTools';
22
+ export * from './confirm/confirmStore';
23
+ export * from './confirm/AgentConfirmHost';
24
+ export * from './adapters/webmcp';
25
+ export * from './adapters/assistant';
26
+ export * from './shell/shellTools';
27
+ export * from './ui/uiTools';
28
+ export { DENY_SELECTOR, INTERACTIVE_SELECTOR, accessibleName, isDenied, isSensitiveField, roleOf, } from './ui/dom';
29
+ export * from './AgentToolsProvider';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Size limits for agent tools.
3
+ *
4
+ * The first four are Chrome's published WebMCP guidance
5
+ * (developer.chrome.com/docs/ai/webmcp/secure-tools): not enforced by the
6
+ * browser, but every tool here keeps to them so an agent's context stays small
7
+ * and a tool reads the same in every channel.
8
+ */
9
+ /** Tool names and parameter names. */
10
+ export declare const AGENT_TOOL_NAME_MAX = 30;
11
+ /** A tool's description. */
12
+ export declare const AGENT_TOOL_DESCRIPTION_MAX = 500;
13
+ /** A parameter's description. */
14
+ export declare const AGENT_TOOL_PARAM_DESCRIPTION_MAX = 150;
15
+ /** One result, as serialized JSON (UTF-8 bytes). */
16
+ export declare const AGENT_TOOL_OUTPUT_MAX_BYTES = 1536;
17
+ /** Longest string any single field of a result keeps before it is clamped. */
18
+ export declare const AGENT_TOOL_OUTPUT_STRING_MAX = 300;
19
+ /** `^[a-z][a-z0-9_]*$` — WebMCP allows more, this is the house style. */
20
+ export declare const AGENT_TOOL_NAME_PATTERN: RegExp;
@@ -0,0 +1,4 @@
1
+ //#region src/shared/agent-tools/limits.ts
2
+ var e = 30, t = 500, n = 150, r = 1536, i = 300, a = /^[a-z][a-z0-9_]*$/;
3
+ //#endregion
4
+ export { t as AGENT_TOOL_DESCRIPTION_MAX, e as AGENT_TOOL_NAME_MAX, a as AGENT_TOOL_NAME_PATTERN, r as AGENT_TOOL_OUTPUT_MAX_BYTES, i as AGENT_TOOL_OUTPUT_STRING_MAX, n as AGENT_TOOL_PARAM_DESCRIPTION_MAX };
@@ -0,0 +1,15 @@
1
+ import { AgentToolErrorCode, AgentToolInitiator } from './types';
2
+ /**
3
+ * One line per tool call: which tool, who asked, how it ended, how long it took.
4
+ *
5
+ * ★ Never the input or the result. Inputs carry what a person typed or an agent
6
+ * made up; results carry workspace data. Neither belongs in a log.
7
+ */
8
+ export declare function logAgentToolCall(entry: {
9
+ tool: string;
10
+ initiator: AgentToolInitiator;
11
+ outcome: 'ok' | AgentToolErrorCode;
12
+ durationMs: number;
13
+ truncated?: boolean;
14
+ }): void;
15
+ export declare function logAgentToolProblem(message: string, context: Record<string, unknown>): void;
@@ -0,0 +1,11 @@
1
+ import { logManager as e } from "../logger/LogManager.js";
2
+ //#region src/shared/agent-tools/log.ts
3
+ var t = e.getLogger("agent-tools");
4
+ function n(e) {
5
+ e.outcome === "ok" ? t.info("agent tool call", e) : t.warn("agent tool call", e);
6
+ }
7
+ function r(e, n) {
8
+ t.warn(e, n);
9
+ }
10
+ //#endregion
11
+ export { n as logAgentToolCall, r as logAgentToolProblem };
@@ -0,0 +1,21 @@
1
+ export interface CappedResult {
2
+ data: unknown;
3
+ truncated: boolean;
4
+ }
5
+ /** Serialized size in UTF-8 bytes — what the agent's context actually pays for. */
6
+ export declare function jsonByteLength(value: unknown): number;
7
+ /**
8
+ * Fit a tool's result into the output budget.
9
+ *
10
+ * Tools are written to return small results; this is the backstop for the one
11
+ * that does not (a long list, a long error text). In order, until it fits:
12
+ * 1. clamp every string to `AGENT_TOOL_OUTPUT_STRING_MAX` characters;
13
+ * 2. drop items from the END of the longest array, repeatedly — a list keeps
14
+ * its first, most relevant rows;
15
+ * 3. give up on the payload and say so, so the agent narrows its query instead
16
+ * of acting on a silently partial object.
17
+ *
18
+ * A value that cannot be serialized (a cycle, a BigInt) is a tool bug: it
19
+ * throws, and `runAgentTool` reports `FAILED`.
20
+ */
21
+ export declare function capResult(value: unknown, maxBytes?: number): CappedResult;
@@ -0,0 +1,51 @@
1
+ import { AGENT_TOOL_OUTPUT_MAX_BYTES as e } from "./limits.js";
2
+ //#region src/shared/agent-tools/outputBudget.ts
3
+ var t = new TextEncoder();
4
+ function n(e) {
5
+ return t.encode(JSON.stringify(e) ?? "null").length;
6
+ }
7
+ function r(t, r = e) {
8
+ let o = JSON.parse(JSON.stringify(t ?? null));
9
+ if (n(o) <= r) return {
10
+ data: o,
11
+ truncated: !1
12
+ };
13
+ let s = i(o);
14
+ if (n(s) <= r) return {
15
+ data: s,
16
+ truncated: !0
17
+ };
18
+ for (let e = 0; e < 1e4; e += 1) {
19
+ let e = a(s);
20
+ if (!e || e.length === 0) break;
21
+ if (e.pop(), n(s) <= r) return {
22
+ data: s,
23
+ truncated: !0
24
+ };
25
+ }
26
+ return {
27
+ data: { note: "The result was too large to return. Narrow the request (filters, a smaller limit)." },
28
+ truncated: !0
29
+ };
30
+ }
31
+ function i(e) {
32
+ if (typeof e == "string") return e.length > 300 ? `${e.slice(0, 299)}…` : e;
33
+ if (Array.isArray(e)) return e.map(i);
34
+ if (e && typeof e == "object") {
35
+ let t = {};
36
+ for (let [n, r] of Object.entries(e)) t[n] = i(r);
37
+ return t;
38
+ }
39
+ return e;
40
+ }
41
+ function a(e) {
42
+ let t = null, r = -1, i = (e) => {
43
+ if (Array.isArray(e)) {
44
+ let a = n(e);
45
+ e.length > 0 && a > r && (t = e, r = a), e.forEach(i);
46
+ } else e && typeof e == "object" && Object.values(e).forEach(i);
47
+ };
48
+ return i(e), t;
49
+ }
50
+ //#endregion
51
+ export { r as capResult, n as jsonByteLength };
@@ -0,0 +1,13 @@
1
+ import { AnyDefinedAgentTool } from './types';
2
+ type Listener = () => void;
3
+ /** Register `tool`. Returns the cleanup that removes exactly this registration. */
4
+ export declare function registerAgentTool(tool: AnyDefinedAgentTool): () => void;
5
+ /** The tools in force (newest per name), sorted by name. Stable between changes. */
6
+ export declare function listAgentTools(): readonly AnyDefinedAgentTool[];
7
+ /** The tool in force for `name`, or null. */
8
+ export declare function getAgentTool(name: string): AnyDefinedAgentTool | null;
9
+ /** Called after every change to the set of tools in force. */
10
+ export declare function subscribeAgentTools(listener: Listener): () => void;
11
+ /** Test seam: forget every registration. */
12
+ export declare function clearAgentTools(): void;
13
+ export {};
@@ -0,0 +1,53 @@
1
+ //#region src/shared/agent-tools/registry.ts
2
+ var e = Symbol.for("burdenoff.fe-libs.agentTools.registry.v1");
3
+ function t() {
4
+ let t = globalThis, n = t[e];
5
+ if (n && n.stacks instanceof Map) return n;
6
+ let r = {
7
+ stacks: /* @__PURE__ */ new Map(),
8
+ listeners: /* @__PURE__ */ new Set(),
9
+ snapshot: []
10
+ };
11
+ return t[e] = r, r;
12
+ }
13
+ function n() {
14
+ let e = t(), n = [];
15
+ for (let t of e.stacks.values()) {
16
+ let e = t[t.length - 1];
17
+ e && n.push(e.tool);
18
+ }
19
+ n.sort((e, t) => e.name.localeCompare(t.name)), e.snapshot = n;
20
+ for (let t of e.listeners) t();
21
+ }
22
+ function r(e) {
23
+ let { stacks: r } = t(), i = Symbol(e.name), a = r.get(e.name) ?? [];
24
+ a.push({
25
+ token: i,
26
+ tool: e
27
+ }), r.set(e.name, a), n();
28
+ let o = !1;
29
+ return () => {
30
+ if (o) return;
31
+ o = !0;
32
+ let r = t().stacks.get(e.name);
33
+ if (!r) return;
34
+ let a = r.findIndex((e) => e.token === i);
35
+ a < 0 || (r.splice(a, 1), r.length === 0 && t().stacks.delete(e.name), n());
36
+ };
37
+ }
38
+ function i() {
39
+ return t().snapshot;
40
+ }
41
+ function a(e) {
42
+ let n = t().stacks.get(e);
43
+ return n?.[n.length - 1]?.tool ?? null;
44
+ }
45
+ function o(e) {
46
+ let { listeners: n } = t();
47
+ return n.add(e), () => n.delete(e);
48
+ }
49
+ function s() {
50
+ t().stacks.clear(), n();
51
+ }
52
+ //#endregion
53
+ export { s as clearAgentTools, a as getAgentTool, i as listAgentTools, r as registerAgentTool, o as subscribeAgentTools };
@@ -0,0 +1,21 @@
1
+ import { AgentToolInitiator, AgentToolResult, AnyDefinedAgentTool } from './types';
2
+ export interface RunAgentToolOptions {
3
+ initiator: AgentToolInitiator;
4
+ /** Cancels the call: a pending confirmation closes, the tool's own requests abort. */
5
+ signal: AbortSignal;
6
+ }
7
+ /**
8
+ * Run a tool for an agent — the ONE path every channel uses, so validation,
9
+ * confirmation, cancellation and the output budget are the same whichever
10
+ * agent asked.
11
+ *
12
+ * 1. Validate the input against the tool's strict schema. Wrong types, missing
13
+ * or unknown fields, oversized strings and out-of-range numbers are refused
14
+ * with a message naming the field, so the agent can correct and retry.
15
+ * 2. A consequential tool asks the person first; declining is `CANCELLED`.
16
+ * 3. Execute with the call's AbortSignal.
17
+ * 4. Cap the result.
18
+ *
19
+ * Never throws: every outcome is an `AgentToolResult`.
20
+ */
21
+ export declare function runAgentTool(tool: AnyDefinedAgentTool, rawInput: unknown, { initiator, signal }: RunAgentToolOptions): Promise<AgentToolResult>;
@@ -0,0 +1,57 @@
1
+ import { AgentToolError as e, agentToolFailure as t, toAgentToolFailure as n } from "./errors.js";
2
+ import { logAgentToolCall as r } from "./log.js";
3
+ import { requestAgentConfirmation as i } from "./confirm/confirmStore.js";
4
+ import { capResult as a } from "./outputBudget.js";
5
+ //#region src/shared/agent-tools/runAgentTool.ts
6
+ var o = 5;
7
+ async function s(o, s, { initiator: u, signal: d }) {
8
+ let f = Date.now(), p = (e) => (r({
9
+ tool: o.name,
10
+ initiator: u,
11
+ outcome: e.ok ? "ok" : e.error.code,
12
+ durationMs: Date.now() - f,
13
+ ...e.ok && e.truncated ? { truncated: !0 } : {}
14
+ }), e);
15
+ if (d.aborted) return p(t("CANCELLED"));
16
+ let m = o.input.safeParse(c(s));
17
+ if (!m.success) return p(t("INVALID_INPUT", l(m.error), !0));
18
+ let h = m.data;
19
+ try {
20
+ if (o.annotations?.consequential) {
21
+ let n = o.confirm?.(h);
22
+ if (!n) throw new e("NEEDS_USER", "This action needs the person to do it.");
23
+ if (!await i(o.title, n, d)) return p(t("CANCELLED", "The person declined this action.", !1));
24
+ }
25
+ let n = await o.execute(h, {
26
+ signal: d,
27
+ initiator: u
28
+ });
29
+ if (d.aborted) return p(t("CANCELLED"));
30
+ let { data: r, truncated: s } = a(n);
31
+ return p(s ? {
32
+ ok: !0,
33
+ data: r,
34
+ truncated: !0
35
+ } : {
36
+ ok: !0,
37
+ data: r
38
+ });
39
+ } catch (e) {
40
+ return p(n(e));
41
+ }
42
+ }
43
+ function c(e) {
44
+ if (e == null) return {};
45
+ if (typeof e == "string") try {
46
+ return JSON.parse(e);
47
+ } catch {
48
+ return e;
49
+ }
50
+ return e;
51
+ }
52
+ function l(e) {
53
+ let t = e.issues.slice(0, o).map((e) => `${e.path.length > 0 ? e.path.join(".") : "input"}: ${e.message}`), n = e.issues.length - o;
54
+ return n > 0 && t.push(`…and ${n} more`), `Invalid input. ${t.join("; ")}`;
55
+ }
56
+ //#endregion
57
+ export { s as runAgentTool };
@@ -0,0 +1,44 @@
1
+ import { LocalDestination } from '../../assistant/localUiCommands';
2
+ import { AnyDefinedAgentTool } from '../types';
3
+ /**
4
+ * ★ BOFF-9783: the tools every product shell offers, whatever the product —
5
+ * where am I, take me somewhere, switch the theme. They read live shell state
6
+ * through `ShellToolsSource`, which `AgentToolsProvider` refreshes on every
7
+ * render, so a tool never acts on a stale workspace or navigation tree.
8
+ */
9
+ export interface ShellToolsSource {
10
+ /** Product id, e.g. the assistant's product namespace. */
11
+ product: string;
12
+ organizationId: string | null;
13
+ workspaceId: string | null;
14
+ projectId: string | null;
15
+ /** The signed-in person's display name, if the shell wants agents to know it. Never an email. */
16
+ userDisplayName: string | null;
17
+ /**
18
+ * The product's description of the current page (the assistant's
19
+ * `gatherPageContext`). Only the entity keys below are passed on.
20
+ */
21
+ pageContext: () => Readonly<Record<string, unknown>> | null;
22
+ /** The places the person's own navigation can take them (permission-filtered). */
23
+ destinations: readonly LocalDestination[];
24
+ /** False while permissions load or a role is being simulated: no navigation then. */
25
+ navigationReady: boolean;
26
+ /** Navigate within the app. */
27
+ navigate: (path: string) => void;
28
+ /** Current theme mode, or null without a theme provider. */
29
+ themeMode: string | null;
30
+ setThemeMode: ((mode: 'light' | 'dark' | 'system') => void) | null;
31
+ }
32
+ /**
33
+ * Resolve what an agent asked for to a destination the person's own navigation
34
+ * offers. A free path is accepted only AT or BELOW a path the navigation lists
35
+ * (a record under a listed page), never a sibling the permission or persona
36
+ * filter removed, so an agent cannot reach a page the sidebar hides from this
37
+ * person.
38
+ */
39
+ export declare function resolveDestination(destinations: readonly LocalDestination[], requested: string): {
40
+ path: string;
41
+ label: string;
42
+ } | null;
43
+ export declare function assertNoUnsavedChanges(): void;
44
+ export declare function createShellTools(getSource: () => ShellToolsSource): AnyDefinedAgentTool[];
@@ -0,0 +1,145 @@
1
+ import { isSafeInAppPath as e } from "../../navigation/inAppPath.js";
2
+ import { unsavedChangesBlocking as t } from "../../navigation/unsavedChanges.js";
3
+ import { AgentToolError as n } from "../errors.js";
4
+ import { defineAgentTool as r } from "../defineAgentTool.js";
5
+ import { z as i } from "zod";
6
+ //#region src/shared/agent-tools/shell/shellTools.ts
7
+ var a = [
8
+ "entityType",
9
+ "entityId",
10
+ "entityName",
11
+ "pageType",
12
+ "activeTab"
13
+ ], o = 25;
14
+ function s(e) {
15
+ let t = {};
16
+ if (!e) return t;
17
+ for (let n of a) {
18
+ let r = e[n];
19
+ typeof r == "string" && r.trim() && (t[n] = r.slice(0, 120));
20
+ }
21
+ return t;
22
+ }
23
+ function c(e) {
24
+ try {
25
+ return e.pageContext();
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function l() {
31
+ if (typeof document > "u") return null;
32
+ let e = document.querySelector("main h1, h1")?.textContent?.replace(/\s+/g, " ").trim();
33
+ return e ? e.slice(0, 120) : null;
34
+ }
35
+ function u(t, n) {
36
+ let r = n.trim(), i = r.toLowerCase(), a = t.find((e) => e.label.toLowerCase() === i || e.id.toLowerCase() === i);
37
+ if (a) return {
38
+ path: a.path,
39
+ label: a.label
40
+ };
41
+ if (!r.startsWith("/") || !e(r)) return null;
42
+ let o = r.split(/[?#]/)[0] ?? r, s = null;
43
+ for (let e of t) for (let t of [e.path, ...e.aliases ?? []]) {
44
+ let n = t.split(/[?#]/)[0]?.replace(/\/+$/, "") ?? "";
45
+ n && (o === n || o.startsWith(`${n}/`)) && (!s || n.length > s.length) && (s = {
46
+ path: o === n ? e.path : r,
47
+ label: e.label,
48
+ length: n.length
49
+ });
50
+ }
51
+ return s ? {
52
+ path: s.path,
53
+ label: s.label
54
+ } : null;
55
+ }
56
+ function d(e) {
57
+ if (!e.navigationReady) throw new n("CONFLICT", "The app's navigation is still loading. Try again in a moment.", { retryable: !0 });
58
+ }
59
+ function f() {
60
+ let e = t("assistant");
61
+ if (e) throw new n("CONFLICT", `The page has unsaved changes (${e.map((e) => e.label).join(", ")}). Ask the person to save or discard them first.`);
62
+ }
63
+ function p(e) {
64
+ return [
65
+ r({
66
+ name: "app_get_context",
67
+ title: "Get current context",
68
+ description: "Returns where the person is in the app: product, current page path and heading, the record the page shows (entity type and id), and the active organization, workspace and project ids. Use it first to understand \"this\", \"here\" or \"current\".",
69
+ input: i.strictObject({}),
70
+ annotations: {
71
+ readOnly: !0,
72
+ untrustedContent: !0
73
+ },
74
+ execute: () => {
75
+ let t = e();
76
+ return {
77
+ product: t.product,
78
+ path: typeof window > "u" ? null : window.location.pathname,
79
+ title: typeof document > "u" ? null : document.title.slice(0, 120),
80
+ heading: l(),
81
+ ...s(c(t)),
82
+ organizationId: t.organizationId,
83
+ workspaceId: t.workspaceId,
84
+ projectId: t.projectId,
85
+ ...t.userDisplayName ? { userDisplayName: t.userDisplayName } : {}
86
+ };
87
+ }
88
+ }),
89
+ r({
90
+ name: "app_list_pages",
91
+ title: "List pages",
92
+ description: "Lists the pages the person's navigation offers (label and path). Use it to find where to go before calling app_navigate.",
93
+ input: i.strictObject({ offset: i.number().int().min(0).max(1e3).default(0).describe("Index of the first page to return, for paging.") }),
94
+ annotations: { readOnly: !0 },
95
+ execute: ({ offset: t }) => {
96
+ let n = e();
97
+ d(n);
98
+ let { destinations: r } = n, i = r.slice(t, t + o), a = t + i.length;
99
+ return {
100
+ pages: i.map((e) => ({
101
+ label: e.context ? `${e.context} › ${e.label}` : e.label,
102
+ path: e.path
103
+ })),
104
+ ...a < r.length ? { nextOffset: a } : {}
105
+ };
106
+ }
107
+ }),
108
+ r({
109
+ name: "app_navigate",
110
+ title: "Open a page",
111
+ description: "Opens a page of the app in the person's tab. Pass a page label from app_list_pages (e.g. \"Runs\") or an in-app path starting with \"/\". Refused while the current page has unsaved changes.",
112
+ input: i.strictObject({ destination: i.string().trim().min(1).max(200).describe("A page label from app_list_pages, or an in-app path starting with \"/\".") }),
113
+ execute: ({ destination: t }) => {
114
+ let r = e();
115
+ d(r);
116
+ let i = u(r.destinations, t);
117
+ if (!i) {
118
+ let e = r.destinations.slice(0, 12).map((e) => e.label).join(", ");
119
+ throw new n("NOT_FOUND", `No page "${t.slice(0, 60)}" in this app's navigation. Known pages include: ${e}. Use app_list_pages for all.`, { retryable: !0 });
120
+ }
121
+ return f(), r.navigate(i.path), {
122
+ navigatedTo: i.path,
123
+ page: i.label
124
+ };
125
+ }
126
+ }),
127
+ r({
128
+ name: "app_set_theme",
129
+ title: "Change theme",
130
+ description: "Switches the app between light, dark and system appearance.",
131
+ input: i.strictObject({ mode: i.enum([
132
+ "light",
133
+ "dark",
134
+ "system"
135
+ ]).describe("The appearance to switch to.") }),
136
+ execute: ({ mode: t }) => {
137
+ let { setThemeMode: r } = e();
138
+ if (!r) throw new n("NEEDS_USER", "This app does not let the theme be changed here.");
139
+ return r(t), { mode: t };
140
+ }
141
+ })
142
+ ];
143
+ }
144
+ //#endregion
145
+ export { f as assertNoUnsavedChanges, p as createShellTools, u as resolveDestination };
@@ -0,0 +1,6 @@
1
+ import { Window } from 'happy-dom';
2
+ export interface InstalledDom {
3
+ window: Window;
4
+ restore: () => void;
5
+ }
6
+ export declare function installDom(url?: string): InstalledDom;
@@ -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;