@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.
Files changed (142) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +33 -6
  3. package/dist/core/a2ui/agent-to-renderer.d.ts +3 -1
  4. package/dist/core/a2ui/agent-to-renderer.js +1 -1
  5. package/dist/core/a2ui/catalog.d.ts +14 -5
  6. package/dist/core/a2ui/catalog.js +5 -5
  7. package/dist/core/a2ui/common-types.d.ts +11 -3
  8. package/dist/core/a2ui/common-types.js +5 -3
  9. package/dist/core/a2ui/index.d.ts +1 -1
  10. package/dist/core/a2ui/index.js +1 -1
  11. package/dist/core/a2ui/interpreter.d.ts +45 -10
  12. package/dist/core/a2ui/interpreter.js +38 -43
  13. package/dist/core/a2ui/json-pointer.d.ts +16 -4
  14. package/dist/core/a2ui/json-pointer.js +7 -7
  15. package/dist/core/a2ui/renderer-to-agent.d.ts +22 -8
  16. package/dist/core/a2ui/renderer-to-agent.js +5 -5
  17. package/dist/core/a2ui/resolve.d.ts +7 -2
  18. package/dist/core/a2ui/resolve.js +10 -9
  19. package/dist/core/a2ui/tree.d.ts +8 -2
  20. package/dist/core/a2ui/tree.js +2 -2
  21. package/dist/core/ag-ui.d.ts +17 -9
  22. package/dist/core/ag-ui.js +4 -4
  23. package/dist/core/annotated-actions-script.d.ts +13 -0
  24. package/dist/core/annotated-actions-script.js +137 -0
  25. package/dist/core/capability.d.ts +12 -3
  26. package/dist/core/capability.js +3 -3
  27. package/dist/core/dom/dom-page-driver.d.ts +8 -4
  28. package/dist/core/dom/dom-page-driver.js +19 -20
  29. package/dist/core/dom/index.d.ts +3 -3
  30. package/dist/core/dom/index.js +1 -1
  31. package/dist/core/dom/model-context.d.ts +18 -8
  32. package/dist/core/dom/model-context.js +12 -15
  33. package/dist/core/dom/native-model-context.d.ts +6 -0
  34. package/dist/core/dom/native-model-context.js +19 -0
  35. package/dist/core/element-handles.d.ts +6 -2
  36. package/dist/core/element-handles.js +3 -3
  37. package/dist/core/gen-ui/encoder.d.ts +21 -11
  38. package/dist/core/gen-ui/encoder.js +8 -7
  39. package/dist/core/gen-ui/events.d.ts +6 -5
  40. package/dist/core/gen-ui/events.js +6 -5
  41. package/dist/core/gen-ui/index.d.ts +1 -1
  42. package/dist/core/gen-ui/index.js +1 -1
  43. package/dist/core/guards.d.ts +17 -5
  44. package/dist/core/guards.js +6 -6
  45. package/dist/core/handle.d.ts +12 -5
  46. package/dist/core/handle.js +9 -8
  47. package/dist/core/index.d.ts +3 -1
  48. package/dist/core/index.js +3 -1
  49. package/dist/core/list-handles.d.ts +4 -1
  50. package/dist/core/list-handles.js +1 -1
  51. package/dist/core/mcp-ui-apps.d.ts +39 -9
  52. package/dist/core/mcp-ui-apps.js +10 -8
  53. package/dist/core/messages.d.ts +6 -0
  54. package/dist/core/messages.js +6 -0
  55. package/dist/core/page-capabilities.d.ts +0 -11
  56. package/dist/core/page-capabilities.js +13 -1
  57. package/dist/core/page-driver.d.ts +32 -11
  58. package/dist/core/page-executor.d.ts +8 -2
  59. package/dist/core/page-executor.js +31 -31
  60. package/dist/core/webmcp.d.ts +27 -8
  61. package/dist/core/webmcp.js +14 -14
  62. package/dist/index.d.ts +30 -0
  63. package/dist/index.js +30 -0
  64. package/dist/skills/install/archive.d.ts +8 -0
  65. package/dist/skills/install/archive.js +47 -0
  66. package/dist/skills/install/fetch-adapter.d.ts +6 -0
  67. package/dist/skills/install/fetch-adapter.js +6 -0
  68. package/dist/skills/install/github.d.ts +21 -0
  69. package/dist/skills/install/github.js +92 -0
  70. package/dist/skills/install/index.d.ts +11 -0
  71. package/dist/skills/install/index.js +11 -0
  72. package/dist/skills/install/install-service.d.ts +44 -0
  73. package/dist/skills/install/install-service.js +121 -0
  74. package/dist/skills/install/layout.d.ts +13 -0
  75. package/dist/skills/install/layout.js +22 -0
  76. package/dist/skills/install/live-registration.d.ts +61 -0
  77. package/dist/skills/install/live-registration.js +64 -0
  78. package/dist/skills/install/node-filesystem.d.ts +4 -0
  79. package/dist/skills/install/node-filesystem.js +41 -0
  80. package/dist/skills/install/ports.d.ts +101 -0
  81. package/dist/skills/install/ports.js +2 -0
  82. package/dist/skills/install/state.d.ts +13 -0
  83. package/dist/skills/install/state.js +23 -0
  84. package/dist/skills/install/validation.d.ts +45 -0
  85. package/dist/skills/install/validation.js +104 -0
  86. package/package.json +20 -5
  87. package/dist/a2ui/agent-to-renderer.d.ts +0 -613
  88. package/dist/a2ui/agent-to-renderer.js +0 -191
  89. package/dist/a2ui/catalog.d.ts +0 -73
  90. package/dist/a2ui/catalog.js +0 -304
  91. package/dist/a2ui/common-types.d.ts +0 -401
  92. package/dist/a2ui/common-types.js +0 -130
  93. package/dist/a2ui/index.d.ts +0 -16
  94. package/dist/a2ui/index.js +0 -16
  95. package/dist/a2ui/interpreter.d.ts +0 -71
  96. package/dist/a2ui/interpreter.js +0 -337
  97. package/dist/a2ui/json-pointer.d.ts +0 -48
  98. package/dist/a2ui/json-pointer.js +0 -174
  99. package/dist/a2ui/renderer-to-agent.d.ts +0 -342
  100. package/dist/a2ui/renderer-to-agent.js +0 -111
  101. package/dist/a2ui/resolve.d.ts +0 -28
  102. package/dist/a2ui/resolve.js +0 -144
  103. package/dist/a2ui/tree.d.ts +0 -66
  104. package/dist/a2ui/tree.js +0 -74
  105. package/dist/ag-ui.d.ts +0 -82
  106. package/dist/ag-ui.js +0 -85
  107. package/dist/agui/encoder.d.ts +0 -50
  108. package/dist/agui/encoder.js +0 -97
  109. package/dist/agui/events.d.ts +0 -59
  110. package/dist/agui/events.js +0 -15
  111. package/dist/capability.d.ts +0 -81
  112. package/dist/capability.js +0 -79
  113. package/dist/dom/dom-page-driver.d.ts +0 -36
  114. package/dist/dom/dom-page-driver.js +0 -601
  115. package/dist/dom/index.d.ts +0 -17
  116. package/dist/dom/index.js +0 -17
  117. package/dist/dom/model-context.d.ts +0 -69
  118. package/dist/dom/model-context.js +0 -44
  119. package/dist/element-handles.d.ts +0 -120
  120. package/dist/element-handles.js +0 -39
  121. package/dist/gen-ui/encoder.d.ts +0 -50
  122. package/dist/gen-ui/encoder.js +0 -97
  123. package/dist/gen-ui/events.d.ts +0 -59
  124. package/dist/gen-ui/events.js +0 -15
  125. package/dist/gen-ui/index.d.ts +0 -18
  126. package/dist/gen-ui/index.js +0 -17
  127. package/dist/guards.d.ts +0 -96
  128. package/dist/guards.js +0 -186
  129. package/dist/handle.d.ts +0 -41
  130. package/dist/handle.js +0 -41
  131. package/dist/mcp-ui-apps.d.ts +0 -223
  132. package/dist/mcp-ui-apps.js +0 -252
  133. package/dist/mcp-ui.d.ts +0 -133
  134. package/dist/mcp-ui.js +0 -134
  135. package/dist/page-capabilities.d.ts +0 -14
  136. package/dist/page-capabilities.js +0 -129
  137. package/dist/page-driver.d.ts +0 -120
  138. package/dist/page-driver.js +0 -2
  139. package/dist/page-executor.d.ts +0 -94
  140. package/dist/page-executor.js +0 -320
  141. package/dist/webmcp.d.ts +0 -202
  142. 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: readonly CapabilityDef[], id: string): CapabilityDef | undefined;
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: CapabilityDef, input: Record<string, unknown>): string | null;
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: readonly CapabilityDef[], hasSession: boolean): readonly CapabilityDef[];
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
@@ -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 DomPageDriverOptions {
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(options: DomPageDriverOptions): PageDriver;
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: ParentNode): string | undefined;
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
  }
@@ -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 source-map.md's "The DOM split".
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
@@ -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 source-map.md's "The DOM split".
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
- * Feature-detected access to the browser's (draft) WebMCP surface —
5
- * `document.modelContext`, falling back to the deprecated `navigator.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 ever reads what's there.
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 reads `document`/`navigator` — `webmcp.ts`'s own module doc is explicit
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(tool: AgentModelContextToolRegistration, options?: AgentModelContextRegisterToolOptions): Promise<void>;
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?(name: string): void;
67
+ unregisterTool?(required: {
68
+ name: string;
69
+ }, _optional?: Record<string, never>): void;
66
70
  }
67
- /** Returns the page's WebMCP surface if one is installed (native or polyfilled), or `undefined`. */
68
- export declare function getAgentModelContext(): AgentModelContextLike | undefined;
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
- * Feature-detected access to the browser's (draft) WebMCP surface —
5
- * `document.modelContext`, falling back to the deprecated `navigator.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 ever reads what's there.
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 reads `document`/`navigator` — `webmcp.ts`'s own module doc is explicit
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
- function isModelContext(value) {
32
- return typeof value === 'object' && value !== null && typeof value.registerTool === 'function';
33
- }
34
- /** Returns the page's WebMCP surface if one is installed (native or polyfilled), or `undefined`. */
35
- export function getAgentModelContext() {
36
- const doc = globalThis.document;
37
- if (isModelContext(doc?.modelContext))
38
- return doc.modelContext;
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: string): boolean;
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: string): string;
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
  }