@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
@@ -51,27 +51,39 @@ export interface PageDriver {
51
51
  * @param filter - Optional role/substring narrowing.
52
52
  * @returns Descriptors. `label` may be raw page text; the executor normalizes it.
53
53
  */
54
- findElements(filter: FindElementsFilter): Promise<readonly AgentElementDescriptor[]>;
54
+ findElements(_required: Record<string, never>, filter?: FindElementsFilter): Promise<readonly AgentElementDescriptor[]>;
55
55
  /**
56
56
  * The pages this host can navigate to.
57
57
  *
58
58
  * An allowlist, so `page.navigate` refuses anything the host has not published rather than
59
59
  * accepting a caller-supplied URL.
60
60
  */
61
- listPages(): Promise<readonly PageSummary[]>;
61
+ listPages(_required: Record<string, never>, _optional?: Record<string, never>): Promise<readonly PageSummary[]>;
62
62
  /**
63
63
  * The attributes of a field, for the fill guard.
64
64
  *
65
65
  * @param handle - A validated element handle.
66
66
  * @returns The field's attributes, or `null` when the handle does not resolve to an input.
67
67
  */
68
- describeField(handle: string): Promise<FieldDescriptor | null>;
68
+ describeField(required: {
69
+ handle: string;
70
+ }, _optional?: Record<string, never>): Promise<FieldDescriptor | null>;
69
71
  /** Draws a transient marker. The executor has already clamped `durationMs`. */
70
- highlight(handle: string, durationMs: number): Promise<void>;
71
- scrollTo(handle: string): Promise<void>;
72
- click(handle: string): Promise<void>;
72
+ highlight(required: {
73
+ handle: string;
74
+ durationMs: number;
75
+ }, _optional?: Record<string, never>): Promise<void>;
76
+ scrollTo(required: {
77
+ handle: string;
78
+ }, _optional?: Record<string, never>): Promise<void>;
79
+ click(required: {
80
+ handle: string;
81
+ }, _optional?: Record<string, never>): Promise<void>;
73
82
  /** Called only after the fill guard has passed. */
74
- fill(handle: string, text: string): Promise<void>;
83
+ fill(required: {
84
+ handle: string;
85
+ text: string;
86
+ }, _optional?: Record<string, never>): Promise<void>;
75
87
  /**
76
88
  * Chooses — or, in a multi-select, removes — an option in a dropdown, matching visible text
77
89
  * before value.
@@ -89,9 +101,16 @@ export interface PageDriver {
89
101
  * @throws When the handle is not a dropdown, when no option matches, or when `selected: false`
90
102
  * is requested on a single-select.
91
103
  */
92
- selectOption(handle: string, option: string, selected?: boolean): Promise<void>;
104
+ selectOption(required: {
105
+ handle: string;
106
+ option: string;
107
+ }, optional?: {
108
+ selected?: boolean | undefined;
109
+ }): Promise<void>;
93
110
  /** Called only after `page` has been checked against {@link PageDriver.listPages}. */
94
- navigate(page: string): Promise<void>;
111
+ navigate(required: {
112
+ page: string;
113
+ }, _optional?: Record<string, never>): Promise<void>;
95
114
  /**
96
115
  * What the element currently is: its live text, a field's contents, checked/disabled/visible.
97
116
  *
@@ -103,7 +122,9 @@ export interface PageDriver {
103
122
  * @returns The raw state, or `null` when the handle no longer resolves — which is itself an
104
123
  * observation (a click that removed its own target), not an error.
105
124
  */
106
- describeState?(handle: string): Promise<AgentElementRawState | null>;
125
+ describeState?(required: {
126
+ handle: string;
127
+ }, _optional?: Record<string, never>): Promise<AgentElementRawState | null>;
107
128
  /**
108
129
  * Resolves once the surface has finished reacting to the write that just happened.
109
130
  *
@@ -115,6 +136,6 @@ export interface PageDriver {
115
136
  * Implementations must be bounded: a hidden tab suspends animation frames indefinitely, so a
116
137
  * naive "wait for the next frame" never resolves for exactly the background agent this is for.
117
138
  */
118
- settle?(): Promise<void>;
139
+ settle?(_required: Record<string, never>, _optional?: Record<string, never>): Promise<void>;
119
140
  }
120
141
  //# sourceMappingURL=page-driver.d.ts.map
@@ -68,7 +68,9 @@ export interface PageWriteObservation {
68
68
  * guard does — a secrecy rule re-derived by each driver is a secrecy rule that will eventually be
69
69
  * derived wrong. A value arriving with no descriptor to check is withheld rather than trusted.
70
70
  */
71
- export declare function projectElementState(raw: AgentElementRawState): AgentElementState;
71
+ export declare function projectElementState({ raw }: {
72
+ raw: AgentElementRawState;
73
+ }, _optional?: Record<string, never>): AgentElementState;
72
74
  /**
73
75
  * What page is showing and how much it publishes, at one moment — the before/after pair
74
76
  * `page.navigate` reports. Not {@link PageSummary} (`page-driver.js`): that names one entry in the
@@ -90,5 +92,9 @@ export interface PageActivitySnapshot {
90
92
  * a field no automated caller may fill, or the page is not published. Refusals are errors on
91
93
  * purpose: a caller must be told why, not handed a silent no-op it will retry forever.
92
94
  */
93
- export declare function executePageCapability(driver: PageDriver, capabilityId: string, input: Record<string, unknown>): Promise<unknown>;
95
+ export declare function executePageCapability({ driver, capabilityId, input }: {
96
+ driver: PageDriver;
97
+ capabilityId: string;
98
+ input: Record<string, unknown>;
99
+ }, _optional?: Record<string, never>): Promise<unknown>;
94
100
  //# sourceMappingURL=page-executor.d.ts.map
@@ -34,7 +34,7 @@ export const MAX_HIGHLIGHT_MS = 15_000;
34
34
  export const MAX_STATEFUL_ELEMENTS = 50;
35
35
  function requireHandle(input, key) {
36
36
  const value = input[key];
37
- if (typeof value !== 'string' || !isValidElementHandle(value)) {
37
+ if (typeof value !== 'string' || !isValidElementHandle({ handle: value })) {
38
38
  throw new Error(`"${key}" must be a published element handle from page.find_elements — `
39
39
  + 'lowercase words joined by single hyphens, never a CSS selector');
40
40
  }
@@ -56,8 +56,8 @@ function isAgentElementRole(value) {
56
56
  * guard does — a secrecy rule re-derived by each driver is a secrecy rule that will eventually be
57
57
  * derived wrong. A value arriving with no descriptor to check is withheld rather than trusted.
58
58
  */
59
- export function projectElementState(raw) {
60
- const text = normalizeAgentLabel(raw.text);
59
+ export function projectElementState({ raw }, _optional = {}) {
60
+ const text = normalizeAgentLabel({ raw: raw.text });
61
61
  // Computed once, up front, so `checked` is gated by the exact same refusal as `value` below
62
62
  // rather than being copied through unconditionally. A checkbox/radio's `checked` is a second
63
63
  // secrecy channel, not a formatting detail: for a field like `health_disclosure_password`,
@@ -68,7 +68,7 @@ export function projectElementState(raw) {
68
68
  // it, exactly like a `<select>`'s `options` below. The secret is which option is checked, not
69
69
  // what the option is called; withholding the label too would blind a caller to the page's own
70
70
  // ontology for no reduction in what actually leaks.
71
- const refusal = raw.field !== undefined ? findFieldReadRefusal(raw.field) : null;
71
+ const refusal = raw.field !== undefined ? findFieldReadRefusal({ field: raw.field }) : null;
72
72
  // The one element whose `text` is NOT page-authored chrome: an editable region, whose contents
73
73
  // are the user's data and whose driver has no `value` to report them as. It is gated by exactly
74
74
  // the rules `value` is gated by below, including the "arrived with nothing to check it against"
@@ -77,7 +77,7 @@ export function projectElementState(raw) {
77
77
  ? undefined
78
78
  : raw.field === undefined
79
79
  ? 'this element reported contents without the attributes needed to check them for secrets'
80
- : refusal !== null ? describeFieldReadRefusal(refusal) : undefined;
80
+ : refusal !== null ? describeFieldReadRefusal({ refusal }) : undefined;
81
81
  const base = {
82
82
  text: withheldText === undefined ? text.text : '',
83
83
  textTruncated: withheldText === undefined ? text.truncated : false,
@@ -88,7 +88,7 @@ export function projectElementState(raw) {
88
88
  // Bounded like every other page-authored string, but never withheld: the options a dropdown
89
89
  // offers are part of the UI's shape, not the user's data.
90
90
  ...(raw.options !== undefined
91
- ? { options: raw.options.map((option) => normalizeAgentLabel(option).text) }
91
+ ? { options: raw.options.map((option) => normalizeAgentLabel({ raw: option }).text) }
92
92
  : {}),
93
93
  };
94
94
  if (raw.value === undefined)
@@ -100,8 +100,8 @@ export function projectElementState(raw) {
100
100
  };
101
101
  }
102
102
  if (refusal !== null)
103
- return { ...base, valueWithheld: describeFieldReadRefusal(refusal) };
104
- const value = normalizeAgentLabel(raw.value);
103
+ return { ...base, valueWithheld: describeFieldReadRefusal({ refusal }) };
104
+ const value = normalizeAgentLabel({ raw: raw.value });
105
105
  return { ...base, value: value.text, valueTruncated: value.truncated };
106
106
  }
107
107
  /**
@@ -133,17 +133,17 @@ function sameElementState(a, b) {
133
133
  * not report state.
134
134
  */
135
135
  async function observeElement(driver, handle) {
136
- if (driver.describeState === undefined || !isValidElementHandle(handle))
136
+ if (driver.describeState === undefined || !isValidElementHandle({ handle }))
137
137
  return undefined;
138
- const raw = await driver.describeState(handle);
139
- return raw === null ? undefined : projectElementState(raw);
138
+ const raw = await driver.describeState({ handle });
139
+ return raw === null ? undefined : projectElementState({ raw });
140
140
  }
141
141
  /** Runs a write between two readings of its target, waiting for the surface to settle in between. */
142
142
  async function observeWrite(driver, handle, write) {
143
143
  const before = await observeElement(driver, handle);
144
144
  await write();
145
145
  if (driver.settle !== undefined)
146
- await driver.settle();
146
+ await driver.settle({});
147
147
  const after = await observeElement(driver, handle);
148
148
  return {
149
149
  ...(before !== undefined ? { before } : {}),
@@ -158,7 +158,7 @@ async function observeWrite(driver, handle, write) {
158
158
  * surface that publishes nothing has no page id to report, and says so rather than guessing.
159
159
  */
160
160
  async function summarizePage(driver) {
161
- const elements = await driver.findElements({});
161
+ const elements = await driver.findElements({}, {});
162
162
  return {
163
163
  page: elements.find((element) => element.page !== undefined)?.page,
164
164
  elementCount: elements.length,
@@ -175,11 +175,11 @@ async function summarizePage(driver) {
175
175
  * a field no automated caller may fill, or the page is not published. Refusals are errors on
176
176
  * purpose: a caller must be told why, not handed a silent no-op it will retry forever.
177
177
  */
178
- export async function executePageCapability(driver, capabilityId, input) {
178
+ export async function executePageCapability({ driver, capabilityId, input }, _optional = {}) {
179
179
  const capability = PAGE_CAPABILITIES.find((entry) => entry.id === capabilityId);
180
180
  if (capability === undefined)
181
181
  throw new Error(`unknown page capability: ${capabilityId}`);
182
- const inputError = findCapabilityInputError(capability, input);
182
+ const inputError = findCapabilityInputError({ capability, input });
183
183
  if (inputError !== null) {
184
184
  // The schema rides along with the refusal rather than making a caller spend a second
185
185
  // round trip on describe_tool to learn what it already tried to guess — ai-control-plane.md
@@ -202,11 +202,11 @@ export async function executePageCapability(driver, capabilityId, input) {
202
202
  ...(isAgentElementRole(role) ? { role } : {}),
203
203
  ...(typeof input['query'] === 'string' ? { query: input['query'] } : {}),
204
204
  };
205
- const [found, rawPages] = await Promise.all([driver.findElements(filter), driver.listPages()]);
205
+ const [found, rawPages] = await Promise.all([driver.findElements({}, filter), driver.listPages({})]);
206
206
  const withState = input['withState'] === true;
207
207
  const observing = withState && driver.describeState !== undefined;
208
208
  const elements = await Promise.all(found.map(async (element, index) => {
209
- const label = normalizeAgentLabel(element.label);
209
+ const label = normalizeAgentLabel({ raw: element.label });
210
210
  const base = {
211
211
  handle: element.handle,
212
212
  role: element.role,
@@ -224,7 +224,7 @@ export async function executePageCapability(driver, capabilityId, input) {
224
224
  // every `data-agent-page` label short and clean.
225
225
  const pages = rawPages.map((page) => ({
226
226
  id: page.id,
227
- label: normalizeAgentLabel(page.label).text,
227
+ label: normalizeAgentLabel({ raw: page.label }).text,
228
228
  }));
229
229
  return {
230
230
  elements,
@@ -239,17 +239,17 @@ export async function executePageCapability(driver, capabilityId, input) {
239
239
  case 'page.highlight': {
240
240
  const handle = requireHandle(input, 'handle');
241
241
  const durationMs = clampHighlightDuration(input['durationMs']);
242
- await driver.highlight(handle, durationMs);
242
+ await driver.highlight({ handle, durationMs });
243
243
  return { highlighted: handle, durationMs };
244
244
  }
245
245
  case 'page.scroll_to': {
246
246
  const handle = requireHandle(input, 'handle');
247
- await driver.scrollTo(handle);
247
+ await driver.scrollTo({ handle });
248
248
  return { scrolledTo: handle };
249
249
  }
250
250
  case 'page.click': {
251
251
  const handle = requireHandle(input, 'handle');
252
- const observation = await observeWrite(driver, handle, () => driver.click(handle));
252
+ const observation = await observeWrite(driver, handle, () => driver.click({ handle }));
253
253
  return { clicked: handle, ...observation };
254
254
  }
255
255
  case 'page.fill': {
@@ -259,14 +259,14 @@ export async function executePageCapability(driver, capabilityId, input) {
259
259
  const text = input['text'];
260
260
  // Ask the page what this field actually is before writing to it. A handle proves the page
261
261
  // published the element; it says nothing about whether it holds a password.
262
- const field = await driver.describeField(handle);
262
+ const field = await driver.describeField({ handle });
263
263
  if (field === null)
264
264
  throw new Error(`"${handle}" is not a fillable field`);
265
- const refusal = findFieldFillRefusal(field);
265
+ const refusal = findFieldFillRefusal({ field });
266
266
  if (refusal !== null) {
267
- throw new Error(`refusing to fill "${handle}": ${describeFieldRefusal(refusal)}`);
267
+ throw new Error(`refusing to fill "${handle}": ${describeFieldRefusal({ refusal })}`);
268
268
  }
269
- const observation = await observeWrite(driver, handle, () => driver.fill(handle, text));
269
+ const observation = await observeWrite(driver, handle, () => driver.fill({ handle, text }));
270
270
  return { filled: handle, ...observation };
271
271
  }
272
272
  case 'page.select_option': {
@@ -279,7 +279,7 @@ export async function executePageCapability(driver, capabilityId, input) {
279
279
  // No field guard: a dropdown's options are authored by the page, so choosing one reveals
280
280
  // nothing the page had not already published, and none of the credential field types this
281
281
  // surface refuses can be a `<select>`.
282
- const observation = await observeWrite(driver, handle, () => driver.selectOption(handle, option, selected));
282
+ const observation = await observeWrite(driver, handle, () => driver.selectOption({ handle, option }, { selected }));
283
283
  // `optionSelected` names the boolean rather than reusing `selected`, which already means
284
284
  // "the handle this call acted on" for every verb in this switch (`clicked`, `filled`, …).
285
285
  return { selected: handle, option, optionSelected: selected, ...observation };
@@ -287,7 +287,7 @@ export async function executePageCapability(driver, capabilityId, input) {
287
287
  case 'page.navigate': {
288
288
  // Required and string-typed by the manifest, already enforced above. See page.fill.
289
289
  const page = input['page'];
290
- const pages = await driver.listPages();
290
+ const pages = await driver.listPages({});
291
291
  if (!pages.some((candidate) => candidate.id === page)) {
292
292
  // Every other piece of page-authored (or, here, caller-authored-but-page-shaped) text
293
293
  // this system hands to a model goes through normalizeAgentLabel first — bounded, control-
@@ -301,14 +301,14 @@ export async function executePageCapability(driver, capabilityId, input) {
301
301
  // is looking straight at the real menu, so it is worth spending the label here even though
302
302
  // `page.find_elements` already carries the same information — a caller that jumped
303
303
  // straight to `page.navigate` without calling that first still lands on the right id.
304
- const safePage = normalizeAgentLabel(page).text;
305
- const safePages = pages.map((candidate) => `${normalizeAgentLabel(candidate.id).text} (${normalizeAgentLabel(candidate.label).text})`);
304
+ const safePage = normalizeAgentLabel({ raw: page }).text;
305
+ const safePages = pages.map((candidate) => `${normalizeAgentLabel({ raw: candidate.id }).text} (${normalizeAgentLabel({ raw: candidate.label }).text})`);
306
306
  throw new Error(`"${safePage}" is not a published page. Available: ${safePages.length > 0 ? safePages.join(', ') : '(none)'}`);
307
307
  }
308
308
  const before = await summarizePage(driver);
309
- await driver.navigate(page);
309
+ await driver.navigate({ page });
310
310
  if (driver.settle !== undefined)
311
- await driver.settle();
311
+ await driver.settle({});
312
312
  const after = await summarizePage(driver);
313
313
  return { navigatedTo: page, before, after };
314
314
  }
@@ -27,7 +27,7 @@
27
27
  * tool. Neither belongs on the `ModelContextTool` dict itself, so they are not fields of
28
28
  * {@link WebMcpToolRegistration} — they ride along on its optional `registerOptions`, computed
29
29
  * from {@link ToWebMcpToolOptions}, so a caller has exactly one object to destructure:
30
- * `modelContext.registerTool(reg, reg.registerOptions)`.
30
+ * `modelContext.registerTool({ tool: reg }, reg.registerOptions)`.
31
31
  * 2. **`title`/`annotations`** — real `ModelContextTool` dict fields (`USVString title`,
32
32
  * `ToolAnnotations annotations`), now on {@link WebMcpToolRegistration}. `annotations.readOnlyHint`
33
33
  * defaults from {@link CapabilityDef.risk} (`'read'` → `true`) since Jini's manifests already
@@ -68,7 +68,7 @@ export interface WebMcpToolRegistration {
68
68
  * `ModelContextRegisterToolOptions`, not part of the `ModelContextTool` dict this object
69
69
  * otherwise models. Present only when `options.signal` or `options.exposedTo` was supplied.
70
70
  * Bundled here rather than returned as a second value so `toWebMcpTool`'s output stays a single
71
- * object a caller can pass straight through: `modelContext.registerTool(reg, reg.registerOptions)`.
71
+ * object a caller can pass straight through: `modelContext.registerTool({ tool: reg }, reg.registerOptions)`.
72
72
  */
73
73
  readonly registerOptions?: WebMcpRegisterToolOptions;
74
74
  }
@@ -146,10 +146,15 @@ export interface ToWebMcpToolOptions {
146
146
  export declare class WebMcpConfirmationRequiredError extends Error {
147
147
  readonly capabilityId: string;
148
148
  readonly reason: 'declined' | 'no-handler';
149
- constructor(capabilityId: string, reason: 'declined' | 'no-handler');
149
+ constructor({ capabilityId, reason }: {
150
+ capabilityId: string;
151
+ reason: 'declined' | 'no-handler';
152
+ }, _optional?: Record<string, never>);
150
153
  }
151
154
  /** Whether `name` satisfies the spec's tool-name rule. Exported so a caller can pre-check a manifest without registering anything. */
152
- export declare function isValidWebMcpToolName(name: string): boolean;
155
+ export declare function isValidWebMcpToolName({ name }: {
156
+ name: string;
157
+ }, _optional?: Record<string, never>): boolean;
153
158
  /**
154
159
  * Thrown synchronously by {@link toWebMcpTool} for a capability whose `id` cannot legally become a
155
160
  * WebMCP tool name. Distinct from {@link WebMcpConfirmationRequiredError}: this is a manifest
@@ -157,7 +162,9 @@ export declare function isValidWebMcpToolName(name: string): boolean;
157
162
  */
158
163
  export declare class InvalidWebMcpToolNameError extends Error {
159
164
  readonly capabilityId: string;
160
- constructor(capabilityId: string);
165
+ constructor({ capabilityId }: {
166
+ capabilityId: string;
167
+ }, _optional?: Record<string, never>);
161
168
  }
162
169
  /**
163
170
  * Projects one capability into a WebMCP registration.
@@ -173,7 +180,7 @@ export declare class InvalidWebMcpToolNameError extends Error {
173
180
  * capability reaching the page with nothing in front of it — not the authorization gap.
174
181
  *
175
182
  * @param capability - The capability to expose.
176
- * @param execute - Runs the capability. Receives the raw argument object, already checked against
183
+ * @param execute - Runs the capability. Receives `{ id, args }`, with args already checked against
177
184
  * `capability.inputSchema` — a caller-supplied value that fails validation never reaches this.
178
185
  * @param options - Optional confirmation handler, title/annotations, and registration options
179
186
  * (`signal`/`exposedTo`); required in practice for any capability declaring `requiresConfirmation`.
@@ -183,7 +190,13 @@ export declare class InvalidWebMcpToolNameError extends Error {
183
190
  * @throws {@link WebMcpConfirmationRequiredError} from `execute`, when confirmation is required
184
191
  * and is either declined or unobtainable.
185
192
  */
186
- export declare function toWebMcpTool(capability: CapabilityDef, execute: (id: string, args: Record<string, unknown>) => Promise<unknown>, options?: ToWebMcpToolOptions): WebMcpToolRegistration;
193
+ export declare function toWebMcpTool({ capability, execute }: {
194
+ capability: CapabilityDef;
195
+ execute: (required: {
196
+ id: string;
197
+ args: Record<string, unknown>;
198
+ }) => Promise<unknown>;
199
+ }, options?: ToWebMcpToolOptions): WebMcpToolRegistration;
187
200
  /**
188
201
  * Projects a whole manifest.
189
202
  *
@@ -198,5 +211,11 @@ export declare function toWebMcpTool(capability: CapabilityDef, execute: (id: st
198
211
  * @throws {@link InvalidWebMcpToolNameError} synchronously, for the first capability whose `id`
199
212
  * cannot be a WebMCP tool name.
200
213
  */
201
- export declare function toWebMcpTools(capabilities: readonly CapabilityDef[], execute: (id: string, args: Record<string, unknown>) => Promise<unknown>, options?: ToWebMcpToolOptions): readonly WebMcpToolRegistration[];
214
+ export declare function toWebMcpTools({ capabilities, execute }: {
215
+ capabilities: readonly CapabilityDef[];
216
+ execute: (required: {
217
+ id: string;
218
+ args: Record<string, unknown>;
219
+ }) => Promise<unknown>;
220
+ }, options?: ToWebMcpToolOptions): readonly WebMcpToolRegistration[];
202
221
  //# sourceMappingURL=webmcp.d.ts.map
@@ -27,7 +27,7 @@
27
27
  * tool. Neither belongs on the `ModelContextTool` dict itself, so they are not fields of
28
28
  * {@link WebMcpToolRegistration} — they ride along on its optional `registerOptions`, computed
29
29
  * from {@link ToWebMcpToolOptions}, so a caller has exactly one object to destructure:
30
- * `modelContext.registerTool(reg, reg.registerOptions)`.
30
+ * `modelContext.registerTool({ tool: reg }, reg.registerOptions)`.
31
31
  * 2. **`title`/`annotations`** — real `ModelContextTool` dict fields (`USVString title`,
32
32
  * `ToolAnnotations annotations`), now on {@link WebMcpToolRegistration}. `annotations.readOnlyHint`
33
33
  * defaults from {@link CapabilityDef.risk} (`'read'` → `true`) since Jini's manifests already
@@ -52,7 +52,7 @@ import { findCapabilityInputError } from './capability.js';
52
52
  export class WebMcpConfirmationRequiredError extends Error {
53
53
  capabilityId;
54
54
  reason;
55
- constructor(capabilityId, reason) {
55
+ constructor({ capabilityId, reason }, _optional = {}) {
56
56
  super(reason === 'declined'
57
57
  ? `WebMCP: "${capabilityId}" was declined by the user`
58
58
  : `WebMCP: "${capabilityId}" requires confirmation, but no requestUserInteraction handler was supplied`);
@@ -69,7 +69,7 @@ export class WebMcpConfirmationRequiredError extends Error {
69
69
  */
70
70
  const WEBMCP_TOOL_NAME_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
71
71
  /** Whether `name` satisfies the spec's tool-name rule. Exported so a caller can pre-check a manifest without registering anything. */
72
- export function isValidWebMcpToolName(name) {
72
+ export function isValidWebMcpToolName({ name }, _optional = {}) {
73
73
  return WEBMCP_TOOL_NAME_PATTERN.test(name);
74
74
  }
75
75
  /**
@@ -79,7 +79,7 @@ export function isValidWebMcpToolName(name) {
79
79
  */
80
80
  export class InvalidWebMcpToolNameError extends Error {
81
81
  capabilityId;
82
- constructor(capabilityId) {
82
+ constructor({ capabilityId }, _optional = {}) {
83
83
  super(`WebMCP: capability id "${capabilityId}" is not a valid tool name — the spec requires 1-128 `
84
84
  + 'ASCII alphanumeric characters plus "_", "-", "." '
85
85
  + '(https://webmachinelearning.github.io/webmcp/#dom-modelcontext-registertool)');
@@ -101,7 +101,7 @@ export class InvalidWebMcpToolNameError extends Error {
101
101
  * capability reaching the page with nothing in front of it — not the authorization gap.
102
102
  *
103
103
  * @param capability - The capability to expose.
104
- * @param execute - Runs the capability. Receives the raw argument object, already checked against
104
+ * @param execute - Runs the capability. Receives `{ id, args }`, with args already checked against
105
105
  * `capability.inputSchema` — a caller-supplied value that fails validation never reaches this.
106
106
  * @param options - Optional confirmation handler, title/annotations, and registration options
107
107
  * (`signal`/`exposedTo`); required in practice for any capability declaring `requiresConfirmation`.
@@ -111,9 +111,9 @@ export class InvalidWebMcpToolNameError extends Error {
111
111
  * @throws {@link WebMcpConfirmationRequiredError} from `execute`, when confirmation is required
112
112
  * and is either declined or unobtainable.
113
113
  */
114
- export function toWebMcpTool(capability, execute, options = {}) {
115
- if (!isValidWebMcpToolName(capability.id)) {
116
- throw new InvalidWebMcpToolNameError(capability.id);
114
+ export function toWebMcpTool({ capability, execute }, options = {}) {
115
+ if (!isValidWebMcpToolName({ name: capability.id })) {
116
+ throw new InvalidWebMcpToolNameError({ capabilityId: capability.id });
117
117
  }
118
118
  const { title, annotations, signal, exposedTo } = options;
119
119
  const resolvedAnnotations = {
@@ -133,21 +133,21 @@ export function toWebMcpTool(capability, execute, options = {}) {
133
133
  // Schema-on-error, matching page-executor.ts's discipline for page.* — WebMCP defines no
134
134
  // input validation of its own, so a wrong-schema call would otherwise reach `execute` (or a
135
135
  // confirmation prompt for one) with bad arguments instead of a same-turn correctable error.
136
- const inputError = findCapabilityInputError(capability, input);
136
+ const inputError = findCapabilityInputError({ capability, input });
137
137
  if (inputError !== null) {
138
138
  throw new Error(`${capability.id}: ${inputError}. Expected input: ${JSON.stringify(capability.inputSchema)}`);
139
139
  }
140
140
  if (capability.requiresConfirmation === true) {
141
141
  const { requestUserInteraction } = options;
142
142
  if (!requestUserInteraction) {
143
- throw new WebMcpConfirmationRequiredError(capability.id, 'no-handler');
143
+ throw new WebMcpConfirmationRequiredError({ capabilityId: capability.id, reason: 'no-handler' });
144
144
  }
145
145
  const approved = await requestUserInteraction({ capability, args: input });
146
146
  if (approved !== true) {
147
- throw new WebMcpConfirmationRequiredError(capability.id, 'declined');
147
+ throw new WebMcpConfirmationRequiredError({ capabilityId: capability.id, reason: 'declined' });
148
148
  }
149
149
  }
150
- return execute(capability.id, input);
150
+ return execute({ id: capability.id, args: input });
151
151
  },
152
152
  annotations: resolvedAnnotations,
153
153
  ...(registerOptions !== undefined ? { registerOptions } : {}),
@@ -167,7 +167,7 @@ export function toWebMcpTool(capability, execute, options = {}) {
167
167
  * @throws {@link InvalidWebMcpToolNameError} synchronously, for the first capability whose `id`
168
168
  * cannot be a WebMCP tool name.
169
169
  */
170
- export function toWebMcpTools(capabilities, execute, options = {}) {
171
- return capabilities.map((capability) => toWebMcpTool(capability, execute, options));
170
+ export function toWebMcpTools({ capabilities, execute }, options = {}) {
171
+ return capabilities.map((capability) => toWebMcpTool({ capability, execute }, options));
172
172
  }
173
173
  //# sourceMappingURL=webmcp.js.map
package/dist/index.d.ts CHANGED
@@ -2,6 +2,36 @@
2
2
  * Compatibility root — re-exports `./core` unchanged so existing `from '@jini-ai/agentic'`
3
3
  * imports keep resolving. New code should prefer the explicit `@jini-ai/agentic/core` import;
4
4
  * see `core/README.md` for why this package is split the way it is.
5
+
6
+ * Archived provenance rationale:
7
+ * ## The DOM split (why this package has two entry points)
8
+ *
9
+ * Everything under `src/` except `src/dom/` compiles under `tsconfig.json`, which extends the repo's
10
+ * `tsconfig.base.json` unmodified (`lib: ["ES2023"]`, no `DOM`) and **excludes `src/dom` entirely**.
11
+ * `src/dom/` compiles separately under `tsconfig.dom.json`, the only config in this package with
12
+ * `DOM`/`DOM.Iterable` in `lib`.
13
+ *
14
+ * This is a compile-time guarantee, not a convention: a `document`/`window` reference anywhere
15
+ * outside `src/dom/**` fails `tsc -p tsconfig.json` with "Cannot find name 'document'" (verified
16
+ * during the 2026-07-26 extraction — see the extraction's report for the exact command and error
17
+ * text). That is what proves the policy layer (`page-executor.ts`'s guards, refusals, and
18
+ * allowlist-enforcement) cannot quietly reach into a live page; only `src/dom/dom-page-driver.ts`,
19
+ * the one `PageDriver` implementation that legitimately needs a DOM, may.
20
+ *
21
+ * Two `package.json` exports follow the same split:
22
+ *
23
+ * - `.` (`dist/index.d.ts`/`dist/index.js`, built by `tsconfig.json`) — the DOM-free vocabulary,
24
+ * policy gate, and protocol projections. What `@jini/chat-core`, `@jini/daemon` (structurally, see
25
+ * below), and (once §5 of the plan lands) `@jini/ui` depend on.
26
+ * - `./dom` (`dist/dom/index.d.ts`/`dist/dom/index.js`, built by `tsconfig.dom.json`) — the browser
27
+ * `PageDriver`. What `@jini/chat-react` depends on.
28
+ *
29
+ * `scripts/check-engine-boundaries.ts` R2 normally allows only bare `@jini/<name>` imports (one
30
+ * entry point per package), with a single named exception for `@jini/core/internal`. This extraction
31
+ * added a second, identically-gated exception for the exact literal `@jini/agentic/dom` — not a
32
+ * pattern — because `@jini/chat-react` genuinely needs the DOM half and there is no third package for
33
+ * it to live in without recreating the sprawl this plan exists to reduce (23 packages vs. a locked
34
+ * 14). See that script's own module doc for the up-to-date rule list.
5
35
  */
6
36
  export * from './core/index.js';
7
37
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -2,6 +2,36 @@
2
2
  * Compatibility root — re-exports `./core` unchanged so existing `from '@jini-ai/agentic'`
3
3
  * imports keep resolving. New code should prefer the explicit `@jini-ai/agentic/core` import;
4
4
  * see `core/README.md` for why this package is split the way it is.
5
+
6
+ * Archived provenance rationale:
7
+ * ## The DOM split (why this package has two entry points)
8
+ *
9
+ * Everything under `src/` except `src/dom/` compiles under `tsconfig.json`, which extends the repo's
10
+ * `tsconfig.base.json` unmodified (`lib: ["ES2023"]`, no `DOM`) and **excludes `src/dom` entirely**.
11
+ * `src/dom/` compiles separately under `tsconfig.dom.json`, the only config in this package with
12
+ * `DOM`/`DOM.Iterable` in `lib`.
13
+ *
14
+ * This is a compile-time guarantee, not a convention: a `document`/`window` reference anywhere
15
+ * outside `src/dom/**` fails `tsc -p tsconfig.json` with "Cannot find name 'document'" (verified
16
+ * during the 2026-07-26 extraction — see the extraction's report for the exact command and error
17
+ * text). That is what proves the policy layer (`page-executor.ts`'s guards, refusals, and
18
+ * allowlist-enforcement) cannot quietly reach into a live page; only `src/dom/dom-page-driver.ts`,
19
+ * the one `PageDriver` implementation that legitimately needs a DOM, may.
20
+ *
21
+ * Two `package.json` exports follow the same split:
22
+ *
23
+ * - `.` (`dist/index.d.ts`/`dist/index.js`, built by `tsconfig.json`) — the DOM-free vocabulary,
24
+ * policy gate, and protocol projections. What `@jini/chat-core`, `@jini/daemon` (structurally, see
25
+ * below), and (once §5 of the plan lands) `@jini/ui` depend on.
26
+ * - `./dom` (`dist/dom/index.d.ts`/`dist/dom/index.js`, built by `tsconfig.dom.json`) — the browser
27
+ * `PageDriver`. What `@jini/chat-react` depends on.
28
+ *
29
+ * `scripts/check-engine-boundaries.ts` R2 normally allows only bare `@jini/<name>` imports (one
30
+ * entry point per package), with a single named exception for `@jini/core/internal`. This extraction
31
+ * added a second, identically-gated exception for the exact literal `@jini/agentic/dom` — not a
32
+ * pattern — because `@jini/chat-react` genuinely needs the DOM half and there is no third package for
33
+ * it to live in without recreating the sprawl this plan exists to reduce (23 packages vs. a locked
34
+ * 14). See that script's own module doc for the up-to-date rule list.
5
35
  */
6
36
  export * from './core/index.js';
7
37
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,8 @@
1
+ import type { ArchiveReaderPort } from "./ports.js";
2
+ import { type SkillUploadFile } from "./validation.js";
3
+ /** Read bounded archive content only; paths are validated before opening any entry stream. */
4
+ export declare function readSkillArchive({ base64, archiveReader }: {
5
+ base64: string;
6
+ archiveReader: ArchiveReaderPort;
7
+ }, _optional?: Record<string, never>): Promise<SkillUploadFile[]>;
8
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { decodeSkillBase64, MAX_SKILL_BYTES, MAX_SKILL_FILES, MAX_SKILL_FILE_BYTES, SkillInputError, validateSkillPath } from "./validation.js";
2
+ /** Read bounded archive content only; paths are validated before opening any entry stream. */
3
+ export async function readSkillArchive({ base64, archiveReader }, _optional = {}) {
4
+ try {
5
+ const reader = await archiveReader.open({ bytes: decodeSkillBase64({ value: base64 }) });
6
+ const files = [];
7
+ let total = 0;
8
+ let count = 0;
9
+ try {
10
+ for await (const entry of reader.entries) {
11
+ if (++count > MAX_SKILL_FILES)
12
+ throw new SkillInputError({ message: "A skill archive may contain at most 256 entries." });
13
+ validateSkillPath({ filePath: entry.path.replace(/\/$/, "") });
14
+ if (entry.kind !== "file" && entry.kind !== "directory")
15
+ throw new SkillInputError({ message: "Skill archives may contain only regular files and directories." });
16
+ if (entry.kind === "directory")
17
+ continue;
18
+ if (!Number.isSafeInteger(entry.size) || entry.size < 0 || entry.size > MAX_SKILL_FILE_BYTES)
19
+ throw new SkillInputError({ message: "Skill archive file exceeds 1 MiB." });
20
+ const chunks = [];
21
+ let size = 0;
22
+ {
23
+ for await (const chunk of entry.read({})) {
24
+ size += chunk.length;
25
+ total += chunk.length;
26
+ if (size > MAX_SKILL_FILE_BYTES || total > MAX_SKILL_BYTES)
27
+ throw new SkillInputError({ message: "Skill archive exceeds its decompressed size limit." });
28
+ chunks.push(Buffer.from(chunk));
29
+ }
30
+ }
31
+ if (size !== entry.size)
32
+ throw new SkillInputError({ message: "Skill archive file size does not match its metadata." });
33
+ files.push({ path: entry.path, contentBase64: Buffer.concat(chunks).toString("base64") });
34
+ }
35
+ return files;
36
+ }
37
+ finally {
38
+ reader.close({});
39
+ }
40
+ }
41
+ catch (error) {
42
+ if (error instanceof SkillInputError)
43
+ throw error;
44
+ throw new SkillInputError({ message: "Could not read skill ZIP. Use a valid ZIP with regular files only." });
45
+ }
46
+ }
47
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1,6 @@
1
+ import type { SkillFetchPort } from './ports.js';
2
+ /** Wrap an explicitly supplied native fetch without adding transport defaults or changing responses. */
3
+ export declare function createSkillFetchAdapter(required: {
4
+ fetch: typeof fetch;
5
+ }, _optional?: Record<string, never>): SkillFetchPort;
6
+ //# sourceMappingURL=fetch-adapter.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Wrap an explicitly supplied native fetch without adding transport defaults or changing responses. */
2
+ export function createSkillFetchAdapter(required, _optional = {}) {
3
+ const fetchImpl = required.fetch;
4
+ return ({ url }, optional = {}) => fetchImpl(url, optional);
5
+ }
6
+ //# sourceMappingURL=fetch-adapter.js.map