@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.
- package/dist/credentials/SchemaForm.js +2 -0
- package/dist/shared/agent-tools/AgentToolsProvider.d.ts +33 -0
- package/dist/shared/agent-tools/AgentToolsProvider.js +48 -0
- package/dist/shared/agent-tools/adapters/assistant.d.ts +85 -0
- package/dist/shared/agent-tools/adapters/assistant.js +110 -0
- package/dist/shared/agent-tools/adapters/webmcp.d.ts +69 -0
- package/dist/shared/agent-tools/adapters/webmcp.js +81 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.d.ts +12 -0
- package/dist/shared/agent-tools/confirm/AgentConfirmHost.js +34 -0
- package/dist/shared/agent-tools/confirm/confirmStore.d.ts +37 -0
- package/dist/shared/agent-tools/confirm/confirmStore.js +58 -0
- package/dist/shared/agent-tools/defineAgentTool.d.ts +13 -0
- package/dist/shared/agent-tools/defineAgentTool.js +27 -0
- package/dist/shared/agent-tools/errors.d.ts +33 -0
- package/dist/shared/agent-tools/errors.js +105 -0
- package/dist/shared/agent-tools/index.d.ts +29 -0
- package/dist/shared/agent-tools/limits.d.ts +20 -0
- package/dist/shared/agent-tools/limits.js +4 -0
- package/dist/shared/agent-tools/log.d.ts +15 -0
- package/dist/shared/agent-tools/log.js +11 -0
- package/dist/shared/agent-tools/outputBudget.d.ts +21 -0
- package/dist/shared/agent-tools/outputBudget.js +51 -0
- package/dist/shared/agent-tools/registry.d.ts +13 -0
- package/dist/shared/agent-tools/registry.js +53 -0
- package/dist/shared/agent-tools/runAgentTool.d.ts +21 -0
- package/dist/shared/agent-tools/runAgentTool.js +57 -0
- package/dist/shared/agent-tools/shell/shellTools.d.ts +44 -0
- package/dist/shared/agent-tools/shell/shellTools.js +145 -0
- package/dist/shared/agent-tools/testSupport/installDom.d.ts +6 -0
- package/dist/shared/agent-tools/types.d.ts +106 -0
- package/dist/shared/agent-tools/ui/dom.d.ts +43 -0
- package/dist/shared/agent-tools/ui/dom.js +178 -0
- package/dist/shared/agent-tools/ui/refs.d.ts +8 -0
- package/dist/shared/agent-tools/ui/refs.js +20 -0
- package/dist/shared/agent-tools/ui/uiTools.d.ts +40 -0
- package/dist/shared/agent-tools/ui/uiTools.js +277 -0
- package/dist/shared/agent-tools/useAgentTools.d.ts +14 -0
- package/dist/shared/agent-tools/useAgentTools.js +14 -0
- package/dist/shared/assistant/api.d.ts +18 -0
- package/dist/shared/assistant/api.js +145 -131
- package/dist/shared/assistant/avatar/live/LiveAvatarStage.js +2 -3
- package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +2 -3
- package/dist/shared/assistant/avatar/live/avatarPorts.d.ts +9 -0
- package/dist/shared/assistant/avatar/live/avatarTransport.d.ts +25 -3
- package/dist/shared/assistant/avatar/live/avatarTransport.js +29 -2
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +22 -4
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +113 -81
- package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +15 -1
- package/dist/shared/assistant/avatar/live/liveAvatarController.js +204 -186
- package/dist/shared/assistant/avatar/live/liveKitAvatarRoom.js +6 -1
- package/dist/shared/assistant/ui/AssistantChatArea.js +1158 -1148
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +1 -1
- package/dist/shared/assistant/writeProposals.d.ts +6 -0
- package/dist/shared/providers/shell/ActiveContextProvider.d.ts +5 -0
- package/dist/shared/providers/shell/ActiveContextProvider.js +4 -1
- package/dist/shared/providers/shell/index.d.ts +1 -1
- package/dist/shared-agent-tools.d.ts +2 -0
- package/dist/shared-agent-tools.js +16 -0
- package/dist/shared-assistant.js +285 -285
- package/dist/shared-providers-shell.js +20 -20
- package/dist/ui/alert-dialog.js +2 -0
- package/package.json +7 -2
|
@@ -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 {};
|