@athenaintel/react 0.60.1 → 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 (105) hide show
  1. package/README.md +157 -5
  2. package/dist/agent-ui/AgentTarget.cjs +11 -0
  3. package/dist/agent-ui/AgentTarget.cjs.map +1 -0
  4. package/dist/agent-ui/AgentTarget.d.ts +17 -0
  5. package/dist/agent-ui/AgentTarget.js +11 -0
  6. package/dist/agent-ui/AgentTarget.js.map +1 -0
  7. package/dist/agent-ui/AgentUiLayer.cjs +387 -0
  8. package/dist/agent-ui/AgentUiLayer.cjs.map +1 -0
  9. package/dist/agent-ui/AgentUiLayer.d.ts +40 -0
  10. package/dist/agent-ui/AgentUiLayer.js +387 -0
  11. package/dist/agent-ui/AgentUiLayer.js.map +1 -0
  12. package/dist/agent-ui/contract.cjs +104 -0
  13. package/dist/agent-ui/contract.cjs.map +1 -0
  14. package/dist/agent-ui/contract.d.ts +113 -0
  15. package/dist/agent-ui/contract.js +104 -0
  16. package/dist/agent-ui/contract.js.map +1 -0
  17. package/dist/agent-ui/driver.cjs +335 -0
  18. package/dist/agent-ui/driver.cjs.map +1 -0
  19. package/dist/agent-ui/driver.d.ts +66 -0
  20. package/dist/agent-ui/driver.js +335 -0
  21. package/dist/agent-ui/driver.js.map +1 -0
  22. package/dist/agent-ui/index.cjs +33 -0
  23. package/dist/agent-ui/index.cjs.map +1 -0
  24. package/dist/agent-ui/index.d.ts +19 -0
  25. package/dist/agent-ui/index.js +33 -0
  26. package/dist/agent-ui/index.js.map +1 -0
  27. package/dist/agent-ui/pace.cjs +121 -0
  28. package/dist/agent-ui/pace.cjs.map +1 -0
  29. package/dist/agent-ui/pace.d.ts +66 -0
  30. package/dist/agent-ui/pace.js +121 -0
  31. package/dist/agent-ui/pace.js.map +1 -0
  32. package/dist/agent-ui/screen.cjs +471 -0
  33. package/dist/agent-ui/screen.cjs.map +1 -0
  34. package/dist/agent-ui/screen.d.ts +97 -0
  35. package/dist/agent-ui/screen.js +471 -0
  36. package/dist/agent-ui/screen.js.map +1 -0
  37. package/dist/agent-ui/secret-fields.cjs +50 -0
  38. package/dist/agent-ui/secret-fields.cjs.map +1 -0
  39. package/dist/agent-ui/secret-fields.d.ts +15 -0
  40. package/dist/agent-ui/secret-fields.js +50 -0
  41. package/dist/agent-ui/secret-fields.js.map +1 -0
  42. package/dist/agent-ui/stage.cjs +196 -0
  43. package/dist/agent-ui/stage.cjs.map +1 -0
  44. package/dist/agent-ui/stage.d.ts +110 -0
  45. package/dist/agent-ui/stage.js +196 -0
  46. package/dist/agent-ui/stage.js.map +1 -0
  47. package/dist/agent-ui/tools.cjs +448 -0
  48. package/dist/agent-ui/tools.cjs.map +1 -0
  49. package/dist/agent-ui/tools.d.ts +103 -0
  50. package/dist/agent-ui/tools.js +448 -0
  51. package/dist/agent-ui/tools.js.map +1 -0
  52. package/dist/agent-ui/use-agent-target.cjs +63 -0
  53. package/dist/agent-ui/use-agent-target.cjs.map +1 -0
  54. package/dist/agent-ui/use-agent-target.d.ts +27 -0
  55. package/dist/agent-ui/use-agent-target.js +63 -0
  56. package/dist/agent-ui/use-agent-target.js.map +1 -0
  57. package/dist/agent-ui/use-agent-ui.cjs +15 -0
  58. package/dist/agent-ui/use-agent-ui.cjs.map +1 -0
  59. package/dist/agent-ui/use-agent-ui.d.ts +24 -0
  60. package/dist/agent-ui/use-agent-ui.js +15 -0
  61. package/dist/agent-ui/use-agent-ui.js.map +1 -0
  62. package/dist/chat/AthenaChat.cjs +17 -4
  63. package/dist/chat/AthenaChat.cjs.map +1 -1
  64. package/dist/chat/AthenaChat.d.ts +13 -0
  65. package/dist/chat/AthenaChat.js +17 -4
  66. package/dist/chat/AthenaChat.js.map +1 -1
  67. package/dist/chat/StatewireClientToolBridge.cjs +17 -12
  68. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  69. package/dist/chat/StatewireClientToolBridge.d.ts +4 -2
  70. package/dist/chat/StatewireClientToolBridge.js +17 -12
  71. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  72. package/dist/chat/StatewireClientToolConsentCard.cjs +3 -2
  73. package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -1
  74. package/dist/chat/StatewireClientToolConsentCard.d.ts +2 -1
  75. package/dist/chat/StatewireClientToolConsentCard.js +3 -2
  76. package/dist/chat/StatewireClientToolConsentCard.js.map +1 -1
  77. package/dist/chat/chat-voice-controls.cjs +5 -2
  78. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  79. package/dist/chat/chat-voice-controls.d.ts +6 -1
  80. package/dist/chat/chat-voice-controls.js +6 -3
  81. package/dist/chat/chat-voice-controls.js.map +1 -1
  82. package/dist/chat/client-tool-write-consent.cjs +16 -5
  83. package/dist/chat/client-tool-write-consent.cjs.map +1 -1
  84. package/dist/chat/client-tool-write-consent.d.ts +12 -4
  85. package/dist/chat/client-tool-write-consent.js +16 -5
  86. package/dist/chat/client-tool-write-consent.js.map +1 -1
  87. package/dist/chat-ui/messages/message-components.cjs +1 -1
  88. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  89. package/dist/chat-ui/messages/message-components.d.ts +2 -2
  90. package/dist/chat-ui/messages/message-components.js +1 -1
  91. package/dist/chat-ui/messages/message-components.js.map +1 -1
  92. package/dist/index.cjs +7 -0
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.d.ts +6 -0
  95. package/dist/index.js +7 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/provider/AthenaProvider.cjs +37 -8
  98. package/dist/provider/AthenaProvider.cjs.map +1 -1
  99. package/dist/provider/AthenaProvider.d.ts +18 -1
  100. package/dist/provider/AthenaProvider.js +33 -4
  101. package/dist/provider/AthenaProvider.js.map +1 -1
  102. package/dist/react/package.json.cjs +1 -1
  103. package/dist/react/package.json.js +1 -1
  104. package/dist/styles.css +1 -1
  105. package/package.json +6 -1
package/README.md CHANGED
@@ -318,6 +318,13 @@ function AddMenu() {
318
318
  - `useAthenaComposerActions()` is `null` outside a composer slot. Both default
319
319
  to `true`: a host that sets neither sees no change.
320
320
 
321
+ ### A welcome of your own
322
+
323
+ `welcomeContent` replaces the greeting (`welcomeMessage`, `welcomeSubtext`) in
324
+ the empty thread with what the host renders, in the same place: Athena Desktop
325
+ draws one centred line there whose project name opens its project menu. Absent,
326
+ the greeting shows as before.
327
+
321
328
  ### Composer slash commands
322
329
 
323
330
  `AthenaChat`'s `composer.slashCommands` (and `TiptapComposer`'s
@@ -1172,7 +1179,8 @@ const frontendTools = {
1172
1179
 
1173
1180
  Before the tool's first call in a conversation runs, `AthenaChat` shows a card
1174
1181
  above the composer ("The agent wants to save a note on this record.") with
1175
- **Allow** and **Decline**. Allow covers the rest of that conversation; Decline
1182
+ **Allow** and **Decline**. Allow covers that tool for the rest of the
1183
+ conversation, and no other: a second marked tool asks for itself. Decline
1176
1184
  answers the agent with an error for the waiting calls, and a later call asks
1177
1185
  again. Calls to unmarked tools in the same turn still run. The answer is the
1178
1186
  person's own: nothing is remembered across a reload, and someone else signing
@@ -1180,13 +1188,17 @@ in to the same mounted chat is asked for themselves. The mark also reaches
1180
1188
  Athena (`requires_write`), which then never answers the call for the person
1181
1189
  while their tab is away.
1182
1190
 
1191
+ - The card names who is asking with the name you gave the agent
1192
+ (`agentUi={{ agentName }}`), else "The agent".
1183
1193
  - A host with its own chat mounts `StatewireClientToolConsentCard` (also in
1184
- `@athenaintel/react/chat-ui/statewire`; `agentName` names who is asking), or
1185
- draws its own on `useClientToolWriteConsent()`: `{ request, allow, decline }`,
1186
- `request` holding the waiting tools (`name`, `summary`) or `null`.
1194
+ `@athenaintel/react/chat-ui/statewire`; its `agentName` prop overrides the
1195
+ name), or draws its own on `useClientToolWriteConsent()`:
1196
+ `{ request, allow, decline }`, `request` holding the waiting tools (`name`,
1197
+ `summary`) and that `agentName`, or `null`.
1187
1198
  - To ask in a dialog of your own instead, pass `confirmClientToolWrites` on
1188
1199
  `<AthenaProvider>`: it receives the same request plus a `signal`, and
1189
- resolves `true` (allow the conversation) or `false` (decline). The signal
1200
+ resolves `true` (allow those tools for the conversation) or `false`
1201
+ (decline). The signal
1190
1202
  aborts when the question has no call left to answer: close the dialog then
1191
1203
  (its promise need not settle, and a late answer is ignored). A confirmer
1192
1204
  that throws counts as a decline.
@@ -1213,6 +1225,129 @@ execute: async (args, { abortSignal }) => {
1213
1225
  },
1214
1226
  ```
1215
1227
 
1228
+ ## Agent-drivable UI
1229
+
1230
+ An app can let its agent work on the screen the person has open: read what is
1231
+ there, point at something, walk the person through steps and, when asked,
1232
+ operate a control. Everything the agent does is shown on the page as it
1233
+ happens: it scrolls there, outlines the element, says what it is doing and
1234
+ types character by character, so the person can follow and learn. Ask "how do
1235
+ I filter this list?" and the agent shows it on the list itself.
1236
+
1237
+ The app decides what the agent can see and touch. Nothing is discovered: an
1238
+ element the app did not mark does not exist for the agent.
1239
+
1240
+ ```tsx
1241
+ import { AthenaProvider, agentRegion, agentTarget } from '@athenaintel/react';
1242
+
1243
+ <AthenaProvider agentUi={{ agentName: 'Jichard' }} appId={publicAppId}>
1244
+ <div {...agentRegion({ id: 'filters', label: 'Search and filters' })}>
1245
+ <input
1246
+ aria-label="Search investors"
1247
+ {...agentTarget({ id: 'search', description: 'Narrows the table as it is typed into.' })}
1248
+ />
1249
+ <button {...agentTarget({ id: 'filters.clear' })}>Clear filters</button>
1250
+ </div>
1251
+ {rows.map((row) => (
1252
+ <a key={row.id} href={row.href} {...agentTarget({ id: 'investor.open', key: row.id, label: row.name })}>
1253
+ {row.name}
1254
+ </a>
1255
+ ))}
1256
+ <AthenaChat />
1257
+ </AthenaProvider>
1258
+ ```
1259
+
1260
+ **Marking targets.** A target is a few `data-agent-*` attributes on the
1261
+ element itself, so any component that passes `data-*` props to its DOM node
1262
+ works, a plain web component included. `agentTarget(options)` writes them:
1263
+
1264
+ | Option | What it says |
1265
+ |--------|--------------|
1266
+ | `id` | What the agent names the target by: lowercase words joined by `.`, `-` or `_`, stable across renders (`filters.stage`). An id that is not one leaves the element unmarked and logs why. |
1267
+ | `key` | Which one, when several targets share an id (a row's key). The agent names it `investor.open[asset_1]`. |
1268
+ | `label` | What a person calls it. Defaults to the element's accessible name (`aria-label`, its `<label>`, its placeholder, its text). |
1269
+ | `description` | One line for the agent on what it is for. |
1270
+ | `actions` | What the agent may do to it: `click`, `type`, `select`, `toggle`, `clear`. Defaults to what the element allows by nature (a button is clicked, a text field typed into). `[]`: it may only be pointed at. |
1271
+ | `danger` | Acting on it changes or destroys something: the person confirms every time, and only the actions you list are allowed. |
1272
+ | `sensitive` | Its value is never read back to the agent and never typed by it, and its own text never names it. Password, one-time-code and card fields are treated this way without being marked. |
1273
+
1274
+ `agentRegion({ id, label })` names a part of the screen; its targets are
1275
+ grouped under it. For a control that clicks and keystrokes cannot drive (a
1276
+ custom select, a date picker), `useAgentTarget` takes `handlers` that replace
1277
+ the tool's own input, and `<AgentTarget>` marks the element inside it without
1278
+ touching that element's component:
1279
+
1280
+ ```tsx
1281
+ import { AgentTarget, useAgentTarget } from '@athenaintel/react';
1282
+
1283
+ const stage = useAgentTarget({
1284
+ id: 'filters.stage',
1285
+ label: 'Stage filter',
1286
+ handlers: { select: ({ option }) => setStage(option), options: () => STAGES, value: () => stage },
1287
+ });
1288
+ <StagePicker {...stage} />
1289
+
1290
+ <AgentTarget id="deal.save" label="Save deal">
1291
+ <Button onClick={save}>Save</Button>
1292
+ </AgentTarget>
1293
+ ```
1294
+
1295
+ **What the agent gets.** A fixed handful of tools that name targets by id,
1296
+ declared with every message like any `frontendTools` entry (so `appId` must
1297
+ be a recognised one):
1298
+
1299
+ | Tool | What it does | Changes something |
1300
+ |------|--------------|-------------------|
1301
+ | `read_screen` | Lists the targets on screen: id, role, label, value, state and allowed actions, grouped by region. `region` and `query` narrow it; a long table lists its first 25 rows and counts the rest. | no |
1302
+ | `point_at` | Scrolls to a target, outlines it and shows a note beside it for a few seconds (5 by default, 30 at most). `spotlight` dims the rest of the page. | no |
1303
+ | `show_steps` | A walkthrough of up to 12 steps, each a target and a sentence or two. It advances by itself; the person can pause, go back, skip ahead or close it. | no |
1304
+ | `act_on` | Operates one target: scrolls there, outlines it, moves a cursor onto it, then clicks, types, chooses an option, flips a switch or clears a field, through the element's own events, so your handlers, validation and undo run as they do for a person. | yes |
1305
+ | `go_to` | Opens one of the pages you list. Declared only when you pass `routes` and `navigate`. | no |
1306
+
1307
+ Your own tools stay the way to do a whole job (set these filters, open that
1308
+ record): they take one call and say what changed. The screen tools are for
1309
+ showing, and for the controls no tool of yours covers.
1310
+
1311
+ **Asking first, and stopping.** `act_on` is marked `requiresWriteConsent`, so
1312
+ the chat's consent card asks before the agent first acts in a conversation
1313
+ (see [Frontend tools that ask first, and stop](#frontend-tools-that-ask-first-and-stop)).
1314
+ A `danger` target is asked about on the page itself, every time. The agent
1315
+ acts on the one element it named, as that element is at the moment of acting:
1316
+ several rows sharing an id must be named by key, and an element that changed
1317
+ while the person read the question (another row, disabled, now a password
1318
+ field) is refused. What a keyboard could not do, it does not do: a read-only
1319
+ field is not typed into, nor a field past its `maxlength`. The person's own
1320
+ click, keypress or scroll stops an action in flight and holds back whatever
1321
+ else the agent asked for in the same turn, as does stopping the run; Escape
1322
+ closes whatever the agent is showing, and the keyboard goes back to where it
1323
+ was. With `prefers-reduced-motion` the same steps play without motion: scrolls
1324
+ jump, the cursor lands in place and typed text arrives whole.
1325
+
1326
+ **Options** (`agentUi={{ … }}`; `true` takes the defaults):
1327
+
1328
+ | Option | Default | Purpose |
1329
+ |--------|---------|---------|
1330
+ | `agentName` | `"The agent"` | Who the overlay says is acting, and who the chat's consent card says is asking. |
1331
+ | `speed` | `"normal"` | `"quick"` halves the waits; `"instant"` removes them. |
1332
+ | `allowActing` | `true` | `false` declares no `act_on`: the agent looks and points only (a read-only or "ask" mode). |
1333
+ | `actConsent` | `"host"` | `"overlay"` asks on the page (once per visit) instead of through the chat's consent card. |
1334
+ | `routes`, `navigate` | none | The pages `go_to` may open, and how to open one. |
1335
+ | `describePage` | none | Facts about the page on screen that `read_screen` passes on (its name, the record open). |
1336
+ | `classNames` | none | Restyle the overlay's pieces: `mark`, `caption`, `cursor`, `tour`, `question`. |
1337
+
1338
+ The overlay uses the SDK's own tokens (`primary`, `popover`, `border`), so it
1339
+ follows your theme. To wire the pieces yourself, or draw your own overlay, use
1340
+ `@athenaintel/react/agent-ui`: `useAgentUi(options)` returns `{ agentUi, tools }`
1341
+ (spread `tools` into `frontendTools`), `<AgentUiLayer agentUi={agentUi} />`
1342
+ draws the stage, and `agentUi.stage` (`getState`, `subscribe`) is everything
1343
+ the overlay shows, for a layer of your own.
1344
+
1345
+ **What it does not do.** It works inside one document: a target in an iframe
1346
+ is not seen. A row a virtualized list has not rendered is not on the screen.
1347
+ Nothing is discovered without a mark. None of it is a permission boundary:
1348
+ the tools run in the person's tab as the signed-in person, and your backend's
1349
+ checks, or Athena's, decide what any click may do.
1350
+
1216
1351
  ## Chat voice in AthenaChat
1217
1352
 
1218
1353
  `AthenaChat` includes realtime voice alongside dictation where the deployment
@@ -1246,6 +1381,12 @@ or a typed send. A host's own "Continue on…" control offers to send the
1246
1381
  conversation in voice mode only while `useChatVoiceEnabled({ enabled })` is
1247
1382
  true: the same workspace gate the composer's voice control uses.
1248
1383
 
1384
+ A host can fold voice into Send, as ChatGPT's composer does, with
1385
+ `composer={{ voiceInSend: true }}`: while the draft is empty and no run or call
1386
+ is live, one ink circle with a waveform starts the call; once the person types,
1387
+ Send takes its place. A call that is connecting or live keeps its own cancel or
1388
+ mute beside Send or Stop. Default `false`: voice and Send side by side.
1389
+
1249
1390
  Use `<AthenaChat voice={false} />` to omit realtime voice in a host that cannot
1250
1391
  support it, or `voice={{ model: 'gemini-3.8-live' }}` to request a host-offered
1251
1392
  voice model. A custom `livekit` adapter and `onDiagnostic` observer are accepted
@@ -1409,6 +1550,17 @@ Both use the same bearer / API key as session creation. The full recipe
1409
1550
  template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
1410
1551
  exported if you hold the bridge state yourself.
1411
1552
 
1553
+ ## What's new in 0.61.0
1554
+
1555
+ **An app can let its agent work on the screen the person has open.** With `agentUi` on `<AthenaProvider>`, the agent reads the controls your app marks (`agentTarget`, `useAgentTarget`, `<AgentTarget>`), points at one, walks the person through steps on the page, and operates a control once the person allows it. Everything it does is drawn on the page: an outline, a note, a cursor. Nothing unmarked exists for the agent, and password-like fields are never read or typed into. See [Agent-drivable UI](#agent-drivable-ui); the rest of the module is `@athenaintel/react/agent-ui`.
1556
+
1557
+ **A yes to one frontend tool is no longer a yes to another.** A tool marked `requiresWriteConsent` is asked about once per conversation, by itself. In 0.60.1, one Allow covered every marked tool for the rest of that conversation.
1558
+
1559
+ Also in this release:
1560
+ - The consent card names the agent when the app names it (`agentUi={{ agentName }}`), and the request a host's own consent UI receives carries that `agentName`.
1561
+ - `AthenaChat` takes `welcomeContent`, the host's own empty-state welcome in place of the greeting, and `composer={{ voiceInSend: true }}`, one button for voice and Send.
1562
+ - The composer's border darkens a step while it holds focus, in place of the focus ring.
1563
+
1412
1564
  ## What's new in 0.60.1
1413
1565
 
1414
1566
  **The `@` menu's asset icons draw in apps a computer serves.** In those apps, Marathon's `/_athena/auth` mints `appUrl` as the app's own origin, which serves no Athena icons. So 0.60.0 asked the app itself for the SVGs, and every row fell back to its type's Lucide icon. The icons, mailbox logos, chips and drive cards now load from the deployment's frontend there (`staging-app.athenaintel.com`, `app.athenaintel.com`), the same frontend "Open in Athena" links go to (`assetIconAppUrl`, on `athenaLinkAppUrl`).
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const radixUi = require("radix-ui");
5
+ const useAgentTarget = require("./use-agent-target.cjs");
6
+ function AgentTarget({ children, ...options }) {
7
+ const props = useAgentTarget.useAgentTarget(options);
8
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Slot.Root, { ...props, children });
9
+ }
10
+ exports.AgentTarget = AgentTarget;
11
+ //# sourceMappingURL=AgentTarget.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentTarget.cjs","sources":["../../src/agent-ui/AgentTarget.tsx"],"sourcesContent":["'use client';\n\nimport { Slot } from 'radix-ui';\nimport type { ReactNode } from 'react';\nimport { type UseAgentTargetOptions, useAgentTarget } from './use-agent-target';\n\nexport interface AgentTargetProps extends UseAgentTargetOptions {\n /** The one element that becomes the target. It must pass `data-*` props to its DOM node. */\n children: ReactNode;\n}\n\n/**\n * Make the element inside an agent target, without touching the element's own\n * component: its markup, styles and behaviour stay exactly as they are.\n *\n * ```tsx\n * <AgentTarget id=\"deal.save\" label=\"Save deal\">\n * <Button onClick={save}>Save</Button>\n * </AgentTarget>\n * ```\n */\nexport function AgentTarget({ children, ...options }: AgentTargetProps) {\n const props = useAgentTarget(options);\n return <Slot.Root {...props}>{children}</Slot.Root>;\n}\n"],"names":["useAgentTarget","Slot"],"mappings":";;;;;AAqBO,SAAS,YAAY,EAAE,UAAU,GAAG,WAA6B;AACtE,QAAM,QAAQA,eAAAA,eAAe,OAAO;AACpC,wCAAQC,QAAAA,KAAK,MAAL,EAAW,GAAG,OAAQ,UAAS;AACzC;;"}
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type UseAgentTargetOptions } from './use-agent-target';
3
+ export interface AgentTargetProps extends UseAgentTargetOptions {
4
+ /** The one element that becomes the target. It must pass `data-*` props to its DOM node. */
5
+ children: ReactNode;
6
+ }
7
+ /**
8
+ * Make the element inside an agent target, without touching the element's own
9
+ * component: its markup, styles and behaviour stay exactly as they are.
10
+ *
11
+ * ```tsx
12
+ * <AgentTarget id="deal.save" label="Save deal">
13
+ * <Button onClick={save}>Save</Button>
14
+ * </AgentTarget>
15
+ * ```
16
+ */
17
+ export declare function AgentTarget({ children, ...options }: AgentTargetProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Slot } from "radix-ui";
3
+ import { useAgentTarget } from "./use-agent-target.js";
4
+ function AgentTarget({ children, ...options }) {
5
+ const props = useAgentTarget(options);
6
+ return /* @__PURE__ */ jsx(Slot.Root, { ...props, children });
7
+ }
8
+ export {
9
+ AgentTarget
10
+ };
11
+ //# sourceMappingURL=AgentTarget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentTarget.js","sources":["../../src/agent-ui/AgentTarget.tsx"],"sourcesContent":["'use client';\n\nimport { Slot } from 'radix-ui';\nimport type { ReactNode } from 'react';\nimport { type UseAgentTargetOptions, useAgentTarget } from './use-agent-target';\n\nexport interface AgentTargetProps extends UseAgentTargetOptions {\n /** The one element that becomes the target. It must pass `data-*` props to its DOM node. */\n children: ReactNode;\n}\n\n/**\n * Make the element inside an agent target, without touching the element's own\n * component: its markup, styles and behaviour stay exactly as they are.\n *\n * ```tsx\n * <AgentTarget id=\"deal.save\" label=\"Save deal\">\n * <Button onClick={save}>Save</Button>\n * </AgentTarget>\n * ```\n */\nexport function AgentTarget({ children, ...options }: AgentTargetProps) {\n const props = useAgentTarget(options);\n return <Slot.Root {...props}>{children}</Slot.Root>;\n}\n"],"names":[],"mappings":";;;AAqBO,SAAS,YAAY,EAAE,UAAU,GAAG,WAA6B;AACtE,QAAM,QAAQ,eAAe,OAAO;AACpC,6BAAQ,KAAK,MAAL,EAAW,GAAG,OAAQ,UAAS;AACzC;"}
@@ -0,0 +1,387 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const lucideReact = require("lucide-react");
5
+ const require$$0 = require("react");
6
+ const reactDom = require("react-dom");
7
+ const lib_ownerDocument = require("../lib/owner-document.cjs");
8
+ const themeStyleContext = require("../provider/theme-style-context.cjs");
9
+ const contract = require("./contract.cjs");
10
+ const cnfast = require("cnfast");
11
+ const MARK_INSET_PX = 4;
12
+ const CARD_GAP_PX = 10;
13
+ const CARD_WIDTH_PX = 320;
14
+ const CARD_ROOM_PX = 150;
15
+ const EDGE_PX = 8;
16
+ function useElementBox(element) {
17
+ const [box, setBox] = require$$0.useState(null);
18
+ require$$0.useEffect(() => {
19
+ const view = element == null ? void 0 : element.ownerDocument.defaultView;
20
+ if (!element || !view) {
21
+ setBox(null);
22
+ return;
23
+ }
24
+ let frame = 0;
25
+ let last = "";
26
+ const read = () => {
27
+ const rect = element.isConnected ? element.getBoundingClientRect() : null;
28
+ const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;
29
+ const signature = next ? [next.top, next.left, next.width, next.height].map(Math.round).join(":") : "gone";
30
+ if (signature !== last) {
31
+ last = signature;
32
+ setBox(next);
33
+ }
34
+ frame = view.requestAnimationFrame(read);
35
+ };
36
+ read();
37
+ return () => view.cancelAnimationFrame(frame);
38
+ }, [element]);
39
+ return box;
40
+ }
41
+ function viewportOf(doc) {
42
+ const view = doc == null ? void 0 : doc.defaultView;
43
+ return { width: (view == null ? void 0 : view.innerWidth) ?? 1024, height: (view == null ? void 0 : view.innerHeight) ?? 768 };
44
+ }
45
+ function cardPosition({ box, viewport }) {
46
+ const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);
47
+ if (!box) {
48
+ return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };
49
+ }
50
+ const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));
51
+ const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;
52
+ if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {
53
+ return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };
54
+ }
55
+ return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };
56
+ }
57
+ const TONE_CLASS = {
58
+ point: "border-primary ring-primary/25",
59
+ act: "border-primary ring-primary/25 border-dashed",
60
+ ask: "border-amber-500 ring-amber-500/25"
61
+ };
62
+ function Mark({
63
+ box,
64
+ tone,
65
+ spotlight,
66
+ leaving,
67
+ className
68
+ }) {
69
+ return /* @__PURE__ */ jsxRuntime.jsx(
70
+ "div",
71
+ {
72
+ "data-testid": "agent-ui-mark",
73
+ "data-tone": tone,
74
+ className: cnfast.cn(
75
+ "pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none",
76
+ TONE_CLASS[tone],
77
+ spotlight && "shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]",
78
+ leaving ? "opacity-0" : "opacity-100",
79
+ className
80
+ ),
81
+ style: {
82
+ top: box.top - MARK_INSET_PX,
83
+ left: box.left - MARK_INSET_PX,
84
+ width: box.width + MARK_INSET_PX * 2,
85
+ height: box.height + MARK_INSET_PX * 2
86
+ }
87
+ }
88
+ );
89
+ }
90
+ function Card({
91
+ box,
92
+ viewport,
93
+ fit = false,
94
+ className,
95
+ children,
96
+ ...props
97
+ }) {
98
+ const { width, ...position } = cardPosition({ box, viewport });
99
+ return /* @__PURE__ */ jsxRuntime.jsx(
100
+ "div",
101
+ {
102
+ ...props,
103
+ className: cnfast.cn(
104
+ "pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg",
105
+ className
106
+ ),
107
+ style: fit ? { ...position, maxWidth: width } : { ...position, width },
108
+ children
109
+ }
110
+ );
111
+ }
112
+ function Cursor({
113
+ box,
114
+ presses,
115
+ glideMs,
116
+ className
117
+ }) {
118
+ const x = box.left + box.width / 2;
119
+ const y = box.top + box.height / 2;
120
+ const [arrived, setArrived] = require$$0.useState(false);
121
+ require$$0.useEffect(() => {
122
+ const frame = requestAnimationFrame(() => setArrived(true));
123
+ return () => cancelAnimationFrame(frame);
124
+ }, []);
125
+ return /* @__PURE__ */ jsxRuntime.jsxs(
126
+ "div",
127
+ {
128
+ "data-testid": "agent-ui-cursor",
129
+ className: cnfast.cn("pointer-events-none fixed top-0 left-0 motion-reduce:transition-none", className),
130
+ style: {
131
+ transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,
132
+ transitionProperty: "transform",
133
+ transitionTimingFunction: "ease-out",
134
+ transitionDuration: `${glideMs}ms`
135
+ },
136
+ children: [
137
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2Icon, { className: "size-5 fill-primary text-primary-foreground drop-shadow" }),
138
+ presses > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
139
+ "span",
140
+ {
141
+ "data-testid": "agent-ui-press",
142
+ className: "-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]"
143
+ },
144
+ presses
145
+ ) : null
146
+ ]
147
+ }
148
+ );
149
+ }
150
+ function TourCard({
151
+ tour,
152
+ stage,
153
+ box,
154
+ viewport,
155
+ className
156
+ }) {
157
+ const step = tour.steps[tour.index];
158
+ const last = tour.index >= tour.steps.length - 1;
159
+ const progressRef = require$$0.useRef(null);
160
+ const advancing = tour.playing && !tour.searching;
161
+ const durationMs = (step == null ? void 0 : step.durationMs) ?? 0;
162
+ require$$0.useEffect(() => {
163
+ const bar = progressRef.current;
164
+ if (!bar || typeof bar.animate !== "function") return;
165
+ if (!advancing) return;
166
+ const animation = bar.animate([{ width: "0%" }, { width: "100%" }], {
167
+ duration: durationMs,
168
+ fill: "forwards",
169
+ easing: "linear"
170
+ });
171
+ return () => animation.cancel();
172
+ }, [advancing, durationMs, tour.index]);
173
+ if (!step) return null;
174
+ return /* @__PURE__ */ jsxRuntime.jsxs(
175
+ Card,
176
+ {
177
+ box,
178
+ viewport,
179
+ className,
180
+ role: "dialog",
181
+ "aria-label": tour.title ?? "Walkthrough",
182
+ "data-testid": "agent-ui-tour",
183
+ children: [
184
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
185
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "min-w-0 truncate text-muted-foreground text-xs", children: [
186
+ tour.title ? `${tour.title} · ` : "",
187
+ "Step ",
188
+ tour.index + 1,
189
+ " of ",
190
+ tour.steps.length
191
+ ] }),
192
+ /* @__PURE__ */ jsxRuntime.jsx(
193
+ "button",
194
+ {
195
+ type: "button",
196
+ "aria-label": "Close the walkthrough",
197
+ onClick: () => stage.endTour(),
198
+ className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
199
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5" })
200
+ }
201
+ )
202
+ ] }),
203
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "break-words pt-1.5 leading-snug", "data-testid": "agent-ui-tour-text", children: step.text }),
204
+ !tour.searching && !tour.element ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pt-1 text-muted-foreground text-xs", children: "This one is not on the page you have open." }) : null,
205
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: progressRef, className: "h-full w-0 bg-primary" }) }),
206
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 pt-2", children: [
207
+ /* @__PURE__ */ jsxRuntime.jsx(
208
+ "button",
209
+ {
210
+ type: "button",
211
+ "aria-label": "Previous step",
212
+ disabled: tour.index === 0,
213
+ onClick: () => stage.tourBack(),
214
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
215
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-4" })
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsxRuntime.jsx(
219
+ "button",
220
+ {
221
+ type: "button",
222
+ "aria-label": tour.playing ? "Pause the walkthrough" : "Play the walkthrough",
223
+ onClick: () => stage.tourTogglePlaying(),
224
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
225
+ children: tour.playing ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PauseIcon, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlayIcon, { className: "size-4" })
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsxRuntime.jsxs(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ onClick: () => stage.tourNext(),
233
+ 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",
234
+ children: [
235
+ last ? "Done" : "Next",
236
+ last ? null : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
237
+ ]
238
+ }
239
+ )
240
+ ] })
241
+ ]
242
+ }
243
+ );
244
+ }
245
+ function QuestionCard({
246
+ question,
247
+ stage,
248
+ box,
249
+ viewport,
250
+ className
251
+ }) {
252
+ return /* @__PURE__ */ jsxRuntime.jsxs(
253
+ Card,
254
+ {
255
+ box,
256
+ viewport,
257
+ className,
258
+ role: "alertdialog",
259
+ "aria-label": question.title,
260
+ "data-testid": "agent-ui-question",
261
+ children: [
262
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "break-words font-semibold leading-snug", children: question.title }),
263
+ question.body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "break-words pt-1 text-muted-foreground leading-snug", children: question.body }) : null,
264
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2.5", children: [
265
+ /* @__PURE__ */ jsxRuntime.jsx(
266
+ "button",
267
+ {
268
+ type: "button",
269
+ onClick: () => stage.answer(true),
270
+ className: "rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90",
271
+ children: question.allowLabel
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsxRuntime.jsx(
275
+ "button",
276
+ {
277
+ type: "button",
278
+ onClick: () => stage.answer(false),
279
+ className: "rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted",
280
+ children: question.declineLabel
281
+ }
282
+ )
283
+ ] })
284
+ ]
285
+ }
286
+ );
287
+ }
288
+ function AgentUiLayer({ agentUi, classNames }) {
289
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
290
+ const { stage } = agentUi;
291
+ const state = require$$0.useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);
292
+ const ownerDocument = lib_ownerDocument.useOwnerDocument();
293
+ const portalContainer = lib_ownerDocument.usePortalContainer();
294
+ const themeStyle = themeStyleContext.useAthenaThemeStyle();
295
+ const tourElement = ((_a = state.tour) == null ? void 0 : _a.element) ?? null;
296
+ const markedElement = state.tour ? tourElement : ((_b = state.mark) == null ? void 0 : _b.element) ?? null;
297
+ const markBox = useElementBox(markedElement);
298
+ const cursorBox = useElementBox(((_c = state.cursor) == null ? void 0 : _c.element) ?? null);
299
+ const questionBox = useElementBox(((_d = state.question) == null ? void 0 : _d.element) ?? null);
300
+ const showing = state.tour !== null || state.mark !== null;
301
+ require$$0.useEffect(() => {
302
+ if (!showing || !ownerDocument) return;
303
+ const onKeyDown = (event) => {
304
+ if (event.key !== "Escape") return;
305
+ stage.endTour();
306
+ stage.clearMark();
307
+ };
308
+ ownerDocument.addEventListener("keydown", onKeyDown);
309
+ return () => ownerDocument.removeEventListener("keydown", onKeyDown);
310
+ }, [ownerDocument, showing, stage]);
311
+ const container = portalContainer ?? (ownerDocument == null ? void 0 : ownerDocument.body);
312
+ if (!container) return null;
313
+ const idle = !state.mark && !state.cursor && !state.tour && !state.question;
314
+ if (idle) return null;
315
+ const viewport = viewportOf(ownerDocument);
316
+ const caption = state.tour ? null : ((_e = state.mark) == null ? void 0 : _e.caption) ?? null;
317
+ return reactDom.createPortal(
318
+ /* @__PURE__ */ jsxRuntime.jsxs(
319
+ "div",
320
+ {
321
+ ...{ [contract.AGENT_ATTR.layer]: "" },
322
+ className: "athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]",
323
+ style: themeStyle,
324
+ children: [
325
+ markBox && (state.tour || state.mark) ? /* @__PURE__ */ jsxRuntime.jsx(
326
+ Mark,
327
+ {
328
+ box: markBox,
329
+ tone: state.tour ? "point" : ((_f = state.mark) == null ? void 0 : _f.tone) ?? "point",
330
+ spotlight: state.tour ? false : ((_g = state.mark) == null ? void 0 : _g.spotlight) ?? false,
331
+ leaving: state.tour ? false : ((_h = state.mark) == null ? void 0 : _h.leaving) ?? false,
332
+ className: classNames == null ? void 0 : classNames.mark
333
+ }
334
+ ) : null,
335
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", children: caption && markBox && !state.question ? /* @__PURE__ */ jsxRuntime.jsx(
336
+ Card,
337
+ {
338
+ box: markBox,
339
+ viewport,
340
+ fit: true,
341
+ className: cnfast.cn(
342
+ "pointer-events-none transition-opacity duration-500 motion-reduce:transition-none",
343
+ ((_i = state.mark) == null ? void 0 : _i.leaving) ? "opacity-0" : "opacity-100",
344
+ classNames == null ? void 0 : classNames.caption
345
+ ),
346
+ "data-testid": "agent-ui-caption",
347
+ children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "break-words leading-snug", children: caption })
348
+ }
349
+ ) : null }),
350
+ state.tour ? /* @__PURE__ */ jsxRuntime.jsx(
351
+ TourCard,
352
+ {
353
+ tour: state.tour,
354
+ stage,
355
+ box: markBox,
356
+ viewport,
357
+ className: classNames == null ? void 0 : classNames.tour
358
+ }
359
+ ) : null,
360
+ state.question ? /* @__PURE__ */ jsxRuntime.jsx(
361
+ QuestionCard,
362
+ {
363
+ question: state.question,
364
+ stage,
365
+ box: questionBox,
366
+ viewport,
367
+ className: classNames == null ? void 0 : classNames.question
368
+ }
369
+ ) : null,
370
+ state.cursor && cursorBox ? /* @__PURE__ */ jsxRuntime.jsx(
371
+ Cursor,
372
+ {
373
+ box: cursorBox,
374
+ presses: state.cursor.presses,
375
+ glideMs: agentUi.pace().cursorGlideMs,
376
+ className: classNames == null ? void 0 : classNames.cursor
377
+ }
378
+ ) : null
379
+ ]
380
+ }
381
+ ),
382
+ container
383
+ );
384
+ }
385
+ exports.AgentUiLayer = AgentUiLayer;
386
+ exports.cardPosition = cardPosition;
387
+ //# sourceMappingURL=AgentUiLayer.cjs.map