@classic-homes/page-agent-core 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -2
- package/dist/index.d.ts +456 -0
- package/dist/index.js +1136 -0
- package/dist/styles.css +295 -0
- package/package.json +50 -4
package/README.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @classic-homes/page-agent-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Framework-agnostic page layer for an in-app AI agent: it lets the agent
|
|
4
|
+
**point at things on the host page** (outline, spotlight, a labelled ghost
|
|
5
|
+
cursor, scroll into view, switch a tab, go to another page) without ever
|
|
6
|
+
running model-generated selectors, HTML or code.
|
|
7
|
+
|
|
8
|
+
Most apps install the framework binding instead (it draws the effects):
|
|
9
|
+
`@classic-homes/page-agent-svelte`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @classic-homes/page-agent-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
@import '@classic-homes/page-agent-core/styles.css';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No runtime dependencies. ESM only (the registry, config and effect store are
|
|
20
|
+
module singletons). Safe to import during SSR.
|
|
21
|
+
|
|
22
|
+
## What is in here
|
|
23
|
+
|
|
24
|
+
- **Registry**: `registerAgentTarget({ el, id, label, kind })` →
|
|
25
|
+
`{ update, destroy }`, `getAgentTargets`, batched `onAgentTargetsChange`,
|
|
26
|
+
and `scanAgentAttributes` for markup targets (`data-agent-id`,
|
|
27
|
+
`data-agent-label`, `data-agent-kind`, `data-agent-description`, plus the
|
|
28
|
+
host's alias attributes such as `data-tour`).
|
|
29
|
+
- **Resolution**: `resolveTarget({ id } | { text, within? })`. By id
|
|
30
|
+
(registry, then markup), or by visible text (exact, then case-insensitive;
|
|
31
|
+
the smallest match, promoted to the button/tab/link carrying the same
|
|
32
|
+
text). Several matches return `ambiguous` with up to 8 candidates rather
|
|
33
|
+
than guessing.
|
|
34
|
+
- **Snapshot**: `listTargets()` (max 200, `{ id, label, kind, visible,
|
|
35
|
+
inViewport }`), `listTabs()` and `readPageText({ maxChars,
|
|
36
|
+
ignoreSelector })`.
|
|
37
|
+
- **Effects**: `highlight(ref, { style: 'outline' | 'spotlight', label,
|
|
38
|
+
durationMs })`, `point(ref, { label, durationMs })`, `scrollToTarget(ref)`,
|
|
39
|
+
`clearEffects()`, `getEffects()` / `onEffectsChange()` (safe for
|
|
40
|
+
`useSyncExternalStore`) and `rectOf(effect)` for drawing. Effects expire
|
|
41
|
+
(default 8 s, max 60 s); at most 8 at once, one pointer and one spotlight.
|
|
42
|
+
`attachPageAgent()` wires Escape and `popstate` to clear; call
|
|
43
|
+
`notifyNavigated()` after SPA navigation.
|
|
44
|
+
- **Executor**: `configurePageAgent({ navigate, aliasAttributes,
|
|
45
|
+
ignoreSelector })` and `executePageCall(call)`, the single entry point for
|
|
46
|
+
the model's tool calls. `PAGE_TOOL_NAMES` and `PAGE_TOOLS` (plain-JSON
|
|
47
|
+
input shapes) let the agent server mirror the contract exactly.
|
|
48
|
+
- **Geometry**: `unionRects`, `inflate`, `clampToViewport`, `placeLabel`,
|
|
49
|
+
`pointerTarget` for the UI layer.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {
|
|
53
|
+
attachPageAgent,
|
|
54
|
+
configurePageAgent,
|
|
55
|
+
executePageCall,
|
|
56
|
+
} from '@classic-homes/page-agent-core';
|
|
57
|
+
import { goto } from '$app/navigation';
|
|
58
|
+
|
|
59
|
+
configurePageAgent({ navigate: (path) => goto(path), aliasAttributes: ['data-tour'] });
|
|
60
|
+
const detach = attachPageAgent();
|
|
61
|
+
|
|
62
|
+
// From the chat's client-call hook:
|
|
63
|
+
const result = await executePageCall({
|
|
64
|
+
tool: 'highlight',
|
|
65
|
+
input: { target: { id: 'closing-date' } },
|
|
66
|
+
});
|
|
67
|
+
// → { status: 'ok', output: { effectId, target: { id, label }, style, scrolled, inViewport } }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<section data-agent-id="closing-date" data-agent-label="Closing date">…</section>
|
|
72
|
+
<div data-agent-ignore><!-- never read, never pointed at --></div>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Tools
|
|
76
|
+
|
|
77
|
+
| Tool | Input | Does |
|
|
78
|
+
| ------------------ | ----------------------------------------- | --------------------------------------------------------- |
|
|
79
|
+
| `list_targets` | `{ kind?, limit? }` | Named targets and tabs on the page |
|
|
80
|
+
| `highlight` | `{ target, style?, label?, durationMs? }` | Outline or spotlight, scrolling into view first if needed |
|
|
81
|
+
| `point` | `{ target, label?, durationMs? }` | Moves the ghost cursor to the target |
|
|
82
|
+
| `scroll_to` | `{ target }` | Scrolls into view (no-op if already fully visible) |
|
|
83
|
+
| `clear_highlights` | `{}` | Removes every effect |
|
|
84
|
+
| `select_tab` | `{ target }` | Clicks a `role="tab"`, `ok` once `aria-selected="true"` |
|
|
85
|
+
| `navigate` | `{ path }` | Calls the host's `navigate` with a same-origin path |
|
|
86
|
+
|
|
87
|
+
`target` is `{ id }` or `{ text, within? }`. Results are `{ status, message?,
|
|
88
|
+
output? }` with `status` one of `ok`, `not_found`, `ambiguous`, `hidden`,
|
|
89
|
+
`refused`, `error`.
|
|
90
|
+
|
|
91
|
+
## Security
|
|
92
|
+
|
|
93
|
+
`executePageCall` treats its input as untrusted:
|
|
94
|
+
|
|
95
|
+
- Every field is checked by hand (type, length, enum, range); unknown tools,
|
|
96
|
+
unknown keys and malformed input return `refused`. It never throws.
|
|
97
|
+
- Targets are ids (compared verbatim with attribute values) or visible
|
|
98
|
+
text. No CSS selectors, HTML or code are accepted.
|
|
99
|
+
- Nothing is clicked except a non-disabled `role="tab"` element.
|
|
100
|
+
- `navigate` accepts only paths starting with a single `/` (no `//`,
|
|
101
|
+
backslashes, schemes, whitespace or control characters, also after
|
|
102
|
+
decoding) and goes through the host's handler; without one it is refused.
|
|
103
|
+
- `ok` is reported only once the effect is in place (the effect is in the
|
|
104
|
+
store, the tab is selected, the host's navigation resolved).
|
|
105
|
+
- `[data-agent-ignore]`, the host `ignoreSelector` and the layer's own UI
|
|
106
|
+
(`.chpa`) are invisible to resolution, snapshots and `readPageText`, which
|
|
107
|
+
also never reads password, hidden, card or one-time-code field values.
|
|
108
|
+
Treat page text as untrusted data on the server.
|
|
109
|
+
|
|
110
|
+
## Styles
|
|
111
|
+
|
|
112
|
+
`styles.css` holds the `.chpa-*` classes the bindings render: the
|
|
113
|
+
click-through layer (`.chpa-layer`), outlines (`.chpa-outline` +
|
|
114
|
+
`.chpa-tag`), the spotlight (`.chpa-spotlight`, dims with a spread shadow and
|
|
115
|
+
keeps the page clickable), the ghost cursor (`.chpa-pointer`,
|
|
116
|
+
`.chpa-pointer-icon`, `.chpa-pointer-label`), the "Agent is pointing ·
|
|
117
|
+
Dismiss" chip (`.chpa-chip`, `.chpa-chip-text`, `.chpa-chip-btn`, 44 px
|
|
118
|
+
target) and a live region (`.chpa-sr`). Override `--chpa-accent` (default
|
|
119
|
+
`#6d28d9`) and `--chpa-z` (default `2147482000`, below the Access
|
|
120
|
+
Inspector). Dark mode follows `.dark` / `[data-theme='dark']`; reduced
|
|
121
|
+
motion drops the glide and pulse.
|
|
122
|
+
|
|
123
|
+
## Testing
|
|
124
|
+
|
|
125
|
+
jsdom has no layout, so pass `configurePageAgent({ measure })` to supply
|
|
126
|
+
element boxes. The style rules (`hidden`, `display: none`, `visibility`,
|
|
127
|
+
`opacity: 0`) still run for real.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `@classic-homes/page-agent-core`.
|
|
3
|
+
*
|
|
4
|
+
* Everything the model can send arrives as a {@link PageCall} and is
|
|
5
|
+
* validated by hand in `executePageCall` before any of it touches the DOM.
|
|
6
|
+
* Nothing in a call is ever used as a CSS selector, HTML or code.
|
|
7
|
+
*/
|
|
8
|
+
/** What a target is, so the model can tell a section from a button. */
|
|
9
|
+
type AgentTargetKind = 'region' | 'tab' | 'field' | 'action';
|
|
10
|
+
/** A named thing on the page the agent may point at. */
|
|
11
|
+
interface AgentTarget {
|
|
12
|
+
el: Element;
|
|
13
|
+
/** Stable id the model uses, e.g. `closing-date`. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Human label, e.g. "Closing date". */
|
|
16
|
+
label: string;
|
|
17
|
+
kind: AgentTargetKind;
|
|
18
|
+
/** Optional extra hint for the model. */
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* How a call names its target: a registered/attribute id, or visible text
|
|
23
|
+
* (optionally scoped to inside another target, by id). Never a selector.
|
|
24
|
+
*/
|
|
25
|
+
type TargetRef = {
|
|
26
|
+
id: string;
|
|
27
|
+
} | {
|
|
28
|
+
text: string;
|
|
29
|
+
within?: string;
|
|
30
|
+
};
|
|
31
|
+
type HighlightStyle = 'outline' | 'spotlight';
|
|
32
|
+
type EffectKind = 'outline' | 'spotlight' | 'pointer';
|
|
33
|
+
/** One visual effect on the page (an outline, the spotlight or the ghost cursor). */
|
|
34
|
+
interface PageEffect {
|
|
35
|
+
/** Unique per effect, e.g. `chpa-3`. */
|
|
36
|
+
id: string;
|
|
37
|
+
kind: EffectKind;
|
|
38
|
+
el: Element;
|
|
39
|
+
/** Plain-text caption from the call (capped, control characters removed). */
|
|
40
|
+
label?: string;
|
|
41
|
+
/** Label of the target itself, for announcements ("Closing date"). */
|
|
42
|
+
targetLabel: string;
|
|
43
|
+
/** Target id when the target has one. */
|
|
44
|
+
targetId?: string;
|
|
45
|
+
createdAt: number;
|
|
46
|
+
expiresAt: number;
|
|
47
|
+
}
|
|
48
|
+
interface HighlightOptions {
|
|
49
|
+
style?: HighlightStyle;
|
|
50
|
+
label?: string;
|
|
51
|
+
/** Default 8000, clamped to 1000…60000. */
|
|
52
|
+
durationMs?: number;
|
|
53
|
+
}
|
|
54
|
+
interface PointOptions {
|
|
55
|
+
label?: string;
|
|
56
|
+
/** Default 8000, clamped to 1000…60000. */
|
|
57
|
+
durationMs?: number;
|
|
58
|
+
}
|
|
59
|
+
interface ListTargetsInput {
|
|
60
|
+
kind?: AgentTargetKind;
|
|
61
|
+
/** Default 200 (the maximum). */
|
|
62
|
+
limit?: number;
|
|
63
|
+
}
|
|
64
|
+
interface HighlightInput {
|
|
65
|
+
target: TargetRef;
|
|
66
|
+
style?: HighlightStyle;
|
|
67
|
+
label?: string;
|
|
68
|
+
durationMs?: number;
|
|
69
|
+
}
|
|
70
|
+
interface PointInput {
|
|
71
|
+
target: TargetRef;
|
|
72
|
+
label?: string;
|
|
73
|
+
durationMs?: number;
|
|
74
|
+
}
|
|
75
|
+
interface ScrollToInput {
|
|
76
|
+
target: TargetRef;
|
|
77
|
+
}
|
|
78
|
+
interface SelectTabInput {
|
|
79
|
+
target: TargetRef;
|
|
80
|
+
}
|
|
81
|
+
interface NavigateInput {
|
|
82
|
+
/** Same-origin app path: starts with a single `/`, e.g. `/homes/123?tab=docs`. */
|
|
83
|
+
path: string;
|
|
84
|
+
}
|
|
85
|
+
/** A tool call from the model, discriminated on `tool`. */
|
|
86
|
+
type PageCall = {
|
|
87
|
+
tool: 'list_targets';
|
|
88
|
+
input?: ListTargetsInput;
|
|
89
|
+
} | {
|
|
90
|
+
tool: 'highlight';
|
|
91
|
+
input: HighlightInput;
|
|
92
|
+
} | {
|
|
93
|
+
tool: 'point';
|
|
94
|
+
input: PointInput;
|
|
95
|
+
} | {
|
|
96
|
+
tool: 'scroll_to';
|
|
97
|
+
input: ScrollToInput;
|
|
98
|
+
} | {
|
|
99
|
+
tool: 'clear_highlights';
|
|
100
|
+
input?: Record<string, never>;
|
|
101
|
+
} | {
|
|
102
|
+
tool: 'select_tab';
|
|
103
|
+
input: SelectTabInput;
|
|
104
|
+
} | {
|
|
105
|
+
tool: 'navigate';
|
|
106
|
+
input: NavigateInput;
|
|
107
|
+
};
|
|
108
|
+
type PageToolName = PageCall['tool'];
|
|
109
|
+
type PageCallStatus = 'ok' | 'not_found' | 'ambiguous' | 'hidden' | 'refused' | 'error';
|
|
110
|
+
/** What goes back to the model. `ok` only once the effect is confirmed. */
|
|
111
|
+
interface PageCallResult {
|
|
112
|
+
status: PageCallStatus;
|
|
113
|
+
/** Short plain-text explanation, safe to show the model. */
|
|
114
|
+
message?: string;
|
|
115
|
+
/** Tool-specific data, JSON-serialisable. */
|
|
116
|
+
output?: unknown;
|
|
117
|
+
}
|
|
118
|
+
interface TargetSummary {
|
|
119
|
+
id: string;
|
|
120
|
+
label: string;
|
|
121
|
+
kind: AgentTargetKind;
|
|
122
|
+
visible: boolean;
|
|
123
|
+
inViewport: boolean;
|
|
124
|
+
}
|
|
125
|
+
interface TabSummary {
|
|
126
|
+
/** Present when the tab has a registered or attribute id. */
|
|
127
|
+
id?: string;
|
|
128
|
+
label: string;
|
|
129
|
+
selected: boolean;
|
|
130
|
+
disabled: boolean;
|
|
131
|
+
visible: boolean;
|
|
132
|
+
}
|
|
133
|
+
interface Candidate {
|
|
134
|
+
id?: string;
|
|
135
|
+
label: string;
|
|
136
|
+
}
|
|
137
|
+
interface ListTargetsOutput {
|
|
138
|
+
targets: TargetSummary[];
|
|
139
|
+
tabs: TabSummary[];
|
|
140
|
+
truncated: boolean;
|
|
141
|
+
}
|
|
142
|
+
/** Host router hook. Resolve (or return true / nothing) once navigation finished. */
|
|
143
|
+
type NavigateHandler = (path: string) => void | boolean | Promise<void | boolean>;
|
|
144
|
+
/**
|
|
145
|
+
* Reads an element's layout box. Return null when it has none. The default
|
|
146
|
+
* uses `getClientRects()` / `getBoundingClientRect()`; tests (jsdom has no
|
|
147
|
+
* layout) and unusual hosts can swap it. Style checks (`display`,
|
|
148
|
+
* `visibility`, `hidden`, `inert`) always run regardless.
|
|
149
|
+
*/
|
|
150
|
+
type MeasureElement = (el: Element) => DOMRect | null;
|
|
151
|
+
interface PageAgentConfig {
|
|
152
|
+
/** Called by the `navigate` tool. Without it, `navigate` is refused. */
|
|
153
|
+
navigate?: NavigateHandler;
|
|
154
|
+
/** Extra id attributes scanned as targets, e.g. `['data-tour']`. Must match `data-[a-z0-9-]+`. */
|
|
155
|
+
aliasAttributes?: string[];
|
|
156
|
+
/** Host CSS selector for regions the agent must never see or point at (in addition to `[data-agent-ignore]`). */
|
|
157
|
+
ignoreSelector?: string;
|
|
158
|
+
/** Layout reader; see {@link MeasureElement}. */
|
|
159
|
+
measure?: MeasureElement;
|
|
160
|
+
/** How long `select_tab` waits for `aria-selected="true"`. Default 600 ms, max 5000. */
|
|
161
|
+
tabSettleMs?: number;
|
|
162
|
+
}
|
|
163
|
+
interface ResolvedPageAgentConfig {
|
|
164
|
+
navigate: NavigateHandler | null;
|
|
165
|
+
aliasAttributes: string[];
|
|
166
|
+
ignoreSelector: string | null;
|
|
167
|
+
measure: MeasureElement | null;
|
|
168
|
+
tabSettleMs: number;
|
|
169
|
+
}
|
|
170
|
+
type ResolveResult = {
|
|
171
|
+
status: 'ok';
|
|
172
|
+
el: Element;
|
|
173
|
+
id?: string;
|
|
174
|
+
label: string;
|
|
175
|
+
} | {
|
|
176
|
+
status: 'not_found';
|
|
177
|
+
message: string;
|
|
178
|
+
} | {
|
|
179
|
+
status: 'hidden';
|
|
180
|
+
message: string;
|
|
181
|
+
} | {
|
|
182
|
+
status: 'ambiguous';
|
|
183
|
+
message: string;
|
|
184
|
+
candidates: Candidate[];
|
|
185
|
+
};
|
|
186
|
+
type ResolveFailure = Exclude<ResolveResult, {
|
|
187
|
+
status: 'ok';
|
|
188
|
+
}>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Configure the page agent once, at app start. Later calls merge into the
|
|
192
|
+
* current config (pass `navigate: undefined` to keep the handler). Alias
|
|
193
|
+
* attributes that are not plain `data-*` names are dropped.
|
|
194
|
+
*/
|
|
195
|
+
declare function configurePageAgent(next: PageAgentConfig): ResolvedPageAgentConfig;
|
|
196
|
+
declare function getPageAgentConfig(): ResolvedPageAgentConfig;
|
|
197
|
+
|
|
198
|
+
declare const EFFECT_DEFAULT_MS = 8000;
|
|
199
|
+
declare const EFFECT_MIN_MS = 1000;
|
|
200
|
+
declare const EFFECT_MAX_MS = 60000;
|
|
201
|
+
/** Concurrent effects; the oldest is dropped beyond this. */
|
|
202
|
+
declare const EFFECTS_MAX = 8;
|
|
203
|
+
/** Longest caption on an effect. */
|
|
204
|
+
declare const EFFECT_LABEL_MAX = 80;
|
|
205
|
+
type EffectOutcome = {
|
|
206
|
+
status: 'ok';
|
|
207
|
+
effect: PageEffect;
|
|
208
|
+
} | ResolveFailure;
|
|
209
|
+
type ScrollOutcome = {
|
|
210
|
+
status: 'ok';
|
|
211
|
+
el: Element;
|
|
212
|
+
scrolled: boolean;
|
|
213
|
+
} | ResolveFailure;
|
|
214
|
+
/** Default 8 s; anything else clamped to 1…60 s. Non-numbers get the default. */
|
|
215
|
+
declare function clampDuration(ms: number | undefined): number;
|
|
216
|
+
/** Outline (default) or spotlight a target. Does not scroll; see {@link scrollToTarget}. */
|
|
217
|
+
declare function highlight(ref: TargetRef | Element, options?: HighlightOptions): EffectOutcome;
|
|
218
|
+
/** Move the ghost cursor to a target (replaces the previous pointer). */
|
|
219
|
+
declare function point(ref: TargetRef | Element, options?: PointOptions): EffectOutcome;
|
|
220
|
+
/**
|
|
221
|
+
* Bring a target into view, scrolling each scrollable ancestor and then the
|
|
222
|
+
* window only as far as needed. No-op when it is already fully visible.
|
|
223
|
+
* Smooth unless the user prefers reduced motion.
|
|
224
|
+
*/
|
|
225
|
+
declare function scrollToTarget(ref: TargetRef | Element): ScrollOutcome;
|
|
226
|
+
/** Drop every effect now. */
|
|
227
|
+
declare function clearEffects(): void;
|
|
228
|
+
/**
|
|
229
|
+
* Current effects (stable array between changes, safe for
|
|
230
|
+
* `useSyncExternalStore`). Effects whose element left the document are
|
|
231
|
+
* dropped here, with a change notification.
|
|
232
|
+
*/
|
|
233
|
+
declare function getEffects(): readonly PageEffect[];
|
|
234
|
+
/** Called (batched, once per microtask) whenever effects change. */
|
|
235
|
+
declare function onEffectsChange(listener: () => void): () => void;
|
|
236
|
+
/** Viewport rect of an effect's element, or null when it has no layout box (skip drawing). */
|
|
237
|
+
declare function rectOf(effect: PageEffect): DOMRect | null;
|
|
238
|
+
/** Tell the agent layer the route changed (SPA navigation): clears every effect. */
|
|
239
|
+
declare function notifyNavigated(): void;
|
|
240
|
+
/**
|
|
241
|
+
* Wire Escape-to-clear and clear-on-history-navigation (`popstate`). Call
|
|
242
|
+
* once from the app shell; returns the detach function. Browser-only, no-op
|
|
243
|
+
* on the server. Escape only acts while something is shown, and never stops
|
|
244
|
+
* the event, so dialogs still get it.
|
|
245
|
+
*/
|
|
246
|
+
declare function attachPageAgent(): () => void;
|
|
247
|
+
/**
|
|
248
|
+
* Scroll `el` into view through its scroll containers. Unlike
|
|
249
|
+
* `scrollIntoView`, never scrolls `overflow: hidden` clippers (carousels,
|
|
250
|
+
* clamp boxes) and does nothing when the element is already fully in view.
|
|
251
|
+
* Returns whether anything scrolled.
|
|
252
|
+
*/
|
|
253
|
+
declare function scrollIntoContainers(el: Element): boolean;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* A same-origin app path, normalised, or null. Accepts only paths starting
|
|
257
|
+
* with exactly one `/`; refuses `//host`, backslashes (raw or `%5C`),
|
|
258
|
+
* encoded leading slashes, schemes (`javascript:`, `https:`), whitespace,
|
|
259
|
+
* control characters and malformed escapes.
|
|
260
|
+
*/
|
|
261
|
+
declare function parseAppPath(path: unknown): string | null;
|
|
262
|
+
/**
|
|
263
|
+
* Run one tool call from the model. Accepts `unknown` on purpose: anything
|
|
264
|
+
* that is not `{ tool, input }` with a known tool and a valid input comes
|
|
265
|
+
* back `refused`. Never throws.
|
|
266
|
+
*/
|
|
267
|
+
declare function executePageCall(call: unknown): Promise<PageCallResult>;
|
|
268
|
+
|
|
269
|
+
/** Longest label taken from markup or registration. */
|
|
270
|
+
declare const TARGET_LABEL_MAX = 80;
|
|
271
|
+
interface AgentTargetHandle {
|
|
272
|
+
update(next: Partial<Omit<AgentTarget, 'el'>>): void;
|
|
273
|
+
destroy(): void;
|
|
274
|
+
}
|
|
275
|
+
declare function registerAgentTarget(entry: AgentTarget): AgentTargetHandle;
|
|
276
|
+
/** Registered targets whose element is still in the document. */
|
|
277
|
+
declare function getAgentTargets(): AgentTarget[];
|
|
278
|
+
/** Called (batched, once per microtask) whenever a registration changes. */
|
|
279
|
+
declare function onAgentTargetsChange(listener: () => void): () => void;
|
|
280
|
+
/**
|
|
281
|
+
* Targets marked in plain markup, for pages that cannot use the framework
|
|
282
|
+
* helpers, plus the host's alias attributes (`configurePageAgent({
|
|
283
|
+
* aliasAttributes: ['data-tour'] })`):
|
|
284
|
+
*
|
|
285
|
+
* ```html
|
|
286
|
+
* <section data-agent-id="closing" data-agent-label="Closing date" data-agent-kind="region">…</section>
|
|
287
|
+
* <button data-tour="export-button">Export</button>
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* Ignored subtrees are not filtered here; resolution and snapshots do that.
|
|
291
|
+
*/
|
|
292
|
+
declare function scanAgentAttributes(root?: ParentNode): AgentTarget[];
|
|
293
|
+
|
|
294
|
+
/** Longest id a call may use. */
|
|
295
|
+
declare const TARGET_ID_MAX = 128;
|
|
296
|
+
/** Longest text a call may search for. */
|
|
297
|
+
declare const TARGET_TEXT_MAX = 200;
|
|
298
|
+
/** Most candidates returned with `ambiguous`. */
|
|
299
|
+
declare const CANDIDATES_MAX = 8;
|
|
300
|
+
interface ResolveOptions {
|
|
301
|
+
/**
|
|
302
|
+
* Only `role="tab"` elements count (for `select_tab`): a text match is
|
|
303
|
+
* promoted to its enclosing tab, and matches outside any tab are dropped.
|
|
304
|
+
*/
|
|
305
|
+
tabsOnly?: boolean;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Find the element a call means. Never guesses: several equally good text
|
|
309
|
+
* matches return `ambiguous` with a capped candidate list (labels and ids
|
|
310
|
+
* only, never markup). Ignored regions and the layer's own UI are invisible
|
|
311
|
+
* to it; elements without a layout box resolve as `hidden`.
|
|
312
|
+
*/
|
|
313
|
+
declare function resolveTarget(ref: TargetRef, options?: ResolveOptions): ResolveResult;
|
|
314
|
+
/** Id of an element: its registration, else its markup attribute. */
|
|
315
|
+
declare function targetIdOf(el: Element): string | undefined;
|
|
316
|
+
|
|
317
|
+
/** Most targets / tabs `listTargets()` / `listTabs()` return. */
|
|
318
|
+
declare const LIST_MAX = 200;
|
|
319
|
+
/** Default and maximum `readPageText` budget. */
|
|
320
|
+
declare const PAGE_TEXT_DEFAULT = 20000;
|
|
321
|
+
declare const PAGE_TEXT_MAX = 100000;
|
|
322
|
+
declare const TRUNCATION_MARKER = "\n[\u2026truncated]";
|
|
323
|
+
/**
|
|
324
|
+
* Named targets on the page (registered and markup), ignored regions
|
|
325
|
+
* removed, at most `limit` (default and max 200). Visible ones first.
|
|
326
|
+
*/
|
|
327
|
+
declare function listTargets(options?: {
|
|
328
|
+
kind?: AgentTargetKind;
|
|
329
|
+
limit?: number;
|
|
330
|
+
}): TargetSummary[];
|
|
331
|
+
/** Every `[role="tab"]` outside ignored regions, with label and state. */
|
|
332
|
+
declare function listTabs(options?: {
|
|
333
|
+
limit?: number;
|
|
334
|
+
}): TabSummary[];
|
|
335
|
+
interface ReadPageTextOptions {
|
|
336
|
+
/** Default 20000, max 100000. */
|
|
337
|
+
maxChars?: number;
|
|
338
|
+
/** Extra host selector to skip, on top of `[data-agent-ignore]`, `.chpa` and the configured `ignoreSelector`. */
|
|
339
|
+
ignoreSelector?: string;
|
|
340
|
+
/** Default `document.body`. */
|
|
341
|
+
root?: Element;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Visible page text for the model (treat it as untrusted data on the
|
|
345
|
+
* server). Skips ignored regions, the layer's own UI, script/style/template/
|
|
346
|
+
* noscript, hidden elements, and secret inputs (passwords, card fields,
|
|
347
|
+
* one-time codes, hidden inputs). Form values of other text inputs are
|
|
348
|
+
* included in brackets. Truncated with {@link TRUNCATION_MARKER}.
|
|
349
|
+
*/
|
|
350
|
+
declare function readPageText(options?: ReadPageTextOptions): string;
|
|
351
|
+
|
|
352
|
+
declare const PAGE_TOOL_NAMES: readonly ["list_targets", "highlight", "point", "scroll_to", "clear_highlights", "select_tab", "navigate"];
|
|
353
|
+
/** Longest path `navigate` accepts. */
|
|
354
|
+
declare const NAVIGATE_PATH_MAX = 2048;
|
|
355
|
+
/** Longest raw `label` a call may send (it is then cut to {@link EFFECT_LABEL_MAX}). */
|
|
356
|
+
declare const CALL_LABEL_MAX = 200;
|
|
357
|
+
/** JSON-schema-like shape, enough to generate a zod schema or a tool definition. */
|
|
358
|
+
interface JsonShape {
|
|
359
|
+
type?: 'object' | 'string' | 'number' | 'integer' | 'boolean';
|
|
360
|
+
description?: string;
|
|
361
|
+
properties?: Record<string, JsonShape>;
|
|
362
|
+
required?: readonly string[];
|
|
363
|
+
additionalProperties?: boolean;
|
|
364
|
+
enum?: readonly string[];
|
|
365
|
+
oneOf?: readonly JsonShape[];
|
|
366
|
+
minLength?: number;
|
|
367
|
+
maxLength?: number;
|
|
368
|
+
minimum?: number;
|
|
369
|
+
maximum?: number;
|
|
370
|
+
default?: unknown;
|
|
371
|
+
pattern?: string;
|
|
372
|
+
}
|
|
373
|
+
interface PageToolDescription {
|
|
374
|
+
name: PageToolName;
|
|
375
|
+
description: string;
|
|
376
|
+
input: JsonShape;
|
|
377
|
+
}
|
|
378
|
+
declare const PAGE_TOOLS: readonly PageToolDescription[];
|
|
379
|
+
|
|
380
|
+
/** The layer's own UI. Nothing inside it is ever a target or page text. */
|
|
381
|
+
declare const LAYER_CLASS = "chpa";
|
|
382
|
+
/**
|
|
383
|
+
* Per-call memo of "this element and its ancestors are not hidden by
|
|
384
|
+
* `hidden`, `display: none` or `opacity: 0`". Snapshots check up to 200
|
|
385
|
+
* targets that share most of their ancestors; without it every target
|
|
386
|
+
* re-reads the computed style of the whole chain.
|
|
387
|
+
*/
|
|
388
|
+
type RenderCache = Map<Element, boolean>;
|
|
389
|
+
/**
|
|
390
|
+
* Rendered and perceivable: connected, no `hidden` / `display: none` /
|
|
391
|
+
* `opacity: 0` on it or an ancestor, not `visibility: hidden`, and with a
|
|
392
|
+
* layout box. The style walk runs even when `measure` is swapped, so jsdom
|
|
393
|
+
* tests exercise the same rules as browsers. Pass one `cache` for a batch of
|
|
394
|
+
* checks made against an unchanging DOM.
|
|
395
|
+
*/
|
|
396
|
+
declare function isRendered(el: Element, cache?: RenderCache): boolean;
|
|
397
|
+
interface ViewportSize {
|
|
398
|
+
width: number;
|
|
399
|
+
height: number;
|
|
400
|
+
}
|
|
401
|
+
declare function viewportSize(): ViewportSize;
|
|
402
|
+
declare function prefersReducedMotion(): boolean;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Pure rect maths for drawing effects. No DOM access: callers pass the
|
|
406
|
+
* viewport size (see `viewportSize()` on the UI side).
|
|
407
|
+
*/
|
|
408
|
+
interface Box {
|
|
409
|
+
top: number;
|
|
410
|
+
left: number;
|
|
411
|
+
width: number;
|
|
412
|
+
height: number;
|
|
413
|
+
}
|
|
414
|
+
interface Size {
|
|
415
|
+
width: number;
|
|
416
|
+
height: number;
|
|
417
|
+
}
|
|
418
|
+
interface Point {
|
|
419
|
+
top: number;
|
|
420
|
+
left: number;
|
|
421
|
+
}
|
|
422
|
+
type RectLike = Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>;
|
|
423
|
+
/** Smallest box covering every rect; null for none. */
|
|
424
|
+
declare function unionRects(rects: readonly RectLike[]): Box | null;
|
|
425
|
+
/** Grow a box by `pad` on every side (outline breathing room). */
|
|
426
|
+
declare function inflate(rect: RectLike, pad: number): Box;
|
|
427
|
+
/** The part of a box inside the viewport (minus `margin`); null when none is. */
|
|
428
|
+
declare function clampToViewport(rect: RectLike, viewport: Size, margin?: number): Box | null;
|
|
429
|
+
/** Wholly inside the viewport. */
|
|
430
|
+
declare function isFullyInViewport(rect: RectLike, viewport: Size): boolean;
|
|
431
|
+
/** At least partly inside the viewport. */
|
|
432
|
+
declare function intersectsViewport(rect: RectLike, viewport: Size): boolean;
|
|
433
|
+
interface LabelPlacement extends Point {
|
|
434
|
+
placement: 'above' | 'below';
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Where to put a caption of `size` next to a target rect: above its
|
|
438
|
+
* top-left corner, or below when there is no room above (or `preferBelow`),
|
|
439
|
+
* then clamped into the viewport. Same rules as feedback-core's selection
|
|
440
|
+
* button, re-implemented to keep this package dependency-free.
|
|
441
|
+
*/
|
|
442
|
+
declare function placeLabel(rect: RectLike, size: Size, viewport: Size, opts?: {
|
|
443
|
+
gap?: number;
|
|
444
|
+
preferBelow?: boolean;
|
|
445
|
+
}): LabelPlacement;
|
|
446
|
+
/**
|
|
447
|
+
* Where the ghost cursor's tip should rest: just inside the target's
|
|
448
|
+
* bottom-right quarter, clamped into the viewport so it never points off
|
|
449
|
+
* screen.
|
|
450
|
+
*/
|
|
451
|
+
declare function pointerTarget(rect: RectLike, viewport: Size, margin?: number): Point;
|
|
452
|
+
|
|
453
|
+
/** Back to a clean slate: default config, no effects. Registrations are kept (their owners destroy them). */
|
|
454
|
+
declare function resetPageAgent(): void;
|
|
455
|
+
|
|
456
|
+
export { type AgentTarget, type AgentTargetHandle, type AgentTargetKind, type Box, CALL_LABEL_MAX, CANDIDATES_MAX, type Candidate, EFFECTS_MAX, EFFECT_DEFAULT_MS, EFFECT_LABEL_MAX, EFFECT_MAX_MS, EFFECT_MIN_MS, type EffectKind, type EffectOutcome, type HighlightInput, type HighlightOptions, type HighlightStyle, type JsonShape, LAYER_CLASS, LIST_MAX, type LabelPlacement, type ListTargetsInput, type ListTargetsOutput, type MeasureElement, NAVIGATE_PATH_MAX, type NavigateHandler, type NavigateInput, PAGE_TEXT_DEFAULT, PAGE_TEXT_MAX, PAGE_TOOLS, PAGE_TOOL_NAMES, type PageAgentConfig, type PageCall, type PageCallResult, type PageCallStatus, type PageEffect, type PageToolDescription, type PageToolName, type Point, type PointInput, type PointOptions, type ReadPageTextOptions, type RenderCache, type ResolveFailure, type ResolveOptions, type ResolveResult, type ResolvedPageAgentConfig, type ScrollOutcome, type ScrollToInput, type SelectTabInput, type Size, TARGET_ID_MAX, TARGET_LABEL_MAX, TARGET_TEXT_MAX, TRUNCATION_MARKER, type TabSummary, type TargetRef, type TargetSummary, type ViewportSize, attachPageAgent, clampDuration, clampToViewport, clearEffects, configurePageAgent, executePageCall, getAgentTargets, getEffects, getPageAgentConfig, highlight, inflate, intersectsViewport, isFullyInViewport, isRendered, listTabs, listTargets, notifyNavigated, onAgentTargetsChange, onEffectsChange, parseAppPath, placeLabel, point, pointerTarget, prefersReducedMotion, readPageText, rectOf, registerAgentTarget, resetPageAgent, resolveTarget, scanAgentAttributes, scrollIntoContainers, scrollToTarget, targetIdOf, unionRects, viewportSize };
|