@jini-ai/agentic 0.4.0 → 0.4.2

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 (66) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/core/a2ui/index.d.ts +1 -0
  3. package/dist/core/a2ui/index.js +1 -0
  4. package/dist/core/a2ui/surface-properties.d.ts +34 -0
  5. package/dist/core/a2ui/surface-properties.js +49 -0
  6. package/dist/core/annotated-actions-script.d.ts +13 -0
  7. package/dist/core/annotated-actions-script.js +137 -0
  8. package/dist/core/index.d.ts +1 -0
  9. package/dist/core/index.js +1 -0
  10. package/package.json +2 -2
  11. package/dist/a2ui/agent-to-renderer.d.ts +0 -613
  12. package/dist/a2ui/agent-to-renderer.js +0 -191
  13. package/dist/a2ui/catalog.d.ts +0 -73
  14. package/dist/a2ui/catalog.js +0 -304
  15. package/dist/a2ui/common-types.d.ts +0 -401
  16. package/dist/a2ui/common-types.js +0 -130
  17. package/dist/a2ui/index.d.ts +0 -16
  18. package/dist/a2ui/index.js +0 -16
  19. package/dist/a2ui/interpreter.d.ts +0 -71
  20. package/dist/a2ui/interpreter.js +0 -337
  21. package/dist/a2ui/json-pointer.d.ts +0 -48
  22. package/dist/a2ui/json-pointer.js +0 -174
  23. package/dist/a2ui/renderer-to-agent.d.ts +0 -342
  24. package/dist/a2ui/renderer-to-agent.js +0 -111
  25. package/dist/a2ui/resolve.d.ts +0 -28
  26. package/dist/a2ui/resolve.js +0 -144
  27. package/dist/a2ui/tree.d.ts +0 -66
  28. package/dist/a2ui/tree.js +0 -74
  29. package/dist/ag-ui.d.ts +0 -82
  30. package/dist/ag-ui.js +0 -85
  31. package/dist/agui/encoder.d.ts +0 -50
  32. package/dist/agui/encoder.js +0 -97
  33. package/dist/agui/events.d.ts +0 -59
  34. package/dist/agui/events.js +0 -15
  35. package/dist/capability.d.ts +0 -81
  36. package/dist/capability.js +0 -79
  37. package/dist/dom/dom-page-driver.d.ts +0 -36
  38. package/dist/dom/dom-page-driver.js +0 -601
  39. package/dist/dom/index.d.ts +0 -17
  40. package/dist/dom/index.js +0 -17
  41. package/dist/dom/model-context.d.ts +0 -69
  42. package/dist/dom/model-context.js +0 -44
  43. package/dist/element-handles.d.ts +0 -120
  44. package/dist/element-handles.js +0 -39
  45. package/dist/gen-ui/encoder.d.ts +0 -50
  46. package/dist/gen-ui/encoder.js +0 -97
  47. package/dist/gen-ui/events.d.ts +0 -59
  48. package/dist/gen-ui/events.js +0 -15
  49. package/dist/gen-ui/index.d.ts +0 -18
  50. package/dist/gen-ui/index.js +0 -17
  51. package/dist/guards.d.ts +0 -96
  52. package/dist/guards.js +0 -186
  53. package/dist/handle.d.ts +0 -41
  54. package/dist/handle.js +0 -41
  55. package/dist/mcp-ui-apps.d.ts +0 -223
  56. package/dist/mcp-ui-apps.js +0 -252
  57. package/dist/mcp-ui.d.ts +0 -133
  58. package/dist/mcp-ui.js +0 -134
  59. package/dist/page-capabilities.d.ts +0 -14
  60. package/dist/page-capabilities.js +0 -129
  61. package/dist/page-driver.d.ts +0 -120
  62. package/dist/page-driver.js +0 -2
  63. package/dist/page-executor.d.ts +0 -94
  64. package/dist/page-executor.js +0 -320
  65. package/dist/webmcp.d.ts +0 -202
  66. package/dist/webmcp.js +0 -173
@@ -1,120 +0,0 @@
1
- /**
2
- * @module page-driver
3
- *
4
- * The seam between "what the agent asked for" and "the actual page".
5
- *
6
- * Every method here speaks in handles and plain data — no elements, no nodes, no DOM types at
7
- * all. That is load-bearing rather than stylistic: this package compiles with `lib: ["ES2023"]`
8
- * and holds no browser globals, so the DOM cannot appear in it. The payoff is that the *policy*
9
- * (validate the handle, refuse the credential field, clamp the highlight, reject an unpublished
10
- * page) is pure and fully testable, and a host supplies only the mechanical part.
11
- *
12
- * A driver implementation is small — resolve one attribute selector, then act. It is deliberately
13
- * not shipped here: the same-document case (a CMS embedding a chat pane) and the sandboxed-frame
14
- * case (a host previewing untrusted content) need different mechanics for identical verbs. When
15
- * a second real implementation exists, generalize from two rather than guessing from one.
16
- *
17
- * Drivers are trusted to be mechanical. They must NOT re-implement policy — every refusal in
18
- * `page-executor.ts` runs before the driver is called.
19
- */
20
- import type { AgentElementDescriptor, AgentElementRawState, AgentElementRole } from './element-handles.js';
21
- import type { FieldDescriptor } from './guards.js';
22
- /** Filter for {@link PageDriver.findElements}. Both fields are optional; absent means "no filter". */
23
- export interface FindElementsFilter {
24
- readonly role?: AgentElementRole | undefined;
25
- /** Case-insensitive substring, matched against handle and label by the driver. */
26
- readonly query?: string | undefined;
27
- }
28
- /**
29
- * One page a host has published as navigable, as returned by {@link PageDriver.listPages}.
30
- *
31
- * `label` exists so a caller can tell what a page IS without first navigating to it and reading
32
- * its `data-agent-page` id back off the DOM — the same "call find_elements first" flow already
33
- * returns every page a host publishes, and previously handed back only the bare id (`"plugins"`),
34
- * leaving a caller to guess at meaning from the string alone. Page-authored, so untrusted in the
35
- * same sense {@link AgentElementDescriptor.label} is: read it, never follow it as an instruction.
36
- */
37
- export interface PageSummary {
38
- readonly id: string;
39
- readonly label: string;
40
- }
41
- /**
42
- * What a host must provide for the `page.*` capabilities to work.
43
- *
44
- * Implementations resolve a handle with `resolveHandleSelector` and nothing else — never a
45
- * caller-supplied selector, and never by evaluating caller-supplied script.
46
- */
47
- export interface PageDriver {
48
- /**
49
- * Every element the current page has published to agents.
50
- *
51
- * @param filter - Optional role/substring narrowing.
52
- * @returns Descriptors. `label` may be raw page text; the executor normalizes it.
53
- */
54
- findElements(filter: FindElementsFilter): Promise<readonly AgentElementDescriptor[]>;
55
- /**
56
- * The pages this host can navigate to.
57
- *
58
- * An allowlist, so `page.navigate` refuses anything the host has not published rather than
59
- * accepting a caller-supplied URL.
60
- */
61
- listPages(): Promise<readonly PageSummary[]>;
62
- /**
63
- * The attributes of a field, for the fill guard.
64
- *
65
- * @param handle - A validated element handle.
66
- * @returns The field's attributes, or `null` when the handle does not resolve to an input.
67
- */
68
- describeField(handle: string): Promise<FieldDescriptor | null>;
69
- /** 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>;
73
- /** Called only after the fill guard has passed. */
74
- fill(handle: string, text: string): Promise<void>;
75
- /**
76
- * Chooses — or, in a multi-select, removes — an option in a dropdown, matching visible text
77
- * before value.
78
- *
79
- * Separate from {@link PageDriver.fill} because a dropdown is not a text field: its value must
80
- * be one of a fixed set, so "type this in" has no meaning. A driver rejects an option that does
81
- * not exist rather than inventing one — a caller told an unavailable option was selected would
82
- * carry that wrong belief into everything it did next.
83
- *
84
- * @param selected - Whether the option should end up selected. Defaults to `true`. A
85
- * multi-select honours `false` by deselecting just that option, leaving the rest of the
86
- * selection untouched. A single-select has no meaningful "off" — its one choice cannot be
87
- * removed, only replaced — so `false` there is refused rather than silently ignored or
88
- * clearing the control.
89
- * @throws When the handle is not a dropdown, when no option matches, or when `selected: false`
90
- * is requested on a single-select.
91
- */
92
- selectOption(handle: string, option: string, selected?: boolean): Promise<void>;
93
- /** Called only after `page` has been checked against {@link PageDriver.listPages}. */
94
- navigate(page: string): Promise<void>;
95
- /**
96
- * What the element currently is: its live text, a field's contents, checked/disabled/visible.
97
- *
98
- * Optional, and honestly so. A driver over a surface that cannot be read back — a fire-and-forget
99
- * native host, a write-only bridge — should omit it rather than fabricate a state, and the
100
- * executor then reports observation as unavailable instead of implying an empty page.
101
- *
102
- * @param handle - A validated element handle.
103
- * @returns The raw state, or `null` when the handle no longer resolves — which is itself an
104
- * observation (a click that removed its own target), not an error.
105
- */
106
- describeState?(handle: string): Promise<AgentElementRawState | null>;
107
- /**
108
- * Resolves once the surface has finished reacting to the write that just happened.
109
- *
110
- * Without this, reading state back immediately after an action reads the DOM *before* the
111
- * framework has re-rendered, and every write reports "nothing changed" — the precise failure
112
- * this observation channel exists to fix. Optional because a surface with no async render has
113
- * nothing to wait for; a DOM driver does, and must implement it.
114
- *
115
- * Implementations must be bounded: a hidden tab suspends animation frames indefinitely, so a
116
- * naive "wait for the next frame" never resolves for exactly the background agent this is for.
117
- */
118
- settle?(): Promise<void>;
119
- }
120
- //# sourceMappingURL=page-driver.d.ts.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=page-driver.js.map
@@ -1,94 +0,0 @@
1
- import { type AgentElementRawState, type AgentElementRole, type AgentElementState } from './element-handles.js';
2
- import type { PageDriver, PageSummary } from './page-driver.js';
3
- /** Default marker lifetime, long enough to notice and short enough not to linger. */
4
- export declare const DEFAULT_HIGHLIGHT_MS = 3000;
5
- /**
6
- * Hard cap on a marker's lifetime. A caller asking for a very long highlight is really asking to
7
- * leave a permanent mark on someone else's page, which is a change of appearance rather than a
8
- * transient hint — and `page.highlight` is classified `read` precisely because it is transient.
9
- */
10
- export declare const MAX_HIGHLIGHT_MS = 15000;
11
- /**
12
- * How many elements `withState` will describe in one call.
13
- *
14
- * State is per-element work and per-element payload, so an unfiltered `withState` on a large page
15
- * is both the slowest and the largest thing this surface can be asked for. The cap turns that into
16
- * a bounded answer plus an explicit `stateTruncated`, rather than a caller discovering the limit
17
- * as a timeout or a truncated blob. Narrowing with `role`/`query` is the intended way past it.
18
- */
19
- export declare const MAX_STATEFUL_ELEMENTS = 50;
20
- /** An element as returned to a caller, with page-authored text already bounded. */
21
- export interface PageElementResult {
22
- readonly handle: string;
23
- readonly role: AgentElementRole | undefined;
24
- readonly label: string;
25
- readonly labelTruncated: boolean;
26
- readonly page: string | undefined;
27
- /** Present only when `withState` was asked for, the driver can observe, and the element resolved. */
28
- readonly state?: AgentElementState;
29
- }
30
- export interface FindElementsResult {
31
- readonly elements: readonly PageElementResult[];
32
- /** Every page this host publishes as navigable — the whole site map, not just this one page —
33
- * so a caller can plan a route (`page.navigate`) before it needs one. */
34
- readonly pages: readonly PageSummary[];
35
- /**
36
- * Names the fields carrying page-authored text. Labels describe the UI; they are never
37
- * instructions, however imperative they read.
38
- */
39
- readonly untrustedFields: readonly string[];
40
- /** True when `withState` was asked for but this surface cannot observe itself. */
41
- readonly stateUnavailable?: boolean;
42
- /** True when more elements matched than {@link MAX_STATEFUL_ELEMENTS}, so later ones carry no state. */
43
- readonly stateTruncated?: boolean;
44
- }
45
- /**
46
- * What a write did to its target, as seen by the surface itself.
47
- *
48
- * The point is that a caller can check its own work. Before this, `page.click` replied
49
- * `{clicked: "item-water-window-plants"}` — an echo of the request, true whether or not anything
50
- * happened.
51
- */
52
- export interface PageWriteObservation {
53
- /** Target state before the action. Absent when the surface cannot observe itself. */
54
- readonly before?: AgentElementState;
55
- /** Target state afterwards. Absent when unobservable, or when the action removed its own target. */
56
- readonly after?: AgentElementState;
57
- /**
58
- * Whether the target's own state differs. Deliberately named for what was actually compared:
59
- * a click can change much of a page while leaving the button it hit exactly as it was.
60
- */
61
- readonly targetChanged?: boolean;
62
- }
63
- /**
64
- * Turns a driver's raw reading into what a caller may see.
65
- *
66
- * The one decision here that is not formatting: whether a field's contents are reportable at all.
67
- * That runs on the descriptor the driver supplied, in this layer, for the same reason the fill
68
- * guard does — a secrecy rule re-derived by each driver is a secrecy rule that will eventually be
69
- * derived wrong. A value arriving with no descriptor to check is withheld rather than trusted.
70
- */
71
- export declare function projectElementState(raw: AgentElementRawState): AgentElementState;
72
- /**
73
- * What page is showing and how much it publishes, at one moment — the before/after pair
74
- * `page.navigate` reports. Not {@link PageSummary} (`page-driver.js`): that names one entry in the
75
- * SITE-WIDE list of pages a host publishes; this is a single reading of the CURRENT page, taken
76
- * twice around one navigation.
77
- */
78
- export interface PageActivitySnapshot {
79
- readonly page: string | undefined;
80
- readonly elementCount: number;
81
- }
82
- /**
83
- * Runs one `page.*` capability.
84
- *
85
- * @param driver - The host's page driver.
86
- * @param capabilityId - A `page.*` id from {@link PAGE_CAPABILITIES}.
87
- * @param input - Raw caller arguments, validated here against the declared schema.
88
- * @returns The capability's result.
89
- * @throws If the id is unknown, the input fails validation, a handle is malformed, the target is
90
- * a field no automated caller may fill, or the page is not published. Refusals are errors on
91
- * purpose: a caller must be told why, not handed a silent no-op it will retry forever.
92
- */
93
- export declare function executePageCapability(driver: PageDriver, capabilityId: string, input: Record<string, unknown>): Promise<unknown>;
94
- //# sourceMappingURL=page-executor.d.ts.map
@@ -1,320 +0,0 @@
1
- /**
2
- * @module page-executor
3
- *
4
- * The six `page.*` verbs, as policy over a {@link PageDriver}.
5
- *
6
- * Every refusal happens here, before the driver is called: schema validation, handle validation,
7
- * the field guard, the page allowlist, highlight clamping. A driver is mechanical and is trusted
8
- * to be — which means a second driver (same-document, sandboxed frame, a future native host)
9
- * inherits identical behavior for free rather than re-deriving it and getting one case wrong.
10
- *
11
- * Nothing here touches the DOM. That is what makes the interesting half testable without a
12
- * browser: "does it refuse to type into a password box" is a unit test, not an integration test.
13
- */
14
- import { findCapabilityInputError, } from './capability.js';
15
- import { AGENT_ELEMENT_ROLES, isValidElementHandle, } from './element-handles.js';
16
- import { describeFieldReadRefusal, describeFieldRefusal, findFieldFillRefusal, findFieldReadRefusal, normalizeAgentLabel, } from './guards.js';
17
- import { PAGE_CAPABILITIES } from './page-capabilities.js';
18
- /** Default marker lifetime, long enough to notice and short enough not to linger. */
19
- export const DEFAULT_HIGHLIGHT_MS = 3_000;
20
- /**
21
- * Hard cap on a marker's lifetime. A caller asking for a very long highlight is really asking to
22
- * leave a permanent mark on someone else's page, which is a change of appearance rather than a
23
- * transient hint — and `page.highlight` is classified `read` precisely because it is transient.
24
- */
25
- export const MAX_HIGHLIGHT_MS = 15_000;
26
- /**
27
- * How many elements `withState` will describe in one call.
28
- *
29
- * State is per-element work and per-element payload, so an unfiltered `withState` on a large page
30
- * is both the slowest and the largest thing this surface can be asked for. The cap turns that into
31
- * a bounded answer plus an explicit `stateTruncated`, rather than a caller discovering the limit
32
- * as a timeout or a truncated blob. Narrowing with `role`/`query` is the intended way past it.
33
- */
34
- export const MAX_STATEFUL_ELEMENTS = 50;
35
- function requireHandle(input, key) {
36
- const value = input[key];
37
- if (typeof value !== 'string' || !isValidElementHandle(value)) {
38
- throw new Error(`"${key}" must be a published element handle from page.find_elements — `
39
- + 'lowercase words joined by single hyphens, never a CSS selector');
40
- }
41
- return value;
42
- }
43
- function clampHighlightDuration(value) {
44
- if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
45
- return DEFAULT_HIGHLIGHT_MS;
46
- return Math.min(value, MAX_HIGHLIGHT_MS);
47
- }
48
- function isAgentElementRole(value) {
49
- return typeof value === 'string' && AGENT_ELEMENT_ROLES.includes(value);
50
- }
51
- /**
52
- * Turns a driver's raw reading into what a caller may see.
53
- *
54
- * The one decision here that is not formatting: whether a field's contents are reportable at all.
55
- * That runs on the descriptor the driver supplied, in this layer, for the same reason the fill
56
- * guard does — a secrecy rule re-derived by each driver is a secrecy rule that will eventually be
57
- * derived wrong. A value arriving with no descriptor to check is withheld rather than trusted.
58
- */
59
- export function projectElementState(raw) {
60
- const text = normalizeAgentLabel(raw.text);
61
- // Computed once, up front, so `checked` is gated by the exact same refusal as `value` below
62
- // rather than being copied through unconditionally. A checkbox/radio's `checked` is a second
63
- // secrecy channel, not a formatting detail: for a field like `health_disclosure_password`,
64
- // *which* option is ticked is itself the secret, even though it is a boolean and `value` was
65
- // already withheld. `text` is deliberately NOT gated here, on purpose, even for a refused
66
- // field: for a checkbox/radio, `text` is the option's own visible label (e.g. "I am HIV
67
- // positive") — page-authored content the page already renders openly to anyone who looks at
68
- // it, exactly like a `<select>`'s `options` below. The secret is which option is checked, not
69
- // what the option is called; withholding the label too would blind a caller to the page's own
70
- // ontology for no reduction in what actually leaks.
71
- const refusal = raw.field !== undefined ? findFieldReadRefusal(raw.field) : null;
72
- // The one element whose `text` is NOT page-authored chrome: an editable region, whose contents
73
- // are the user's data and whose driver has no `value` to report them as. It is gated by exactly
74
- // the rules `value` is gated by below, including the "arrived with nothing to check it against"
75
- // one — an unverifiable secret is withheld, not trusted.
76
- const withheldText = raw.textIsValue !== true
77
- ? undefined
78
- : raw.field === undefined
79
- ? 'this element reported contents without the attributes needed to check them for secrets'
80
- : refusal !== null ? describeFieldReadRefusal(refusal) : undefined;
81
- const base = {
82
- text: withheldText === undefined ? text.text : '',
83
- textTruncated: withheldText === undefined ? text.truncated : false,
84
- ...(withheldText !== undefined ? { textWithheld: withheldText } : {}),
85
- ...(raw.checked !== undefined && refusal === null ? { checked: raw.checked } : {}),
86
- ...(raw.disabled !== undefined ? { disabled: raw.disabled } : {}),
87
- ...(raw.visible !== undefined ? { visible: raw.visible } : {}),
88
- // Bounded like every other page-authored string, but never withheld: the options a dropdown
89
- // offers are part of the UI's shape, not the user's data.
90
- ...(raw.options !== undefined
91
- ? { options: raw.options.map((option) => normalizeAgentLabel(option).text) }
92
- : {}),
93
- };
94
- if (raw.value === undefined)
95
- return base;
96
- if (raw.field === undefined) {
97
- return {
98
- ...base,
99
- valueWithheld: 'this element reported a value without the attributes needed to check it for secrets',
100
- };
101
- }
102
- if (refusal !== null)
103
- return { ...base, valueWithheld: describeFieldReadRefusal(refusal) };
104
- const value = normalizeAgentLabel(raw.value);
105
- return { ...base, value: value.text, valueTruncated: value.truncated };
106
- }
107
- /**
108
- * Whether two readings of the same element are indistinguishable to a caller.
109
- *
110
- * Compares only what the caller can actually see: a withheld value is compared as "withheld",
111
- * so a secret that changed reads as unchanged. That is the honest answer — a change no caller may
112
- * observe is not one this can report — and is why the field it feeds is named `targetChanged`.
113
- */
114
- function sameElementState(a, b) {
115
- return a.text === b.text
116
- // Compared for the same reason `valueWithheld` is: without it, an editable region whose
117
- // contents are withheld reads as `text: ''` before and after, and a write into it would
118
- // report `targetChanged: false` — an answer that says "nothing happened" rather than the
119
- // honest "no caller may observe whether it did".
120
- && a.textWithheld === b.textWithheld
121
- && a.value === b.value
122
- && a.valueWithheld === b.valueWithheld
123
- && a.checked === b.checked
124
- && a.disabled === b.disabled
125
- && a.visible === b.visible;
126
- }
127
- /**
128
- * Reads one element's state, or `undefined` when there is nothing to read.
129
- *
130
- * Two different absences collapse here on purpose: a driver that cannot observe at all, and an
131
- * element that no longer resolves. A caller distinguishes them from context — `before` present
132
- * with `after` absent means the action removed its own target; both absent means the surface does
133
- * not report state.
134
- */
135
- async function observeElement(driver, handle) {
136
- if (driver.describeState === undefined || !isValidElementHandle(handle))
137
- return undefined;
138
- const raw = await driver.describeState(handle);
139
- return raw === null ? undefined : projectElementState(raw);
140
- }
141
- /** Runs a write between two readings of its target, waiting for the surface to settle in between. */
142
- async function observeWrite(driver, handle, write) {
143
- const before = await observeElement(driver, handle);
144
- await write();
145
- if (driver.settle !== undefined)
146
- await driver.settle();
147
- const after = await observeElement(driver, handle);
148
- return {
149
- ...(before !== undefined ? { before } : {}),
150
- ...(after !== undefined ? { after } : {}),
151
- ...(before !== undefined && after !== undefined
152
- ? { targetChanged: !sameElementState(before, after) }
153
- : {}),
154
- };
155
- }
156
- /**
157
- * Reads the showing page from the elements themselves, which is where a driver reports it. A
158
- * surface that publishes nothing has no page id to report, and says so rather than guessing.
159
- */
160
- async function summarizePage(driver) {
161
- const elements = await driver.findElements({});
162
- return {
163
- page: elements.find((element) => element.page !== undefined)?.page,
164
- elementCount: elements.length,
165
- };
166
- }
167
- /**
168
- * Runs one `page.*` capability.
169
- *
170
- * @param driver - The host's page driver.
171
- * @param capabilityId - A `page.*` id from {@link PAGE_CAPABILITIES}.
172
- * @param input - Raw caller arguments, validated here against the declared schema.
173
- * @returns The capability's result.
174
- * @throws If the id is unknown, the input fails validation, a handle is malformed, the target is
175
- * a field no automated caller may fill, or the page is not published. Refusals are errors on
176
- * purpose: a caller must be told why, not handed a silent no-op it will retry forever.
177
- */
178
- export async function executePageCapability(driver, capabilityId, input) {
179
- const capability = PAGE_CAPABILITIES.find((entry) => entry.id === capabilityId);
180
- if (capability === undefined)
181
- throw new Error(`unknown page capability: ${capabilityId}`);
182
- const inputError = findCapabilityInputError(capability, input);
183
- if (inputError !== null) {
184
- // The schema rides along with the refusal rather than making a caller spend a second
185
- // round trip on describe_tool to learn what it already tried to guess — ai-control-plane.md
186
- // §29.4's "schema-on-error" requirement, empirically forced by a real run: page.fill was
187
- // called with `handle` instead of the argument's old name, `element`, twice before the caller
188
- // gave up and looked the schema up separately. One JSON-Schema object, embedded once, turned
189
- // that into a same-turn self-correction for every page.* verb this gate guards, not just the
190
- // one that got measured — self-correction is why that mismatch was survivable at all, given
191
- // every verb here needs a handle from `page.find_elements`, whose own response has always
192
- // called this same value `handle` (`AgentElementDescriptor.handle`). The argument itself was
193
- // later renamed from `element` to `handle` to match, closing the gap this comment describes
194
- // rather than merely papering over it turn by turn.
195
- throw new Error(`${capabilityId}: ${inputError}. Expected input: ${JSON.stringify(capability.inputSchema)}`);
196
- }
197
- switch (capabilityId) {
198
- case 'page.find_elements': {
199
- const role = input['role'];
200
- // The schema's enum check has already run; this narrows the type for the driver.
201
- const filter = {
202
- ...(isAgentElementRole(role) ? { role } : {}),
203
- ...(typeof input['query'] === 'string' ? { query: input['query'] } : {}),
204
- };
205
- const [found, rawPages] = await Promise.all([driver.findElements(filter), driver.listPages()]);
206
- const withState = input['withState'] === true;
207
- const observing = withState && driver.describeState !== undefined;
208
- const elements = await Promise.all(found.map(async (element, index) => {
209
- const label = normalizeAgentLabel(element.label);
210
- const base = {
211
- handle: element.handle,
212
- role: element.role,
213
- label: label.text,
214
- labelTruncated: label.truncated,
215
- page: element.page,
216
- };
217
- if (!observing || index >= MAX_STATEFUL_ELEMENTS)
218
- return base;
219
- const state = await observeElement(driver, element.handle);
220
- return state === undefined ? base : { ...base, state };
221
- }));
222
- // `label` is page-authored the same way an element's is — bounded here for the same reason
223
- // `page.navigate`'s refusal message bounds it below, rather than trusting a host to have kept
224
- // every `data-agent-page` label short and clean.
225
- const pages = rawPages.map((page) => ({
226
- id: page.id,
227
- label: normalizeAgentLabel(page.label).text,
228
- }));
229
- return {
230
- elements,
231
- pages,
232
- untrustedFields: observing
233
- ? ['elements[].label', 'elements[].state.text', 'elements[].state.value', 'pages[].label']
234
- : ['elements[].label', 'pages[].label'],
235
- ...(withState && !observing ? { stateUnavailable: true } : {}),
236
- ...(observing && found.length > MAX_STATEFUL_ELEMENTS ? { stateTruncated: true } : {}),
237
- };
238
- }
239
- case 'page.highlight': {
240
- const handle = requireHandle(input, 'handle');
241
- const durationMs = clampHighlightDuration(input['durationMs']);
242
- await driver.highlight(handle, durationMs);
243
- return { highlighted: handle, durationMs };
244
- }
245
- case 'page.scroll_to': {
246
- const handle = requireHandle(input, 'handle');
247
- await driver.scrollTo(handle);
248
- return { scrolledTo: handle };
249
- }
250
- case 'page.click': {
251
- const handle = requireHandle(input, 'handle');
252
- const observation = await observeWrite(driver, handle, () => driver.click(handle));
253
- return { clicked: handle, ...observation };
254
- }
255
- case 'page.fill': {
256
- const handle = requireHandle(input, 'handle');
257
- // Declared required and typed `string` in the manifest, and `findCapabilityInputError`
258
- // enforced both above — re-checking here would be an unreachable branch, not a safeguard.
259
- const text = input['text'];
260
- // Ask the page what this field actually is before writing to it. A handle proves the page
261
- // published the element; it says nothing about whether it holds a password.
262
- const field = await driver.describeField(handle);
263
- if (field === null)
264
- throw new Error(`"${handle}" is not a fillable field`);
265
- const refusal = findFieldFillRefusal(field);
266
- if (refusal !== null) {
267
- throw new Error(`refusing to fill "${handle}": ${describeFieldRefusal(refusal)}`);
268
- }
269
- const observation = await observeWrite(driver, handle, () => driver.fill(handle, text));
270
- return { filled: handle, ...observation };
271
- }
272
- case 'page.select_option': {
273
- const handle = requireHandle(input, 'handle');
274
- // Required and string-typed by the manifest, already enforced above. See page.fill.
275
- const option = input['option'];
276
- // Optional and boolean-typed by the manifest; absent means "select it", same as before this
277
- // argument existed.
278
- const selected = input['selected'] === false ? false : true;
279
- // No field guard: a dropdown's options are authored by the page, so choosing one reveals
280
- // nothing the page had not already published, and none of the credential field types this
281
- // surface refuses can be a `<select>`.
282
- const observation = await observeWrite(driver, handle, () => driver.selectOption(handle, option, selected));
283
- // `optionSelected` names the boolean rather than reusing `selected`, which already means
284
- // "the handle this call acted on" for every verb in this switch (`clicked`, `filled`, …).
285
- return { selected: handle, option, optionSelected: selected, ...observation };
286
- }
287
- case 'page.navigate': {
288
- // Required and string-typed by the manifest, already enforced above. See page.fill.
289
- const page = input['page'];
290
- const pages = await driver.listPages();
291
- if (!pages.some((candidate) => candidate.id === page)) {
292
- // Every other piece of page-authored (or, here, caller-authored-but-page-shaped) text
293
- // this system hands to a model goes through normalizeAgentLabel first — bounded, control-
294
- // and bidi-stripped. This refusal was the one path that skipped it: the raw `page`
295
- // argument, and the `pages` this host actually publishes (itself read verbatim off
296
- // `data-agent-page` attributes with no length bound), were interpolated straight into the
297
- // thrown message. A 5000+-char bidi payload survived verbatim as a result. The lookup
298
- // above still runs on the raw values — only the text that reaches the message is bounded.
299
- //
300
- // `id (label)`, not just the id: a refusal is the one moment a caller that guessed wrong
301
- // is looking straight at the real menu, so it is worth spending the label here even though
302
- // `page.find_elements` already carries the same information — a caller that jumped
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})`);
306
- throw new Error(`"${safePage}" is not a published page. Available: ${safePages.length > 0 ? safePages.join(', ') : '(none)'}`);
307
- }
308
- const before = await summarizePage(driver);
309
- await driver.navigate(page);
310
- if (driver.settle !== undefined)
311
- await driver.settle();
312
- const after = await summarizePage(driver);
313
- return { navigatedTo: page, before, after };
314
- }
315
- /* c8 ignore next 2 -- unreachable: the id was matched against PAGE_CAPABILITIES above. */
316
- default:
317
- throw new Error(`unhandled page capability: ${capabilityId}`);
318
- }
319
- }
320
- //# sourceMappingURL=page-executor.js.map