@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
@@ -132,6 +132,7 @@ var l = typeof CSS < "u" && typeof CSS.supports == "function" && CSS.supports("-
132
132
  C,
133
133
  /* @__PURE__ */ c("div", {
134
134
  className: "relative",
135
+ ...u.secret ? { "data-agent-sensitive": "" } : {},
135
136
  children: [/* @__PURE__ */ s("textarea", {
136
137
  id: e,
137
138
  "aria-label": b,
@@ -161,6 +162,7 @@ var l = typeof CSS < "u" && typeof CSS.supports == "function" && CSS.supports("-
161
162
  C,
162
163
  /* @__PURE__ */ c("div", {
163
164
  className: "relative",
165
+ ...u.secret ? { "data-agent-sensitive": "" } : {},
164
166
  children: [/* @__PURE__ */ s("input", {
165
167
  id: e,
166
168
  "aria-label": b,
@@ -0,0 +1,33 @@
1
+ import { FC, ReactNode } from 'react';
2
+ export interface AgentToolsProviderProps {
3
+ /** Product id (the assistant's product namespace), reported by `app_get_context`. */
4
+ product: string;
5
+ /** Publish the registered tools to the browser's agent through WebMCP, where available. */
6
+ webmcp?: boolean;
7
+ /** Offer `app_get_context`, `app_list_pages`, `app_navigate`, `app_set_theme`. */
8
+ shellTools?: boolean;
9
+ /** Offer the generic UI tools (`ui_snapshot`, `ui_click`, `ui_fill`, `ui_select`, `ui_press_key`). */
10
+ uiTools?: boolean;
11
+ /** The product's page description (the assistant seam's `gatherPageContext`). */
12
+ gatherPageContext?: () => Readonly<Record<string, unknown>>;
13
+ /** In-app navigation. Default: push the path and announce it (`navigateInApp`). */
14
+ navigate?: (path: string) => void;
15
+ /** The signed-in person's display name for `app_get_context`. Never pass an email. */
16
+ userDisplayName?: string | null;
17
+ children?: ReactNode;
18
+ }
19
+ /**
20
+ * ★ BOFF-9783: mount once per app shell, inside the shell's providers, to let
21
+ * AI agents act in the app.
22
+ *
23
+ * It registers the shared tools (shell + UI, as switched on), mounts the host
24
+ * that asks the person to confirm consequential actions, and — when the
25
+ * browser implements WebMCP and the page is top-level — publishes every
26
+ * registered tool, the product's own included, to the browser's agent. Where
27
+ * WebMCP is missing the app is unchanged: tools stay registered for the in-app
28
+ * assistant, nothing is published.
29
+ *
30
+ * Product tools are registered separately, by the components that own them,
31
+ * with `useAgentTools`.
32
+ */
33
+ export declare const AgentToolsProvider: FC<AgentToolsProviderProps>;
@@ -0,0 +1,48 @@
1
+ import { useActiveContextOrNull as e } from "../providers/shell/ActiveContextProvider.js";
2
+ import { useThemeOrNull as t } from "../providers/shell/ThemeProvider.js";
3
+ import { navigateInApp as n } from "../navigation/inAppPath.js";
4
+ import { navDestinations as r } from "../assistant/localUiCommands.js";
5
+ import { useVisibleNavItems as i } from "../../chrome/mobileTabBar/useVisibleNavItems.js";
6
+ import { useAgentTools as a } from "./useAgentTools.js";
7
+ import { AgentConfirmHost as o } from "./confirm/AgentConfirmHost.js";
8
+ import { getWebMcpModelContext as s, startWebMcpBridge as c } from "./adapters/webmcp.js";
9
+ import { createShellTools as l } from "./shell/shellTools.js";
10
+ import { createUiTools as u } from "./ui/uiTools.js";
11
+ import { useEffect as d, useMemo as f, useRef as p } from "react";
12
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
13
+ //#region src/shared/agent-tools/AgentToolsProvider.tsx
14
+ var _ = ({ product: _, webmcp: y = !0, shellTools: b = !0, uiTools: x = !0, gatherPageContext: S, navigate: C, userDisplayName: w = null, children: T }) => {
15
+ let E = e(), D = t(), { items: O, permissionsSettled: k, simulating: A } = i(), j = k && !A, M = p(null), N = {
16
+ product: _,
17
+ organizationId: E?.organizationId ?? null,
18
+ workspaceId: E?.workspaceId ?? null,
19
+ projectId: E?.projectId ?? null,
20
+ userDisplayName: w,
21
+ pageContext: () => v(S),
22
+ destinations: j ? r(O) : [],
23
+ navigationReady: j,
24
+ navigate: C ?? ((e) => void n(e)),
25
+ themeMode: D?.mode ?? null,
26
+ setThemeMode: D ? (e) => D.setMode(e) : null
27
+ };
28
+ return d(() => {
29
+ M.current = N;
30
+ }), a(f(() => [...b ? l(() => {
31
+ if (!M.current) throw Error("agent tools source missing");
32
+ return M.current;
33
+ }) : [], ...x ? u() : []], [b, x])), d(() => {
34
+ if (!y) return;
35
+ let e = s();
36
+ if (e) return c(e);
37
+ }, [y]), /* @__PURE__ */ g(m, { children: [T, /* @__PURE__ */ h(o, {})] });
38
+ };
39
+ function v(e) {
40
+ if (!e) return null;
41
+ try {
42
+ return e();
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ //#endregion
48
+ export { _ as AgentToolsProvider };
@@ -0,0 +1,85 @@
1
+ import { AgentToolResult, AnyDefinedAgentTool } from '../types';
2
+ /**
3
+ * ★ BOFF-9786: the in-app assistant's channel to the page's agent tools
4
+ * ("browser tools").
5
+ *
6
+ * The assistant's agent runs in a sandbox, not in this tab. To act in the
7
+ * page it runs a helper that mints a call; the sandbox binds the call to this
8
+ * person's session and lists it on `GET /sessions/:id/messages` as
9
+ * `clientToolRequests`. The panel hands that list to a turn's
10
+ * `ClientToolRunner`, which runs each call ONCE through `runAgentTool` — the
11
+ * same validation, confirmation, cancellation and output cap WebMCP uses —
12
+ * and posts the result back. The agent never sees anything the tool did not
13
+ * return.
14
+ */
15
+ /** One tool as the prompt body's `clientTools` manifest describes it. */
16
+ export interface ClientToolManifestEntry {
17
+ name: string;
18
+ title: string;
19
+ description: string;
20
+ inputSchema: Record<string, unknown>;
21
+ annotations: {
22
+ readOnly: boolean;
23
+ untrustedContent: boolean;
24
+ consequential: boolean;
25
+ };
26
+ }
27
+ /** The sandbox refuses a larger manifest whole; stay well inside it. */
28
+ export declare const CLIENT_TOOL_MANIFEST_MAX_BYTES: number;
29
+ /** The sandbox's tool-count cap (ai-assistant `MANIFEST_MAX_TOOLS`). */
30
+ export declare const CLIENT_TOOL_MANIFEST_MAX_TOOLS = 48;
31
+ /**
32
+ * The tools this turn offers: the registered tools that fit the manifest cap,
33
+ * and the manifest itself. The runner may run ONLY these definitions, so a
34
+ * turn never invokes a tool it did not advertise (one the cap left out, or a
35
+ * page's replacement registered after the prompt was sent).
36
+ */
37
+ export interface ClientToolOffer {
38
+ tools: readonly AnyDefinedAgentTool[];
39
+ manifest: ClientToolManifestEntry[];
40
+ }
41
+ export declare function offerClientTools(tools?: readonly AnyDefinedAgentTool[]): ClientToolOffer;
42
+ /**
43
+ * The tools registered right now, as the prompt's `clientTools`. Product tools
44
+ * come first, then the shared ones (`app_`, `ui_`), so if the cap is reached
45
+ * the generic tools are the ones left out. Empty when nothing is registered.
46
+ */
47
+ export declare function buildClientToolManifest(tools?: readonly AnyDefinedAgentTool[]): ClientToolManifestEntry[];
48
+ /** One call as `clientToolRequests` lists it. */
49
+ export interface ClientToolRequest {
50
+ callId: string;
51
+ tool: string;
52
+ input: Record<string, unknown>;
53
+ }
54
+ /**
55
+ * Polls stop when a turn ends however it ends (done, stopped, an error the
56
+ * loop throws). A call still running this long after the last poll has nobody
57
+ * listening for its result: it is cancelled, closing any open confirmation.
58
+ */
59
+ export declare const CLIENT_TOOL_ORPHAN_MS = 20000;
60
+ /** The well-formed entries of an untrusted `clientToolRequests` value. */
61
+ export declare function parseClientToolRequests(raw: unknown): ClientToolRequest[];
62
+ export interface ClientToolRunnerOptions {
63
+ /** The definitions this turn advertised (`offerClientTools().tools`). */
64
+ tools: readonly AnyDefinedAgentTool[];
65
+ /** Post one result to the sandbox. Rejects when the post did not land. */
66
+ post: (callId: string, result: AgentToolResult) => Promise<void>;
67
+ /** Shown in the chat while a tool runs ("Using: Read a run"), cleared with null. */
68
+ onActivity?: (title: string | null) => void;
69
+ /** Clock and timer seams for tests. */
70
+ now?: () => number;
71
+ orphanMs?: number;
72
+ }
73
+ export interface ClientToolRunner {
74
+ /** The `clientToolRequests` of one poll. New calls start; vanished ones are cancelled. */
75
+ receive: (raw: unknown) => void;
76
+ /** The turn is over: cancel everything still running. Idempotent. */
77
+ dispose: () => void;
78
+ }
79
+ /**
80
+ * One runner per assistant turn. Each call runs at most once, however many
81
+ * polls list it; a call the sandbox stops listing (its helper ended — the turn
82
+ * was stopped, or it timed out) is cancelled, which also closes a pending
83
+ * confirmation.
84
+ */
85
+ export declare function createClientToolRunner(options: ClientToolRunnerOptions): ClientToolRunner;
@@ -0,0 +1,110 @@
1
+ import { agentToolFailure as e } from "../errors.js";
2
+ import { logAgentToolProblem as t } from "../log.js";
3
+ import { getAgentTool as n, listAgentTools as r } from "../registry.js";
4
+ import { runAgentTool as i } from "../runAgentTool.js";
5
+ //#region src/shared/agent-tools/adapters/assistant.ts
6
+ var a = 40 * 1024, o = 48, s = new TextEncoder();
7
+ function c(e) {
8
+ let t = e.annotations ?? {};
9
+ return {
10
+ name: e.name,
11
+ title: e.title,
12
+ description: e.description,
13
+ inputSchema: e.inputSchema,
14
+ annotations: {
15
+ readOnly: t.readOnly === !0,
16
+ untrustedContent: t.untrustedContent === !0,
17
+ consequential: t.consequential === !0
18
+ }
19
+ };
20
+ }
21
+ function l(e = r()) {
22
+ let t = u(e), n = new Set(t.map((e) => e.name));
23
+ return {
24
+ tools: e.filter((e) => n.has(e.name)),
25
+ manifest: t
26
+ };
27
+ }
28
+ function u(e = r()) {
29
+ let n = (e) => e.startsWith("app_") || e.startsWith("ui_"), i = [...e.filter((e) => !n(e.name)), ...e.filter((e) => n(e.name))], a = [], o = 2;
30
+ for (let e of i) {
31
+ let n = c(e), r = s.encode(JSON.stringify(n)).length + 1;
32
+ if (a.length >= 48 || o + r > 40960) {
33
+ t("client tool manifest cap reached", { left: e.name });
34
+ continue;
35
+ }
36
+ a.push(n), o += r;
37
+ }
38
+ return a;
39
+ }
40
+ var d = /^ct_[0-9a-f]{32}$/, f = 4, p = 2e4;
41
+ function m(e) {
42
+ if (!Array.isArray(e)) return [];
43
+ let t = [];
44
+ for (let n of e.slice(0, f)) {
45
+ if (typeof n != "object" || !n || Array.isArray(n)) continue;
46
+ let { callId: e, tool: r, input: i } = n;
47
+ typeof e != "string" || !d.test(e) || typeof r != "string" || r.length === 0 || r.length > 64 || typeof i != "object" || !i || Array.isArray(i) || t.push({
48
+ callId: e,
49
+ tool: r,
50
+ input: i
51
+ });
52
+ }
53
+ return t;
54
+ }
55
+ function h(r) {
56
+ let a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set(), s = !1, c = r.now ?? Date.now, l = r.orphanMs ?? 2e4, u = c(), d, f = () => {
57
+ d !== void 0 && clearInterval(d), d = void 0;
58
+ }, p = () => {
59
+ d === void 0 && (d = setInterval(() => {
60
+ if (a.size === 0) return f();
61
+ if (!(c() - u < l)) {
62
+ for (let e of a.values()) e.abort();
63
+ f();
64
+ }
65
+ }, Math.max(1e3, Math.floor(l / 4))));
66
+ }, h = () => {
67
+ if (!r.onActivity) return;
68
+ let e = [...a.keys()].map((e) => g.get(e)).filter((e) => typeof e == "string");
69
+ r.onActivity(e.length > 0 ? e.join(", ") : null);
70
+ }, g = /* @__PURE__ */ new Map(), _ = async (e, n) => {
71
+ for (let t = 0; t < 2; t += 1) try {
72
+ await r.post(e, n);
73
+ return;
74
+ } catch {}
75
+ t("client tool result not delivered", { callId: e });
76
+ }, v = (t) => {
77
+ o.add(t.callId);
78
+ let s = r.tools.find((e) => e.name === t.tool), c = s && n(t.tool) === s ? s : null;
79
+ if (!c) {
80
+ _(t.callId, e("NOT_FOUND", `The page does not offer "${t.tool.slice(0, 40)}" right now. It may have changed; use the current browser tools.`, !0));
81
+ return;
82
+ }
83
+ let l = new AbortController();
84
+ a.set(t.callId, l), g.set(t.callId, c.title), h(), p(), i(c, t.input, {
85
+ initiator: "assistant",
86
+ signal: l.signal
87
+ }).then((e) => {
88
+ if (!l.signal.aborted) return _(t.callId, e);
89
+ }).finally(() => {
90
+ a.delete(t.callId), g.delete(t.callId), h();
91
+ });
92
+ };
93
+ return {
94
+ receive(e) {
95
+ if (s) return;
96
+ u = c();
97
+ let t = m(e), n = new Set(t.map((e) => e.callId));
98
+ for (let [e, t] of a) n.has(e) || t.abort();
99
+ for (let e of t) o.has(e.callId) || v(e);
100
+ },
101
+ dispose() {
102
+ if (!s) {
103
+ s = !0, f();
104
+ for (let e of a.values()) e.abort();
105
+ }
106
+ }
107
+ };
108
+ }
109
+ //#endregion
110
+ export { a as CLIENT_TOOL_MANIFEST_MAX_BYTES, o as CLIENT_TOOL_MANIFEST_MAX_TOOLS, p as CLIENT_TOOL_ORPHAN_MS, u as buildClientToolManifest, h as createClientToolRunner, l as offerClientTools, m as parseClientToolRequests };
@@ -0,0 +1,69 @@
1
+ import { AnyDefinedAgentTool } from '../types';
2
+ /**
3
+ * ★ BOFF-9783: publish the registered agent tools to the browser's own agent
4
+ * through W3C WebMCP — `document.modelContext.registerTool()`, per the Web
5
+ * Machine Learning CG draft (webmachinelearning.github.io/webmcp, 2026-10-20)
6
+ * and Chrome's origin-trial implementation (Chrome 149+, Edge 150+).
7
+ *
8
+ * The API is experimental: everything here is feature-detected and every call
9
+ * guarded, so a browser without it — or with an older or newer shape of it —
10
+ * gets exactly the app it had before.
11
+ */
12
+ /** The parts of the WebMCP `ModelContext` interface this adapter uses. */
13
+ export interface WebMcpModelContext {
14
+ registerTool(tool: WebMcpToolDescriptor, options?: WebMcpRegisterToolOptions): Promise<void>;
15
+ }
16
+ export interface WebMcpToolDescriptor {
17
+ name: string;
18
+ title?: string;
19
+ description: string;
20
+ inputSchema: Record<string, unknown>;
21
+ execute: (input: unknown, options?: {
22
+ signal?: AbortSignal;
23
+ }) => Promise<unknown>;
24
+ annotations?: {
25
+ readOnlyHint?: boolean;
26
+ untrustedContentHint?: boolean;
27
+ consequentialHint?: boolean;
28
+ };
29
+ }
30
+ export interface WebMcpRegisterToolOptions {
31
+ signal?: AbortSignal;
32
+ /**
33
+ * Origins other than ours allowed to see the tool. ★ Never set: our tools
34
+ * act with the person's session, and no other site is trusted with that.
35
+ */
36
+ exposedTo?: never;
37
+ }
38
+ /** The window members the availability check reads (a seam for tests). */
39
+ export interface WebMcpWindow {
40
+ isSecureContext: boolean;
41
+ self: unknown;
42
+ top: unknown;
43
+ document: unknown;
44
+ }
45
+ /**
46
+ * `document.modelContext` when it is usable here, else null.
47
+ *
48
+ * ★★ Never inside a frame. The `tools` permission policy only switches the API
49
+ * off in cross-origin frames by DEFAULT — an embedding page can switch it back
50
+ * on with `allow="tools"`, and our own `Permissions-Policy: tools=(self)` header
51
+ * does not prevent that (inside the frame, "self" is us). The app is framable
52
+ * on purpose in places (the public workflow embed), so the rule is simply: a
53
+ * framed copy of the app registers nothing.
54
+ */
55
+ export declare function getWebMcpModelContext(win?: WebMcpWindow | undefined): WebMcpModelContext | null;
56
+ export declare function toWebMcpDescriptor(tool: AnyDefinedAgentTool): WebMcpToolDescriptor;
57
+ /**
58
+ * Keep `modelContext` in step with the registry until the returned stop
59
+ * function is called.
60
+ *
61
+ * One `AbortController` per published tool: aborting it unregisters the tool
62
+ * (the spec's cleanup mechanism). Changes to one name are serialised on a
63
+ * promise chain — abort the old registration, wait for its `registerTool` to
64
+ * settle, then register the new one — because registering a name that is
65
+ * still registered rejects with `InvalidStateError`. If that happens anyway
66
+ * (another copy of the app's code registered it), the adapter retries once on
67
+ * the next tick and then logs and gives up on that name.
68
+ */
69
+ export declare function startWebMcpBridge(modelContext: WebMcpModelContext): () => void;
@@ -0,0 +1,81 @@
1
+ import { logAgentToolProblem as e } from "../log.js";
2
+ import { listAgentTools as t, subscribeAgentTools as n } from "../registry.js";
3
+ import { runAgentTool as r } from "../runAgentTool.js";
4
+ //#region src/shared/agent-tools/adapters/webmcp.ts
5
+ function i(e = typeof window > "u" ? void 0 : window) {
6
+ if (!e) return null;
7
+ try {
8
+ if (!e.isSecureContext || e.top !== e.self) return null;
9
+ let t = e.document?.modelContext;
10
+ return !t || typeof t != "object" ? null : typeof t.registerTool == "function" ? t : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function a(e) {
16
+ return !!e && typeof e == "object" && e.name === "InvalidStateError";
17
+ }
18
+ function o(e) {
19
+ let t = e.annotations ?? {};
20
+ return {
21
+ name: e.name,
22
+ title: e.title,
23
+ description: e.description,
24
+ inputSchema: e.inputSchema,
25
+ execute: (t, n) => r(e, t, {
26
+ initiator: "webmcp",
27
+ signal: n?.signal ?? new AbortController().signal
28
+ }),
29
+ annotations: {
30
+ readOnlyHint: t.readOnly === !0,
31
+ untrustedContentHint: t.untrustedContent === !0,
32
+ consequentialHint: t.consequential === !0
33
+ }
34
+ };
35
+ }
36
+ function s(r) {
37
+ let i = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), c = !1, l = async (t, n) => {
38
+ for (let i = 0; i < 2; i += 1) {
39
+ if (n.aborted || c) return;
40
+ try {
41
+ await r.registerTool(o(t), { signal: n });
42
+ return;
43
+ } catch (r) {
44
+ if (n.aborted) return;
45
+ if (i === 0 && a(r)) {
46
+ await new Promise((e) => setTimeout(e, 0));
47
+ continue;
48
+ }
49
+ e("WebMCP registerTool failed", {
50
+ tool: t.name,
51
+ error: r?.name ?? "unknown"
52
+ });
53
+ return;
54
+ }
55
+ }
56
+ }, u = (e, t) => {
57
+ let n = i.get(e);
58
+ if (n && (n.controller.abort(), i.delete(e)), !t) return;
59
+ let r = new AbortController();
60
+ i.set(e, {
61
+ tool: t,
62
+ controller: r
63
+ });
64
+ let a = (s.get(e) ?? Promise.resolve()).then(() => l(t, r.signal));
65
+ s.set(e, a);
66
+ }, d = () => {
67
+ if (c) return;
68
+ let e = new Map(t().map((e) => [e.name, e]));
69
+ for (let [t, n] of e) i.get(t)?.tool !== n && u(t, n);
70
+ for (let t of [...i.keys()]) e.has(t) || u(t, null);
71
+ };
72
+ d();
73
+ let f = n(d);
74
+ return () => {
75
+ c = !0, f();
76
+ for (let { controller: e } of i.values()) e.abort();
77
+ i.clear();
78
+ };
79
+ }
80
+ //#endregion
81
+ export { i as getWebMcpModelContext, s as startWebMcpBridge, o as toWebMcpDescriptor };
@@ -0,0 +1,12 @@
1
+ import { FC } from 'react';
2
+ /** Test-id prefix of the dialog; UI tools treat it as a deny zone. */
3
+ export declare const AGENT_CONFIRM_TEST_ID_PREFIX = "agent-tool-confirm";
4
+ /**
5
+ * Shows the confirmation an agent tool is waiting on.
6
+ *
7
+ * It reuses the assistant's control confirmation dialog, and with it every rule
8
+ * that makes a confirmation a person's decision: focus starts on Cancel, Enter
9
+ * decides nothing, confirm arms only after a delay and counts a trusted press
10
+ * only. Mounted once, by `AgentToolsProvider`.
11
+ */
12
+ export declare const AgentConfirmHost: FC;
@@ -0,0 +1,34 @@
1
+ import { useOptionalI18n as e } from "../../providers/shell/I18nProvider.js";
2
+ import { AssistantControlConfirmDialog as t } from "../../assistant/ui/AssistantControlConfirmDialog.js";
3
+ import { currentConfirmation as n, registerConfirmHost as r, settle as i, subscribeConfirmations as a } from "./confirmStore.js";
4
+ import { useCallback as o, useEffect as s, useSyncExternalStore as c } from "react";
5
+ import { jsx as l } from "react/jsx-runtime";
6
+ //#region src/shared/agent-tools/confirm/AgentConfirmHost.tsx
7
+ var u = "agent-tool-confirm", d = () => {
8
+ let d = e(), p = c(a, n, n);
9
+ s(() => r(), []);
10
+ let m = o((e, t, n) => {
11
+ let r = `agentTools.${e}`, i = d?.t(r, t, n);
12
+ return i && i !== r ? i : f(t, n);
13
+ }, [d]);
14
+ if (!p) return null;
15
+ let h = `${p.confirmation.body}\n\n${m("confirm.requestedBy", "An AI agent asked to do this ({{tool}}). Nothing happens unless you confirm.", { tool: p.toolTitle })}`;
16
+ return /* @__PURE__ */ l(t, {
17
+ open: !0,
18
+ copy: {
19
+ title: p.confirmation.title,
20
+ body: h,
21
+ confirm: p.confirmation.confirmLabel
22
+ },
23
+ tr: m,
24
+ cancelLabel: m("confirm.cancel", "Cancel"),
25
+ testIdPrefix: u,
26
+ onConfirm: () => i(p.id, !0),
27
+ onDismiss: () => i(p.id, !1)
28
+ }, p.id);
29
+ };
30
+ function f(e, t) {
31
+ return t ? Object.entries(t).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), e) : e;
32
+ }
33
+ //#endregion
34
+ export { u as AGENT_CONFIRM_TEST_ID_PREFIX, d as AgentConfirmHost };
@@ -0,0 +1,37 @@
1
+ import { AgentToolConfirmation } from '../types';
2
+ /**
3
+ * The queue of confirmations agent tools are waiting on.
4
+ *
5
+ * ★ Page-global state, for the same reason as the tool registry: a tool may be
6
+ * registered anywhere in the tree, the host that shows the dialog is mounted
7
+ * once by `AgentToolsProvider`. One dialog at a time, first come first served.
8
+ *
9
+ * ★★ What makes a consequential tool safe to expose is that the dialog's
10
+ * confirm button counts a TRUSTED press only (`AssistantControlConfirmDialog`):
11
+ * a click a script dispatched — including an agent's own `ui_click` — is
12
+ * ignored. With no host mounted there is no way to ask, and the request fails
13
+ * with `NEEDS_USER` rather than running unconfirmed.
14
+ */
15
+ export interface PendingConfirmation {
16
+ id: number;
17
+ toolTitle: string;
18
+ confirmation: AgentToolConfirmation;
19
+ resolve: (confirmed: boolean) => void;
20
+ }
21
+ type Listener = () => void;
22
+ export declare function subscribeConfirmations(listener: Listener): () => void;
23
+ /** The confirmation on screen (the head of the queue), or null. */
24
+ export declare function currentConfirmation(): PendingConfirmation | null;
25
+ /** A host announces itself while mounted. Returns its cleanup. */
26
+ export declare function registerConfirmHost(): () => void;
27
+ /** Answer the confirmation `id`. Idempotent: a second answer is ignored. */
28
+ export declare function settle(id: number, confirmed: boolean): void;
29
+ /**
30
+ * Ask the person to confirm. Resolves true only on their confirm press; false
31
+ * on cancel. Rejects with `CANCELLED` when `signal` aborts (the dialog closes),
32
+ * and with `NEEDS_USER` when no host is mounted to ask.
33
+ */
34
+ export declare function requestAgentConfirmation(toolTitle: string, confirmation: AgentToolConfirmation, signal: AbortSignal): Promise<boolean>;
35
+ /** Test seam: drop every request and host. */
36
+ export declare function resetConfirmations(): void;
37
+ export {};
@@ -0,0 +1,58 @@
1
+ import { AgentToolError as e } from "../errors.js";
2
+ //#region src/shared/agent-tools/confirm/confirmStore.ts
3
+ var t = Symbol.for("burdenoff.fe-libs.agentTools.confirmations.v1");
4
+ function n() {
5
+ let e = globalThis, n = e[t];
6
+ if (n && n.listeners instanceof Set) return n;
7
+ let r = {
8
+ queue: [],
9
+ nextId: 1,
10
+ hosts: 0,
11
+ listeners: /* @__PURE__ */ new Set()
12
+ };
13
+ return e[t] = r, r;
14
+ }
15
+ function r() {
16
+ for (let e of n().listeners) e();
17
+ }
18
+ function i(e) {
19
+ let { listeners: t } = n();
20
+ return t.add(e), () => t.delete(e);
21
+ }
22
+ function a() {
23
+ return n().queue[0] ?? null;
24
+ }
25
+ function o() {
26
+ return n().hosts += 1, () => {
27
+ let e = n();
28
+ if (e.hosts = Math.max(0, e.hosts - 1), e.hosts === 0) for (let t of [...e.queue]) s(t.id, !1);
29
+ };
30
+ }
31
+ function s(e, t) {
32
+ let i = n(), a = i.queue.find((t) => t.id === e);
33
+ a && (i.queue = i.queue.filter((t) => t.id !== e), r(), a.resolve(t));
34
+ }
35
+ function c(t, i, a) {
36
+ return n().hosts === 0 ? Promise.reject(new e("NEEDS_USER", "This action needs the person to do it in the page.")) : a.aborted ? Promise.reject(new e("CANCELLED", "The action was cancelled.")) : new Promise((o, s) => {
37
+ let c = n(), l = c.nextId;
38
+ c.nextId += 1;
39
+ let u = () => {
40
+ let t = c.queue.some((e) => e.id === l);
41
+ c.queue = c.queue.filter((e) => e.id !== l), t && r(), s(new e("CANCELLED", "The action was cancelled."));
42
+ };
43
+ a.addEventListener("abort", u, { once: !0 }), c.queue = [...c.queue, {
44
+ id: l,
45
+ toolTitle: t,
46
+ confirmation: i,
47
+ resolve: (e) => {
48
+ a.removeEventListener("abort", u), o(e);
49
+ }
50
+ }], r();
51
+ });
52
+ }
53
+ function l() {
54
+ let e = n();
55
+ e.queue = [], e.hosts = 0, r();
56
+ }
57
+ //#endregion
58
+ export { a as currentConfirmation, o as registerConfirmHost, c as requestAgentConfirmation, l as resetConfirmations, s as settle, i as subscribeConfirmations };
@@ -0,0 +1,13 @@
1
+ import { AgentToolDefinition, AgentToolInputSchema, DefinedAgentTool } from './types';
2
+ /**
3
+ * Check a tool definition once, when the module that declares it loads, and
4
+ * derive the JSON Schema every channel advertises.
5
+ *
6
+ * ★ It THROWS on a definition that breaks a limit. Definitions are static code,
7
+ * so a broken one fails the first test or page load that imports it, never a
8
+ * person's agent call in production.
9
+ *
10
+ * The schema is derived from the zod object rather than written by hand, so
11
+ * what the agent is told and what `runAgentTool` enforces cannot drift apart.
12
+ */
13
+ export declare function defineAgentTool<S extends AgentToolInputSchema, O>(definition: AgentToolDefinition<S, O>): DefinedAgentTool<S, O>;
@@ -0,0 +1,27 @@
1
+ import { AGENT_TOOL_NAME_PATTERN as e } from "./limits.js";
2
+ import { z as t } from "zod";
3
+ //#region src/shared/agent-tools/defineAgentTool.ts
4
+ function n(t) {
5
+ let { name: n, title: i, description: a, annotations: o, confirm: s } = t, c = (e) => {
6
+ throw TypeError(`Agent tool "${n}": ${e}`);
7
+ };
8
+ (!e.test(n) || n.length > 30) && c(`name must match ${e} and be at most 30 chars`), i.trim() || c("title is required"), a.trim() || c("description is required"), a.length > 500 && c(`description is ${a.length} chars (max 500)`), o?.consequential && !s && c("a consequential tool must say what the person confirms (`confirm`)"), o?.consequential && o.readOnly && c("a tool cannot be both readOnly and consequential");
9
+ let l = r(t.input);
10
+ l.additionalProperties !== !1 && c("input must be a strict object (z.strictObject) so unknown keys are rejected");
11
+ let u = l.properties ?? {};
12
+ for (let [e, t] of Object.entries(u)) {
13
+ e.length > 30 && c(`parameter "${e}" is longer than 30 chars`);
14
+ let n = t.description;
15
+ typeof n != "string" || !n.trim() ? c(`parameter "${e}" needs a .describe()`) : n.length > 150 && c(`parameter "${e}" description is ${n.length} chars (max 150)`);
16
+ }
17
+ return Object.freeze({
18
+ ...t,
19
+ inputSchema: l
20
+ });
21
+ }
22
+ function r(e) {
23
+ let n = { ...t.toJSONSchema(e, { io: "input" }) };
24
+ return delete n.$schema, n;
25
+ }
26
+ //#endregion
27
+ export { n as defineAgentTool };
@@ -0,0 +1,33 @@
1
+ import { AgentToolErrorCode, AgentToolFailure } from './types';
2
+ /**
3
+ * An expected failure a tool reports to the agent: the code is machine-readable,
4
+ * the message is written for the model to act on ("No run with that id in this
5
+ * workspace"). Never put input values, tokens or response bodies in it.
6
+ */
7
+ /**
8
+ * A brand every copy of fe-libs shares (`Symbol.for`): a microfrontend's copy
9
+ * of this class and the shell's are different constructors, so `instanceof`
10
+ * alone would turn a product tool's expected error into a generic FAILED.
11
+ */
12
+ declare const AGENT_TOOL_ERROR_BRAND: unique symbol;
13
+ export declare class AgentToolError extends Error {
14
+ readonly code: AgentToolErrorCode;
15
+ readonly retryable: boolean;
16
+ readonly [AGENT_TOOL_ERROR_BRAND] = true;
17
+ constructor(code: AgentToolErrorCode, message: string, options?: {
18
+ retryable?: boolean;
19
+ });
20
+ }
21
+ /** An AgentToolError from ANY copy of fe-libs (brand + shape, not `instanceof`). */
22
+ export declare function isAgentToolError(error: unknown): error is AgentToolError;
23
+ /**
24
+ * Turn anything a tool threw into the failure the agent sees.
25
+ *
26
+ * ★ Only the error CODE survives from a GraphQL or network error, mapped to a
27
+ * fixed sentence. A client error's message can embed the whole request,
28
+ * variables included (graphql-request's `ClientError` does), and a server
29
+ * message can carry another tenant's identifiers; neither may reach an agent.
30
+ */
31
+ export declare function toAgentToolFailure(error: unknown): AgentToolFailure;
32
+ export declare function agentToolFailure(code: AgentToolErrorCode, message?: string, retryable?: boolean): AgentToolFailure;
33
+ export {};