@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,387 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { XIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, MousePointer2Icon } from "lucide-react";
3
+ import { useSyncExternalStore, useEffect, useState, useRef } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { useOwnerDocument, usePortalContainer } from "../lib/owner-document.js";
6
+ import { useAthenaThemeStyle } from "../provider/theme-style-context.js";
7
+ import { AGENT_ATTR } from "./contract.js";
8
+ import { cn } from "cnfast";
9
+ const MARK_INSET_PX = 4;
10
+ const CARD_GAP_PX = 10;
11
+ const CARD_WIDTH_PX = 320;
12
+ const CARD_ROOM_PX = 150;
13
+ const EDGE_PX = 8;
14
+ function useElementBox(element) {
15
+ const [box, setBox] = useState(null);
16
+ useEffect(() => {
17
+ const view = element == null ? void 0 : element.ownerDocument.defaultView;
18
+ if (!element || !view) {
19
+ setBox(null);
20
+ return;
21
+ }
22
+ let frame = 0;
23
+ let last = "";
24
+ const read = () => {
25
+ const rect = element.isConnected ? element.getBoundingClientRect() : null;
26
+ const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;
27
+ const signature = next ? [next.top, next.left, next.width, next.height].map(Math.round).join(":") : "gone";
28
+ if (signature !== last) {
29
+ last = signature;
30
+ setBox(next);
31
+ }
32
+ frame = view.requestAnimationFrame(read);
33
+ };
34
+ read();
35
+ return () => view.cancelAnimationFrame(frame);
36
+ }, [element]);
37
+ return box;
38
+ }
39
+ function viewportOf(doc) {
40
+ const view = doc == null ? void 0 : doc.defaultView;
41
+ return { width: (view == null ? void 0 : view.innerWidth) ?? 1024, height: (view == null ? void 0 : view.innerHeight) ?? 768 };
42
+ }
43
+ function cardPosition({ box, viewport }) {
44
+ const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);
45
+ if (!box) {
46
+ return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };
47
+ }
48
+ const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));
49
+ const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;
50
+ if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {
51
+ return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };
52
+ }
53
+ return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };
54
+ }
55
+ const TONE_CLASS = {
56
+ point: "border-primary ring-primary/25",
57
+ act: "border-primary ring-primary/25 border-dashed",
58
+ ask: "border-amber-500 ring-amber-500/25"
59
+ };
60
+ function Mark({
61
+ box,
62
+ tone,
63
+ spotlight,
64
+ leaving,
65
+ className
66
+ }) {
67
+ return /* @__PURE__ */ jsx(
68
+ "div",
69
+ {
70
+ "data-testid": "agent-ui-mark",
71
+ "data-tone": tone,
72
+ className: cn(
73
+ "pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none",
74
+ TONE_CLASS[tone],
75
+ spotlight && "shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]",
76
+ leaving ? "opacity-0" : "opacity-100",
77
+ className
78
+ ),
79
+ style: {
80
+ top: box.top - MARK_INSET_PX,
81
+ left: box.left - MARK_INSET_PX,
82
+ width: box.width + MARK_INSET_PX * 2,
83
+ height: box.height + MARK_INSET_PX * 2
84
+ }
85
+ }
86
+ );
87
+ }
88
+ function Card({
89
+ box,
90
+ viewport,
91
+ fit = false,
92
+ className,
93
+ children,
94
+ ...props
95
+ }) {
96
+ const { width, ...position } = cardPosition({ box, viewport });
97
+ return /* @__PURE__ */ jsx(
98
+ "div",
99
+ {
100
+ ...props,
101
+ className: cn(
102
+ "pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg",
103
+ className
104
+ ),
105
+ style: fit ? { ...position, maxWidth: width } : { ...position, width },
106
+ children
107
+ }
108
+ );
109
+ }
110
+ function Cursor({
111
+ box,
112
+ presses,
113
+ glideMs,
114
+ className
115
+ }) {
116
+ const x = box.left + box.width / 2;
117
+ const y = box.top + box.height / 2;
118
+ const [arrived, setArrived] = useState(false);
119
+ useEffect(() => {
120
+ const frame = requestAnimationFrame(() => setArrived(true));
121
+ return () => cancelAnimationFrame(frame);
122
+ }, []);
123
+ return /* @__PURE__ */ jsxs(
124
+ "div",
125
+ {
126
+ "data-testid": "agent-ui-cursor",
127
+ className: cn("pointer-events-none fixed top-0 left-0 motion-reduce:transition-none", className),
128
+ style: {
129
+ transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,
130
+ transitionProperty: "transform",
131
+ transitionTimingFunction: "ease-out",
132
+ transitionDuration: `${glideMs}ms`
133
+ },
134
+ children: [
135
+ /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-5 fill-primary text-primary-foreground drop-shadow" }),
136
+ presses > 0 ? /* @__PURE__ */ jsx(
137
+ "span",
138
+ {
139
+ "data-testid": "agent-ui-press",
140
+ className: "-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]"
141
+ },
142
+ presses
143
+ ) : null
144
+ ]
145
+ }
146
+ );
147
+ }
148
+ function TourCard({
149
+ tour,
150
+ stage,
151
+ box,
152
+ viewport,
153
+ className
154
+ }) {
155
+ const step = tour.steps[tour.index];
156
+ const last = tour.index >= tour.steps.length - 1;
157
+ const progressRef = useRef(null);
158
+ const advancing = tour.playing && !tour.searching;
159
+ const durationMs = (step == null ? void 0 : step.durationMs) ?? 0;
160
+ useEffect(() => {
161
+ const bar = progressRef.current;
162
+ if (!bar || typeof bar.animate !== "function") return;
163
+ if (!advancing) return;
164
+ const animation = bar.animate([{ width: "0%" }, { width: "100%" }], {
165
+ duration: durationMs,
166
+ fill: "forwards",
167
+ easing: "linear"
168
+ });
169
+ return () => animation.cancel();
170
+ }, [advancing, durationMs, tour.index]);
171
+ if (!step) return null;
172
+ return /* @__PURE__ */ jsxs(
173
+ Card,
174
+ {
175
+ box,
176
+ viewport,
177
+ className,
178
+ role: "dialog",
179
+ "aria-label": tour.title ?? "Walkthrough",
180
+ "data-testid": "agent-ui-tour",
181
+ children: [
182
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
183
+ /* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-muted-foreground text-xs", children: [
184
+ tour.title ? `${tour.title} · ` : "",
185
+ "Step ",
186
+ tour.index + 1,
187
+ " of ",
188
+ tour.steps.length
189
+ ] }),
190
+ /* @__PURE__ */ jsx(
191
+ "button",
192
+ {
193
+ type: "button",
194
+ "aria-label": "Close the walkthrough",
195
+ onClick: () => stage.endTour(),
196
+ className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
197
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
198
+ }
199
+ )
200
+ ] }),
201
+ /* @__PURE__ */ jsx("p", { className: "break-words pt-1.5 leading-snug", "data-testid": "agent-ui-tour-text", children: step.text }),
202
+ !tour.searching && !tour.element ? /* @__PURE__ */ jsx("p", { className: "pt-1 text-muted-foreground text-xs", children: "This one is not on the page you have open." }) : null,
203
+ /* @__PURE__ */ jsx("div", { className: "mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx("div", { ref: progressRef, className: "h-full w-0 bg-primary" }) }),
204
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 pt-2", children: [
205
+ /* @__PURE__ */ jsx(
206
+ "button",
207
+ {
208
+ type: "button",
209
+ "aria-label": "Previous step",
210
+ disabled: tour.index === 0,
211
+ onClick: () => stage.tourBack(),
212
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
213
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-4" })
214
+ }
215
+ ),
216
+ /* @__PURE__ */ jsx(
217
+ "button",
218
+ {
219
+ type: "button",
220
+ "aria-label": tour.playing ? "Pause the walkthrough" : "Play the walkthrough",
221
+ onClick: () => stage.tourTogglePlaying(),
222
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
223
+ children: tour.playing ? /* @__PURE__ */ jsx(PauseIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(PlayIcon, { className: "size-4" })
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsxs(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ onClick: () => stage.tourNext(),
231
+ className: "ml-auto flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90",
232
+ children: [
233
+ last ? "Done" : "Next",
234
+ last ? null : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
235
+ ]
236
+ }
237
+ )
238
+ ] })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ function QuestionCard({
244
+ question,
245
+ stage,
246
+ box,
247
+ viewport,
248
+ className
249
+ }) {
250
+ return /* @__PURE__ */ jsxs(
251
+ Card,
252
+ {
253
+ box,
254
+ viewport,
255
+ className,
256
+ role: "alertdialog",
257
+ "aria-label": question.title,
258
+ "data-testid": "agent-ui-question",
259
+ children: [
260
+ /* @__PURE__ */ jsx("p", { className: "break-words font-semibold leading-snug", children: question.title }),
261
+ question.body ? /* @__PURE__ */ jsx("p", { className: "break-words pt-1 text-muted-foreground leading-snug", children: question.body }) : null,
262
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2.5", children: [
263
+ /* @__PURE__ */ jsx(
264
+ "button",
265
+ {
266
+ type: "button",
267
+ onClick: () => stage.answer(true),
268
+ className: "rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90",
269
+ children: question.allowLabel
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(
273
+ "button",
274
+ {
275
+ type: "button",
276
+ onClick: () => stage.answer(false),
277
+ className: "rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted",
278
+ children: question.declineLabel
279
+ }
280
+ )
281
+ ] })
282
+ ]
283
+ }
284
+ );
285
+ }
286
+ function AgentUiLayer({ agentUi, classNames }) {
287
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
288
+ const { stage } = agentUi;
289
+ const state = useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);
290
+ const ownerDocument = useOwnerDocument();
291
+ const portalContainer = usePortalContainer();
292
+ const themeStyle = useAthenaThemeStyle();
293
+ const tourElement = ((_a = state.tour) == null ? void 0 : _a.element) ?? null;
294
+ const markedElement = state.tour ? tourElement : ((_b = state.mark) == null ? void 0 : _b.element) ?? null;
295
+ const markBox = useElementBox(markedElement);
296
+ const cursorBox = useElementBox(((_c = state.cursor) == null ? void 0 : _c.element) ?? null);
297
+ const questionBox = useElementBox(((_d = state.question) == null ? void 0 : _d.element) ?? null);
298
+ const showing = state.tour !== null || state.mark !== null;
299
+ useEffect(() => {
300
+ if (!showing || !ownerDocument) return;
301
+ const onKeyDown = (event) => {
302
+ if (event.key !== "Escape") return;
303
+ stage.endTour();
304
+ stage.clearMark();
305
+ };
306
+ ownerDocument.addEventListener("keydown", onKeyDown);
307
+ return () => ownerDocument.removeEventListener("keydown", onKeyDown);
308
+ }, [ownerDocument, showing, stage]);
309
+ const container = portalContainer ?? (ownerDocument == null ? void 0 : ownerDocument.body);
310
+ if (!container) return null;
311
+ const idle = !state.mark && !state.cursor && !state.tour && !state.question;
312
+ if (idle) return null;
313
+ const viewport = viewportOf(ownerDocument);
314
+ const caption = state.tour ? null : ((_e = state.mark) == null ? void 0 : _e.caption) ?? null;
315
+ return createPortal(
316
+ /* @__PURE__ */ jsxs(
317
+ "div",
318
+ {
319
+ ...{ [AGENT_ATTR.layer]: "" },
320
+ className: "athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]",
321
+ style: themeStyle,
322
+ children: [
323
+ markBox && (state.tour || state.mark) ? /* @__PURE__ */ jsx(
324
+ Mark,
325
+ {
326
+ box: markBox,
327
+ tone: state.tour ? "point" : ((_f = state.mark) == null ? void 0 : _f.tone) ?? "point",
328
+ spotlight: state.tour ? false : ((_g = state.mark) == null ? void 0 : _g.spotlight) ?? false,
329
+ leaving: state.tour ? false : ((_h = state.mark) == null ? void 0 : _h.leaving) ?? false,
330
+ className: classNames == null ? void 0 : classNames.mark
331
+ }
332
+ ) : null,
333
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", children: caption && markBox && !state.question ? /* @__PURE__ */ jsx(
334
+ Card,
335
+ {
336
+ box: markBox,
337
+ viewport,
338
+ fit: true,
339
+ className: cn(
340
+ "pointer-events-none transition-opacity duration-500 motion-reduce:transition-none",
341
+ ((_i = state.mark) == null ? void 0 : _i.leaving) ? "opacity-0" : "opacity-100",
342
+ classNames == null ? void 0 : classNames.caption
343
+ ),
344
+ "data-testid": "agent-ui-caption",
345
+ children: /* @__PURE__ */ jsx("p", { className: "break-words leading-snug", children: caption })
346
+ }
347
+ ) : null }),
348
+ state.tour ? /* @__PURE__ */ jsx(
349
+ TourCard,
350
+ {
351
+ tour: state.tour,
352
+ stage,
353
+ box: markBox,
354
+ viewport,
355
+ className: classNames == null ? void 0 : classNames.tour
356
+ }
357
+ ) : null,
358
+ state.question ? /* @__PURE__ */ jsx(
359
+ QuestionCard,
360
+ {
361
+ question: state.question,
362
+ stage,
363
+ box: questionBox,
364
+ viewport,
365
+ className: classNames == null ? void 0 : classNames.question
366
+ }
367
+ ) : null,
368
+ state.cursor && cursorBox ? /* @__PURE__ */ jsx(
369
+ Cursor,
370
+ {
371
+ box: cursorBox,
372
+ presses: state.cursor.presses,
373
+ glideMs: agentUi.pace().cursorGlideMs,
374
+ className: classNames == null ? void 0 : classNames.cursor
375
+ }
376
+ ) : null
377
+ ]
378
+ }
379
+ ),
380
+ container
381
+ );
382
+ }
383
+ export {
384
+ AgentUiLayer,
385
+ cardPosition
386
+ };
387
+ //# sourceMappingURL=AgentUiLayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentUiLayer.js","sources":["../../src/agent-ui/AgentUiLayer.tsx"],"sourcesContent":["'use client';\n\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n MousePointer2Icon,\n PauseIcon,\n PlayIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type CSSProperties,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useOwnerDocument, usePortalContainer } from '../lib/owner-document';\nimport { cn } from '../lib/utils';\nimport { useAthenaThemeStyle } from '../provider/theme-style-context';\nimport { AGENT_ATTR } from './contract';\nimport type { AgentUiMarkTone, AgentUiQuestion, AgentUiStage, AgentUiTour } from './stage';\nimport type { AgentUi } from './tools';\n\n/*\n * The overlay: what the person sees of the agent on their page. A box around\n * the element it means, a note beside it, a cursor where it clicks, the\n * walkthrough's card and the question it asks before acting.\n *\n * It draws the stage and nothing else. Every piece is restyled through\n * `classNames`; a host that wants something else entirely reads\n * `agentUi.stage` and draws its own.\n */\n\nexport interface AgentUiLayerClassNames {\n /** The box around the element. */\n mark?: string;\n /** The note beside a marked element. */\n caption?: string;\n /** The agent's cursor. */\n cursor?: string;\n /** The walkthrough's card. */\n tour?: string;\n /** The question asked before acting. */\n question?: string;\n}\n\nexport interface AgentUiLayerProps {\n agentUi: AgentUi;\n classNames?: AgentUiLayerClassNames;\n}\n\ninterface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Space between an element and the box drawn around it. */\nconst MARK_INSET_PX = 4;\n/** Space between the box and a card anchored to it. */\nconst CARD_GAP_PX = 10;\n/** A card's width, and the room a card needs below an element to sit there. */\nconst CARD_WIDTH_PX = 320;\nconst CARD_ROOM_PX = 150;\nconst EDGE_PX = 8;\n\n/** Follow an element's box frame by frame while it is shown: it rides scrolling and layout changes. */\nfunction useElementBox(element: Element | null): Box | null {\n const [box, setBox] = useState<Box | null>(null);\n useEffect(() => {\n const view = element?.ownerDocument.defaultView;\n if (!element || !view) {\n setBox(null);\n return;\n }\n let frame = 0;\n let last = '';\n const read = () => {\n const rect = element.isConnected ? element.getBoundingClientRect() : null;\n const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;\n const signature = next\n ? [next.top, next.left, next.width, next.height].map(Math.round).join(':')\n : 'gone';\n if (signature !== last) {\n last = signature;\n setBox(next);\n }\n frame = view.requestAnimationFrame(read);\n };\n read();\n return () => view.cancelAnimationFrame(frame);\n }, [element]);\n return box;\n}\n\ninterface Viewport {\n width: number;\n height: number;\n}\n\nfunction viewportOf(doc: Document | null): Viewport {\n const view = doc?.defaultView;\n return { width: view?.innerWidth ?? 1024, height: view?.innerHeight ?? 768 };\n}\n\n/** Where a card anchored to an element sits: under it when there is room, else over it; at the bottom of the page without one. */\nexport function cardPosition({ box, viewport }: { box: Box | null; viewport: Viewport }): CSSProperties {\n const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);\n if (!box) {\n return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };\n }\n const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));\n const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;\n if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {\n return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };\n }\n return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };\n}\n\nconst TONE_CLASS: Record<AgentUiMarkTone, string> = {\n point: 'border-primary ring-primary/25',\n act: 'border-primary ring-primary/25 border-dashed',\n ask: 'border-amber-500 ring-amber-500/25',\n};\n\nfunction Mark({\n box,\n tone,\n spotlight,\n leaving,\n className,\n}: {\n box: Box;\n tone: AgentUiMarkTone;\n spotlight: boolean;\n leaving: boolean;\n className?: string;\n}) {\n return (\n <div\n data-testid=\"agent-ui-mark\"\n data-tone={tone}\n className={cn(\n 'pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none',\n TONE_CLASS[tone],\n spotlight && 'shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]',\n leaving ? 'opacity-0' : 'opacity-100',\n className,\n )}\n style={{\n top: box.top - MARK_INSET_PX,\n left: box.left - MARK_INSET_PX,\n width: box.width + MARK_INSET_PX * 2,\n height: box.height + MARK_INSET_PX * 2,\n }}\n />\n );\n}\n\nfunction Card({\n box,\n viewport,\n fit = false,\n className,\n children,\n ...props\n}: {\n box: Box | null;\n viewport: Viewport;\n /** As wide as its words need, up to a card's width (a note); otherwise a card's full width. */\n fit?: boolean;\n className?: string;\n children: ReactNode;\n role?: string;\n 'aria-label'?: string;\n 'data-testid'?: string;\n}) {\n const { width, ...position } = cardPosition({ box, viewport });\n return (\n <div\n {...props}\n className={cn(\n 'pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg',\n className,\n )}\n style={fit ? { ...position, maxWidth: width } : { ...position, width }}\n >\n {children}\n </div>\n );\n}\n\nfunction Cursor({\n box,\n presses,\n glideMs,\n className,\n}: {\n box: Box;\n presses: number;\n glideMs: number;\n className?: string;\n}) {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n // The cursor starts a little off the element and glides onto it.\n const [arrived, setArrived] = useState(false);\n useEffect(() => {\n const frame = requestAnimationFrame(() => setArrived(true));\n return () => cancelAnimationFrame(frame);\n }, []);\n return (\n <div\n data-testid=\"agent-ui-cursor\"\n className={cn('pointer-events-none fixed top-0 left-0 motion-reduce:transition-none', className)}\n style={{\n transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,\n transitionProperty: 'transform',\n transitionTimingFunction: 'ease-out',\n transitionDuration: `${glideMs}ms`,\n }}\n >\n <MousePointer2Icon className=\"size-5 fill-primary text-primary-foreground drop-shadow\" />\n {presses > 0 ? (\n <span\n key={presses}\n data-testid=\"agent-ui-press\"\n className=\"-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]\"\n />\n ) : null}\n </div>\n );\n}\n\nfunction TourCard({\n tour,\n stage,\n box,\n viewport,\n className,\n}: {\n tour: AgentUiTour;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n const step = tour.steps[tour.index];\n const last = tour.index >= tour.steps.length - 1;\n const progressRef = useRef<HTMLDivElement>(null);\n const advancing = tour.playing && !tour.searching;\n const durationMs = step?.durationMs ?? 0;\n // The bar fills over the step's time, so the person sees it will move on by itself.\n // `tour.index` restarts it for a step of the same length.\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new step restarts the bar\n useEffect(() => {\n const bar = progressRef.current;\n if (!bar || typeof bar.animate !== 'function') return;\n if (!advancing) return;\n const animation = bar.animate([{ width: '0%' }, { width: '100%' }], {\n duration: durationMs,\n fill: 'forwards',\n easing: 'linear',\n });\n return () => animation.cancel();\n }, [advancing, durationMs, tour.index]);\n if (!step) return null;\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"dialog\"\n aria-label={tour.title ?? 'Walkthrough'}\n data-testid=\"agent-ui-tour\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"min-w-0 truncate text-muted-foreground text-xs\">\n {tour.title ? `${tour.title} · ` : ''}\n Step {tour.index + 1} of {tour.steps.length}\n </p>\n <button\n type=\"button\"\n aria-label=\"Close the walkthrough\"\n onClick={() => stage.endTour()}\n className=\"shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n <p className=\"break-words pt-1.5 leading-snug\" data-testid=\"agent-ui-tour-text\">\n {step.text}\n </p>\n {!tour.searching && !tour.element ? (\n <p className=\"pt-1 text-muted-foreground text-xs\">This one is not on the page you have open.</p>\n ) : null}\n <div className=\"mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted\">\n <div ref={progressRef} className=\"h-full w-0 bg-primary\" />\n </div>\n <div className=\"flex items-center gap-1 pt-2\">\n <button\n type=\"button\"\n aria-label=\"Previous step\"\n disabled={tour.index === 0}\n onClick={() => stage.tourBack()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40\"\n >\n <ChevronLeftIcon className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={tour.playing ? 'Pause the walkthrough' : 'Play the walkthrough'}\n onClick={() => stage.tourTogglePlaying()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n {tour.playing ? <PauseIcon className=\"size-4\" /> : <PlayIcon className=\"size-4\" />}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.tourNext()}\n className=\"ml-auto flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {last ? 'Done' : 'Next'}\n {last ? null : <ChevronRightIcon className=\"size-3.5\" />}\n </button>\n </div>\n </Card>\n );\n}\n\nfunction QuestionCard({\n question,\n stage,\n box,\n viewport,\n className,\n}: {\n question: AgentUiQuestion;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"alertdialog\"\n aria-label={question.title}\n data-testid=\"agent-ui-question\"\n >\n <p className=\"break-words font-semibold leading-snug\">{question.title}</p>\n {question.body ? (\n <p className=\"break-words pt-1 text-muted-foreground leading-snug\">{question.body}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2 pt-2.5\">\n <button\n type=\"button\"\n onClick={() => stage.answer(true)}\n className=\"rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {question.allowLabel}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.answer(false)}\n className=\"rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted\"\n >\n {question.declineLabel}\n </button>\n </div>\n </Card>\n );\n}\n\n/**\n * Draws the agent's stage over the page. Mount one per `AgentUi`, anywhere\n * under the provider; it portals to the document's body and takes no clicks\n * except on its own cards.\n */\nexport function AgentUiLayer({ agentUi, classNames }: AgentUiLayerProps) {\n const { stage } = agentUi;\n const state = useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);\n const ownerDocument = useOwnerDocument();\n const portalContainer = usePortalContainer();\n const themeStyle = useAthenaThemeStyle();\n\n const tourElement = state.tour?.element ?? null;\n const markedElement = state.tour ? tourElement : (state.mark?.element ?? null);\n const markBox = useElementBox(markedElement);\n const cursorBox = useElementBox(state.cursor?.element ?? null);\n const questionBox = useElementBox(state.question?.element ?? null);\n\n // Escape closes what the agent is showing: the person's page is theirs.\n const showing = state.tour !== null || state.mark !== null;\n useEffect(() => {\n if (!showing || !ownerDocument) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n stage.endTour();\n stage.clearMark();\n };\n ownerDocument.addEventListener('keydown', onKeyDown);\n return () => ownerDocument.removeEventListener('keydown', onKeyDown);\n }, [ownerDocument, showing, stage]);\n\n const container = portalContainer ?? ownerDocument?.body;\n if (!container) return null;\n const idle = !state.mark && !state.cursor && !state.tour && !state.question;\n if (idle) return null;\n const viewport = viewportOf(ownerDocument);\n const caption = state.tour ? null : (state.mark?.caption ?? null);\n\n return createPortal(\n <div\n {...{ [AGENT_ATTR.layer]: '' }}\n className=\"athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]\"\n style={themeStyle as CSSProperties | undefined}\n >\n {markBox && (state.tour || state.mark) ? (\n <Mark\n box={markBox}\n tone={state.tour ? 'point' : (state.mark?.tone ?? 'point')}\n spotlight={state.tour ? false : (state.mark?.spotlight ?? false)}\n leaving={state.tour ? false : (state.mark?.leaving ?? false)}\n className={classNames?.mark}\n />\n ) : null}\n {/* What the agent is doing, said aloud for a screen reader too. */}\n <div aria-live=\"polite\">\n {caption && markBox && !state.question ? (\n <Card\n box={markBox}\n viewport={viewport}\n fit\n className={cn(\n 'pointer-events-none transition-opacity duration-500 motion-reduce:transition-none',\n state.mark?.leaving ? 'opacity-0' : 'opacity-100',\n classNames?.caption,\n )}\n data-testid=\"agent-ui-caption\"\n >\n <p className=\"break-words leading-snug\">{caption}</p>\n </Card>\n ) : null}\n </div>\n {state.tour ? (\n <TourCard\n tour={state.tour}\n stage={stage}\n box={markBox}\n viewport={viewport}\n className={classNames?.tour}\n />\n ) : null}\n {state.question ? (\n <QuestionCard\n question={state.question}\n stage={stage}\n box={questionBox}\n viewport={viewport}\n className={classNames?.question}\n />\n ) : null}\n {state.cursor && cursorBox ? (\n <Cursor\n box={cursorBox}\n presses={state.cursor.presses}\n glideMs={agentUi.pace().cursorGlideMs}\n className={classNames?.cursor}\n />\n ) : null}\n </div>,\n container,\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const AGENT_TARGET_ACTIONS = ["click", "type", "select", "toggle", "clear"];
4
+ function isAgentTargetAction(value) {
5
+ return AGENT_TARGET_ACTIONS.some((action) => action === value);
6
+ }
7
+ const AGENT_ATTR = {
8
+ /** The target's id, stable across renders (`investors.search`). */
9
+ target: "data-agent-target",
10
+ /** Which one of several targets sharing an id (a row's key). */
11
+ key: "data-agent-key",
12
+ /** The name the person would call it. Falls back to the element's own accessible name. */
13
+ label: "data-agent-label",
14
+ /** One line for the agent on what it is for. */
15
+ description: "data-agent-description",
16
+ /** Space-separated actions the agent may take, or `none`. Derived from the element when absent. */
17
+ actions: "data-agent-actions",
18
+ /** Present: acting on it changes or destroys something, so the person confirms every time. */
19
+ danger: "data-agent-danger",
20
+ /** Present: its value is never read and never typed. */
21
+ sensitive: "data-agent-sensitive",
22
+ /** On a container: a named part of the screen its targets belong to. */
23
+ region: "data-agent-region",
24
+ /** Present on the overlay's own root: never a target, and input inside it is the overlay's. */
25
+ layer: "data-agent-ui-layer"
26
+ };
27
+ const TARGET_ID_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
28
+ const NO_PROPS = {};
29
+ function isAgentTargetId(id) {
30
+ return id.length <= 80 && TARGET_ID_PATTERN.test(id);
31
+ }
32
+ function agentTarget(options) {
33
+ if (!isAgentTargetId(options.id)) {
34
+ console.error(
35
+ `agentTarget: "${options.id}" is not a target id, so the element is not a target. Use lowercase words joined by ".", "-" or "_" (e.g. "investors.search").`
36
+ );
37
+ return NO_PROPS;
38
+ }
39
+ const props = { [AGENT_ATTR.target]: options.id };
40
+ if (options.key !== void 0 && options.key !== "") props[AGENT_ATTR.key] = options.key;
41
+ if (options.label) props[AGENT_ATTR.label] = options.label;
42
+ if (options.description) props[AGENT_ATTR.description] = options.description;
43
+ if (options.actions) {
44
+ props[AGENT_ATTR.actions] = options.actions.length > 0 ? options.actions.join(" ") : "none";
45
+ }
46
+ if (options.danger) props[AGENT_ATTR.danger] = "";
47
+ if (options.sensitive) props[AGENT_ATTR.sensitive] = "";
48
+ return props;
49
+ }
50
+ function agentRegion({ id, label }) {
51
+ if (!isAgentTargetId(id)) {
52
+ console.error(`agentRegion: "${id}" is not a region id (e.g. "filters"), so the element names no region.`);
53
+ return NO_PROPS;
54
+ }
55
+ return label ? { [AGENT_ATTR.region]: id, [AGENT_ATTR.label]: label } : { [AGENT_ATTR.region]: id };
56
+ }
57
+ function agentTargetRef({ id, key }) {
58
+ return key ? `${id}[${key}]` : id;
59
+ }
60
+ function parseAgentTargetRef(ref) {
61
+ const text = ref.trim();
62
+ const match = /^([^[\]]+)(?:\[(.+)\])?$/.exec(text);
63
+ if (!(match == null ? void 0 : match[1])) return null;
64
+ return { id: match[1].trim(), key: match[2] ?? null };
65
+ }
66
+ const HANDLER_REGISTRY_KEY = Symbol.for("athena.agentUi.targetHandlers");
67
+ function handlerRegistry() {
68
+ const holder = globalThis;
69
+ let registry = holder[HANDLER_REGISTRY_KEY];
70
+ if (!registry) {
71
+ registry = /* @__PURE__ */ new Map();
72
+ holder[HANDLER_REGISTRY_KEY] = registry;
73
+ }
74
+ return registry;
75
+ }
76
+ function registerAgentTargetHandlers({
77
+ ref,
78
+ handlers
79
+ }) {
80
+ const registry = handlerRegistry();
81
+ if (registry.has(ref)) {
82
+ console.error(
83
+ `useAgentTarget: two mounted targets share "${ref}" and both register handlers. Give each its own key.`
84
+ );
85
+ }
86
+ registry.set(ref, handlers);
87
+ return () => {
88
+ if (registry.get(ref) === handlers) registry.delete(ref);
89
+ };
90
+ }
91
+ function agentTargetHandlers(ref) {
92
+ return handlerRegistry().get(ref);
93
+ }
94
+ exports.AGENT_ATTR = AGENT_ATTR;
95
+ exports.AGENT_TARGET_ACTIONS = AGENT_TARGET_ACTIONS;
96
+ exports.agentRegion = agentRegion;
97
+ exports.agentTarget = agentTarget;
98
+ exports.agentTargetHandlers = agentTargetHandlers;
99
+ exports.agentTargetRef = agentTargetRef;
100
+ exports.isAgentTargetAction = isAgentTargetAction;
101
+ exports.isAgentTargetId = isAgentTargetId;
102
+ exports.parseAgentTargetRef = parseAgentTargetRef;
103
+ exports.registerAgentTargetHandlers = registerAgentTargetHandlers;
104
+ //# sourceMappingURL=contract.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.cjs","sources":["../../src/agent-ui/contract.ts"],"sourcesContent":["/*\n * Agent targets: the elements of an app the agent may see, point at and\n * operate on the screen the person has open.\n *\n * The contract is a handful of `data-agent-*` attributes on the element\n * itself. The DOM is the registry: what is in the document is what is on\n * screen, list rows and third-party components need nothing special, and a\n * plain web component can take part without React. `agentTarget()` writes the\n * attributes with types; `useAgentTarget()` also registers handlers for a\n * control that ordinary clicks and typing cannot drive.\n *\n * The app decides what the agent can touch: an element without\n * `data-agent-target` does not exist for the agent, a target lists the actions\n * it allows, and one marked dangerous or sensitive is treated as such by every\n * tool. Nothing here is a permission boundary. The tools drive the same\n * handlers a click would, as the signed-in person.\n */\n\n/** What the agent may do to a target, beyond pointing at it. */\nexport const AGENT_TARGET_ACTIONS = ['click', 'type', 'select', 'toggle', 'clear'] as const;\nexport type AgentTargetAction = (typeof AGENT_TARGET_ACTIONS)[number];\n\nexport function isAgentTargetAction(value: unknown): value is AgentTargetAction {\n return AGENT_TARGET_ACTIONS.some((action) => action === value);\n}\n\n/** The attribute names of the contract. */\nexport const AGENT_ATTR = {\n /** The target's id, stable across renders (`investors.search`). */\n target: 'data-agent-target',\n /** Which one of several targets sharing an id (a row's key). */\n key: 'data-agent-key',\n /** The name the person would call it. Falls back to the element's own accessible name. */\n label: 'data-agent-label',\n /** One line for the agent on what it is for. */\n description: 'data-agent-description',\n /** Space-separated actions the agent may take, or `none`. Derived from the element when absent. */\n actions: 'data-agent-actions',\n /** Present: acting on it changes or destroys something, so the person confirms every time. */\n danger: 'data-agent-danger',\n /** Present: its value is never read and never typed. */\n sensitive: 'data-agent-sensitive',\n /** On a container: a named part of the screen its targets belong to. */\n region: 'data-agent-region',\n /** Present on the overlay's own root: never a target, and input inside it is the overlay's. */\n layer: 'data-agent-ui-layer',\n} as const;\n\n/** What an app says about one target. */\nexport interface AgentTargetOptions {\n /**\n * The target's id: lowercase words joined by `.`, `-` or `_`\n * (`investors.search`, `filters.stage`). Stable across renders and visits,\n * since the agent names targets by it.\n */\n id: string;\n /** Which one, when several targets share the id (a table row's key). */\n key?: string;\n /** The name a person would call it (\"Search investors\"). */\n label?: string;\n /** One line for the agent: what it is for, or what using it does. */\n description?: string;\n /**\n * What the agent may do to it. Absent: what the element allows by its\n * nature (a button is clicked, a text field typed into). An empty list: the\n * agent may only point at it.\n */\n actions?: readonly AgentTargetAction[];\n /** Acting on it changes or destroys something: the person confirms every time. */\n danger?: boolean;\n /** Its value is never read back to the agent and never typed by it. */\n sensitive?: boolean;\n}\n\n/** The props an element takes to become a target. */\nexport type AgentTargetProps = Readonly<Record<string, string>>;\n\nconst TARGET_ID_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;\nconst NO_PROPS: AgentTargetProps = {};\n\n/** Whether `id` is a well-formed target id. */\nexport function isAgentTargetId(id: string): boolean {\n return id.length <= 80 && TARGET_ID_PATTERN.test(id);\n}\n\n/**\n * The props that make an element an agent target. Spread them on the element\n * (any component that passes `data-*` props to its root works):\n *\n * ```tsx\n * <input {...agentTarget({ id: 'investors.search', label: 'Search investors' })} />\n * {rows.map((row) => (\n * <tr key={row.id} {...agentTarget({ id: 'investors.row', key: row.id, label: row.name })} />\n * ))}\n * ```\n */\nexport function agentTarget(options: AgentTargetOptions): AgentTargetProps {\n if (!isAgentTargetId(options.id)) {\n // An id built from data must never take the page down with it: the\n // element is simply not a target, and the console says why.\n console.error(\n `agentTarget: \"${options.id}\" is not a target id, so the element is not a target. Use lowercase words joined by \".\", \"-\" or \"_\" (e.g. \"investors.search\").`,\n );\n return NO_PROPS;\n }\n const props: Record<string, string> = { [AGENT_ATTR.target]: options.id };\n if (options.key !== undefined && options.key !== '') props[AGENT_ATTR.key] = options.key;\n if (options.label) props[AGENT_ATTR.label] = options.label;\n if (options.description) props[AGENT_ATTR.description] = options.description;\n if (options.actions) {\n props[AGENT_ATTR.actions] = options.actions.length > 0 ? options.actions.join(' ') : 'none';\n }\n if (options.danger) props[AGENT_ATTR.danger] = '';\n if (options.sensitive) props[AGENT_ATTR.sensitive] = '';\n return props;\n}\n\n/** The props that name a part of the screen; targets inside it are grouped under it. */\nexport function agentRegion({ id, label }: { id: string; label?: string }): AgentTargetProps {\n if (!isAgentTargetId(id)) {\n console.error(`agentRegion: \"${id}\" is not a region id (e.g. \"filters\"), so the element names no region.`);\n return NO_PROPS;\n }\n return label ? { [AGENT_ATTR.region]: id, [AGENT_ATTR.label]: label } : { [AGENT_ATTR.region]: id };\n}\n\n/** How the agent names one target: its id, with its key in brackets when it has one. */\nexport function agentTargetRef({ id, key }: { id: string; key?: string | null }): string {\n return key ? `${id}[${key}]` : id;\n}\n\n/** A reference split into id and key; `null` when it is not one. */\nexport function parseAgentTargetRef(ref: string): { id: string; key: string | null } | null {\n const text = ref.trim();\n const match = /^([^[\\]]+)(?:\\[(.+)\\])?$/.exec(text);\n if (!match?.[1]) return null;\n return { id: match[1].trim(), key: match[2] ?? null };\n}\n\n/**\n * What a target does when ordinary input cannot drive it (a custom select, a\n * date picker, a canvas). A handler here replaces the tool's own click or\n * keystrokes for that action; it runs the app's own code, so validation and\n * undo apply as for a person.\n */\nexport interface AgentTargetHandlers {\n click?: () => void | Promise<void>;\n /** Called with the text so far as it is typed, then once more with `done: true`. */\n type?: (args: { text: string; done: boolean }) => void | Promise<void>;\n select?: (args: { option: string }) => void | Promise<void>;\n toggle?: (args: { checked: boolean | null }) => void | Promise<void>;\n clear?: () => void | Promise<void>;\n /** The options a `select` may be given, when the element is not a native `<select>`. */\n options?: () => readonly string[];\n /** The target's current value, as the agent should read it. */\n value?: () => string | null;\n}\n\nconst HANDLER_REGISTRY_KEY = Symbol.for('athena.agentUi.targetHandlers');\n\ntype HandlerRegistry = Map<string, AgentTargetHandlers>;\n\n/**\n * One registry per page, on `globalThis`: a host can carry more than one copy\n * of this module (the SDK bundles its own), and a handler registered through\n * one must be found by the tools of another.\n */\nfunction handlerRegistry(): HandlerRegistry {\n const holder = globalThis as typeof globalThis & { [HANDLER_REGISTRY_KEY]?: HandlerRegistry };\n let registry = holder[HANDLER_REGISTRY_KEY];\n if (!registry) {\n registry = new Map();\n holder[HANDLER_REGISTRY_KEY] = registry;\n }\n return registry;\n}\n\n/** Register a target's handlers; returns the unregister function. */\nexport function registerAgentTargetHandlers({\n ref,\n handlers,\n}: {\n ref: string;\n handlers: AgentTargetHandlers;\n}): () => void {\n const registry = handlerRegistry();\n if (registry.has(ref)) {\n // Handlers are found by reference: with two mounted targets sharing one,\n // the agent would be shown one and run the other's.\n console.error(\n `useAgentTarget: two mounted targets share \"${ref}\" and both register handlers. Give each its own key.`,\n );\n }\n registry.set(ref, handlers);\n return () => {\n if (registry.get(ref) === handlers) registry.delete(ref);\n };\n}\n\n/** The handlers registered for a target reference, if any. */\nexport function agentTargetHandlers(ref: string): AgentTargetHandlers | undefined {\n return handlerRegistry().get(ref);\n}\n"],"names":[],"mappings":";;AAmBO,MAAM,uBAAuB,CAAC,SAAS,QAAQ,UAAU,UAAU,OAAO;AAG1E,SAAS,oBAAoB,OAA4C;AAC9E,SAAO,qBAAqB,KAAK,CAAC,WAAW,WAAW,KAAK;AAC/D;AAGO,MAAM,aAAa;AAAA;AAAA,EAExB,QAAQ;AAAA;AAAA,EAER,KAAK;AAAA;AAAA,EAEL,OAAO;AAAA;AAAA,EAEP,aAAa;AAAA;AAAA,EAEb,SAAS;AAAA;AAAA,EAET,QAAQ;AAAA;AAAA,EAER,WAAW;AAAA;AAAA,EAEX,QAAQ;AAAA;AAAA,EAER,OAAO;AACT;AA+BA,MAAM,oBAAoB;AAC1B,MAAM,WAA6B,CAAA;AAG5B,SAAS,gBAAgB,IAAqB;AACnD,SAAO,GAAG,UAAU,MAAM,kBAAkB,KAAK,EAAE;AACrD;AAaO,SAAS,YAAY,SAA+C;AACzE,MAAI,CAAC,gBAAgB,QAAQ,EAAE,GAAG;AAGhC,YAAQ;AAAA,MACN,iBAAiB,QAAQ,EAAE;AAAA,IAAA;AAE7B,WAAO;AAAA,EACT;AACA,QAAM,QAAgC,EAAE,CAAC,WAAW,MAAM,GAAG,QAAQ,GAAA;AACrE,MAAI,QAAQ,QAAQ,UAAa,QAAQ,QAAQ,GAAI,OAAM,WAAW,GAAG,IAAI,QAAQ;AACrF,MAAI,QAAQ,MAAO,OAAM,WAAW,KAAK,IAAI,QAAQ;AACrD,MAAI,QAAQ,YAAa,OAAM,WAAW,WAAW,IAAI,QAAQ;AACjE,MAAI,QAAQ,SAAS;AACnB,UAAM,WAAW,OAAO,IAAI,QAAQ,QAAQ,SAAS,IAAI,QAAQ,QAAQ,KAAK,GAAG,IAAI;AAAA,EACvF;AACA,MAAI,QAAQ,OAAQ,OAAM,WAAW,MAAM,IAAI;AAC/C,MAAI,QAAQ,UAAW,OAAM,WAAW,SAAS,IAAI;AACrD,SAAO;AACT;AAGO,SAAS,YAAY,EAAE,IAAI,SAA2D;AAC3F,MAAI,CAAC,gBAAgB,EAAE,GAAG;AACxB,YAAQ,MAAM,iBAAiB,EAAE,wEAAwE;AACzG,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,EAAE,CAAC,WAAW,MAAM,GAAG,IAAI,CAAC,WAAW,KAAK,GAAG,UAAU,EAAE,CAAC,WAAW,MAAM,GAAG,GAAA;AACjG;AAGO,SAAS,eAAe,EAAE,IAAI,OAAoD;AACvF,SAAO,MAAM,GAAG,EAAE,IAAI,GAAG,MAAM;AACjC;AAGO,SAAS,oBAAoB,KAAwD;AAC1F,QAAM,OAAO,IAAI,KAAA;AACjB,QAAM,QAAQ,2BAA2B,KAAK,IAAI;AAClD,MAAI,EAAC,+BAAQ,IAAI,QAAO;AACxB,SAAO,EAAE,IAAI,MAAM,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAC,KAAK,KAAA;AACjD;AAqBA,MAAM,uBAAuB,OAAO,IAAI,+BAA+B;AASvE,SAAS,kBAAmC;AAC1C,QAAM,SAAS;AACf,MAAI,WAAW,OAAO,oBAAoB;AAC1C,MAAI,CAAC,UAAU;AACb,mCAAe,IAAA;AACf,WAAO,oBAAoB,IAAI;AAAA,EACjC;AACA,SAAO;AACT;AAGO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AACF,GAGe;AACb,QAAM,WAAW,gBAAA;AACjB,MAAI,SAAS,IAAI,GAAG,GAAG;AAGrB,YAAQ;AAAA,MACN,8CAA8C,GAAG;AAAA,IAAA;AAAA,EAErD;AACA,WAAS,IAAI,KAAK,QAAQ;AAC1B,SAAO,MAAM;AACX,QAAI,SAAS,IAAI,GAAG,MAAM,SAAU,UAAS,OAAO,GAAG;AAAA,EACzD;AACF;AAGO,SAAS,oBAAoB,KAA8C;AAChF,SAAO,gBAAA,EAAkB,IAAI,GAAG;AAClC;;;;;;;;;;;"}