@athenaintel/react 0.60.0 → 0.61.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.
Files changed (137) hide show
  1. package/README.md +225 -2
  2. package/dist/agent-runtime/src/client-tool-bridge.cjs +20 -2
  3. package/dist/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
  4. package/dist/agent-runtime/src/client-tool-bridge.d.ts +9 -4
  5. package/dist/agent-runtime/src/client-tool-bridge.js +20 -2
  6. package/dist/agent-runtime/src/client-tool-bridge.js.map +1 -1
  7. package/dist/agent-runtime/src/client-tools.cjs.map +1 -1
  8. package/dist/agent-runtime/src/client-tools.d.ts +9 -0
  9. package/dist/agent-runtime/src/client-tools.js.map +1 -1
  10. package/dist/agent-ui/AgentTarget.cjs +11 -0
  11. package/dist/agent-ui/AgentTarget.cjs.map +1 -0
  12. package/dist/agent-ui/AgentTarget.d.ts +17 -0
  13. package/dist/agent-ui/AgentTarget.js +11 -0
  14. package/dist/agent-ui/AgentTarget.js.map +1 -0
  15. package/dist/agent-ui/AgentUiLayer.cjs +387 -0
  16. package/dist/agent-ui/AgentUiLayer.cjs.map +1 -0
  17. package/dist/agent-ui/AgentUiLayer.d.ts +40 -0
  18. package/dist/agent-ui/AgentUiLayer.js +387 -0
  19. package/dist/agent-ui/AgentUiLayer.js.map +1 -0
  20. package/dist/agent-ui/contract.cjs +104 -0
  21. package/dist/agent-ui/contract.cjs.map +1 -0
  22. package/dist/agent-ui/contract.d.ts +113 -0
  23. package/dist/agent-ui/contract.js +104 -0
  24. package/dist/agent-ui/contract.js.map +1 -0
  25. package/dist/agent-ui/driver.cjs +335 -0
  26. package/dist/agent-ui/driver.cjs.map +1 -0
  27. package/dist/agent-ui/driver.d.ts +66 -0
  28. package/dist/agent-ui/driver.js +335 -0
  29. package/dist/agent-ui/driver.js.map +1 -0
  30. package/dist/agent-ui/index.cjs +33 -0
  31. package/dist/agent-ui/index.cjs.map +1 -0
  32. package/dist/agent-ui/index.d.ts +19 -0
  33. package/dist/agent-ui/index.js +33 -0
  34. package/dist/agent-ui/index.js.map +1 -0
  35. package/dist/agent-ui/pace.cjs +121 -0
  36. package/dist/agent-ui/pace.cjs.map +1 -0
  37. package/dist/agent-ui/pace.d.ts +66 -0
  38. package/dist/agent-ui/pace.js +121 -0
  39. package/dist/agent-ui/pace.js.map +1 -0
  40. package/dist/agent-ui/screen.cjs +471 -0
  41. package/dist/agent-ui/screen.cjs.map +1 -0
  42. package/dist/agent-ui/screen.d.ts +97 -0
  43. package/dist/agent-ui/screen.js +471 -0
  44. package/dist/agent-ui/screen.js.map +1 -0
  45. package/dist/agent-ui/secret-fields.cjs +50 -0
  46. package/dist/agent-ui/secret-fields.cjs.map +1 -0
  47. package/dist/agent-ui/secret-fields.d.ts +15 -0
  48. package/dist/agent-ui/secret-fields.js +50 -0
  49. package/dist/agent-ui/secret-fields.js.map +1 -0
  50. package/dist/agent-ui/stage.cjs +196 -0
  51. package/dist/agent-ui/stage.cjs.map +1 -0
  52. package/dist/agent-ui/stage.d.ts +110 -0
  53. package/dist/agent-ui/stage.js +196 -0
  54. package/dist/agent-ui/stage.js.map +1 -0
  55. package/dist/agent-ui/tools.cjs +448 -0
  56. package/dist/agent-ui/tools.cjs.map +1 -0
  57. package/dist/agent-ui/tools.d.ts +103 -0
  58. package/dist/agent-ui/tools.js +448 -0
  59. package/dist/agent-ui/tools.js.map +1 -0
  60. package/dist/agent-ui/use-agent-target.cjs +63 -0
  61. package/dist/agent-ui/use-agent-target.cjs.map +1 -0
  62. package/dist/agent-ui/use-agent-target.d.ts +27 -0
  63. package/dist/agent-ui/use-agent-target.js +63 -0
  64. package/dist/agent-ui/use-agent-target.js.map +1 -0
  65. package/dist/agent-ui/use-agent-ui.cjs +15 -0
  66. package/dist/agent-ui/use-agent-ui.cjs.map +1 -0
  67. package/dist/agent-ui/use-agent-ui.d.ts +24 -0
  68. package/dist/agent-ui/use-agent-ui.js +15 -0
  69. package/dist/agent-ui/use-agent-ui.js.map +1 -0
  70. package/dist/chat/AthenaChat.cjs +19 -4
  71. package/dist/chat/AthenaChat.cjs.map +1 -1
  72. package/dist/chat/AthenaChat.d.ts +13 -0
  73. package/dist/chat/AthenaChat.js +19 -4
  74. package/dist/chat/AthenaChat.js.map +1 -1
  75. package/dist/chat/StatewireClientToolBridge.cjs +74 -1
  76. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  77. package/dist/chat/StatewireClientToolBridge.d.ts +16 -1
  78. package/dist/chat/StatewireClientToolBridge.js +75 -2
  79. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  80. package/dist/chat/StatewireClientToolConsentCard.cjs +66 -0
  81. package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -0
  82. package/dist/chat/StatewireClientToolConsentCard.d.ts +25 -0
  83. package/dist/chat/StatewireClientToolConsentCard.js +66 -0
  84. package/dist/chat/StatewireClientToolConsentCard.js.map +1 -0
  85. package/dist/chat/chat-voice-controls.cjs +5 -2
  86. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  87. package/dist/chat/chat-voice-controls.d.ts +6 -1
  88. package/dist/chat/chat-voice-controls.js +6 -3
  89. package/dist/chat/chat-voice-controls.js.map +1 -1
  90. package/dist/chat/client-tool-write-consent.cjs +122 -0
  91. package/dist/chat/client-tool-write-consent.cjs.map +1 -0
  92. package/dist/chat/client-tool-write-consent.d.ts +79 -0
  93. package/dist/chat/client-tool-write-consent.js +122 -0
  94. package/dist/chat/client-tool-write-consent.js.map +1 -0
  95. package/dist/chat/statewire-client-tools.cjs +30 -5
  96. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  97. package/dist/chat/statewire-client-tools.d.ts +34 -2
  98. package/dist/chat/statewire-client-tools.js +30 -5
  99. package/dist/chat/statewire-client-tools.js.map +1 -1
  100. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +13 -0
  101. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
  102. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.d.ts +12 -0
  103. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +13 -0
  104. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
  105. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +2 -1
  106. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
  107. package/dist/chat-ui/mentions/suggestions/data/sources/index.js +3 -2
  108. package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
  109. package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs +2 -1
  110. package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs.map +1 -1
  111. package/dist/chat-ui/mentions/use-asset-icon-app-url.d.ts +2 -1
  112. package/dist/chat-ui/mentions/use-asset-icon-app-url.js +2 -1
  113. package/dist/chat-ui/mentions/use-asset-icon-app-url.js.map +1 -1
  114. package/dist/chat-ui/messages/message-components.cjs +1 -1
  115. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  116. package/dist/chat-ui/messages/message-components.d.ts +2 -2
  117. package/dist/chat-ui/messages/message-components.js +1 -1
  118. package/dist/chat-ui/messages/message-components.js.map +1 -1
  119. package/dist/chat-ui/statewire.cjs +4 -0
  120. package/dist/chat-ui/statewire.cjs.map +1 -1
  121. package/dist/chat-ui/statewire.d.ts +2 -0
  122. package/dist/chat-ui/statewire.js +4 -0
  123. package/dist/chat-ui/statewire.js.map +1 -1
  124. package/dist/index.cjs +12 -0
  125. package/dist/index.cjs.map +1 -1
  126. package/dist/index.d.ts +12 -2
  127. package/dist/index.js +13 -1
  128. package/dist/index.js.map +1 -1
  129. package/dist/provider/AthenaProvider.cjs +59 -11
  130. package/dist/provider/AthenaProvider.cjs.map +1 -1
  131. package/dist/provider/AthenaProvider.d.ts +25 -1
  132. package/dist/provider/AthenaProvider.js +55 -7
  133. package/dist/provider/AthenaProvider.js.map +1 -1
  134. package/dist/react/package.json.cjs +1 -1
  135. package/dist/react/package.json.js +1 -1
  136. package/dist/styles.css +1 -1
  137. package/package.json +6 -1
@@ -0,0 +1,471 @@
1
+ import { AGENT_ATTR, parseAgentTargetRef, agentTargetHandlers, isAgentTargetAction, agentTargetRef } from "./contract.js";
2
+ import { isSecretField } from "./secret-fields.js";
3
+ class AgentUiError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "AgentUiError";
7
+ }
8
+ }
9
+ const CONTROL_SELECTOR = 'input:not([type="hidden"]), textarea, select, [contenteditable=""], [contenteditable="true"]';
10
+ const MAX_LABEL_CHARS = 80;
11
+ const MAX_VALUE_CHARS = 200;
12
+ const MAX_DESCRIPTION_CHARS = 240;
13
+ const MAX_OPTIONS = 30;
14
+ const MAX_OPTION_CHARS = 60;
15
+ const MAX_REF_CHARS = 160;
16
+ const MAX_REGIONS = 40;
17
+ const tagOf = (element) => element.tagName.toUpperCase();
18
+ function clip(text, max) {
19
+ const collapsed = text.replace(/\s+/g, " ").trim();
20
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1)}…` : collapsed;
21
+ }
22
+ function inputType(element) {
23
+ return (element.getAttribute("type") ?? "text").toLowerCase();
24
+ }
25
+ function isEditable(element) {
26
+ const editable = element.getAttribute("contenteditable");
27
+ return editable === "" || editable === "true";
28
+ }
29
+ function controlOf(element) {
30
+ if (element.matches(CONTROL_SELECTOR)) return element;
31
+ const tag = tagOf(element);
32
+ if (tag === "BUTTON" || tag === "A") return null;
33
+ return element.querySelector(CONTROL_SELECTOR);
34
+ }
35
+ function roleOf(element, control) {
36
+ var _a, _b;
37
+ const explicit = (_a = element.getAttribute("role")) == null ? void 0 : _a.trim();
38
+ if (explicit) return explicit;
39
+ const subject = control ?? element;
40
+ const tag = tagOf(subject);
41
+ if (tag === "BUTTON" || tag === "SUMMARY") return "button";
42
+ if (tag === "A") return subject.hasAttribute("href") ? "link" : "element";
43
+ if (tag === "SELECT") return "combobox";
44
+ if (tag === "TEXTAREA") return "textbox";
45
+ if (tag === "INPUT") {
46
+ const type = inputType(subject);
47
+ if (type === "checkbox") return "checkbox";
48
+ if (type === "radio") return "radio";
49
+ if (type === "range") return "slider";
50
+ if (type === "button" || type === "submit" || type === "reset" || type === "image") return "button";
51
+ if (type === "search") return "searchbox";
52
+ return "textbox";
53
+ }
54
+ if (isEditable(subject)) return "textbox";
55
+ return ((_b = subject.getAttribute("role")) == null ? void 0 : _b.trim()) || "element";
56
+ }
57
+ function referencedText(element, attribute) {
58
+ const ids = (element.getAttribute(attribute) ?? "").split(/\s+/).filter(Boolean);
59
+ return ids.map((id) => {
60
+ const named = element.ownerDocument.getElementById(id);
61
+ return named ? shownText(named) : "";
62
+ }).join(" ").trim();
63
+ }
64
+ function labelsText(control) {
65
+ if (!control) return "";
66
+ const labels = control.labels;
67
+ if (!labels) return "";
68
+ return Array.from(labels, shownText).join(" ").trim();
69
+ }
70
+ function shownText(element) {
71
+ let text = "";
72
+ for (const node of Array.from(element.childNodes)) {
73
+ if (node.nodeType === 3) text += node.textContent ?? "";
74
+ else if (node.nodeType === 1) {
75
+ const child = node;
76
+ if (child.matches(CONTROL_SELECTOR) || tagOf(child) === "OPTION") continue;
77
+ if (child.hasAttribute(AGENT_ATTR.sensitive)) continue;
78
+ text += ` ${shownText(child)}`;
79
+ }
80
+ }
81
+ return text;
82
+ }
83
+ function labelOf({
84
+ element,
85
+ control,
86
+ sensitive,
87
+ fallback
88
+ }) {
89
+ const subject = control ?? element;
90
+ const candidates = [
91
+ element.getAttribute(AGENT_ATTR.label),
92
+ element.getAttribute("aria-label"),
93
+ subject.getAttribute("aria-label"),
94
+ referencedText(element, "aria-labelledby"),
95
+ referencedText(subject, "aria-labelledby"),
96
+ labelsText(control),
97
+ subject.getAttribute("placeholder"),
98
+ element.getAttribute("title"),
99
+ subject.getAttribute("alt"),
100
+ // A button drawn by an `<input>` shows its `value` as its face.
101
+ !sensitive && tagOf(subject) === "INPUT" && roleOf(element, control) === "button" ? subject.getAttribute("value") : null,
102
+ // A control's own text is its value, never its name.
103
+ sensitive || control !== null && control === element ? null : shownText(element)
104
+ ];
105
+ for (const candidate of candidates) {
106
+ const text = candidate ? clip(candidate, MAX_LABEL_CHARS) : "";
107
+ if (text) return text;
108
+ }
109
+ return fallback;
110
+ }
111
+ function ariaFlag(element, attribute) {
112
+ const value = element.getAttribute(attribute);
113
+ if (value === "true") return true;
114
+ if (value === "false") return false;
115
+ return null;
116
+ }
117
+ function isDisabled(element, control) {
118
+ const subject = control ?? element;
119
+ if (element.getAttribute("aria-disabled") === "true") return true;
120
+ if (subject.getAttribute("aria-disabled") === "true") return true;
121
+ try {
122
+ return subject.matches(":disabled") || element.matches(":disabled");
123
+ } catch {
124
+ return subject.disabled === true;
125
+ }
126
+ }
127
+ function styleHides(element) {
128
+ const view = element.ownerDocument.defaultView;
129
+ for (let node = element; node; node = node.parentElement) {
130
+ if (node.hasAttribute("hidden") || node.hasAttribute("inert")) return true;
131
+ if (node.getAttribute("aria-hidden") === "true") return true;
132
+ const style = view == null ? void 0 : view.getComputedStyle(node);
133
+ if (style && (style.display === "none" || style.visibility === "hidden")) return true;
134
+ }
135
+ return false;
136
+ }
137
+ function isRendered(element) {
138
+ if (!element.isConnected) return false;
139
+ const check = element.checkVisibility;
140
+ if (typeof check === "function") {
141
+ try {
142
+ if (!check.call(element, { visibilityProperty: true })) return false;
143
+ } catch {
144
+ }
145
+ }
146
+ return !styleHides(element);
147
+ }
148
+ function isInViewport(element) {
149
+ const view = element.ownerDocument.defaultView;
150
+ if (!view) return false;
151
+ const rect = element.getBoundingClientRect();
152
+ if (rect.width <= 0 && rect.height <= 0) return false;
153
+ return rect.bottom > 0 && rect.right > 0 && rect.top < view.innerHeight && rect.left < view.innerWidth;
154
+ }
155
+ function selectOptions(control) {
156
+ const options = control.options;
157
+ if (!options) return [];
158
+ return Array.from(options, (option) => option.label || option.text || "").filter(Boolean);
159
+ }
160
+ function guarded(read, fallback) {
161
+ try {
162
+ return read();
163
+ } catch {
164
+ return fallback;
165
+ }
166
+ }
167
+ function controlValue(control) {
168
+ if (tagOf(control) === "SELECT") {
169
+ const select = control;
170
+ const chosen = Array.from(select.selectedOptions ?? [], (option) => option.label || option.text || "");
171
+ return chosen.length > 0 ? chosen.join(", ") : select.value ?? "";
172
+ }
173
+ if (isEditable(control)) return control.textContent ?? "";
174
+ return control.value ?? "";
175
+ }
176
+ function isReadOnly(control) {
177
+ if (!control) return false;
178
+ return control.hasAttribute("readonly") || control.getAttribute("aria-readonly") === "true";
179
+ }
180
+ function isFileInput(control) {
181
+ return control !== null && tagOf(control) === "INPUT" && inputType(control) === "file";
182
+ }
183
+ function derivedActions({
184
+ role,
185
+ control,
186
+ handlers
187
+ }) {
188
+ const actions = /* @__PURE__ */ new Set();
189
+ switch (isFileInput(control) ? "file" : role) {
190
+ case "button":
191
+ case "link":
192
+ case "tab":
193
+ case "menuitem":
194
+ case "option":
195
+ case "treeitem":
196
+ actions.add("click");
197
+ break;
198
+ case "textbox":
199
+ case "searchbox":
200
+ case "spinbutton":
201
+ if (!isReadOnly(control)) {
202
+ actions.add("type");
203
+ actions.add("clear");
204
+ }
205
+ break;
206
+ case "combobox":
207
+ case "listbox":
208
+ if (control && tagOf(control) === "SELECT") actions.add("select");
209
+ else if (control) {
210
+ actions.add("type");
211
+ actions.add("clear");
212
+ }
213
+ break;
214
+ case "checkbox":
215
+ case "radio":
216
+ case "switch":
217
+ actions.add("toggle");
218
+ break;
219
+ }
220
+ if (handlers == null ? void 0 : handlers.click) actions.add("click");
221
+ if (handlers == null ? void 0 : handlers.type) actions.add("type");
222
+ if (handlers == null ? void 0 : handlers.select) actions.add("select");
223
+ if (handlers == null ? void 0 : handlers.toggle) actions.add("toggle");
224
+ if (handlers == null ? void 0 : handlers.clear) actions.add("clear");
225
+ return Array.from(actions);
226
+ }
227
+ function declaredActions(element) {
228
+ const declared = element.getAttribute(AGENT_ATTR.actions);
229
+ if (declared === null) return null;
230
+ return declared.split(/\s+/).filter(isAgentTargetAction);
231
+ }
232
+ function snapshotTarget(element) {
233
+ var _a, _b;
234
+ const id = element.getAttribute(AGENT_ATTR.target) ?? "";
235
+ const key = element.getAttribute(AGENT_ATTR.key) || null;
236
+ const ref = agentTargetRef({ id, key });
237
+ const handlers = agentTargetHandlers(ref) ?? (key ? agentTargetHandlers(id) : void 0);
238
+ const control = controlOf(element);
239
+ const role = roleOf(element, control);
240
+ const danger = element.hasAttribute(AGENT_ATTR.danger);
241
+ const sensitive = element.hasAttribute(AGENT_ATTR.sensitive) || control !== null && isSecretField(control);
242
+ const declared = declaredActions(element);
243
+ const allowed = declared ?? (danger ? [] : derivedActions({ role, control, handlers }));
244
+ const actions = sensitive ? allowed.filter((action) => action !== "type") : allowed;
245
+ const raw = (handlers == null ? void 0 : handlers.value) ? guarded(() => {
246
+ var _a2;
247
+ return ((_a2 = handlers.value) == null ? void 0 : _a2.call(handlers)) ?? null;
248
+ }, null) : control ? controlValue(control) : null;
249
+ const isCheckable = role === "checkbox" || role === "radio" || role === "switch";
250
+ const checkedProperty = control && tagOf(control) === "INPUT" && (inputType(control) === "checkbox" || inputType(control) === "radio") ? control.checked === true : null;
251
+ const everyOption = (handlers == null ? void 0 : handlers.options) ? guarded(() => {
252
+ var _a2;
253
+ return Array.from(((_a2 = handlers.options) == null ? void 0 : _a2.call(handlers)) ?? [], String);
254
+ }, []) : control && tagOf(control) === "SELECT" ? selectOptions(control) : null;
255
+ const options = everyOption ? everyOption.map((option) => clip(option, MAX_OPTION_CHARS)).filter(Boolean).slice(0, MAX_OPTIONS) : null;
256
+ return {
257
+ ref,
258
+ id,
259
+ key,
260
+ element,
261
+ control,
262
+ role,
263
+ label: labelOf({ element, control, sensitive, fallback: ref }),
264
+ description: clip(element.getAttribute(AGENT_ATTR.description) ?? "", MAX_DESCRIPTION_CHARS) || null,
265
+ region: ((_b = (_a = element.parentElement) == null ? void 0 : _a.closest(`[${AGENT_ATTR.region}]`)) == null ? void 0 : _b.getAttribute(AGENT_ATTR.region)) ?? null,
266
+ actions,
267
+ danger,
268
+ sensitive,
269
+ disabled: isDisabled(element, control),
270
+ visible: isRendered(element),
271
+ onScreen: isInViewport(element),
272
+ value: sensitive || isCheckable || raw === null ? null : clip(raw, MAX_VALUE_CHARS),
273
+ filled: sensitive && raw !== null ? raw !== "" : null,
274
+ checked: checkedProperty ?? ariaFlag(element, "aria-checked") ?? ariaFlag(element, "aria-pressed"),
275
+ selected: ariaFlag(element, "aria-selected") ?? (element.hasAttribute("aria-current") ? element.getAttribute("aria-current") !== "false" : null),
276
+ expanded: ariaFlag(element, "aria-expanded"),
277
+ options: options && options.length > 0 ? options : null,
278
+ optionsOmitted: everyOption && options ? Math.max(0, everyOption.length - options.length) : 0,
279
+ readOnly: isReadOnly(control),
280
+ handlers
281
+ };
282
+ }
283
+ function findTargetElements(doc) {
284
+ return Array.from(doc.querySelectorAll(`[${AGENT_ATTR.target}]`)).filter(
285
+ (element) => element.getAttribute(AGENT_ATTR.target) !== "" && !element.closest(`[${AGENT_ATTR.layer}]`)
286
+ );
287
+ }
288
+ function snapshotTargets(doc) {
289
+ return findTargetElements(doc).map(snapshotTarget).filter((target) => target.visible && target.ref.length <= MAX_REF_CHARS);
290
+ }
291
+ function suggestions(targets, wanted) {
292
+ const words = wanted.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2);
293
+ const refs = Array.from(new Set(targets.map((target) => target.ref)));
294
+ const close = refs.filter((ref) => words.some((word) => ref.toLowerCase().includes(word)));
295
+ const listed = (close.length > 0 ? close : refs).slice(0, 8);
296
+ if (listed.length === 0) return "This screen has no targets.";
297
+ return `${close.length > 0 ? "Closest here" : "Targets here"}: ${listed.join(", ")}${refs.length > listed.length ? ", …" : ""}.`;
298
+ }
299
+ function resolveTarget({
300
+ doc,
301
+ ref,
302
+ allowLabel = false,
303
+ exact = false
304
+ }) {
305
+ const parsed = parseAgentTargetRef(ref);
306
+ if (!parsed) throw new AgentUiError(`"${ref}" is not a target. Give a target from read_screen.`);
307
+ const all = snapshotTargets(doc);
308
+ const byId = all.filter(
309
+ (target) => target.id === parsed.id && (parsed.key === null || target.key === parsed.key)
310
+ );
311
+ let matches = byId;
312
+ if (matches.length === 0 && allowLabel) {
313
+ const wanted = ref.trim().toLowerCase();
314
+ const exact2 = all.filter((target) => target.label.toLowerCase() === wanted);
315
+ const partial = all.filter((target) => target.label.toLowerCase().includes(wanted));
316
+ matches = exact2.length > 0 ? exact2 : partial.length === 1 ? partial : [];
317
+ }
318
+ const found = matches.find((target) => target.onScreen) ?? matches[0];
319
+ if (!found) {
320
+ throw new AgentUiError(
321
+ `No target "${ref}" is on the screen now. ${suggestions(all, ref)} Call read_screen for what is here; a target on another page appears once that page is open.`
322
+ );
323
+ }
324
+ if (exact && matches.length > 1) {
325
+ const keyed = Array.from(new Set(matches.map((target) => target.ref))).filter((other) => other !== ref);
326
+ throw new AgentUiError(
327
+ keyed.length > 0 ? `${matches.length} targets share "${ref}". Name the one you mean by its key, e.g. ${keyed.slice(0, 5).join(", ")}.` : `${matches.length} elements on the screen are all "${ref}", so it cannot be operated by name. Point at it instead, and let the person do it.`
328
+ );
329
+ }
330
+ return found;
331
+ }
332
+ async function waitForTarget({
333
+ doc,
334
+ ref,
335
+ timeoutMs,
336
+ signal,
337
+ allowLabel = false
338
+ }) {
339
+ const deadline = Date.now() + timeoutMs;
340
+ for (; ; ) {
341
+ try {
342
+ return resolveTarget({ doc, ref, allowLabel });
343
+ } catch (error) {
344
+ if (!(error instanceof AgentUiError)) throw error;
345
+ }
346
+ if ((signal == null ? void 0 : signal.aborted) || Date.now() >= deadline) return null;
347
+ await new Promise((resolve) => setTimeout(resolve, 120));
348
+ }
349
+ }
350
+ function describeTarget(target) {
351
+ return {
352
+ target: target.ref,
353
+ role: target.role,
354
+ label: target.label,
355
+ ...target.description ? { does: target.description } : {},
356
+ ...target.value !== null ? { value: target.value } : {},
357
+ ...target.filled !== null ? { filled: target.filled } : {},
358
+ ...target.checked !== null ? { checked: target.checked } : {},
359
+ ...target.selected ? { selected: true } : {},
360
+ ...target.expanded !== null ? { expanded: target.expanded } : {},
361
+ ...target.options ? { options: target.options } : {},
362
+ ...target.optionsOmitted > 0 ? { more_options: target.optionsOmitted } : {},
363
+ ...target.readOnly ? { read_only: true } : {},
364
+ can: target.actions.length > 0 ? target.actions : "point only",
365
+ ...target.region ? { region: target.region } : {},
366
+ ...target.disabled ? { disabled: true } : {},
367
+ ...target.danger ? { danger: "the person confirms every action on it" } : {},
368
+ ...target.sensitive ? { sensitive: "its value is never read or typed" } : {},
369
+ ...target.onScreen ? {} : { scrolled_out_of_view: true }
370
+ };
371
+ }
372
+ const READ_SCREEN_MAX_TARGETS = 120;
373
+ const READ_SCREEN_MAX_PER_ID = 25;
374
+ const READ_SCREEN_MAX_CHARS = 24e3;
375
+ function readScreen({
376
+ doc,
377
+ region,
378
+ query,
379
+ maxChars = READ_SCREEN_MAX_CHARS
380
+ }) {
381
+ var _a;
382
+ const all = snapshotTargets(doc);
383
+ const wantedRegion = region == null ? void 0 : region.trim();
384
+ const wantedText = query == null ? void 0 : query.trim().toLowerCase();
385
+ const matching = all.filter((target) => {
386
+ if (wantedRegion && target.region !== wantedRegion) return false;
387
+ if (!wantedText) return true;
388
+ return [target.ref, target.label, target.description ?? "", target.value ?? ""].some(
389
+ (text) => text.toLowerCase().includes(wantedText)
390
+ );
391
+ });
392
+ const perId = /* @__PURE__ */ new Map();
393
+ const overPerId = /* @__PURE__ */ new Map();
394
+ const withinPerId = matching.filter((target) => {
395
+ const seen = (perId.get(target.id) ?? 0) + 1;
396
+ perId.set(target.id, seen);
397
+ if (seen <= READ_SCREEN_MAX_PER_ID) return true;
398
+ overPerId.set(target.id, (overPerId.get(target.id) ?? 0) + 1);
399
+ return false;
400
+ });
401
+ let kept = withinPerId;
402
+ if (kept.length > READ_SCREEN_MAX_TARGETS) {
403
+ const inView = kept.filter((target) => target.onScreen).slice(0, READ_SCREEN_MAX_TARGETS);
404
+ const room = READ_SCREEN_MAX_TARGETS - inView.length;
405
+ const rest = new Set(kept.filter((target) => !target.onScreen).slice(0, room));
406
+ const chosen = new Set(inView);
407
+ kept = kept.filter((target) => chosen.has(target) || rest.has(target));
408
+ }
409
+ const regions = /* @__PURE__ */ new Map();
410
+ for (const target of all) {
411
+ if (!target.region) continue;
412
+ const entry = regions.get(target.region);
413
+ if (entry) entry.targets += 1;
414
+ else {
415
+ const element = (_a = target.element.parentElement) == null ? void 0 : _a.closest(`[${AGENT_ATTR.region}]`);
416
+ const label = (element == null ? void 0 : element.getAttribute(AGENT_ATTR.label)) ?? (element == null ? void 0 : element.getAttribute("aria-label"));
417
+ regions.set(target.region, { label: label ? clip(label, MAX_LABEL_CHARS) : null, targets: 1 });
418
+ }
419
+ }
420
+ const view = doc.defaultView;
421
+ const listed = kept.map(describeTarget);
422
+ const result = (targets) => {
423
+ const omitted = matching.length - targets.length;
424
+ return {
425
+ page: {
426
+ title: clip(doc.title ?? "", MAX_LABEL_CHARS),
427
+ ...view ? { path: `${view.location.pathname}${view.location.search}` } : {}
428
+ },
429
+ ...regions.size > 0 ? {
430
+ regions: Array.from(regions, ([id, entry]) => ({
431
+ region: id,
432
+ ...entry.label ? { label: entry.label } : {},
433
+ targets: entry.targets
434
+ })).slice(0, MAX_REGIONS),
435
+ ...regions.size > MAX_REGIONS ? { more_regions: regions.size - MAX_REGIONS } : {}
436
+ } : {},
437
+ targets,
438
+ ...overPerId.size > 0 ? {
439
+ more_sharing_an_id: Object.fromEntries(overPerId)
440
+ } : {},
441
+ ...omitted > 0 ? {
442
+ omitted,
443
+ note: "Not every target fits one read: narrow it with `region` or `query`."
444
+ } : {},
445
+ ...all.length === 0 ? { note: "This screen has no agent targets: nothing here can be pointed at or operated." } : matching.length === 0 ? { note: "No target matches. Read again without `region` and `query`." } : {}
446
+ };
447
+ };
448
+ let answer = result(listed);
449
+ while (listed.length > 0 && JSON.stringify(answer).length > maxChars) {
450
+ listed.pop();
451
+ answer = result(listed);
452
+ }
453
+ return answer;
454
+ }
455
+ export {
456
+ AgentUiError,
457
+ READ_SCREEN_MAX_CHARS,
458
+ READ_SCREEN_MAX_PER_ID,
459
+ READ_SCREEN_MAX_TARGETS,
460
+ controlOf,
461
+ describeTarget,
462
+ findTargetElements,
463
+ isInViewport,
464
+ isRendered,
465
+ readScreen,
466
+ resolveTarget,
467
+ snapshotTarget,
468
+ snapshotTargets,
469
+ waitForTarget
470
+ };
471
+ //# sourceMappingURL=screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.js","sources":["../../src/agent-ui/screen.ts"],"sourcesContent":["/*\n * Reading the screen: the agent targets in a document, each with the name,\n * state and allowed actions the agent is told, and the lookup the tools use to\n * turn the agent's reference back into an element.\n *\n * Read at call time, never cached: the document is the registry, so a target\n * that is not in it is not on the screen.\n */\n\nimport {\n AGENT_ATTR,\n type AgentTargetAction,\n type AgentTargetHandlers,\n agentTargetHandlers,\n agentTargetRef,\n isAgentTargetAction,\n parseAgentTargetRef,\n} from './contract';\nimport { isSecretField } from './secret-fields';\n\n/** A refusal or a miss the agent is told about, in words it can act on. */\nexport class AgentUiError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AgentUiError';\n }\n}\n\n/** One target as it is on screen now. */\nexport interface AgentTargetSnapshot {\n /** How the agent names it: `id`, or `id[key]`. */\n ref: string;\n id: string;\n key: string | null;\n element: Element;\n /** The form control the target stands for: itself, or the one inside it. */\n control: Element | null;\n role: string;\n label: string;\n description: string | null;\n region: string | null;\n /** What the agent may do to it. Empty: it may only be pointed at. */\n actions: readonly AgentTargetAction[];\n danger: boolean;\n sensitive: boolean;\n disabled: boolean;\n /** Rendered (not hidden by the page). */\n visible: boolean;\n /** Inside the viewport now. */\n onScreen: boolean;\n /** Its value, absent for a sensitive target and for one that has none. */\n value: string | null;\n /** For a sensitive field: whether it holds anything. */\n filled: boolean | null;\n checked: boolean | null;\n selected: boolean | null;\n expanded: boolean | null;\n /** What a `select` may be given. */\n options: readonly string[] | null;\n /** How many more options there are than `options` lists. */\n optionsOmitted: number;\n /** A field the page shows but does not let anyone edit. */\n readOnly: boolean;\n handlers: AgentTargetHandlers | undefined;\n}\n\nconst CONTROL_SELECTOR =\n 'input:not([type=\"hidden\"]), textarea, select, [contenteditable=\"\"], [contenteditable=\"true\"]';\n\nconst MAX_LABEL_CHARS = 80;\nconst MAX_VALUE_CHARS = 200;\nconst MAX_DESCRIPTION_CHARS = 240;\nconst MAX_OPTIONS = 30;\nconst MAX_OPTION_CHARS = 60;\n/** A reference longer than this is not one the agent could pass back reliably. */\nconst MAX_REF_CHARS = 160;\n/** The most regions one read names. */\nconst MAX_REGIONS = 40;\n\nconst tagOf = (element: Element): string => element.tagName.toUpperCase();\n\nfunction clip(text: string, max: number): string {\n const collapsed = text.replace(/\\s+/g, ' ').trim();\n return collapsed.length > max ? `${collapsed.slice(0, max - 1)}…` : collapsed;\n}\n\nfunction inputType(element: Element): string {\n return (element.getAttribute('type') ?? 'text').toLowerCase();\n}\n\nfunction isEditable(element: Element): boolean {\n const editable = element.getAttribute('contenteditable');\n return editable === '' || editable === 'true';\n}\n\n/** The form control a target stands for: itself, or the first one inside it. */\nexport function controlOf(element: Element): Element | null {\n if (element.matches(CONTROL_SELECTOR)) return element;\n const tag = tagOf(element);\n if (tag === 'BUTTON' || tag === 'A') return null;\n return element.querySelector(CONTROL_SELECTOR);\n}\n\nfunction roleOf(element: Element, control: Element | null): string {\n const explicit = element.getAttribute('role')?.trim();\n if (explicit) return explicit;\n const subject = control ?? element;\n const tag = tagOf(subject);\n if (tag === 'BUTTON' || tag === 'SUMMARY') return 'button';\n if (tag === 'A') return subject.hasAttribute('href') ? 'link' : 'element';\n if (tag === 'SELECT') return 'combobox';\n if (tag === 'TEXTAREA') return 'textbox';\n if (tag === 'INPUT') {\n const type = inputType(subject);\n if (type === 'checkbox') return 'checkbox';\n if (type === 'radio') return 'radio';\n if (type === 'range') return 'slider';\n if (type === 'button' || type === 'submit' || type === 'reset' || type === 'image') return 'button';\n if (type === 'search') return 'searchbox';\n return 'textbox';\n }\n if (isEditable(subject)) return 'textbox';\n return subject.getAttribute('role')?.trim() || 'element';\n}\n\nfunction referencedText(element: Element, attribute: string): string {\n const ids = (element.getAttribute(attribute) ?? '').split(/\\s+/).filter(Boolean);\n return ids\n .map((id) => {\n const named = element.ownerDocument.getElementById(id);\n return named ? shownText(named) : '';\n })\n .join(' ')\n .trim();\n}\n\nfunction labelsText(control: Element | null): string {\n if (!control) return '';\n const labels = (control as { labels?: ArrayLike<Element> | null }).labels;\n if (!labels) return '';\n // A `<label>` wrapped around its field holds the field's contents too.\n return Array.from(labels, shownText).join(' ').trim();\n}\n\n/**\n * The words an element shows, without what any field inside it holds: a\n * field's contents are its value, never part of a name.\n */\nfunction shownText(element: Element): string {\n let text = '';\n for (const node of Array.from(element.childNodes)) {\n if (node.nodeType === 3) text += node.textContent ?? '';\n else if (node.nodeType === 1) {\n const child = node as Element;\n if (child.matches(CONTROL_SELECTOR) || tagOf(child) === 'OPTION') continue;\n // Nor what the app marked sensitive inside it: a secret shown as text\n // must not ride out in the name of the element around it.\n if (child.hasAttribute(AGENT_ATTR.sensitive)) continue;\n text += ` ${shownText(child)}`;\n }\n }\n return text;\n}\n\nfunction labelOf({\n element,\n control,\n sensitive,\n fallback,\n}: {\n element: Element;\n control: Element | null;\n /** The target's own text is then a secret, so only what names it is used. */\n sensitive: boolean;\n fallback: string;\n}): string {\n const subject = control ?? element;\n const candidates = [\n element.getAttribute(AGENT_ATTR.label),\n element.getAttribute('aria-label'),\n subject.getAttribute('aria-label'),\n referencedText(element, 'aria-labelledby'),\n referencedText(subject, 'aria-labelledby'),\n labelsText(control),\n subject.getAttribute('placeholder'),\n element.getAttribute('title'),\n subject.getAttribute('alt'),\n // A button drawn by an `<input>` shows its `value` as its face.\n !sensitive && tagOf(subject) === 'INPUT' && roleOf(element, control) === 'button'\n ? subject.getAttribute('value')\n : null,\n // A control's own text is its value, never its name.\n sensitive || (control !== null && control === element) ? null : shownText(element),\n ];\n for (const candidate of candidates) {\n const text = candidate ? clip(candidate, MAX_LABEL_CHARS) : '';\n if (text) return text;\n }\n return fallback;\n}\n\nfunction ariaFlag(element: Element, attribute: string): boolean | null {\n const value = element.getAttribute(attribute);\n if (value === 'true') return true;\n if (value === 'false') return false;\n return null;\n}\n\nfunction isDisabled(element: Element, control: Element | null): boolean {\n const subject = control ?? element;\n if (element.getAttribute('aria-disabled') === 'true') return true;\n if (subject.getAttribute('aria-disabled') === 'true') return true;\n try {\n return subject.matches(':disabled') || element.matches(':disabled');\n } catch {\n return (subject as { disabled?: boolean }).disabled === true;\n }\n}\n\nfunction styleHides(element: Element): boolean {\n const view = element.ownerDocument.defaultView;\n for (let node: Element | null = element; node; node = node.parentElement) {\n if (node.hasAttribute('hidden') || node.hasAttribute('inert')) return true;\n if (node.getAttribute('aria-hidden') === 'true') return true;\n const style = view?.getComputedStyle(node);\n if (style && (style.display === 'none' || style.visibility === 'hidden')) return true;\n }\n return false;\n}\n\n/** Whether the page renders the element at all. */\nexport function isRendered(element: Element): boolean {\n if (!element.isConnected) return false;\n const check = (element as { checkVisibility?: (options?: object) => boolean }).checkVisibility;\n if (typeof check === 'function') {\n try {\n if (!check.call(element, { visibilityProperty: true })) return false;\n } catch {\n /* fall through to the style walk */\n }\n }\n return !styleHides(element);\n}\n\n/** Whether any of the element is inside the viewport now. */\nexport function isInViewport(element: Element): boolean {\n const view = element.ownerDocument.defaultView;\n if (!view) return false;\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 && rect.height <= 0) return false;\n return (\n rect.bottom > 0 && rect.right > 0 && rect.top < view.innerHeight && rect.left < view.innerWidth\n );\n}\n\nfunction selectOptions(control: Element): string[] {\n const options = (control as { options?: ArrayLike<{ label?: string; text?: string }> }).options;\n if (!options) return [];\n return Array.from(options, (option) => option.label || option.text || '').filter(Boolean);\n}\n\n/** `read()`, or `fallback` when it throws. */\nfunction guarded<T>(read: () => T, fallback: T): T {\n try {\n return read();\n } catch {\n return fallback;\n }\n}\n\nfunction controlValue(control: Element): string {\n if (tagOf(control) === 'SELECT') {\n const select = control as unknown as {\n selectedOptions?: ArrayLike<{ label?: string; text?: string }>;\n value?: string;\n };\n const chosen = Array.from(select.selectedOptions ?? [], (option) => option.label || option.text || '');\n return chosen.length > 0 ? chosen.join(', ') : (select.value ?? '');\n }\n if (isEditable(control)) return control.textContent ?? '';\n return (control as { value?: string }).value ?? '';\n}\n\nfunction isReadOnly(control: Element | null): boolean {\n if (!control) return false;\n return control.hasAttribute('readonly') || control.getAttribute('aria-readonly') === 'true';\n}\n\n/** A file input opens the system's file picker, which only the person can use. */\nfunction isFileInput(control: Element | null): boolean {\n return control !== null && tagOf(control) === 'INPUT' && inputType(control) === 'file';\n}\n\nfunction derivedActions({\n role,\n control,\n handlers,\n}: {\n role: string;\n control: Element | null;\n handlers: AgentTargetHandlers | undefined;\n}): AgentTargetAction[] {\n const actions = new Set<AgentTargetAction>();\n switch (isFileInput(control) ? 'file' : role) {\n case 'button':\n case 'link':\n case 'tab':\n case 'menuitem':\n case 'option':\n case 'treeitem':\n actions.add('click');\n break;\n case 'textbox':\n case 'searchbox':\n case 'spinbutton':\n if (!isReadOnly(control)) {\n actions.add('type');\n actions.add('clear');\n }\n break;\n case 'combobox':\n case 'listbox':\n if (control && tagOf(control) === 'SELECT') actions.add('select');\n else if (control) {\n actions.add('type');\n actions.add('clear');\n }\n break;\n case 'checkbox':\n case 'radio':\n case 'switch':\n actions.add('toggle');\n break;\n default:\n break;\n }\n if (handlers?.click) actions.add('click');\n if (handlers?.type) actions.add('type');\n if (handlers?.select) actions.add('select');\n if (handlers?.toggle) actions.add('toggle');\n if (handlers?.clear) actions.add('clear');\n return Array.from(actions);\n}\n\nfunction declaredActions(element: Element): AgentTargetAction[] | null {\n const declared = element.getAttribute(AGENT_ATTR.actions);\n if (declared === null) return null;\n return declared.split(/\\s+/).filter(isAgentTargetAction);\n}\n\n/** One target element as the agent is told about it. */\nexport function snapshotTarget(element: Element): AgentTargetSnapshot {\n const id = element.getAttribute(AGENT_ATTR.target) ?? '';\n const key = element.getAttribute(AGENT_ATTR.key) || null;\n const ref = agentTargetRef({ id, key });\n const handlers = agentTargetHandlers(ref) ?? (key ? agentTargetHandlers(id) : undefined);\n const control = controlOf(element);\n const role = roleOf(element, control);\n const danger = element.hasAttribute(AGENT_ATTR.danger);\n const sensitive =\n element.hasAttribute(AGENT_ATTR.sensitive) || (control !== null && isSecretField(control));\n const declared = declaredActions(element);\n // A dangerous target allows only what the app spelled out for it.\n const allowed = declared ?? (danger ? [] : derivedActions({ role, control, handlers }));\n const actions = sensitive ? allowed.filter((action) => action !== 'type') : allowed;\n\n // A target's own handlers are the app's code: one that throws costs that\n // target its value or options, never the whole read. Where a handler answers\n // for the value, its answer stands (`null`: it has none to show).\n const raw = handlers?.value\n ? guarded(() => handlers.value?.() ?? null, null)\n : control\n ? controlValue(control)\n : null;\n const isCheckable = role === 'checkbox' || role === 'radio' || role === 'switch';\n const checkedProperty =\n control && tagOf(control) === 'INPUT' && (inputType(control) === 'checkbox' || inputType(control) === 'radio')\n ? (control as { checked?: boolean }).checked === true\n : null;\n\n const everyOption = handlers?.options\n ? guarded(() => Array.from(handlers.options?.() ?? [], String), [])\n : control && tagOf(control) === 'SELECT'\n ? selectOptions(control)\n : null;\n const options = everyOption\n ? everyOption\n .map((option) => clip(option, MAX_OPTION_CHARS))\n .filter(Boolean)\n .slice(0, MAX_OPTIONS)\n : null;\n\n return {\n ref,\n id,\n key,\n element,\n control,\n role,\n label: labelOf({ element, control, sensitive, fallback: ref }),\n description: clip(element.getAttribute(AGENT_ATTR.description) ?? '', MAX_DESCRIPTION_CHARS) || null,\n region: element.parentElement?.closest(`[${AGENT_ATTR.region}]`)?.getAttribute(AGENT_ATTR.region) ?? null,\n actions,\n danger,\n sensitive,\n disabled: isDisabled(element, control),\n visible: isRendered(element),\n onScreen: isInViewport(element),\n value: sensitive || isCheckable || raw === null ? null : clip(raw, MAX_VALUE_CHARS),\n filled: sensitive && raw !== null ? raw !== '' : null,\n checked: checkedProperty ?? ariaFlag(element, 'aria-checked') ?? ariaFlag(element, 'aria-pressed'),\n selected:\n ariaFlag(element, 'aria-selected') ??\n (element.hasAttribute('aria-current') ? element.getAttribute('aria-current') !== 'false' : null),\n expanded: ariaFlag(element, 'aria-expanded'),\n options: options && options.length > 0 ? options : null,\n optionsOmitted: everyOption && options ? Math.max(0, everyOption.length - options.length) : 0,\n readOnly: isReadOnly(control),\n handlers,\n };\n}\n\n/** Every target element in the document, in document order. The overlay's own are never targets. */\nexport function findTargetElements(doc: Document): Element[] {\n return Array.from(doc.querySelectorAll(`[${AGENT_ATTR.target}]`)).filter(\n (element) =>\n element.getAttribute(AGENT_ATTR.target) !== '' && !element.closest(`[${AGENT_ATTR.layer}]`),\n );\n}\n\n/** Every rendered target, in document order. */\nexport function snapshotTargets(doc: Document): AgentTargetSnapshot[] {\n return findTargetElements(doc)\n .map(snapshotTarget)\n .filter((target) => target.visible && target.ref.length <= MAX_REF_CHARS);\n}\n\nfunction suggestions(targets: readonly AgentTargetSnapshot[], wanted: string): string {\n const words = wanted.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2);\n const refs = Array.from(new Set(targets.map((target) => target.ref)));\n const close = refs.filter((ref) => words.some((word) => ref.toLowerCase().includes(word)));\n const listed = (close.length > 0 ? close : refs).slice(0, 8);\n if (listed.length === 0) return 'This screen has no targets.';\n return `${close.length > 0 ? 'Closest here' : 'Targets here'}: ${listed.join(', ')}${refs.length > listed.length ? ', …' : ''}.`;\n}\n\n/**\n * The target the agent named. By id (and key); a pointing tool may also name\n * it by its label. Throws an {@link AgentUiError} that says what is on screen\n * instead, so the agent can correct itself in one step.\n */\nexport function resolveTarget({\n doc,\n ref,\n allowLabel = false,\n exact = false,\n}: {\n doc: Document;\n ref: string;\n allowLabel?: boolean;\n /**\n * The reference must name one target and no other (a tool about to act):\n * several targets sharing the id is then a question for the agent, not a\n * choice made for it.\n */\n exact?: boolean;\n}): AgentTargetSnapshot {\n const parsed = parseAgentTargetRef(ref);\n if (!parsed) throw new AgentUiError(`\"${ref}\" is not a target. Give a target from read_screen.`);\n const all = snapshotTargets(doc);\n const byId = all.filter(\n (target) => target.id === parsed.id && (parsed.key === null || target.key === parsed.key),\n );\n let matches = byId;\n if (matches.length === 0 && allowLabel) {\n const wanted = ref.trim().toLowerCase();\n const exact = all.filter((target) => target.label.toLowerCase() === wanted);\n const partial = all.filter((target) => target.label.toLowerCase().includes(wanted));\n matches = exact.length > 0 ? exact : partial.length === 1 ? partial : [];\n }\n const found = matches.find((target) => target.onScreen) ?? matches[0];\n if (!found) {\n throw new AgentUiError(\n `No target \"${ref}\" is on the screen now. ${suggestions(all, ref)} Call read_screen for what is here; a target on another page appears once that page is open.`,\n );\n }\n if (exact && matches.length > 1) {\n const keyed = Array.from(new Set(matches.map((target) => target.ref))).filter((other) => other !== ref);\n throw new AgentUiError(\n keyed.length > 0\n ? `${matches.length} targets share \"${ref}\". Name the one you mean by its key, e.g. ${keyed.slice(0, 5).join(', ')}.`\n : `${matches.length} elements on the screen are all \"${ref}\", so it cannot be operated by name. Point at it instead, and let the person do it.`,\n );\n }\n return found;\n}\n\n/** Wait for a target to be on the page (a page that is still loading), or give up. */\nexport async function waitForTarget({\n doc,\n ref,\n timeoutMs,\n signal,\n allowLabel = false,\n}: {\n doc: Document;\n ref: string;\n timeoutMs: number;\n signal?: AbortSignal;\n allowLabel?: boolean;\n}): Promise<AgentTargetSnapshot | null> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n return resolveTarget({ doc, ref, allowLabel });\n } catch (error) {\n if (!(error instanceof AgentUiError)) throw error;\n }\n if (signal?.aborted || Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, 120));\n }\n}\n\n/** A target as the agent reads it: only what is set, in few words. */\nexport function describeTarget(target: AgentTargetSnapshot): Record<string, unknown> {\n return {\n target: target.ref,\n role: target.role,\n label: target.label,\n ...(target.description ? { does: target.description } : {}),\n ...(target.value !== null ? { value: target.value } : {}),\n ...(target.filled !== null ? { filled: target.filled } : {}),\n ...(target.checked !== null ? { checked: target.checked } : {}),\n ...(target.selected ? { selected: true } : {}),\n ...(target.expanded !== null ? { expanded: target.expanded } : {}),\n ...(target.options ? { options: target.options } : {}),\n ...(target.optionsOmitted > 0 ? { more_options: target.optionsOmitted } : {}),\n ...(target.readOnly ? { read_only: true } : {}),\n can: target.actions.length > 0 ? target.actions : 'point only',\n ...(target.region ? { region: target.region } : {}),\n ...(target.disabled ? { disabled: true } : {}),\n ...(target.danger ? { danger: 'the person confirms every action on it' } : {}),\n ...(target.sensitive ? { sensitive: 'its value is never read or typed' } : {}),\n ...(target.onScreen ? {} : { scrolled_out_of_view: true }),\n };\n}\n\n/** The most targets one read lists. */\nexport const READ_SCREEN_MAX_TARGETS = 120;\n/** The most targets sharing one id (a table's rows) one read lists. */\nexport const READ_SCREEN_MAX_PER_ID = 25;\n/** The most characters one read answers with, under the runtime's cap on a client tool's result. */\nexport const READ_SCREEN_MAX_CHARS = 24_000;\n\nexport interface ReadScreenArgs {\n doc: Document;\n /** Only the targets of this region. */\n region?: string;\n /** Only targets whose reference, label, description or value holds this text. */\n query?: string;\n /** The most characters the answer may take, when the caller adds to it. Defaults to the whole cap. */\n maxChars?: number;\n}\n\n/** What `read_screen` answers: the page, its regions and its targets, bounded. */\nexport function readScreen({\n doc,\n region,\n query,\n maxChars = READ_SCREEN_MAX_CHARS,\n}: ReadScreenArgs): Record<string, unknown> {\n const all = snapshotTargets(doc);\n const wantedRegion = region?.trim();\n const wantedText = query?.trim().toLowerCase();\n const matching = all.filter((target) => {\n if (wantedRegion && target.region !== wantedRegion) return false;\n if (!wantedText) return true;\n return [target.ref, target.label, target.description ?? '', target.value ?? ''].some((text) =>\n text.toLowerCase().includes(wantedText),\n );\n });\n\n // Rows of one table share an id: list the first of them and count the rest.\n const perId = new Map<string, number>();\n const overPerId = new Map<string, number>();\n const withinPerId = matching.filter((target) => {\n const seen = (perId.get(target.id) ?? 0) + 1;\n perId.set(target.id, seen);\n if (seen <= READ_SCREEN_MAX_PER_ID) return true;\n overPerId.set(target.id, (overPerId.get(target.id) ?? 0) + 1);\n return false;\n });\n\n // Over the cap, what is in view is kept first; the order stays the page's.\n let kept = withinPerId;\n if (kept.length > READ_SCREEN_MAX_TARGETS) {\n const inView = kept.filter((target) => target.onScreen).slice(0, READ_SCREEN_MAX_TARGETS);\n const room = READ_SCREEN_MAX_TARGETS - inView.length;\n const rest = new Set(kept.filter((target) => !target.onScreen).slice(0, room));\n const chosen = new Set(inView);\n kept = kept.filter((target) => chosen.has(target) || rest.has(target));\n }\n\n const regions = new Map<string, { label: string | null; targets: number }>();\n for (const target of all) {\n if (!target.region) continue;\n const entry = regions.get(target.region);\n if (entry) entry.targets += 1;\n else {\n const element = target.element.parentElement?.closest(`[${AGENT_ATTR.region}]`);\n const label = element?.getAttribute(AGENT_ATTR.label) ?? element?.getAttribute('aria-label');\n regions.set(target.region, { label: label ? clip(label, MAX_LABEL_CHARS) : null, targets: 1 });\n }\n }\n\n const view = doc.defaultView;\n const listed = kept.map(describeTarget);\n const result = (targets: readonly Record<string, unknown>[]): Record<string, unknown> => {\n const omitted = matching.length - targets.length;\n return {\n page: {\n title: clip(doc.title ?? '', MAX_LABEL_CHARS),\n ...(view ? { path: `${view.location.pathname}${view.location.search}` } : {}),\n },\n ...(regions.size > 0\n ? {\n regions: Array.from(regions, ([id, entry]) => ({\n region: id,\n ...(entry.label ? { label: entry.label } : {}),\n targets: entry.targets,\n })).slice(0, MAX_REGIONS),\n ...(regions.size > MAX_REGIONS ? { more_regions: regions.size - MAX_REGIONS } : {}),\n }\n : {}),\n targets,\n ...(overPerId.size > 0\n ? {\n more_sharing_an_id: Object.fromEntries(overPerId),\n }\n : {}),\n ...(omitted > 0\n ? {\n omitted,\n note: 'Not every target fits one read: narrow it with `region` or `query`.',\n }\n : {}),\n ...(all.length === 0\n ? { note: 'This screen has no agent targets: nothing here can be pointed at or operated.' }\n : matching.length === 0\n ? { note: 'No target matches. Read again without `region` and `query`.' }\n : {}),\n };\n };\n\n // Every field of a target is clipped, so targets are dropped from the end\n // until the answer fits; with none left it still says how many there are.\n let answer = result(listed);\n while (listed.length > 0 && JSON.stringify(answer).length > maxChars) {\n listed.pop();\n answer = result(listed);\n }\n return answer;\n}\n"],"names":["_a","exact"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const SECRET_AUTOCOMPLETE_TOKENS = [
4
+ "current-password",
5
+ "new-password",
6
+ "one-time-code",
7
+ "cc-number",
8
+ "cc-csc"
9
+ ];
10
+ const SECRET_LABEL_PATTERN_SOURCE = [
11
+ "\\b(?:password|passwd|passcode|passphrase|pwd|pin|otp|totp|cvv|cvc|csc|secret|token)\\d*\\b",
12
+ "\\bone time (?:code|password|passcode|pin)\\b",
13
+ "\\b(?:verification|security|authentication|authenticator|auth|2fa|mfa|sms) code\\b",
14
+ "\\b(?:api|access|secret|private) key\\b"
15
+ ].join("|");
16
+ const SECRET_LABEL_PATTERN = new RegExp(SECRET_LABEL_PATTERN_SOURCE, "i");
17
+ function normalizedFieldText(text) {
18
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[^A-Za-z0-9]+/g, " ").toLowerCase();
19
+ }
20
+ function isSecretFieldLabel(text) {
21
+ return SECRET_LABEL_PATTERN.test(normalizedFieldText(text));
22
+ }
23
+ function labelText(element) {
24
+ const own = element.labels;
25
+ const labels = own ? Array.from(own, (node) => node.textContent ?? "").join(" ") : "";
26
+ return [
27
+ element.getAttribute("aria-label"),
28
+ element.getAttribute("placeholder"),
29
+ element.getAttribute("name"),
30
+ element.id,
31
+ labels
32
+ ].join(" ");
33
+ }
34
+ function isSecretField(element) {
35
+ if (element.tagName.toUpperCase() === "INPUT" && (element.getAttribute("type") ?? "").toLowerCase() === "password") {
36
+ return true;
37
+ }
38
+ const hint = (element.getAttribute("autocomplete") ?? "").toLowerCase().split(/\s+/);
39
+ if (hint.some((token) => SECRET_AUTOCOMPLETE_TOKENS.includes(token))) return true;
40
+ const view = element.ownerDocument.defaultView;
41
+ const masking = view == null ? void 0 : view.getComputedStyle(element).getPropertyValue("-webkit-text-security");
42
+ if (masking && masking !== "none") return true;
43
+ return isSecretFieldLabel(labelText(element));
44
+ }
45
+ exports.SECRET_AUTOCOMPLETE_TOKENS = SECRET_AUTOCOMPLETE_TOKENS;
46
+ exports.SECRET_LABEL_PATTERN_SOURCE = SECRET_LABEL_PATTERN_SOURCE;
47
+ exports.isSecretField = isSecretField;
48
+ exports.isSecretFieldLabel = isSecretFieldLabel;
49
+ exports.normalizedFieldText = normalizedFieldText;
50
+ //# sourceMappingURL=secret-fields.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-fields.cjs","sources":["../../src/agent-ui/secret-fields.ts"],"sourcesContent":["/*\n * Fields whose value the agent never reads and never types: a password, a\n * one-time code, a card number or its security code.\n *\n * The rules are the ones Athena's browser tools apply to any web page\n * (`packages/browser-cdp/src/secret-fields.ts`, which this published package\n * cannot import); `secret-fields.test.ts` holds the two together.\n */\n\n/** `autocomplete` tokens that mark a field as holding a secret. */\nexport const SECRET_AUTOCOMPLETE_TOKENS: readonly string[] = [\n 'current-password',\n 'new-password',\n 'one-time-code',\n 'cc-number',\n 'cc-csc',\n];\n\n/** What a secret field's label, name, id or placeholder says. */\nexport const SECRET_LABEL_PATTERN_SOURCE = [\n '\\\\b(?:password|passwd|passcode|passphrase|pwd|pin|otp|totp|cvv|cvc|csc|secret|token)\\\\d*\\\\b',\n '\\\\bone time (?:code|password|passcode|pin)\\\\b',\n '\\\\b(?:verification|security|authentication|authenticator|auth|2fa|mfa|sms) code\\\\b',\n '\\\\b(?:api|access|secret|private) key\\\\b',\n].join('|');\n\nconst SECRET_LABEL_PATTERN = new RegExp(SECRET_LABEL_PATTERN_SOURCE, 'i');\n\n/** `text` as lower-case words: camelCase, runs of capitals and punctuation split. */\nexport function normalizedFieldText(text: string): string {\n return text\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/[^A-Za-z0-9]+/g, ' ')\n .toLowerCase();\n}\n\n/** Whether a field's label, name, id or placeholder says it holds a secret. */\nexport function isSecretFieldLabel(text: string): boolean {\n return SECRET_LABEL_PATTERN.test(normalizedFieldText(text));\n}\n\nfunction labelText(element: Element): string {\n // Read structurally, never with `instanceof`: an element of another\n // document (a popout, an iframe) is not an instance of this one's classes.\n const own = (element as { labels?: ArrayLike<Element> | null }).labels;\n const labels = own ? Array.from(own, (node) => node.textContent ?? '').join(' ') : '';\n return [\n element.getAttribute('aria-label'),\n element.getAttribute('placeholder'),\n element.getAttribute('name'),\n element.id,\n labels,\n ].join(' ');\n}\n\n/**\n * Whether a form control holds a secret: a password input, an `autocomplete`\n * that names a password, a one-time code or a card number or code, a field\n * the page masks, or one whose label, name, id or placeholder says so (which\n * keeps a revealed password hidden, since a reveal toggle keeps the label).\n */\nexport function isSecretField(element: Element): boolean {\n if (\n element.tagName.toUpperCase() === 'INPUT' &&\n (element.getAttribute('type') ?? '').toLowerCase() === 'password'\n ) {\n return true;\n }\n const hint = (element.getAttribute('autocomplete') ?? '').toLowerCase().split(/\\s+/);\n if (hint.some((token) => SECRET_AUTOCOMPLETE_TOKENS.includes(token))) return true;\n const view = element.ownerDocument.defaultView;\n const masking = view?.getComputedStyle(element).getPropertyValue('-webkit-text-security');\n if (masking && masking !== 'none') return true;\n return isSecretFieldLabel(labelText(element));\n}\n"],"names":[],"mappings":";;AAUO,MAAM,6BAAgD;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEV,MAAM,uBAAuB,IAAI,OAAO,6BAA6B,GAAG;AAGjE,SAAS,oBAAoB,MAAsB;AACxD,SAAO,KACJ,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,yBAAyB,OAAO,EACxC,QAAQ,kBAAkB,GAAG,EAC7B,YAAA;AACL;AAGO,SAAS,mBAAmB,MAAuB;AACxD,SAAO,qBAAqB,KAAK,oBAAoB,IAAI,CAAC;AAC5D;AAEA,SAAS,UAAU,SAA0B;AAG3C,QAAM,MAAO,QAAmD;AAChE,QAAM,SAAS,MAAM,MAAM,KAAK,KAAK,CAAC,SAAS,KAAK,eAAe,EAAE,EAAE,KAAK,GAAG,IAAI;AACnF,SAAO;AAAA,IACL,QAAQ,aAAa,YAAY;AAAA,IACjC,QAAQ,aAAa,aAAa;AAAA,IAClC,QAAQ,aAAa,MAAM;AAAA,IAC3B,QAAQ;AAAA,IACR;AAAA,EAAA,EACA,KAAK,GAAG;AACZ;AAQO,SAAS,cAAc,SAA2B;AACvD,MACE,QAAQ,QAAQ,YAAA,MAAkB,YACjC,QAAQ,aAAa,MAAM,KAAK,IAAI,YAAA,MAAkB,YACvD;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,aAAa,cAAc,KAAK,IAAI,YAAA,EAAc,MAAM,KAAK;AACnF,MAAI,KAAK,KAAK,CAAC,UAAU,2BAA2B,SAAS,KAAK,CAAC,EAAG,QAAO;AAC7E,QAAM,OAAO,QAAQ,cAAc;AACnC,QAAM,UAAU,6BAAM,iBAAiB,SAAS,iBAAiB;AACjE,MAAI,WAAW,YAAY,OAAQ,QAAO;AAC1C,SAAO,mBAAmB,UAAU,OAAO,CAAC;AAC9C;;;;;;"}
@@ -0,0 +1,15 @@
1
+ /** `autocomplete` tokens that mark a field as holding a secret. */
2
+ export declare const SECRET_AUTOCOMPLETE_TOKENS: readonly string[];
3
+ /** What a secret field's label, name, id or placeholder says. */
4
+ export declare const SECRET_LABEL_PATTERN_SOURCE: string;
5
+ /** `text` as lower-case words: camelCase, runs of capitals and punctuation split. */
6
+ export declare function normalizedFieldText(text: string): string;
7
+ /** Whether a field's label, name, id or placeholder says it holds a secret. */
8
+ export declare function isSecretFieldLabel(text: string): boolean;
9
+ /**
10
+ * Whether a form control holds a secret: a password input, an `autocomplete`
11
+ * that names a password, a one-time code or a card number or code, a field
12
+ * the page masks, or one whose label, name, id or placeholder says so (which
13
+ * keeps a revealed password hidden, since a reveal toggle keeps the label).
14
+ */
15
+ export declare function isSecretField(element: Element): boolean;