agent-approvals 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/ApprovalGate-CRRh36j-.js +270 -0
- package/dist/ApprovalGate-CRRh36j-.js.map +1 -0
- package/dist/agent-approvals.css +2 -0
- package/dist/ai-sdk/ApprovalGate.d.ts +39 -0
- package/dist/ai-sdk/ApprovalGate.d.ts.map +1 -0
- package/dist/ai-sdk/index.d.ts +5 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/toApprovalRequests.d.ts +10 -0
- package/dist/ai-sdk/toApprovalRequests.d.ts.map +1 -0
- package/dist/ai-sdk/types.d.ts +61 -0
- package/dist/ai-sdk/types.d.ts.map +1 -0
- package/dist/ai-sdk.js +139 -0
- package/dist/ai-sdk.js.map +1 -0
- package/dist/components/ActivityTrail/ActivityTrail.d.ts +14 -0
- package/dist/components/ActivityTrail/ActivityTrail.d.ts.map +1 -0
- package/dist/components/ActivityTrail/types.d.ts +39 -0
- package/dist/components/ActivityTrail/types.d.ts.map +1 -0
- package/dist/components/ApprovalGate/ApprovalGate.d.ts +26 -0
- package/dist/components/ApprovalGate/ApprovalGate.d.ts.map +1 -0
- package/dist/components/ApprovalGate/HoldToConfirm.d.ts +43 -0
- package/dist/components/ApprovalGate/HoldToConfirm.d.ts.map +1 -0
- package/dist/components/ApprovalGate/types.d.ts +66 -0
- package/dist/components/ApprovalGate/types.d.ts.map +1 -0
- package/dist/components/SessionStrip/SessionStrip.d.ts +18 -0
- package/dist/components/SessionStrip/SessionStrip.d.ts.map +1 -0
- package/dist/components/SessionStrip/types.d.ts +31 -0
- package/dist/components/SessionStrip/types.d.ts.map +1 -0
- package/dist/components/StatusIndicator/StatusIndicator.d.ts +17 -0
- package/dist/components/StatusIndicator/StatusIndicator.d.ts.map +1 -0
- package/dist/components/StatusIndicator/types.d.ts +43 -0
- package/dist/components/StatusIndicator/types.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +245 -0
- package/dist/index.js.map +1 -0
- package/dist/theme-macos.css +209 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ladan Johari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# agent-approvals
|
|
2
|
+
|
|
3
|
+
**When an agent needs a person, this is the interface.**
|
|
4
|
+
|
|
5
|
+
Your agent is about to run a shell command, charge $200 to a card, or email 412
|
|
6
|
+
customers. Somebody has to say yes, and the interface they say it in decides
|
|
7
|
+
whether they actually read it first.
|
|
8
|
+
|
|
9
|
+
React components for that moment. Works with Vercel's AI SDK out of the box.
|
|
10
|
+
|
|
11
|
+
<img src="https://raw.githubusercontent.com/ladanjohari/agent-approvals/main/assets/hold-to-approve.gif" alt="An approval request to charge a card, which cannot be undone. A press starts the fill and it snaps straight back, doing nothing. Holding for 1.2 seconds fills it and approves." width="560" />
|
|
12
|
+
|
|
13
|
+
A click is the same motion that just cleared three safe requests, so an action
|
|
14
|
+
that cannot be undone does not accept one. It is held for 1.2 seconds instead.
|
|
15
|
+
Let go early and nothing happens.
|
|
16
|
+
|
|
17
|
+
**Compress the ordinary. Never compress the exception.** Nine routine requests
|
|
18
|
+
can collapse into one row. The one that spends money cannot. The hard part was
|
|
19
|
+
never drawing a dot, it is deciding what may be summarised away and what must
|
|
20
|
+
always stand at full size, and getting that backwards is how a permission prompt
|
|
21
|
+
turns into something people click without reading.
|
|
22
|
+
|
|
23
|
+
[](https://www.npmjs.com/package/agent-approvals)
|
|
24
|
+
|
|
25
|
+
**[Live docs and interactive examples](https://ladanjohari.com/agent-approvals/)**
|
|
26
|
+
|
|
27
|
+
> Status: 0.x, early. The API may still change before 1.0.
|
|
28
|
+
|
|
29
|
+
## Works with Vercel's AI SDK out of the box
|
|
30
|
+
|
|
31
|
+
Vercel's AI SDK lets you mark a tool as needing approval, pauses, and hands your
|
|
32
|
+
app an `approval-requested` tool part. The approve and deny interface is yours to
|
|
33
|
+
build, as [their own cookbook](https://ai-sdk.dev/cookbook/next/human-in-the-loop)
|
|
34
|
+
shows. `ApprovalGate` is that interface, and the adapter speaks the SDK's data
|
|
35
|
+
shape:
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
import { ApprovalGate } from 'agent-approvals/ai-sdk'
|
|
39
|
+
import 'agent-approvals/styles.css'
|
|
40
|
+
|
|
41
|
+
// Outside the component. Which tools can be undone does not change between
|
|
42
|
+
// renders, and an object written inline is a new object on every one.
|
|
43
|
+
const REVERSIBLE = { searchWeb: true, readOrder: true }
|
|
44
|
+
|
|
45
|
+
const { messages, addToolApprovalResponse } = useChat()
|
|
46
|
+
|
|
47
|
+
<ApprovalGate
|
|
48
|
+
messages={messages}
|
|
49
|
+
addToolApprovalResponse={addToolApprovalResponse}
|
|
50
|
+
reversible={REVERSIBLE}
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### The look is optional
|
|
55
|
+
|
|
56
|
+
The package ships neutral on purpose. It declares no font, so it inherits yours,
|
|
57
|
+
and it mixes its neutrals from your text colour, so it is correct in light and
|
|
58
|
+
dark without being configured. That is the right default: a dark vibrancy panel
|
|
59
|
+
landing in someone's light dashboard is not a good default, it is a bug.
|
|
60
|
+
|
|
61
|
+
If you want the look from the documentation site, it is one more import:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import 'agent-approvals/styles.css'
|
|
65
|
+
import 'agent-approvals/theme-macos.css'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
System font, vibrancy, exact capsules, concentric radii, and one accent colour
|
|
69
|
+
doing one job. Every value in it is a custom property on the component rather
|
|
70
|
+
than on `:root`, so nothing leaks into the rest of your app and you can override
|
|
71
|
+
any single value after it.
|
|
72
|
+
|
|
73
|
+
Approvals the SDK settled on its own never appear, answered ones drop out, and
|
|
74
|
+
tools from an MCP server are handled like any other. There is **no dependency on
|
|
75
|
+
the AI SDK and no peer dependency**: the adapter reads the shape of a message
|
|
76
|
+
rather than importing the library, so there is no version to keep in step.
|
|
77
|
+
|
|
78
|
+
The SDK carries no notion of whether an action can be undone, so the adapter
|
|
79
|
+
asks in order: what you said in `reversible`, then what the tool declared with
|
|
80
|
+
`metadata: { reversible: true }` on the server, then `'unknown'`. Silence is
|
|
81
|
+
never read as consent. [Full details in the docs](https://ladanjohari.com/agent-approvals/#ai-sdk).
|
|
82
|
+
|
|
83
|
+
## The four components
|
|
84
|
+
|
|
85
|
+
| Component | What it does | How the rule shows up |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `StatusIndicator` | One session's state | Colour goes only to the states that are exceptions |
|
|
88
|
+
| `ApprovalGate` | Asks for a human decision | Batches reversible requests, never batches destructive ones |
|
|
89
|
+
| `SessionStrip` | Many sessions at a glance | Folds calm sessions, never folds the ones needing a person |
|
|
90
|
+
| `ActivityTrail` | What the agent did | Folds runs of ordinary steps, never folds questions or failures |
|
|
91
|
+
|
|
92
|
+
There will not be a fifth.
|
|
93
|
+
|
|
94
|
+
## Install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install agent-approvals
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```jsx
|
|
101
|
+
import { StatusIndicator } from 'agent-approvals'
|
|
102
|
+
import 'agent-approvals/styles.css'
|
|
103
|
+
|
|
104
|
+
export function Row() {
|
|
105
|
+
return <StatusIndicator state="needsYou" showLabel />
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
React 18 or 19. TypeScript types are included, so your editor lists the props
|
|
110
|
+
and refuses the ones that are not allowed.
|
|
111
|
+
|
|
112
|
+
## What you can change, and what you cannot
|
|
113
|
+
|
|
114
|
+
Every colour is a token you can retint, and each component writes its state into
|
|
115
|
+
the page as a `data-` attribute so you can style any state from your own CSS.
|
|
116
|
+
**You own the values. The components own the meaning.**
|
|
117
|
+
|
|
118
|
+
Not configurable, on purpose:
|
|
119
|
+
|
|
120
|
+
- Which state means what.
|
|
121
|
+
- The ring rather than a fill on error, which is what keeps error and "waiting
|
|
122
|
+
for you" apart for anyone who cannot separate red from amber.
|
|
123
|
+
- Reduce Motion, which is obeyed rather than offered.
|
|
124
|
+
- A destructive approval never being batched, never being pre-approved, and
|
|
125
|
+
never being accepted by default. It takes a 1.2 second hold rather than a
|
|
126
|
+
click, because a click is the same motion that just cleared the safe ones.
|
|
127
|
+
Keyboard and Reduce Motion skip the hold.
|
|
128
|
+
- Not knowing never being cheaper than knowing. `reversible` takes three values:
|
|
129
|
+
`true` batches, `false` is permanent, and `'unknown'` means nobody has said.
|
|
130
|
+
Unknown gets exactly the same barrier as permanent and only a quieter
|
|
131
|
+
sentence, because a gate that prints "this cannot be undone" over a web search
|
|
132
|
+
is spending credibility it will need later.
|
|
133
|
+
- Labels staying readable by a screen reader even when hidden from view.
|
|
134
|
+
|
|
135
|
+
## Where the state model comes from
|
|
136
|
+
|
|
137
|
+
Session Indicator, a macOS menu bar app that watches real agent sessions. These
|
|
138
|
+
are the six states that survived using it daily, including the split most
|
|
139
|
+
products get wrong: **Writing** is the agent producing, **Running a command** is
|
|
140
|
+
something it started that is still going. One will always finish. The other can
|
|
141
|
+
hang forever and never come back to ask you for anything.
|
|
142
|
+
|
|
143
|
+
## Local development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install # fetch the dependencies listed in package.json
|
|
147
|
+
npm run dev # component workbench
|
|
148
|
+
npm run dev:docs # the docs site
|
|
149
|
+
npm test # run the test suite once
|
|
150
|
+
npm run build # produce the publishable files in dist/
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Layout
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
src/ the published library, everything here can ship
|
|
157
|
+
dev/ the local workbench, never published
|
|
158
|
+
docs/ the docs site, deployed to GitHub Pages
|
|
159
|
+
demo/ standalone pages that explain one decision each
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Licence
|
|
163
|
+
|
|
164
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback as n, useEffect as r, useId as i, useRef as a, useState as o } from "react";
|
|
3
|
+
//#region src/components/ApprovalGate/HoldToConfirm.tsx
|
|
4
|
+
function s() {
|
|
5
|
+
let [e, t] = o(!1);
|
|
6
|
+
return r(() => {
|
|
7
|
+
if (typeof window.matchMedia != "function") return;
|
|
8
|
+
let e = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
9
|
+
t(e.matches);
|
|
10
|
+
let n = () => t(e.matches);
|
|
11
|
+
return e.addEventListener("change", n), () => e.removeEventListener("change", n);
|
|
12
|
+
}, []), e;
|
|
13
|
+
}
|
|
14
|
+
var c = 1200;
|
|
15
|
+
function l({ onConfirm: i, consequence: l, describedBy: u }) {
|
|
16
|
+
let d = s(), [f, p] = o(!1), m = a(!1), h = a(void 0), g = n(() => {
|
|
17
|
+
h.current !== void 0 && (clearTimeout(h.current), h.current = void 0), m.current = !1, p(!1);
|
|
18
|
+
}, []), _ = () => {
|
|
19
|
+
m.current = !0, p(!0), h.current = setTimeout(() => {
|
|
20
|
+
h.current = void 0, m.current && (g(), i());
|
|
21
|
+
}, c);
|
|
22
|
+
};
|
|
23
|
+
return r(() => g, [g]), d ? /* @__PURE__ */ e("button", {
|
|
24
|
+
type: "button",
|
|
25
|
+
className: "agent-gate__hold agent-gate__hold--still",
|
|
26
|
+
"aria-label": `Hold to approve: ${l}`,
|
|
27
|
+
"aria-describedby": u,
|
|
28
|
+
"data-pressing": f ? "true" : void 0,
|
|
29
|
+
onPointerDown: (e) => {
|
|
30
|
+
if (e.button === 0) {
|
|
31
|
+
try {
|
|
32
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
33
|
+
} catch {}
|
|
34
|
+
_();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
onPointerUp: g,
|
|
38
|
+
onPointerCancel: g,
|
|
39
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
40
|
+
onKeyDown: (e) => {
|
|
41
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), i());
|
|
42
|
+
},
|
|
43
|
+
children: /* @__PURE__ */ e("span", {
|
|
44
|
+
className: "agent-gate__hold-label",
|
|
45
|
+
children: "Hold to approve"
|
|
46
|
+
})
|
|
47
|
+
}) : /* @__PURE__ */ t("button", {
|
|
48
|
+
type: "button",
|
|
49
|
+
className: "agent-gate__hold",
|
|
50
|
+
"aria-label": `Hold to approve: ${l}`,
|
|
51
|
+
"aria-describedby": u,
|
|
52
|
+
"data-pressing": f ? "true" : void 0,
|
|
53
|
+
onPointerDown: (e) => {
|
|
54
|
+
if (e.button === 0) {
|
|
55
|
+
try {
|
|
56
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
57
|
+
} catch {}
|
|
58
|
+
m.current = !0, p(!0);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
onPointerUp: g,
|
|
62
|
+
onPointerCancel: g,
|
|
63
|
+
onContextMenu: (e) => e.preventDefault(),
|
|
64
|
+
onKeyDown: (e) => {
|
|
65
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), i());
|
|
66
|
+
},
|
|
67
|
+
children: [/* @__PURE__ */ e("span", {
|
|
68
|
+
className: "agent-gate__hold-fill",
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
onTransitionEnd: (e) => {
|
|
71
|
+
e.propertyName === "clip-path" && m.current && (g(), i());
|
|
72
|
+
}
|
|
73
|
+
}), /* @__PURE__ */ e("span", {
|
|
74
|
+
className: "agent-gate__hold-label",
|
|
75
|
+
children: "Hold to approve"
|
|
76
|
+
})]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/components/ApprovalGate/ApprovalGate.tsx
|
|
81
|
+
function u(e) {
|
|
82
|
+
return e.reversible === !0;
|
|
83
|
+
}
|
|
84
|
+
function d(e) {
|
|
85
|
+
return e.reversible === !1;
|
|
86
|
+
}
|
|
87
|
+
function f(e) {
|
|
88
|
+
return e.reversible === !0 ? "yes" : e.reversible === !1 ? "no" : "unknown";
|
|
89
|
+
}
|
|
90
|
+
function p(e) {
|
|
91
|
+
let t = e.some(d);
|
|
92
|
+
return e.length === 1 ? t ? "Cannot be undone. Review to answer." : "Might not be possible to undo. Review to answer." : e.every(d) ? "Cannot be undone. Review each to answer." : t ? "Some cannot be undone. Review each to answer." : "Might not be possible to undo. Review each to answer.";
|
|
93
|
+
}
|
|
94
|
+
function m(e, t) {
|
|
95
|
+
let n = a(e);
|
|
96
|
+
r(() => {
|
|
97
|
+
let r = e < n.current;
|
|
98
|
+
if (n.current = e, !r) return;
|
|
99
|
+
let i = document.activeElement;
|
|
100
|
+
i && i !== document.body || t.current?.focus();
|
|
101
|
+
}, [e, t]);
|
|
102
|
+
}
|
|
103
|
+
function h({ requests: n, onApprove: s, onDeny: c, onDismiss: h, title: g = "Waiting for you", className: _ }) {
|
|
104
|
+
let [v, y] = o([]), [b, x] = o(!1), S = a(null), C = i();
|
|
105
|
+
if (m(n.length, S), r(() => {
|
|
106
|
+
y((e) => {
|
|
107
|
+
if (e.length === 0) return e;
|
|
108
|
+
let t = e.filter((e) => n.some((t) => t.id === e));
|
|
109
|
+
return t.length === e.length ? e : t;
|
|
110
|
+
});
|
|
111
|
+
}, [n]), n.length === 0) return /* @__PURE__ */ e("div", {
|
|
112
|
+
ref: (e) => {
|
|
113
|
+
S.current = e;
|
|
114
|
+
},
|
|
115
|
+
tabIndex: -1,
|
|
116
|
+
children: /* @__PURE__ */ e("p", {
|
|
117
|
+
className: "agent-sr-only",
|
|
118
|
+
role: "status"
|
|
119
|
+
})
|
|
120
|
+
});
|
|
121
|
+
let w = n.filter(u), T = n.filter((e) => !u(e)), E = w.map((e) => e.id), D = n.length, O = `${C}-note`, k = (e) => y((t) => t.includes(e) ? t.filter((t) => t !== e) : [...t, e]);
|
|
122
|
+
return /* @__PURE__ */ t("section", {
|
|
123
|
+
"aria-label": g === !1 ? "Waiting for you" : g,
|
|
124
|
+
ref: S,
|
|
125
|
+
tabIndex: -1,
|
|
126
|
+
className: ["agent-gate", _].filter(Boolean).join(" "),
|
|
127
|
+
children: [
|
|
128
|
+
g === !1 && !h ? null : /* @__PURE__ */ t("header", {
|
|
129
|
+
className: "agent-gate__head",
|
|
130
|
+
children: [g === !1 ? null : /* @__PURE__ */ e("p", {
|
|
131
|
+
className: "agent-gate__title",
|
|
132
|
+
children: g
|
|
133
|
+
}), h ? /* @__PURE__ */ e("button", {
|
|
134
|
+
type: "button",
|
|
135
|
+
className: "agent-gate__disclose",
|
|
136
|
+
"aria-expanded": !b,
|
|
137
|
+
"aria-label": b ? "Show the requests" : "Collapse",
|
|
138
|
+
onClick: () => {
|
|
139
|
+
x((e) => !e), b || h();
|
|
140
|
+
},
|
|
141
|
+
children: /* @__PURE__ */ e("svg", {
|
|
142
|
+
viewBox: "0 0 14 14",
|
|
143
|
+
width: "14",
|
|
144
|
+
height: "14",
|
|
145
|
+
"aria-hidden": "true",
|
|
146
|
+
children: /* @__PURE__ */ e("path", {
|
|
147
|
+
d: "M3.5 8.75 L7 5.25 L10.5 8.75",
|
|
148
|
+
fill: "none",
|
|
149
|
+
stroke: "currentColor",
|
|
150
|
+
strokeWidth: "1.6",
|
|
151
|
+
strokeLinecap: "round",
|
|
152
|
+
strokeLinejoin: "round"
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
}) : null]
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ t("p", {
|
|
158
|
+
className: b ? "agent-gate__waiting" : "agent-gate__waiting agent-sr-only",
|
|
159
|
+
role: "status",
|
|
160
|
+
"data-tone": b && T.length > 0 ? "alarm" : void 0,
|
|
161
|
+
children: [
|
|
162
|
+
D,
|
|
163
|
+
" ",
|
|
164
|
+
D === 1 ? "request needs" : "requests need",
|
|
165
|
+
" your approval.",
|
|
166
|
+
T.length > 0 ? /* @__PURE__ */ t("span", {
|
|
167
|
+
className: "agent-gate__waiting-held",
|
|
168
|
+
children: [
|
|
169
|
+
" ",
|
|
170
|
+
T.length,
|
|
171
|
+
" cannot be undone."
|
|
172
|
+
]
|
|
173
|
+
}) : null
|
|
174
|
+
]
|
|
175
|
+
}),
|
|
176
|
+
!b && w.length > 0 ? /* @__PURE__ */ t("div", {
|
|
177
|
+
className: "agent-gate__group",
|
|
178
|
+
role: "group",
|
|
179
|
+
"aria-label": "Can be undone",
|
|
180
|
+
children: [/* @__PURE__ */ e("ul", {
|
|
181
|
+
className: "agent-gate__list",
|
|
182
|
+
children: w.map((n) => /* @__PURE__ */ t("li", {
|
|
183
|
+
className: "agent-gate__item",
|
|
184
|
+
"data-reversible": f(n),
|
|
185
|
+
children: [/* @__PURE__ */ e("span", {
|
|
186
|
+
className: "agent-gate__consequence",
|
|
187
|
+
children: n.consequence
|
|
188
|
+
}), n.detail ? /* @__PURE__ */ e("code", {
|
|
189
|
+
className: "agent-gate__detail",
|
|
190
|
+
children: n.detail
|
|
191
|
+
}) : null]
|
|
192
|
+
}, n.id))
|
|
193
|
+
}), /* @__PURE__ */ t("div", {
|
|
194
|
+
className: "agent-gate__actions",
|
|
195
|
+
children: [/* @__PURE__ */ e("button", {
|
|
196
|
+
type: "button",
|
|
197
|
+
className: "agent-gate__approve",
|
|
198
|
+
onClick: () => s(E),
|
|
199
|
+
children: w.length === 1 ? "Approve" : `Approve all ${w.length}`
|
|
200
|
+
}), /* @__PURE__ */ e("button", {
|
|
201
|
+
type: "button",
|
|
202
|
+
className: "agent-gate__deny",
|
|
203
|
+
onClick: () => c(E),
|
|
204
|
+
children: "Deny"
|
|
205
|
+
})]
|
|
206
|
+
})]
|
|
207
|
+
}) : null,
|
|
208
|
+
!b && T.length > 0 ? /* @__PURE__ */ t("div", {
|
|
209
|
+
className: "agent-gate__group agent-gate__group--destructive",
|
|
210
|
+
role: "group",
|
|
211
|
+
"aria-label": "Answered one at a time",
|
|
212
|
+
children: [/* @__PURE__ */ e("p", {
|
|
213
|
+
id: O,
|
|
214
|
+
className: "agent-gate__note",
|
|
215
|
+
"data-tone": T.some(d) ? "alarm" : "caution",
|
|
216
|
+
"data-resting": T.every((e) => !v.includes(e.id)) ? "true" : void 0,
|
|
217
|
+
children: p(T)
|
|
218
|
+
}), /* @__PURE__ */ e("ul", {
|
|
219
|
+
className: "agent-gate__list",
|
|
220
|
+
children: T.map((n, r) => {
|
|
221
|
+
let i = v.includes(n.id), a = `${C}-panel-${r}`;
|
|
222
|
+
return /* @__PURE__ */ t("li", {
|
|
223
|
+
className: "agent-gate__item",
|
|
224
|
+
"data-reversible": f(n),
|
|
225
|
+
children: [/* @__PURE__ */ t("button", {
|
|
226
|
+
type: "button",
|
|
227
|
+
className: "agent-gate__reveal",
|
|
228
|
+
"aria-expanded": i,
|
|
229
|
+
"aria-controls": i ? a : void 0,
|
|
230
|
+
onClick: () => k(n.id),
|
|
231
|
+
children: [/* @__PURE__ */ e("span", {
|
|
232
|
+
className: "agent-gate__consequence",
|
|
233
|
+
children: n.consequence
|
|
234
|
+
}), /* @__PURE__ */ t("span", {
|
|
235
|
+
className: "agent-gate__reveal-hint",
|
|
236
|
+
children: [" ", i ? "Close" : "Review"]
|
|
237
|
+
})]
|
|
238
|
+
}), i ? /* @__PURE__ */ t("div", {
|
|
239
|
+
id: a,
|
|
240
|
+
className: "agent-gate__revealed",
|
|
241
|
+
children: [n.detail ? /* @__PURE__ */ e("code", {
|
|
242
|
+
className: "agent-gate__detail",
|
|
243
|
+
children: n.detail
|
|
244
|
+
}) : null, /* @__PURE__ */ t("div", {
|
|
245
|
+
className: "agent-gate__actions",
|
|
246
|
+
children: [/* @__PURE__ */ e(l, {
|
|
247
|
+
onConfirm: () => s([n.id]),
|
|
248
|
+
consequence: n.consequence,
|
|
249
|
+
describedBy: O
|
|
250
|
+
}), /* @__PURE__ */ e("button", {
|
|
251
|
+
type: "button",
|
|
252
|
+
className: "agent-gate__deny",
|
|
253
|
+
"aria-label": `Deny: ${n.consequence}`,
|
|
254
|
+
"aria-describedby": O,
|
|
255
|
+
onClick: () => c([n.id]),
|
|
256
|
+
children: "Deny"
|
|
257
|
+
})]
|
|
258
|
+
})]
|
|
259
|
+
}) : null]
|
|
260
|
+
}, n.id);
|
|
261
|
+
})
|
|
262
|
+
})]
|
|
263
|
+
}) : null
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
export { h as t };
|
|
269
|
+
|
|
270
|
+
//# sourceMappingURL=ApprovalGate-CRRh36j-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalGate-CRRh36j-.js","names":[],"sources":["../src/components/ApprovalGate/HoldToConfirm.tsx","../src/components/ApprovalGate/ApprovalGate.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\n\n/**\n * Whether the person has asked for less motion.\n *\n * Defaults to false so that the common case renders correctly on the first\n * paint, then corrects itself after mounting. Guarded because test\n * environments do not implement matchMedia.\n */\nfunction usePrefersReducedMotion() {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n if (typeof window.matchMedia !== 'function') return\n const query = window.matchMedia('(prefers-reduced-motion: reduce)')\n setReduced(query.matches)\n const onChange = () => setReduced(query.matches)\n query.addEventListener('change', onChange)\n return () => query.removeEventListener('change', onChange)\n }, [])\n\n return reduced\n}\n\n/** The hold, in milliseconds. One number, used by both paths. */\nconst HOLD_MS = 1200\n\n/**\n * Approving something that cannot be undone, by holding rather than clicking.\n *\n * A click is the same motion that cleared the reversible requests a moment\n * earlier. A hold is not: it is continuous, it cannot be produced by a stray\n * press, and it costs about as long as it takes to read the line you are\n * agreeing to. Apple reaches for the same idea when an action must not happen\n * by accident, which is why Apple Pay wants a double click of the side button\n * rather than a tap.\n *\n * Timings are deliberate and come from two different rules:\n *\n * - The fill takes 1.2 seconds and is **linear**, because it maps to real\n * elapsed time and easing would misrepresent how much is left. This is the\n * person deciding, so it is slow. A reflex press is around 150ms, so a second\n * is already past anything that can happen by accident, and longer than that\n * buys no safety and only spends patience.\n * - The release takes 200ms and eases out. This is the system responding, so\n * it is fast. Everything else in this library stays under 300ms for the same\n * reason.\n *\n * Nobody is required to hold anything:\n *\n * - **Keyboard** confirms on Enter or Space, with no hold. Reaching this\n * control already took a deliberate act, because the request had to be\n * opened first, so the separation still holds without demanding that someone\n * keep a key down at all. The button still reads \"Hold to approve\", because\n * changing the words per input device means the label is wrong for anyone who\n * uses both, which is most people.\n * - **Reduce Motion** still holds. The setting asks for less movement, not for\n * less care, and turning the hold into a click there would hand the one\n * population most likely to have it switched on the exact reflex this control\n * exists to defeat. So the fill stops animating and a timer runs instead: the\n * same 1.2 seconds, the same label, nothing moving.\n */\nexport function HoldToConfirm({\n onConfirm,\n consequence,\n describedBy,\n}: {\n onConfirm: () => void\n /** What approving this actually does. Goes into the control's spoken name. */\n consequence: string\n /** The id of the line saying this cannot be undone. */\n describedBy?: string\n}) {\n const reducedMotion = usePrefersReducedMotion()\n const [pressing, setPressing] = useState(false)\n // The transition end handler needs the current value, not the one captured\n // when the handler was created.\n const pressingNow = useRef(false)\n\n // Only the reduced-motion path uses this. The animated path is driven by the\n // fill reaching the far side, which is the same 1.2 seconds measured by the\n // thing the person can actually see.\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n const release = useCallback(() => {\n if (timer.current !== undefined) {\n clearTimeout(timer.current)\n timer.current = undefined\n }\n pressingNow.current = false\n setPressing(false)\n }, [])\n\n const startTimer = () => {\n pressingNow.current = true\n setPressing(true)\n timer.current = setTimeout(() => {\n timer.current = undefined\n // Still down? The pointer handlers clear this the moment it is not.\n if (!pressingNow.current) return\n release()\n onConfirm()\n }, HOLD_MS)\n }\n\n // A hold interrupted by the request being answered elsewhere, or by the gate\n // closing, must not fire after the component is gone.\n useEffect(() => release, [release])\n\n if (reducedMotion) {\n return (\n <button\n type=\"button\"\n className=\"agent-gate__hold agent-gate__hold--still\"\n aria-label={`Hold to approve: ${consequence}`}\n aria-describedby={describedBy}\n data-pressing={pressing ? 'true' : undefined}\n onPointerDown={(event) => {\n if (event.button !== 0) return\n try {\n event.currentTarget.setPointerCapture?.(event.pointerId)\n } catch {\n // No capture available. The hold still works while the pointer\n // stays on the control.\n }\n startTimer()\n }}\n onPointerUp={release}\n onPointerCancel={release}\n onContextMenu={(event) => event.preventDefault()}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onConfirm()\n }\n }}\n >\n <span className=\"agent-gate__hold-label\">Hold to approve</span>\n </button>\n )\n }\n\n return (\n <button\n type=\"button\"\n className=\"agent-gate__hold\"\n /* The visible words stay first in the spoken name, so that anyone driving\n this by voice can still say \"hold to approve\" and be understood. What\n follows is the part the screen changed to show and the ear never\n received: which of the four requests this button belongs to. */\n aria-label={`Hold to approve: ${consequence}`}\n aria-describedby={describedBy}\n data-pressing={pressing ? 'true' : undefined}\n onPointerDown={(event) => {\n // Primary button only. A right click must never start this.\n if (event.button !== 0) return\n // Capture keeps the hold alive if the pointer slides off the button.\n // Losing capture should degrade the hold, never break it, so a missing\n // or unhappy implementation is swallowed rather than thrown.\n try {\n event.currentTarget.setPointerCapture?.(event.pointerId)\n } catch {\n // No capture available. The hold still works while the pointer stays\n // on the control.\n }\n pressingNow.current = true\n setPressing(true)\n }}\n onPointerUp={release}\n onPointerCancel={release}\n // A long press on touch would otherwise raise the system context menu\n // in the middle of the hold.\n onContextMenu={(event) => event.preventDefault()}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onConfirm()\n }\n }}\n >\n <span\n className=\"agent-gate__hold-fill\"\n aria-hidden=\"true\"\n onTransitionEnd={(event) => {\n // Only the fill reaching the far side counts. The 200ms retreat after\n // an early release fires this too, and is ignored because the pointer\n // is no longer down.\n if (event.propertyName === 'clip-path' && pressingNow.current) {\n release()\n onConfirm()\n }\n }}\n />\n <span className=\"agent-gate__hold-label\">Hold to approve</span>\n </button>\n )\n}\n","import { useEffect, useId, useRef, useState } from 'react'\nimport type { ApprovalGateProps, ApprovalRequest } from './types'\nimport { HoldToConfirm } from './HoldToConfirm'\nimport '../sr-only.css'\nimport './ApprovalGate.css'\n\n/** Only an explicit `true` is safe. Everything else is held back. */\nfunction isReversible(request: ApprovalRequest) {\n return request.reversible === true\n}\n\n/** Someone actually looked at this and said it is permanent. */\nfunction isDeclaredPermanent(request: ApprovalRequest) {\n return request.reversible === false\n}\n\n/** For styling from the outside, and for reading the state back off the DOM. */\nfunction reversibility(request: ApprovalRequest) {\n if (request.reversible === true) return 'yes'\n if (request.reversible === false) return 'no'\n return 'unknown'\n}\n\n/**\n * The line above the group that cannot be batched.\n *\n * It exists because the reason those rows have no approve control is an\n * absence, and nobody notices a button that is not there. Its only job is to\n * say why these are separated from the ones above.\n *\n * Two registers, because they are two different claims. \"This cannot be undone\"\n * is a fact somebody vouched for. When nobody vouched for anything the honest\n * sentence is quieter, and it is quieter on purpose: painting an undeclared web\n * search in alarm red is exactly how a gate teaches people to stop seeing alarm\n * red.\n *\n * \"Review to answer\" rather than \"Review to approve\": denying is meant to cost\n * exactly what approving costs, and naming one outcome and not the other puts a\n * thumb on the scale in the one place this component exists to keep level.\n */\nfunction note(requests: ApprovalRequest[]) {\n const anyConfirmed = requests.some(isDeclaredPermanent)\n\n if (requests.length === 1) {\n return anyConfirmed\n ? 'Cannot be undone. Review to answer.'\n : 'Might not be possible to undo. Review to answer.'\n }\n\n if (requests.every(isDeclaredPermanent)) {\n return 'Cannot be undone. Review each to answer.'\n }\n if (!anyConfirmed) {\n return 'Might not be possible to undo. Review each to answer.'\n }\n return 'Some cannot be undone. Review each to answer.'\n}\n\n/**\n * Puts the keyboard somewhere sensible after a request is answered.\n *\n * Answering destroys the control that was just used, and a destroyed control\n * takes the focus with it. The browser's fallback is the top of the document,\n * so without this a keyboard user approves one thing and is silently returned\n * to the page heading, with no idea whether it worked or what is left.\n *\n * Focus goes to the gate itself, which carries the name, so the next thing\n * heard is what is still waiting. It only steps in when the focus was actually\n * lost: if the person is still standing on something real, that is where they\n * meant to be.\n *\n * The count reaching zero is the case this used to miss, and it is the ordinary\n * one: a single request, answered. The gate had nothing left to render, so\n * there was nothing to hand the focus to and it fell to the top of the document\n * anyway, which is the whole thing this exists to prevent. So the gate no\n * longer renders nothing when it is empty. It renders an empty, silent, focus\n * able shell that takes up no space, which also keeps the live region in the\n * page: a region that arrives already carrying its text is not announced, so\n * without the shell the very first request to arrive was heard by nobody.\n */\nfunction useFocusAfterAnswering(count: number, section: { current: HTMLElement | null }) {\n const previous = useRef(count)\n\n useEffect(() => {\n const shrank = count < previous.current\n previous.current = count\n if (!shrank) return\n\n const active = document.activeElement\n if (active && active !== document.body) return\n section.current?.focus()\n }, [count, section])\n}\n\n/**\n * Asks for a human decision without becoming a toll booth.\n *\n * Reversible requests batch: one button clears all of them at once, because\n * being asked four separate times about four safe things is what trains people\n * to stop reading.\n *\n * Everything else never batches. It sits below a rule, collapsed, with no\n * approve control at all until it has been opened, and then it is held rather\n * than clicked. That is the point: the motion that approves the safe ones\n * cannot reach it. Separating a destructive action by position does not work,\n * because a hand already moving does not stop at a horizontal rule. Separating\n * it by gesture does.\n *\n * \"Everything else\" is deliberate. A request nobody declared safe is handled\n * exactly like one declared permanent. Only the wording softens, because in\n * that case the component knows less and should not pretend otherwise.\n *\n * There are no timers here and there is no automatic decision. Dismissing is\n * neither yes nor no, and the requests survive it.\n */\nexport function ApprovalGate({\n requests,\n onApprove,\n onDeny,\n onDismiss,\n title = 'Waiting for you',\n className,\n}: ApprovalGateProps) {\n const [openIds, setOpenIds] = useState<string[]>([])\n const [collapsed, setCollapsed] = useState(false)\n const section = useRef<HTMLElement | null>(null)\n const uid = useId()\n\n useFocusAfterAnswering(requests.length, section)\n\n /* An opened row is a fact about this request, not about this position in the\n * list, so it has to be forgotten when the request goes.\n *\n * Without this the ids pile up forever, and a request that leaves and comes\n * back with the same id, which is what happens whenever the message list\n * reloads, returns already open with \"Hold to approve\" sitting there. Being\n * opened first is the entire separation; a row that reopens itself has\n * skipped it. */\n useEffect(() => {\n setOpenIds((open) => {\n if (open.length === 0) return open\n const stillHere = open.filter((id) => requests.some((request) => request.id === id))\n return stillHere.length === open.length ? open : stillHere\n })\n }, [requests])\n\n /* Empty, but not absent. See useFocusAfterAnswering: this is where the\n * keyboard lands after the last request is answered, and it is where the live\n * region waits so that the first request to arrive is actually announced.\n * Nothing here draws anything. */\n if (requests.length === 0) {\n return (\n <div\n ref={(element) => {\n section.current = element\n }}\n tabIndex={-1}\n >\n <p className=\"agent-sr-only\" role=\"status\" />\n </div>\n )\n }\n\n const safe = requests.filter(isReversible)\n const held = requests.filter((request) => !isReversible(request))\n const safeIds = safe.map((request) => request.id)\n const total = requests.length\n const noteId = `${uid}-note`\n\n const toggle = (id: string) =>\n setOpenIds((open) =>\n open.includes(id) ? open.filter((each) => each !== id) : [...open, id],\n )\n\n return (\n <section\n // The landmark keeps its name even when the visible heading is off, so\n // nothing is taken away from anyone navigating by region.\n aria-label={title === false ? 'Waiting for you' : title}\n // Focusable only on purpose, never in the tab order. This is the landing\n // spot after a request is answered, nothing else.\n ref={section}\n tabIndex={-1}\n className={['agent-gate', className].filter(Boolean).join(' ')}\n >\n {title === false && !onDismiss ? null : (\n <header className=\"agent-gate__head\">\n {/* Deliberately not a heading tag. This component does not know whether\n it sits under an h1 or an h3 in your page, and guessing wrong breaks\n the document outline for anyone navigating by headings. The section\n already carries the name through aria-label, so assistive technology\n still announces it. */}\n {title === false ? null : <p className=\"agent-gate__title\">{title}</p>}\n {onDismiss ? (\n /* A disclosure, not a verb.\n *\n * \"Not now\" removed the card, and a card that vanishes is easy to\n * forget. This collapses to a line that keeps saying what is still\n * waiting, including how many of them are permanent, because the\n * state someone is least likely to look at is exactly the state\n * where the exception most needs naming.\n *\n * onDismiss still fires, so a host that wants to know the person put\n * it away still finds out. What changed is that putting it away no\n * longer makes it silent. */\n <button\n type=\"button\"\n className=\"agent-gate__disclose\"\n aria-expanded={!collapsed}\n aria-label={collapsed ? 'Show the requests' : 'Collapse'}\n onClick={() => {\n setCollapsed((was) => !was)\n if (!collapsed) onDismiss()\n }}\n >\n <svg viewBox=\"0 0 14 14\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path\n d=\"M3.5 8.75 L7 5.25 L10.5 8.75\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n ) : null}\n </header>\n )}\n\n {/* One element doing two jobs, which is why it is always rendered.\n *\n * Visible, it is the collapsed line: the count is ordinary and may be\n * summarised, how many cannot be undone is the exception and is named.\n *\n * Invisible, it is the only thing in the component that speaks. An\n * approval arriving mid-stream is otherwise completely silent to a\n * screen reader, because nothing moves the focus and nothing has\n * changed where the reader is standing. A live region has to already be\n * in the page to be heard when it changes, so it lives here in both\n * states rather than being swapped in when it is wanted. */}\n <p\n className={collapsed ? 'agent-gate__waiting' : 'agent-gate__waiting agent-sr-only'}\n role=\"status\"\n data-tone={collapsed && held.length > 0 ? 'alarm' : undefined}\n >\n {total} {total === 1 ? 'request needs' : 'requests need'} your approval.\n {held.length > 0 ? (\n <span className=\"agent-gate__waiting-held\">\n {' '}\n {held.length} cannot be undone.\n </span>\n ) : null}\n </p>\n\n {!collapsed && safe.length > 0 ? (\n // Named so that arriving at these controls says which pile they clear.\n // On screen the rule and the note below do that job; in a screen reader\n // there is no rule, only two Approve buttons in a row.\n <div className=\"agent-gate__group\" role=\"group\" aria-label=\"Can be undone\">\n <ul className=\"agent-gate__list\">\n {safe.map((request) => (\n <li\n key={request.id}\n className=\"agent-gate__item\"\n data-reversible={reversibility(request)}\n >\n <span className=\"agent-gate__consequence\">{request.consequence}</span>\n {request.detail ? (\n <code className=\"agent-gate__detail\">{request.detail}</code>\n ) : null}\n </li>\n ))}\n </ul>\n <div className=\"agent-gate__actions\">\n <button\n type=\"button\"\n className=\"agent-gate__approve\"\n onClick={() => onApprove(safeIds)}\n >\n {safe.length === 1 ? 'Approve' : `Approve all ${safe.length}`}\n </button>\n <button\n type=\"button\"\n className=\"agent-gate__deny\"\n onClick={() => onDeny(safeIds)}\n >\n Deny\n </button>\n </div>\n </div>\n ) : null}\n\n {!collapsed && held.length > 0 ? (\n <div\n className=\"agent-gate__group agent-gate__group--destructive\"\n role=\"group\"\n aria-label=\"Answered one at a time\"\n >\n <p\n id={noteId}\n className=\"agent-gate__note\"\n data-tone={held.some(isDeclaredPermanent) ? 'alarm' : 'caution'}\n /* Nothing has been opened yet, so nothing is being decided yet. The\n colour arrives with the hold, at the moment the risk is actually\n being taken. A warning that shouts while the page is at rest is\n one people stop seeing. */\n data-resting={held.every((r) => !openIds.includes(r.id)) ? 'true' : undefined}\n >\n {note(held)}\n </p>\n <ul className=\"agent-gate__list\">\n {held.map((request, index) => {\n const isOpen = openIds.includes(request.id)\n // Built from the position rather than the id, because the id is\n // the host's and may be anything, including something a fragment\n // reference cannot address.\n const panelId = `${uid}-panel-${index}`\n return (\n <li\n key={request.id}\n className=\"agent-gate__item\"\n data-reversible={reversibility(request)}\n >\n <button\n type=\"button\"\n className=\"agent-gate__reveal\"\n aria-expanded={isOpen}\n /* Only while the panel exists. Pointing at an id that is\n not in the document is a dead reference, and the panel is\n absent rather than hidden on purpose. */\n aria-controls={isOpen ? panelId : undefined}\n onClick={() => toggle(request.id)}\n >\n <span className=\"agent-gate__consequence\">{request.consequence}</span>\n {/* The leading space is not a typo. Without it the two spans\n run together in the accessible name and the control is\n announced as \"Refunds $240 to the customerReview\". The\n space collapses to nothing on screen, because it sits at\n the start of its own line box. */}\n <span className=\"agent-gate__reveal-hint\">\n {' '}\n {isOpen ? 'Close' : 'Review'}\n </span>\n </button>\n\n {/* The approve control does not exist until this is opened.\n Not hidden, not disabled. Absent. */}\n {isOpen ? (\n <div id={panelId} className=\"agent-gate__revealed\">\n {request.detail ? (\n <code className=\"agent-gate__detail\">{request.detail}</code>\n ) : null}\n <div className=\"agent-gate__actions\">\n {/* Both controls carry the consequence in their name and\n the warning as their description. On screen the row\n above says what this is about; to anyone arriving by\n tab or by a list of buttons, four rows of \"Deny\" say\n nothing at all, and the one sentence that matters\n sits somewhere they will never pass through. */}\n <HoldToConfirm\n onConfirm={() => onApprove([request.id])}\n consequence={request.consequence}\n describedBy={noteId}\n />\n <button\n type=\"button\"\n className=\"agent-gate__deny\"\n aria-label={`Deny: ${request.consequence}`}\n aria-describedby={noteId}\n onClick={() => onDeny([request.id])}\n >\n Deny\n </button>\n </div>\n </div>\n ) : null}\n </li>\n )\n })}\n </ul>\n </div>\n ) : null}\n </section>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.agent-sr-only{white-space:nowrap;clip-path:inset(50%);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.agent-gate{--agent-gate-rule:color-mix(in srgb, currentColor 12%, transparent);--agent-gate-quiet:color-mix(in srgb, currentColor 60%, transparent);--agent-gate-fill:color-mix(in srgb, currentColor 6%, transparent);--agent-gate-danger:#ff3b30;border:1px solid var(--agent-gate-rule);border-radius:14px;max-width:480px;padding:16px;font-size:14px;line-height:1.45;display:block}.agent-gate__head{justify-content:space-between;align-items:baseline;gap:16px;margin-bottom:14px;display:flex}.agent-gate__title{color:inherit;margin:0;font-size:14px;font-weight:500}.agent-gate__list{gap:10px;margin:0;padding:0;list-style:none;display:grid}.agent-gate__item{gap:4px;display:grid}.agent-gate__consequence{display:block}.agent-gate__detail{color:var(--agent-gate-quiet);overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;display:block}.agent-gate__actions{flex-wrap:wrap;gap:8px;margin-top:14px;display:flex}.agent-gate__approve,.agent-gate__approve-one,.agent-gate__deny{font:inherit;border:1px solid var(--agent-gate-rule);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:7px 15px;font-size:13px;transition:background-color .15s ease-out}.agent-gate__approve{background:var(--agent-gate-fill);font-weight:500}.agent-gate__approve-one{border-color:color-mix(in srgb, var(--agent-gate-danger) 55%, transparent);color:var(--agent-gate-danger)}.agent-gate__approve:hover,.agent-gate__deny:hover{background:color-mix(in srgb, currentColor 9%, transparent)}.agent-gate__approve-one:hover{background:color-mix(in srgb, var(--agent-gate-danger) 10%, transparent)}.agent-gate__approve:focus-visible,.agent-gate__approve-one:focus-visible,.agent-gate__deny:focus-visible,.agent-gate__disclose:focus-visible,.agent-gate__hold:focus-visible,.agent-gate__reveal:focus-visible{outline-offset:2px;outline:2px solid}.agent-gate__hold{isolation:isolate;font:inherit;border:1px solid color-mix(in srgb, var(--agent-gate-danger) 55%, transparent);color:var(--agent-gate-danger);cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;background:0 0;border-radius:999px;padding:7px 15px;font-size:13px;transition:transform .16s ease-out;position:relative;overflow:hidden}.agent-gate__hold:active{transform:scale(.97)}.agent-gate__hold-fill{z-index:-1;background:color-mix(in srgb, var(--agent-gate-danger) 18%, transparent);clip-path:inset(0 100% 0 0);transition:clip-path .2s ease-out;position:absolute;inset:0}.agent-gate__hold[data-pressing=true] .agent-gate__hold-fill{clip-path:inset(0);transition:clip-path 1.2s linear}.agent-gate__hold-label{position:relative}@media (hover:hover) and (pointer:fine){.agent-gate__hold:hover{background:color-mix(in srgb, var(--agent-gate-danger) 8%, transparent)}}.agent-gate__group+.agent-gate__group--destructive{border-top:1px solid var(--agent-gate-rule);margin-top:18px;padding-top:16px}.agent-gate__note{color:var(--agent-gate-danger);margin:0 0 12px;font-size:12px}.agent-gate__note[data-tone=caution]{color:var(--agent-gate-quiet)}.agent-gate__note[data-resting]{color:var(--agent-gate-quiet);font-weight:400}.agent-gate__disclose{width:28px;height:28px;color:var(--agent-gate-quiet);cursor:pointer;background:0 0;border:0;border-radius:14px;justify-content:center;align-items:center;margin:-4px -6px -4px 0;padding:0;transition:background-color .15s ease-out,color .15s ease-out;display:inline-flex}.agent-gate__disclose:hover{background:var(--agent-gate-fill);color:inherit}.agent-gate__disclose svg{transition:transform .2s cubic-bezier(.23,1,.32,1)}.agent-gate__disclose[aria-expanded=false] svg{transform:rotate(180deg)}.agent-gate__waiting{margin:0}.agent-gate__waiting-held{color:var(--agent-gate-danger)}.agent-gate__item[data-reversible=unknown] .agent-gate__revealed{border-left-color:var(--agent-gate-rule)}.agent-gate__reveal{width:100%;font:inherit;text-align:left;color:inherit;cursor:pointer;background:0 0;border:0;justify-content:space-between;align-items:baseline;gap:12px;padding:0;display:flex}.agent-gate__reveal-hint{color:var(--agent-gate-quiet);text-underline-offset:3px;flex:none;font-size:12px;text-decoration:underline}.agent-gate__revealed{border-left:2px solid color-mix(in srgb, var(--agent-gate-danger) 40%, transparent);margin-top:8px;padding-left:12px}@media (prefers-reduced-motion:reduce){.agent-gate__approve,.agent-gate__approve-one,.agent-gate__deny,.agent-gate__disclose,.agent-gate__disclose svg{transition:none}}.agent-gate__hold--still[data-pressing=true]{background:color-mix(in srgb, var(--agent-gate-danger) 18%, transparent)}.agent-gate__hold--still,.agent-gate__hold--still:active{transition:none;transform:none}.agent-status{--agent-color-idle:color-mix(in srgb, currentColor 18%, transparent);--agent-color-working:currentColor;--agent-color-running:currentColor;--agent-color-needs-you:#e8952a;--agent-color-error:#ff3b30;--agent-color-done:#34c759;align-items:center;gap:.5em;line-height:1;display:inline-flex}.agent-status--sm{--agent-dot-size:6px}.agent-status--md{--agent-dot-size:8px}.agent-status--lg{--agent-dot-size:12px}.agent-status__dot{width:var(--agent-dot-size);height:var(--agent-dot-size);background:var(--agent-dot-color,var(--agent-color-idle));border-radius:50%;flex:none;transition:opacity .2s ease-out,transform .2s ease-out}.agent-status[data-state=idle] .agent-status__dot{--agent-dot-color:var(--agent-color-idle)}.agent-status[data-state=working] .agent-status__dot{--agent-dot-color:var(--agent-color-working)}.agent-status[data-state=running] .agent-status__dot{--agent-dot-color:var(--agent-color-running);animation:1.6s ease-in-out infinite agent-breathe}.agent-status[data-state=needsYou] .agent-status__dot{--agent-dot-color:var(--agent-color-needs-you)}.agent-status[data-state=done] .agent-status__dot{--agent-dot-color:var(--agent-color-done)}.agent-status[data-state=error] .agent-status__dot{border:max(1px, calc(var(--agent-dot-size) / 3.5)) solid var(--agent-color-error);background:0 0}@keyframes agent-breathe{0%,to{opacity:1;transform:scale(1)}50%{opacity:.15;transform:scale(.35)}}@media (prefers-reduced-motion:reduce){.agent-status[data-state=running] .agent-status__dot{animation:none}.agent-status__dot{transition:none}}@media (forced-colors:active){.agent-status__label.agent-sr-only{white-space:nowrap;clip-path:none;width:auto;height:auto;margin:0;position:static;overflow:visible}.agent-status__dot{background:canvastext;border-color:canvastext}.agent-status[data-state=idle] .agent-status__dot,.agent-status[data-state=done] .agent-status__dot{background:graytext}.agent-status[data-state=needsYou] .agent-status__dot{background:highlight}.agent-status[data-state=error] .agent-status__dot{background:canvas;border-color:canvastext}}.agent-strip{--agent-strip-rule:color-mix(in srgb, currentColor 12%, transparent);--agent-strip-quiet:color-mix(in srgb, currentColor 55%, transparent);--agent-strip-hover:color-mix(in srgb, currentColor 7%, transparent);font-size:14px;line-height:1.4;display:block}.agent-strip__list{margin:0;padding:0;list-style:none}.agent-strip__row{text-align:left;border-radius:8px;align-items:center;gap:10px;width:100%;padding:7px 8px;display:flex}.agent-strip__row--button{font:inherit;color:inherit;cursor:pointer;background:0 0;border:0;transition:background-color .12s ease-out}.agent-strip__row--button:hover{background:var(--agent-strip-hover)}.agent-strip__row--button:focus-visible{outline-offset:-2px;outline:2px solid}.agent-strip__status{flex:none;min-width:9em}.agent-strip__name{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;overflow:hidden}.agent-strip__elapsed{color:var(--agent-strip-quiet);font-variant-numeric:tabular-nums;flex:none;font-size:12px}.agent-strip__item:not([data-actionable]) .agent-strip__name{color:var(--agent-strip-quiet)}.agent-strip__summary{border-top:1px solid var(--agent-strip-rule);color:var(--agent-strip-quiet);margin:8px 0 0;padding:10px 8px 0;font-size:12px}@media (prefers-reduced-motion:reduce){.agent-strip__row--button{transition:none}}@media (forced-colors:active){.agent-strip{--agent-strip-quiet:GrayText;--agent-strip-rule:CanvasText}.agent-strip__status{min-width:11em}}.agent-trail{--agent-trail-rule:color-mix(in srgb, currentColor 14%, transparent);--agent-trail-quiet:color-mix(in srgb, currentColor 55%, transparent);--agent-trail-needs-you:#e8952a;--agent-trail-error:#ff3b30;font-size:14px;line-height:1.45;display:block}.agent-trail__earlier{color:var(--agent-trail-quiet);margin:0 0 10px;padding-left:22px;font-size:12px}.agent-trail__list,.agent-trail__sublist{margin:0;padding:0;list-style:none}.agent-trail__item{align-items:flex-start;gap:10px;padding:6px 0 6px 22px;display:flex;position:relative}.agent-trail__item:before{content:"";background:var(--agent-trail-rule);width:1px;position:absolute;top:0;bottom:0;left:5px}.agent-trail__item:first-child:before{top:14px}.agent-trail__item:last-child:before{height:14px;bottom:auto}.agent-trail__node{background:var(--agent-trail-quiet);border-radius:50%;width:7px;height:7px;position:absolute;top:12px;left:2px}.agent-trail__item[data-kind=ask] .agent-trail__node{background:var(--agent-trail-needs-you)}.agent-trail__item[data-kind=error] .agent-trail__node{border:2px solid var(--agent-trail-error);background:0 0}.agent-trail__item[data-kind=ask] .agent-trail__summary,.agent-trail__item[data-kind=error] .agent-trail__summary{font-weight:500}.agent-trail__body{flex:auto;min-width:0;display:block}.agent-trail__summary{display:block}.agent-trail__detail{color:var(--agent-trail-quiet);overflow-wrap:anywhere;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;display:block}.agent-trail__at{color:var(--agent-trail-quiet);font-variant-numeric:tabular-nums;flex:none;padding-top:1px;font-size:12px}.agent-trail__group{width:100%;font:inherit;text-align:left;color:var(--agent-trail-quiet);cursor:pointer;background:0 0;border:0;align-items:baseline;gap:10px;padding:0;display:flex}.agent-trail__group-hint{text-underline-offset:3px;flex:none;font-size:12px;text-decoration:underline}.agent-trail__group:focus-visible{outline-offset:2px;outline:2px solid}.agent-trail__sublist{border-left:1px solid var(--agent-trail-rule);gap:5px;margin-top:6px;padding-left:12px;display:grid}.agent-trail__subitem{color:var(--agent-trail-quiet);font-size:13px}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentApprovalGateProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* `ApprovalGate`, wired to the AI SDK.
|
|
4
|
+
*
|
|
5
|
+
* The SDK gives developers an `approval-requested` state and no interface at
|
|
6
|
+
* all, and its own cookbook tells them to build the buttons themselves. This is
|
|
7
|
+
* the interface. Hand it `messages` and `addToolApprovalResponse` from
|
|
8
|
+
* `useChat` and it works.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { ApprovalGate } from 'agent-approvals/ai-sdk'
|
|
12
|
+
* import 'agent-approvals/styles.css'
|
|
13
|
+
*
|
|
14
|
+
* // Outside the component, not inline in the JSX. Which of your tools can be
|
|
15
|
+
* // undone does not change between renders, and an object literal written in
|
|
16
|
+
* // the middle of the markup is a new object every time, which defeats the
|
|
17
|
+
* // memo below and rescans every message on every streamed token.
|
|
18
|
+
* const REVERSIBLE = { searchWeb: true, readFile: true }
|
|
19
|
+
*
|
|
20
|
+
* const { messages, addToolApprovalResponse } = useChat()
|
|
21
|
+
*
|
|
22
|
+
* <ApprovalGate
|
|
23
|
+
* messages={messages}
|
|
24
|
+
* addToolApprovalResponse={addToolApprovalResponse}
|
|
25
|
+
* reversible={REVERSIBLE}
|
|
26
|
+
* />
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Renders nothing when there is nothing to answer, so it can sit permanently in
|
|
30
|
+
* a layout without being conditionally mounted.
|
|
31
|
+
*
|
|
32
|
+
* This file writes no markup of its own. It reads the shape of a message, works
|
|
33
|
+
* out what is waiting, and hands that to the component. Everything to do with
|
|
34
|
+
* keyboard, focus and screen readers lives in `components/ApprovalGate`, which
|
|
35
|
+
* is the thing that actually owns the DOM. Counting the attributes in this file
|
|
36
|
+
* measures nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ApprovalGate({ messages, addToolApprovalResponse, reversible, describe, ...rest }: AgentApprovalGateProps): import("react").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=ApprovalGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalGate.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/ApprovalGate.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AA8CrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,uBAAuB,EACvB,UAAU,EACV,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,sBAAsB,+BAyBxB"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ApprovalGate } from './ApprovalGate';
|
|
2
|
+
export { toApprovalRequests } from './toApprovalRequests';
|
|
3
|
+
export type { AgentApprovalGateProps, AgentMessageLike, DescribeOption, ReversibleOption, ReversibleRule, ToApprovalRequestsOptions, } from './types';
|
|
4
|
+
export type { ApprovalRequest, Reversibility } from '../components/ApprovalGate/types';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,yBAAyB,GAC1B,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ApprovalRequest } from '../components/ApprovalGate/types';
|
|
2
|
+
import type { AgentMessageLike, ToApprovalRequestsOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Turns AI SDK messages into the requests `ApprovalGate` understands.
|
|
5
|
+
*
|
|
6
|
+
* Pure, so it can be tested without a model, a server or a browser. Pass
|
|
7
|
+
* `messages` from `useChat` straight in.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toApprovalRequests(messages: readonly AgentMessageLike[] | undefined, options?: ToApprovalRequestsOptions): ApprovalRequest[];
|
|
10
|
+
//# sourceMappingURL=toApprovalRequests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toApprovalRequests.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/toApprovalRequests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,kCAAkC,CAAA;AACtF,OAAO,KAAK,EACV,gBAAgB,EAGhB,yBAAyB,EAC1B,MAAM,SAAS,CAAA;AAuKhB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,EACjD,OAAO,GAAE,yBAA8B,GACtC,eAAe,EAAE,CA6BnB"}
|