@jini-ai/agentic 0.3.7 → 0.4.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/CHANGELOG.md +99 -0
- package/README.md +33 -6
- package/dist/core/a2ui/agent-to-renderer.d.ts +3 -1
- package/dist/core/a2ui/agent-to-renderer.js +1 -1
- package/dist/core/a2ui/catalog.d.ts +14 -5
- package/dist/core/a2ui/catalog.js +5 -5
- package/dist/core/a2ui/common-types.d.ts +11 -3
- package/dist/core/a2ui/common-types.js +5 -3
- package/dist/core/a2ui/index.d.ts +1 -1
- package/dist/core/a2ui/index.js +1 -1
- package/dist/core/a2ui/interpreter.d.ts +45 -10
- package/dist/core/a2ui/interpreter.js +38 -43
- package/dist/core/a2ui/json-pointer.d.ts +16 -4
- package/dist/core/a2ui/json-pointer.js +7 -7
- package/dist/core/a2ui/renderer-to-agent.d.ts +22 -8
- package/dist/core/a2ui/renderer-to-agent.js +5 -5
- package/dist/core/a2ui/resolve.d.ts +7 -2
- package/dist/core/a2ui/resolve.js +10 -9
- package/dist/core/a2ui/tree.d.ts +8 -2
- package/dist/core/a2ui/tree.js +2 -2
- package/dist/core/ag-ui.d.ts +17 -9
- package/dist/core/ag-ui.js +4 -4
- package/dist/core/annotated-actions-script.d.ts +13 -0
- package/dist/core/annotated-actions-script.js +137 -0
- package/dist/core/capability.d.ts +12 -3
- package/dist/core/capability.js +3 -3
- package/dist/core/dom/dom-page-driver.d.ts +8 -4
- package/dist/core/dom/dom-page-driver.js +19 -20
- package/dist/core/dom/index.d.ts +3 -3
- package/dist/core/dom/index.js +1 -1
- package/dist/core/dom/model-context.d.ts +18 -8
- package/dist/core/dom/model-context.js +12 -15
- package/dist/core/dom/native-model-context.d.ts +6 -0
- package/dist/core/dom/native-model-context.js +19 -0
- package/dist/core/element-handles.d.ts +6 -2
- package/dist/core/element-handles.js +3 -3
- package/dist/core/gen-ui/encoder.d.ts +21 -11
- package/dist/core/gen-ui/encoder.js +8 -7
- package/dist/core/gen-ui/events.d.ts +6 -5
- package/dist/core/gen-ui/events.js +6 -5
- package/dist/core/gen-ui/index.d.ts +1 -1
- package/dist/core/gen-ui/index.js +1 -1
- package/dist/core/guards.d.ts +17 -5
- package/dist/core/guards.js +6 -6
- package/dist/core/handle.d.ts +12 -5
- package/dist/core/handle.js +9 -8
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +3 -1
- package/dist/core/list-handles.d.ts +4 -1
- package/dist/core/list-handles.js +1 -1
- package/dist/core/mcp-ui-apps.d.ts +39 -9
- package/dist/core/mcp-ui-apps.js +10 -8
- package/dist/core/messages.d.ts +6 -0
- package/dist/core/messages.js +6 -0
- package/dist/core/page-capabilities.d.ts +0 -11
- package/dist/core/page-capabilities.js +13 -1
- package/dist/core/page-driver.d.ts +32 -11
- package/dist/core/page-executor.d.ts +8 -2
- package/dist/core/page-executor.js +31 -31
- package/dist/core/webmcp.d.ts +27 -8
- package/dist/core/webmcp.js +14 -14
- package/dist/index.d.ts +30 -0
- package/dist/index.js +30 -0
- package/dist/skills/install/archive.d.ts +8 -0
- package/dist/skills/install/archive.js +47 -0
- package/dist/skills/install/fetch-adapter.d.ts +6 -0
- package/dist/skills/install/fetch-adapter.js +6 -0
- package/dist/skills/install/github.d.ts +21 -0
- package/dist/skills/install/github.js +92 -0
- package/dist/skills/install/index.d.ts +11 -0
- package/dist/skills/install/index.js +11 -0
- package/dist/skills/install/install-service.d.ts +44 -0
- package/dist/skills/install/install-service.js +121 -0
- package/dist/skills/install/layout.d.ts +13 -0
- package/dist/skills/install/layout.js +22 -0
- package/dist/skills/install/live-registration.d.ts +61 -0
- package/dist/skills/install/live-registration.js +64 -0
- package/dist/skills/install/node-filesystem.d.ts +4 -0
- package/dist/skills/install/node-filesystem.js +41 -0
- package/dist/skills/install/ports.d.ts +101 -0
- package/dist/skills/install/ports.js +2 -0
- package/dist/skills/install/state.d.ts +13 -0
- package/dist/skills/install/state.js +23 -0
- package/dist/skills/install/validation.d.ts +45 -0
- package/dist/skills/install/validation.js +104 -0
- package/package.json +20 -5
- package/dist/a2ui/agent-to-renderer.d.ts +0 -613
- package/dist/a2ui/agent-to-renderer.js +0 -191
- package/dist/a2ui/catalog.d.ts +0 -73
- package/dist/a2ui/catalog.js +0 -304
- package/dist/a2ui/common-types.d.ts +0 -401
- package/dist/a2ui/common-types.js +0 -130
- package/dist/a2ui/index.d.ts +0 -16
- package/dist/a2ui/index.js +0 -16
- package/dist/a2ui/interpreter.d.ts +0 -71
- package/dist/a2ui/interpreter.js +0 -337
- package/dist/a2ui/json-pointer.d.ts +0 -48
- package/dist/a2ui/json-pointer.js +0 -174
- package/dist/a2ui/renderer-to-agent.d.ts +0 -342
- package/dist/a2ui/renderer-to-agent.js +0 -111
- package/dist/a2ui/resolve.d.ts +0 -28
- package/dist/a2ui/resolve.js +0 -144
- package/dist/a2ui/tree.d.ts +0 -66
- package/dist/a2ui/tree.js +0 -74
- package/dist/ag-ui.d.ts +0 -82
- package/dist/ag-ui.js +0 -85
- package/dist/agui/encoder.d.ts +0 -50
- package/dist/agui/encoder.js +0 -97
- package/dist/agui/events.d.ts +0 -59
- package/dist/agui/events.js +0 -15
- package/dist/capability.d.ts +0 -81
- package/dist/capability.js +0 -79
- package/dist/dom/dom-page-driver.d.ts +0 -36
- package/dist/dom/dom-page-driver.js +0 -601
- package/dist/dom/index.d.ts +0 -17
- package/dist/dom/index.js +0 -17
- package/dist/dom/model-context.d.ts +0 -69
- package/dist/dom/model-context.js +0 -44
- package/dist/element-handles.d.ts +0 -120
- package/dist/element-handles.js +0 -39
- package/dist/gen-ui/encoder.d.ts +0 -50
- package/dist/gen-ui/encoder.js +0 -97
- package/dist/gen-ui/events.d.ts +0 -59
- package/dist/gen-ui/events.js +0 -15
- package/dist/gen-ui/index.d.ts +0 -18
- package/dist/gen-ui/index.js +0 -17
- package/dist/guards.d.ts +0 -96
- package/dist/guards.js +0 -186
- package/dist/handle.d.ts +0 -41
- package/dist/handle.js +0 -41
- package/dist/mcp-ui-apps.d.ts +0 -223
- package/dist/mcp-ui-apps.js +0 -252
- package/dist/mcp-ui.d.ts +0 -133
- package/dist/mcp-ui.js +0 -134
- package/dist/page-capabilities.d.ts +0 -14
- package/dist/page-capabilities.js +0 -129
- package/dist/page-driver.d.ts +0 -120
- package/dist/page-driver.js +0 -2
- package/dist/page-executor.d.ts +0 -94
- package/dist/page-executor.js +0 -320
- package/dist/webmcp.d.ts +0 -202
- package/dist/webmcp.js +0 -173
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-free script asset for data-toolname/data-tooldescription actions.
|
|
3
|
+
* Native tool* forms remain the browser's responsibility. No polyfill, transport,
|
|
4
|
+
* field-value reader, arbitrary selector input or cross-origin exposure is added.
|
|
5
|
+
* The host owns consent copy and its browser preference key. This string asset
|
|
6
|
+
* can be inlined into static exports without importing a DOM runtime on Node.
|
|
7
|
+
*/
|
|
8
|
+
export function createAnnotatedActionsScript({ preferenceKey, confirmationMessage = 'Allow this browser-agent action?', confirmationMessages = {} }, _optional = {}) {
|
|
9
|
+
const config = JSON.stringify({ preferenceKey, confirmationMessage, confirmationMessages }).replace(/</g, '\\u003c');
|
|
10
|
+
return String.raw `(function install(config) {
|
|
11
|
+
'use strict';
|
|
12
|
+
var doc = document;
|
|
13
|
+
var locale = (doc.documentElement.lang || 'en').toLowerCase();
|
|
14
|
+
var messages = config.confirmationMessages;
|
|
15
|
+
var confirmation = Object.keys(messages).find(function (key) { return key.toLowerCase() === locale; });
|
|
16
|
+
var confirmationMessage = messages[confirmation] || messages[locale.split('-')[0]] || config.confirmationMessage;
|
|
17
|
+
var stopped = false;
|
|
18
|
+
var registrations = [];
|
|
19
|
+
var observer;
|
|
20
|
+
function enabled() {
|
|
21
|
+
try { return !config.preferenceKey || localStorage.getItem(config.preferenceKey) !== 'false'; }
|
|
22
|
+
catch (_) { return true; }
|
|
23
|
+
}
|
|
24
|
+
function stripNativeForms() {
|
|
25
|
+
doc.querySelectorAll('form[toolname]').forEach(function (form) {
|
|
26
|
+
['toolname', 'tooldescription', 'toolautosubmit'].forEach(function (name) { form.removeAttribute(name); });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function stop(event) {
|
|
30
|
+
stopped = true;
|
|
31
|
+
registrations.forEach(function (controller) { controller.abort(); });
|
|
32
|
+
registrations = [];
|
|
33
|
+
if (observer) observer.disconnect();
|
|
34
|
+
window.removeEventListener('storage', onStorage);
|
|
35
|
+
window.removeEventListener('pagehide', stop);
|
|
36
|
+
// A cached Document executes no scripts when the browser restores it.
|
|
37
|
+
// Reinstall on pageshow, while old callbacks remain permanently stopped.
|
|
38
|
+
if (event && event.persisted) window.addEventListener('pageshow', function () { install(config); }, { once: true });
|
|
39
|
+
}
|
|
40
|
+
function onStorage(event) {
|
|
41
|
+
if (event.key !== null && event.key !== config.preferenceKey) return;
|
|
42
|
+
if (!enabled()) { stripNativeForms(); stop(); }
|
|
43
|
+
}
|
|
44
|
+
if (!enabled()) { stripNativeForms(); return; }
|
|
45
|
+
var context = [doc.modelContext, typeof navigator !== 'undefined' && navigator.modelContext].find(function (value) {
|
|
46
|
+
return value && typeof value.registerTool === 'function';
|
|
47
|
+
});
|
|
48
|
+
if (!context || typeof context.registerTool !== 'function') return;
|
|
49
|
+
function visible(element) {
|
|
50
|
+
if (!element.isConnected || element.closest('[hidden], [inert], [aria-hidden="true"], dialog:not([open])')) return false;
|
|
51
|
+
for (var current = element; current; current = current.parentElement) {
|
|
52
|
+
var style = window.getComputedStyle(current);
|
|
53
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
function descriptor(element) {
|
|
58
|
+
var name = element.getAttribute('data-toolname') || '';
|
|
59
|
+
var description = element.getAttribute('data-tooldescription') || '';
|
|
60
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/.test(name) || !description.trim() || !visible(element)) return null;
|
|
61
|
+
if (element.matches(':disabled, [aria-disabled="true"]')) return null;
|
|
62
|
+
if (element.tagName === 'A') {
|
|
63
|
+
if (!element.hasAttribute('href') || element.hasAttribute('download')) return null;
|
|
64
|
+
var url;
|
|
65
|
+
try { url = new URL(element.getAttribute('href'), doc.baseURI); } catch (_) { return null; }
|
|
66
|
+
if (!/^https?:$/.test(url.protocol) || url.origin !== window.location.origin || url.username || url.password) return null;
|
|
67
|
+
return { name: name, description: description, href: url.href };
|
|
68
|
+
}
|
|
69
|
+
// Inputs and form submission are intentionally absent: declarative forms
|
|
70
|
+
// preserve native validation and human submission, with no secret fields.
|
|
71
|
+
if (element.tagName !== 'BUTTON' || element.type !== 'button') return null;
|
|
72
|
+
return { name: name, description: description, href: null };
|
|
73
|
+
}
|
|
74
|
+
function refresh() {
|
|
75
|
+
registrations.forEach(function (controller) { controller.abort(); });
|
|
76
|
+
registrations = [];
|
|
77
|
+
if (stopped || !enabled()) { stripNativeForms(); stop(); return; }
|
|
78
|
+
var counts = new Map();
|
|
79
|
+
doc.querySelectorAll('[data-toolname], form[toolname]').forEach(function (element) {
|
|
80
|
+
if (!visible(element)) return;
|
|
81
|
+
var name = element.getAttribute('data-toolname') || element.getAttribute('toolname');
|
|
82
|
+
counts.set(name, (counts.get(name) || 0) + 1);
|
|
83
|
+
});
|
|
84
|
+
doc.querySelectorAll('[data-toolname]').forEach(function (element) {
|
|
85
|
+
var action = descriptor(element);
|
|
86
|
+
if (!action || counts.get(action.name) !== 1) return;
|
|
87
|
+
var controller = new AbortController();
|
|
88
|
+
registrations.push(controller);
|
|
89
|
+
function current() {
|
|
90
|
+
var latest = descriptor(element);
|
|
91
|
+
var matches = Array.from(doc.querySelectorAll('[data-toolname], form[toolname]')).filter(function (candidate) {
|
|
92
|
+
return visible(candidate) && (candidate.getAttribute('data-toolname') || candidate.getAttribute('toolname')) === action.name;
|
|
93
|
+
});
|
|
94
|
+
if (stopped || controller.signal.aborted || !enabled() || !latest ||
|
|
95
|
+
matches.length !== 1 || latest.name !== action.name || latest.description !== action.description || latest.href !== action.href) {
|
|
96
|
+
throw new Error('Browser-agent action is unavailable');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
var tool = {
|
|
100
|
+
name: action.name,
|
|
101
|
+
description: action.description,
|
|
102
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
103
|
+
annotations: { readOnlyHint: action.href !== null },
|
|
104
|
+
execute: async function (args) {
|
|
105
|
+
current();
|
|
106
|
+
if (!args || typeof args !== 'object' || Array.isArray(args) || Object.keys(args).length !== 0) {
|
|
107
|
+
throw new Error('This action takes no arguments');
|
|
108
|
+
}
|
|
109
|
+
if (action.href !== null) {
|
|
110
|
+
// Navigation has no click-handler side effects and cannot forward
|
|
111
|
+
// arbitrary agent-supplied URLs or use a theme's cross-origin base.
|
|
112
|
+
window.location.assign(action.href);
|
|
113
|
+
return { navigationRequested: true };
|
|
114
|
+
}
|
|
115
|
+
if (!window.confirm(confirmationMessage + '\n\n' + action.description)) {
|
|
116
|
+
throw new Error('Browser-agent action declined');
|
|
117
|
+
}
|
|
118
|
+
current();
|
|
119
|
+
element.click();
|
|
120
|
+
// Activation acknowledges the UI event, never business success.
|
|
121
|
+
return { activated: true };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
Promise.resolve(context.registerTool(tool, { signal: controller.signal })).catch(function () { controller.abort(); });
|
|
126
|
+
} catch (_) { controller.abort(); }
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
window.addEventListener('pagehide', stop);
|
|
130
|
+
window.addEventListener('storage', onStorage);
|
|
131
|
+
refresh();
|
|
132
|
+
observer = new MutationObserver(refresh);
|
|
133
|
+
observer.observe(doc.documentElement, { subtree: true, childList: true, attributes: true,
|
|
134
|
+
attributeFilter: ['data-toolname', 'data-tooldescription', 'toolname', 'href', 'download', 'type', 'disabled', 'aria-disabled', 'hidden', 'inert', 'aria-hidden', 'style', 'class', 'open'] });
|
|
135
|
+
})(` + config + ');';
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=annotated-actions-script.js.map
|
|
@@ -55,7 +55,10 @@ export interface CapabilityDef {
|
|
|
55
55
|
* @param id - Capability id to resolve.
|
|
56
56
|
* @returns The definition, or `undefined` when the id is not in the manifest.
|
|
57
57
|
*/
|
|
58
|
-
export declare function findCapability(capabilities
|
|
58
|
+
export declare function findCapability({ capabilities, id }: {
|
|
59
|
+
capabilities: readonly CapabilityDef[];
|
|
60
|
+
id: string;
|
|
61
|
+
}, _optional?: Record<string, never>): CapabilityDef | undefined;
|
|
59
62
|
/**
|
|
60
63
|
* Checks caller input against a capability's declared schema.
|
|
61
64
|
*
|
|
@@ -69,7 +72,10 @@ export declare function findCapability(capabilities: readonly CapabilityDef[], i
|
|
|
69
72
|
* @param input - Raw caller-supplied arguments.
|
|
70
73
|
* @returns An error message, or `null` when the input is acceptable.
|
|
71
74
|
*/
|
|
72
|
-
export declare function findCapabilityInputError(capability
|
|
75
|
+
export declare function findCapabilityInputError({ capability, input }: {
|
|
76
|
+
capability: CapabilityDef;
|
|
77
|
+
input: Record<string, unknown>;
|
|
78
|
+
}, _optional?: Record<string, never>): string | null;
|
|
73
79
|
/**
|
|
74
80
|
* The capabilities a caller may use given what is currently connected.
|
|
75
81
|
*
|
|
@@ -77,5 +83,8 @@ export declare function findCapabilityInputError(capability: CapabilityDef, inpu
|
|
|
77
83
|
* @param hasSession - Whether a live frontend session is attached.
|
|
78
84
|
* @returns Only the capabilities that can actually be satisfied right now.
|
|
79
85
|
*/
|
|
80
|
-
export declare function availableCapabilities(capabilities
|
|
86
|
+
export declare function availableCapabilities({ capabilities, hasSession }: {
|
|
87
|
+
capabilities: readonly CapabilityDef[];
|
|
88
|
+
hasSession: boolean;
|
|
89
|
+
}, _optional?: Record<string, never>): readonly CapabilityDef[];
|
|
81
90
|
//# sourceMappingURL=capability.d.ts.map
|
package/dist/core/capability.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @param id - Capability id to resolve.
|
|
18
18
|
* @returns The definition, or `undefined` when the id is not in the manifest.
|
|
19
19
|
*/
|
|
20
|
-
export function findCapability(capabilities, id) {
|
|
20
|
+
export function findCapability({ capabilities, id }, _optional = {}) {
|
|
21
21
|
return capabilities.find((capability) => capability.id === id);
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -33,7 +33,7 @@ export function findCapability(capabilities, id) {
|
|
|
33
33
|
* @param input - Raw caller-supplied arguments.
|
|
34
34
|
* @returns An error message, or `null` when the input is acceptable.
|
|
35
35
|
*/
|
|
36
|
-
export function findCapabilityInputError(capability, input) {
|
|
36
|
+
export function findCapabilityInputError({ capability, input }, _optional = {}) {
|
|
37
37
|
const { properties, required, additionalProperties } = capability.inputSchema;
|
|
38
38
|
for (const name of required ?? []) {
|
|
39
39
|
if (input[name] === undefined)
|
|
@@ -73,7 +73,7 @@ export function findCapabilityInputError(capability, input) {
|
|
|
73
73
|
* @param hasSession - Whether a live frontend session is attached.
|
|
74
74
|
* @returns Only the capabilities that can actually be satisfied right now.
|
|
75
75
|
*/
|
|
76
|
-
export function availableCapabilities(capabilities, hasSession) {
|
|
76
|
+
export function availableCapabilities({ capabilities, hasSession }, _optional = {}) {
|
|
77
77
|
return hasSession ? capabilities : capabilities.filter((c) => c.surface === 'server');
|
|
78
78
|
}
|
|
79
79
|
//# sourceMappingURL=capability.js.map
|
|
@@ -4,15 +4,17 @@ export interface DomPageDriverPage {
|
|
|
4
4
|
/** Shown to a caller by `page.find_elements`/`page.navigate`'s refusal message — see
|
|
5
5
|
* `PageSummary.label` for why this exists at all. */
|
|
6
6
|
readonly label: string;
|
|
7
|
-
readonly navigate: () => void;
|
|
7
|
+
readonly navigate: (required: Record<string, never>) => void;
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
9
|
+
export interface DomPageDriverRequired {
|
|
10
10
|
/** The subtree to expose. Never pass `document`. */
|
|
11
11
|
readonly root: ParentNode & {
|
|
12
12
|
querySelector: Element['querySelector'];
|
|
13
13
|
};
|
|
14
14
|
/** Page ids this driver may navigate to, each with its label and how to get there. */
|
|
15
15
|
readonly pages: Readonly<Record<string, DomPageDriverPage>>;
|
|
16
|
+
}
|
|
17
|
+
export interface DomPageDriverOptions {
|
|
16
18
|
/**
|
|
17
19
|
* The id of the page currently shown, reported on every element.
|
|
18
20
|
*
|
|
@@ -30,7 +32,9 @@ export interface DomPageDriverOptions {
|
|
|
30
32
|
* @param options - The subtree, the navigable pages, and the current page id.
|
|
31
33
|
* @returns A driver ready to hand to `executePageCapability`.
|
|
32
34
|
*/
|
|
33
|
-
export declare function createDomPageDriver(
|
|
35
|
+
export declare function createDomPageDriver({ root, pages }: DomPageDriverRequired, options?: DomPageDriverOptions): PageDriver;
|
|
34
36
|
/** Reads the current page's `data-agent-page`, for hosts that tag the body. */
|
|
35
|
-
export declare function currentAgentPage(root
|
|
37
|
+
export declare function currentAgentPage({ root }: {
|
|
38
|
+
root: ParentNode;
|
|
39
|
+
}, _optional?: Record<string, never>): string | undefined;
|
|
36
40
|
//# sourceMappingURL=dom-page-driver.d.ts.map
|
|
@@ -228,8 +228,7 @@ function describe(element, page) {
|
|
|
228
228
|
* @param options - The subtree, the navigable pages, and the current page id.
|
|
229
229
|
* @returns A driver ready to hand to `executePageCapability`.
|
|
230
230
|
*/
|
|
231
|
-
export function createDomPageDriver(options) {
|
|
232
|
-
const { root, pages } = options;
|
|
231
|
+
export function createDomPageDriver({ root, pages }, options = {}) {
|
|
233
232
|
/**
|
|
234
233
|
* The page a specific element belongs to — its own nearest `[data-agent-page]` ancestor, not a
|
|
235
234
|
* single document-wide guess.
|
|
@@ -257,7 +256,7 @@ export function createDomPageDriver(options) {
|
|
|
257
256
|
* publishes the same handle twice makes `find`'s single-match resolution ambiguous — surfaced
|
|
258
257
|
* here so both callers below can refuse rather than silently pick one.
|
|
259
258
|
*/
|
|
260
|
-
const findAll = (handle) => Array.from(root.querySelectorAll(resolveHandleSelector(handle)))
|
|
259
|
+
const findAll = (handle) => Array.from(root.querySelectorAll(resolveHandleSelector({ handle })))
|
|
261
260
|
.filter((element) => !isPrivate(element));
|
|
262
261
|
/**
|
|
263
262
|
* More than one element answers to the same handle — a page-authoring bug (duplicate
|
|
@@ -312,7 +311,7 @@ export function createDomPageDriver(options) {
|
|
|
312
311
|
return candidate ?? element;
|
|
313
312
|
};
|
|
314
313
|
return {
|
|
315
|
-
async findElements(filter) {
|
|
314
|
+
async findElements(_required, filter = {}) {
|
|
316
315
|
const found = Array.from(root.querySelectorAll(`[${AGENT_ELEMENT_ATTRIBUTE}]`))
|
|
317
316
|
.filter((element) => !isPrivate(element))
|
|
318
317
|
.map((element) => describe(element, pageOf(element)))
|
|
@@ -327,16 +326,16 @@ export function createDomPageDriver(options) {
|
|
|
327
326
|
|| element.label.toLowerCase().includes(query);
|
|
328
327
|
});
|
|
329
328
|
},
|
|
330
|
-
async listPages() {
|
|
329
|
+
async listPages(_required, _optional = {}) {
|
|
331
330
|
// `Object.entries`, same own-enumerable-keys-only guarantee `Object.keys` gives `navigate`'s
|
|
332
331
|
// own `hasOwnProperty` check above — no risk of a `pages.label`/`pages.navigate`-shaped
|
|
333
332
|
// prototype entry leaking in as a fake page id.
|
|
334
333
|
return Object.entries(pages).map(([id, page]) => ({ id, label: page.label }));
|
|
335
334
|
},
|
|
336
|
-
async describeField(handle) {
|
|
335
|
+
async describeField({ handle }, _optional = {}) {
|
|
337
336
|
return fieldDescriptorOf(controlOf(find(handle)));
|
|
338
337
|
},
|
|
339
|
-
async describeState(handle) {
|
|
338
|
+
async describeState({ handle }, _optional = {}) {
|
|
340
339
|
// Unlike every other method here, a handle that no longer resolves is an answer rather than
|
|
341
340
|
// an error: an element that a click removed is exactly what the caller is asking about. A
|
|
342
341
|
// handle resolving to *more than one* element is a different situation entirely — not an
|
|
@@ -402,7 +401,7 @@ export function createDomPageDriver(options) {
|
|
|
402
401
|
* would hang for the whole life of a backgrounded tab; without the frames every write would
|
|
403
402
|
* report its own target unchanged, because the read would beat the render.
|
|
404
403
|
*/
|
|
405
|
-
async settle() {
|
|
404
|
+
async settle(_required, _optional = {}) {
|
|
406
405
|
await new Promise((resolve) => {
|
|
407
406
|
let timer;
|
|
408
407
|
// No re-entry guard: whichever side loses the race calls this again, and both of its
|
|
@@ -422,7 +421,7 @@ export function createDomPageDriver(options) {
|
|
|
422
421
|
raf(() => raf(finish));
|
|
423
422
|
});
|
|
424
423
|
},
|
|
425
|
-
async highlight(handle, durationMs) {
|
|
424
|
+
async highlight({ handle, durationMs }, _optional = {}) {
|
|
426
425
|
const element = find(handle);
|
|
427
426
|
// Same refusal as `click()`, for the same reason: an SVG (or other non-HTMLElement) target
|
|
428
427
|
// has no `.style` to draw an outline on, so silently returning here drew nothing while
|
|
@@ -448,16 +447,16 @@ export function createDomPageDriver(options) {
|
|
|
448
447
|
}, durationMs);
|
|
449
448
|
highlights.set(handle, { timer, restore });
|
|
450
449
|
},
|
|
451
|
-
async scrollTo(handle) {
|
|
450
|
+
async scrollTo({ handle }, _optional = {}) {
|
|
452
451
|
find(handle).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
453
452
|
},
|
|
454
|
-
async click(handle) {
|
|
453
|
+
async click({ handle }, _optional = {}) {
|
|
455
454
|
const control = controlOf(find(handle));
|
|
456
455
|
if (!(control instanceof HTMLElement))
|
|
457
456
|
throw new Error(`"${handle}" is not clickable`);
|
|
458
457
|
control.click();
|
|
459
458
|
},
|
|
460
|
-
async fill(handle, text) {
|
|
459
|
+
async fill({ handle, text }, _optional = {}) {
|
|
461
460
|
const control = controlOf(find(handle));
|
|
462
461
|
if (isEditableRegion(control)) {
|
|
463
462
|
// A contenteditable region has no `value`; its content *is* its children, and a real
|
|
@@ -483,7 +482,7 @@ export function createDomPageDriver(options) {
|
|
|
483
482
|
control.dispatchEvent(new Event('input', { bubbles: true }));
|
|
484
483
|
control.dispatchEvent(new Event('change', { bubbles: true }));
|
|
485
484
|
},
|
|
486
|
-
async selectOption(handle, option, selected = true) {
|
|
485
|
+
async selectOption({ handle, option }, { selected = true } = {}) {
|
|
487
486
|
const control = controlOf(find(handle));
|
|
488
487
|
if (!(control instanceof HTMLSelectElement)) {
|
|
489
488
|
throw new Error(`"${handle}" is not a dropdown`);
|
|
@@ -518,8 +517,8 @@ export function createDomPageDriver(options) {
|
|
|
518
517
|
// the same shape of problem regardless of which side authored it. `handle` needs no
|
|
519
518
|
// treatment: it is already validated ASCII, length-capped by isValidElementHandle before
|
|
520
519
|
// this driver is ever called.
|
|
521
|
-
const safeOption = normalizeAgentLabel(option).text;
|
|
522
|
-
const available = options.map((entry) => normalizeAgentLabel(entry.text.trim()).text)
|
|
520
|
+
const safeOption = normalizeAgentLabel({ raw: option }).text;
|
|
521
|
+
const available = options.map((entry) => normalizeAgentLabel({ raw: entry.text.trim() }).text)
|
|
523
522
|
.filter((text) => text.length > 0);
|
|
524
523
|
throw new Error(`"${safeOption}" is not an option of "${handle}". Available: ${available.length > 0 ? available.join(', ') : '(none)'}`);
|
|
525
524
|
}
|
|
@@ -535,7 +534,7 @@ export function createDomPageDriver(options) {
|
|
|
535
534
|
// ignoring this would tell the caller a deselect happened when nothing changed, and clearing
|
|
536
535
|
// the control would invent a third state (no selection) that a native <select> cannot hold.
|
|
537
536
|
throw new Error(`"${handle}" is a single-select; its choice cannot be removed, only replaced — `
|
|
538
|
-
+ `select a different option instead of deselecting "${normalizeAgentLabel(option).text}"`);
|
|
537
|
+
+ `select a different option instead of deselecting "${normalizeAgentLabel({ raw: option }).text}"`);
|
|
539
538
|
}
|
|
540
539
|
else {
|
|
541
540
|
// Same prototype-setter dance as `fill`, for the same reason: React tracks the previous
|
|
@@ -549,7 +548,7 @@ export function createDomPageDriver(options) {
|
|
|
549
548
|
control.dispatchEvent(new Event('input', { bubbles: true }));
|
|
550
549
|
control.dispatchEvent(new Event('change', { bubbles: true }));
|
|
551
550
|
},
|
|
552
|
-
async navigate(page) {
|
|
551
|
+
async navigate({ page }, _optional = {}) {
|
|
553
552
|
// `hasOwnProperty`, not `pages[page]`: a plain object literal inherits everything
|
|
554
553
|
// `Object.prototype` contributes, so a bare lookup answered `navigate("constructor")` with
|
|
555
554
|
// the `Object` constructor — a function the host never published, which this then called,
|
|
@@ -564,8 +563,8 @@ export function createDomPageDriver(options) {
|
|
|
564
563
|
// the executor entirely, hands `page` in unsanitized, so the same bound-and-strip treatment
|
|
565
564
|
// applies here too rather than assuming the only caller is the one that already checked.
|
|
566
565
|
if (!published)
|
|
567
|
-
throw new Error(`"${normalizeAgentLabel(page).text}" is not a published page`);
|
|
568
|
-
pages[page].navigate();
|
|
566
|
+
throw new Error(`"${normalizeAgentLabel({ raw: page }).text}" is not a published page`);
|
|
567
|
+
pages[page].navigate({});
|
|
569
568
|
},
|
|
570
569
|
};
|
|
571
570
|
}
|
|
@@ -616,7 +615,7 @@ function fillEditableRegion(control, text) {
|
|
|
616
615
|
control.dispatchEvent(new Event('input', { bubbles: true }));
|
|
617
616
|
}
|
|
618
617
|
/** Reads the current page's `data-agent-page`, for hosts that tag the body. */
|
|
619
|
-
export function currentAgentPage(root) {
|
|
618
|
+
export function currentAgentPage({ root }, _optional = {}) {
|
|
620
619
|
const tagged = root.querySelector(`[${AGENT_PAGE_ATTRIBUTE}]`);
|
|
621
620
|
return tagged?.getAttribute(AGENT_PAGE_ATTRIBUTE) ?? undefined;
|
|
622
621
|
}
|
package/dist/core/dom/index.d.ts
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The one `PageDriver` (see `@jini-ai/agentic`'s `page-driver.ts`) that reads and writes a real DOM
|
|
5
5
|
* subtree. Split into its own entry point, compiled under its own `DOM`-lib `tsconfig.dom.json`,
|
|
6
|
-
* so the rest of this package can stay provably DOM-free — see
|
|
6
|
+
* so the rest of this package can stay provably DOM-free — see archived provenance ledger's "The DOM split".
|
|
7
7
|
*
|
|
8
8
|
* `@jini-ai/chat-react` is this package's one in-repo consumer today.
|
|
9
9
|
*/
|
|
10
|
-
export { createDomPageDriver, currentAgentPage, type DomPageDriverOptions, type DomPageDriverPage, } from './dom-page-driver.js';
|
|
10
|
+
export { createDomPageDriver, currentAgentPage, type DomPageDriverRequired, type DomPageDriverOptions, type DomPageDriverPage, } from './dom-page-driver.js';
|
|
11
11
|
/**
|
|
12
12
|
* WebMCP feature detection — moved 2026-07-26 from `@jini-ai/ui`'s `agent-tools/model-context.ts`
|
|
13
13
|
* (plan §8 step 6). See `model-context.ts`'s module doc for why it lives here rather than
|
|
14
14
|
* alongside `../webmcp.ts`'s DOM-free projection.
|
|
15
15
|
*/
|
|
16
|
-
export { getAgentModelContext, type AgentModelContextLike, type AgentModelContextToolRegistration, } from './model-context.js';
|
|
16
|
+
export { getAgentModelContext, type AgentModelContextLike, type ModelContextHostPort, type AgentModelContextRegisterToolOptions, type AgentModelContextToolRegistration, } from './model-context.js';
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/dom/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The one `PageDriver` (see `@jini-ai/agentic`'s `page-driver.ts`) that reads and writes a real DOM
|
|
5
5
|
* subtree. Split into its own entry point, compiled under its own `DOM`-lib `tsconfig.dom.json`,
|
|
6
|
-
* so the rest of this package can stay provably DOM-free — see
|
|
6
|
+
* so the rest of this package can stay provably DOM-free — see archived provenance ledger's "The DOM split".
|
|
7
7
|
*
|
|
8
8
|
* `@jini-ai/chat-react` is this package's one in-repo consumer today.
|
|
9
9
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module dom/model-context
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `document.modelContext`,
|
|
4
|
+
* Host-injected access to the browser's (draft) WebMCP surface —
|
|
5
|
+
* the host supplies `document.modelContext`, followed by the deprecated `navigator.modelContext`
|
|
6
6
|
* alias (the spec moved the getter from Navigator to Document in 2026;
|
|
7
7
|
* see the polyfill ecosystem's own back-compat posture). No `@mcp-b/*` or
|
|
8
8
|
* other WebMCP package dependency: this module only needs the shape, the
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
* bridges already use.
|
|
12
12
|
* A page that wants `document.modelContext` to actually exist in a browser
|
|
13
13
|
* without native support must load a real polyfill itself (e.g.
|
|
14
|
-
* `@mcp-b/webmcp-polyfill`) — this module only
|
|
14
|
+
* `@mcp-b/webmcp-polyfill`) — this module adapts only the candidates the host supplies.
|
|
15
15
|
*
|
|
16
16
|
* Moved 2026-07-26 from `@jini-ai/ui/src/features/agent-tools/model-context.ts` (plan §4/§8 step 6).
|
|
17
17
|
* Lives under `src/dom/`, not alongside `../webmcp.ts`'s pure `CapabilityDef` → WebMCP-tool
|
|
18
|
-
* projection, because it
|
|
18
|
+
* projection, because it adapts the native browser API — `webmcp.ts`'s own module doc is explicit
|
|
19
19
|
* that "that detection is deliberately NOT here: it touches browser globals, and this package
|
|
20
20
|
* holds none," which is exactly the DOM-free guarantee `src/dom/` exists to carve an exception
|
|
21
21
|
* for. See `dom/index.ts`'s re-export of this module.
|
|
@@ -56,14 +56,24 @@ export interface AgentModelContextLike {
|
|
|
56
56
|
* ModelContextRegisterToolOptions options = {})` — it is **async**. Callers must not assume it
|
|
57
57
|
* has taken effect synchronously, and must not let a rejection escape.
|
|
58
58
|
*/
|
|
59
|
-
registerTool(
|
|
59
|
+
registerTool(required: {
|
|
60
|
+
tool: AgentModelContextToolRegistration;
|
|
61
|
+
}, options?: AgentModelContextRegisterToolOptions): Promise<void>;
|
|
60
62
|
/**
|
|
61
63
|
* NOT in the spec — the draft's only unregistration mechanism is aborting the `signal` passed to
|
|
62
64
|
* `registerTool`. Kept optional purely so a polyfill that happens to expose one can be used;
|
|
63
65
|
* never rely on its presence, and always pass a `signal` as the real cleanup path.
|
|
64
66
|
*/
|
|
65
|
-
unregisterTool?(
|
|
67
|
+
unregisterTool?(required: {
|
|
68
|
+
name: string;
|
|
69
|
+
}, _optional?: Record<string, never>): void;
|
|
66
70
|
}
|
|
67
|
-
/**
|
|
68
|
-
export
|
|
71
|
+
/** Ordered host candidates: modern document first, then any deprecated navigator alias. */
|
|
72
|
+
export interface ModelContextHostPort {
|
|
73
|
+
candidates(required: Record<string, never>): readonly unknown[];
|
|
74
|
+
}
|
|
75
|
+
/** Adapts the first installed native surface to Jini's argument-object port. */
|
|
76
|
+
export declare function getAgentModelContext({ host }: {
|
|
77
|
+
host: ModelContextHostPort;
|
|
78
|
+
}, _optional?: Record<string, never>): AgentModelContextLike | undefined;
|
|
69
79
|
//# sourceMappingURL=model-context.d.ts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module dom/model-context
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `document.modelContext`,
|
|
4
|
+
* Host-injected access to the browser's (draft) WebMCP surface —
|
|
5
|
+
* the host supplies `document.modelContext`, followed by the deprecated `navigator.modelContext`
|
|
6
6
|
* alias (the spec moved the getter from Navigator to Document in 2026;
|
|
7
7
|
* see the polyfill ecosystem's own back-compat posture). No `@mcp-b/*` or
|
|
8
8
|
* other WebMCP package dependency: this module only needs the shape, the
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
* bridges already use.
|
|
12
12
|
* A page that wants `document.modelContext` to actually exist in a browser
|
|
13
13
|
* without native support must load a real polyfill itself (e.g.
|
|
14
|
-
* `@mcp-b/webmcp-polyfill`) — this module only
|
|
14
|
+
* `@mcp-b/webmcp-polyfill`) — this module adapts only the candidates the host supplies.
|
|
15
15
|
*
|
|
16
16
|
* Moved 2026-07-26 from `@jini-ai/ui/src/features/agent-tools/model-context.ts` (plan §4/§8 step 6).
|
|
17
17
|
* Lives under `src/dom/`, not alongside `../webmcp.ts`'s pure `CapabilityDef` → WebMCP-tool
|
|
18
|
-
* projection, because it
|
|
18
|
+
* projection, because it adapts the native browser API — `webmcp.ts`'s own module doc is explicit
|
|
19
19
|
* that "that detection is deliberately NOT here: it touches browser globals, and this package
|
|
20
20
|
* holds none," which is exactly the DOM-free guarantee `src/dom/` exists to carve an exception
|
|
21
21
|
* for. See `dom/index.ts`'s re-export of this module.
|
|
@@ -28,17 +28,14 @@
|
|
|
28
28
|
* fixture: a raw (non-`toWebMcpTool`) registration carrying a `title` failed TypeScript's
|
|
29
29
|
* excess-property check against the un-widened type here.
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const nav = globalThis.navigator;
|
|
40
|
-
if (isModelContext(nav?.modelContext))
|
|
41
|
-
return nav.modelContext;
|
|
31
|
+
import { adaptNativeModelContext } from './native-model-context.js';
|
|
32
|
+
/** Adapts the first installed native surface to Jini's argument-object port. */
|
|
33
|
+
export function getAgentModelContext({ host }, _optional = {}) {
|
|
34
|
+
for (const value of host.candidates({})) {
|
|
35
|
+
const context = adaptNativeModelContext({ value });
|
|
36
|
+
if (context)
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
42
39
|
return undefined;
|
|
43
40
|
}
|
|
44
41
|
//# sourceMappingURL=model-context.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AgentModelContextLike } from './model-context.js';
|
|
2
|
+
/** Preserve native receiver binding and stable port identity across feature detection. */
|
|
3
|
+
export declare function adaptNativeModelContext({ value }: {
|
|
4
|
+
value: unknown;
|
|
5
|
+
}): AgentModelContextLike | undefined;
|
|
6
|
+
//# sourceMappingURL=native-model-context.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const adapters = new WeakMap();
|
|
2
|
+
/** Preserve native receiver binding and stable port identity across feature detection. */
|
|
3
|
+
export function adaptNativeModelContext({ value }) {
|
|
4
|
+
if (typeof value !== 'object' || value === null || typeof value.registerTool !== 'function')
|
|
5
|
+
return undefined;
|
|
6
|
+
const existing = adapters.get(value);
|
|
7
|
+
if (existing)
|
|
8
|
+
return existing;
|
|
9
|
+
const native = value;
|
|
10
|
+
const adapter = {
|
|
11
|
+
registerTool: ({ tool }, options) => native.registerTool(tool, options),
|
|
12
|
+
...(typeof native.unregisterTool === 'function'
|
|
13
|
+
? { unregisterTool: ({ name }) => native.unregisterTool(name) }
|
|
14
|
+
: {}),
|
|
15
|
+
};
|
|
16
|
+
adapters.set(value, adapter);
|
|
17
|
+
return adapter;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=native-model-context.js.map
|
|
@@ -28,7 +28,9 @@ export declare const AGENT_ELEMENT_ROLES: readonly AgentElementRole[];
|
|
|
28
28
|
* @param handle - Candidate handle, typically caller-supplied.
|
|
29
29
|
* @returns True when it is safe to interpolate into an attribute selector.
|
|
30
30
|
*/
|
|
31
|
-
export declare function isValidElementHandle(handle
|
|
31
|
+
export declare function isValidElementHandle({ handle }: {
|
|
32
|
+
handle: string;
|
|
33
|
+
}, _optional?: Record<string, never>): boolean;
|
|
32
34
|
/**
|
|
33
35
|
* Builds the one selector shape this system will ever resolve.
|
|
34
36
|
*
|
|
@@ -36,7 +38,9 @@ export declare function isValidElementHandle(handle: string): boolean;
|
|
|
36
38
|
* @returns The attribute selector for that handle.
|
|
37
39
|
* @throws If the handle is not valid — never fall back to treating it as a raw selector.
|
|
38
40
|
*/
|
|
39
|
-
export declare function resolveHandleSelector(handle
|
|
41
|
+
export declare function resolveHandleSelector({ handle }: {
|
|
42
|
+
handle: string;
|
|
43
|
+
}, _optional?: Record<string, never>): string;
|
|
40
44
|
/** A tagged element as described back to a caller. `label` is page-authored, so it is untrusted. */
|
|
41
45
|
export interface AgentElementDescriptor {
|
|
42
46
|
readonly handle: string;
|
|
@@ -25,7 +25,7 @@ const MAX_HANDLE_LENGTH = 128;
|
|
|
25
25
|
* @param handle - Candidate handle, typically caller-supplied.
|
|
26
26
|
* @returns True when it is safe to interpolate into an attribute selector.
|
|
27
27
|
*/
|
|
28
|
-
export function isValidElementHandle(handle) {
|
|
28
|
+
export function isValidElementHandle({ handle }, _optional = {}) {
|
|
29
29
|
return handle.length > 0 && handle.length <= MAX_HANDLE_LENGTH && HANDLE_PATTERN.test(handle);
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -35,8 +35,8 @@ export function isValidElementHandle(handle) {
|
|
|
35
35
|
* @returns The attribute selector for that handle.
|
|
36
36
|
* @throws If the handle is not valid — never fall back to treating it as a raw selector.
|
|
37
37
|
*/
|
|
38
|
-
export function resolveHandleSelector(handle) {
|
|
39
|
-
if (!isValidElementHandle(handle)) {
|
|
38
|
+
export function resolveHandleSelector({ handle }, _optional = {}) {
|
|
39
|
+
if (!isValidElementHandle({ handle })) {
|
|
40
40
|
throw new Error(`invalid element handle "${handle.slice(0, MAX_HANDLE_LENGTH)}": `
|
|
41
41
|
+ 'handles are lowercase words joined by single hyphens, and are never CSS selectors');
|
|
42
42
|
}
|