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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +164 -0
  3. package/dist/ApprovalGate-CRRh36j-.js +270 -0
  4. package/dist/ApprovalGate-CRRh36j-.js.map +1 -0
  5. package/dist/agent-approvals.css +2 -0
  6. package/dist/ai-sdk/ApprovalGate.d.ts +39 -0
  7. package/dist/ai-sdk/ApprovalGate.d.ts.map +1 -0
  8. package/dist/ai-sdk/index.d.ts +5 -0
  9. package/dist/ai-sdk/index.d.ts.map +1 -0
  10. package/dist/ai-sdk/toApprovalRequests.d.ts +10 -0
  11. package/dist/ai-sdk/toApprovalRequests.d.ts.map +1 -0
  12. package/dist/ai-sdk/types.d.ts +61 -0
  13. package/dist/ai-sdk/types.d.ts.map +1 -0
  14. package/dist/ai-sdk.js +139 -0
  15. package/dist/ai-sdk.js.map +1 -0
  16. package/dist/components/ActivityTrail/ActivityTrail.d.ts +14 -0
  17. package/dist/components/ActivityTrail/ActivityTrail.d.ts.map +1 -0
  18. package/dist/components/ActivityTrail/types.d.ts +39 -0
  19. package/dist/components/ActivityTrail/types.d.ts.map +1 -0
  20. package/dist/components/ApprovalGate/ApprovalGate.d.ts +26 -0
  21. package/dist/components/ApprovalGate/ApprovalGate.d.ts.map +1 -0
  22. package/dist/components/ApprovalGate/HoldToConfirm.d.ts +43 -0
  23. package/dist/components/ApprovalGate/HoldToConfirm.d.ts.map +1 -0
  24. package/dist/components/ApprovalGate/types.d.ts +66 -0
  25. package/dist/components/ApprovalGate/types.d.ts.map +1 -0
  26. package/dist/components/SessionStrip/SessionStrip.d.ts +18 -0
  27. package/dist/components/SessionStrip/SessionStrip.d.ts.map +1 -0
  28. package/dist/components/SessionStrip/types.d.ts +31 -0
  29. package/dist/components/SessionStrip/types.d.ts.map +1 -0
  30. package/dist/components/StatusIndicator/StatusIndicator.d.ts +17 -0
  31. package/dist/components/StatusIndicator/StatusIndicator.d.ts.map +1 -0
  32. package/dist/components/StatusIndicator/types.d.ts +43 -0
  33. package/dist/components/StatusIndicator/types.d.ts.map +1 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +245 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/theme-macos.css +209 -0
  39. package/package.json +97 -0
package/dist/index.js ADDED
@@ -0,0 +1,245 @@
1
+ import { t as e } from "./ApprovalGate-CRRh36j-.js";
2
+ import { Fragment as t, jsx as n, jsxs as r } from "react/jsx-runtime";
3
+ import { useState as i } from "react";
4
+ //#region src/components/StatusIndicator/StatusIndicator.tsx
5
+ var a = {
6
+ idle: "Idle",
7
+ working: "Writing",
8
+ running: "Running a command",
9
+ needsYou: "Waiting for you",
10
+ error: "Error",
11
+ done: "Done"
12
+ };
13
+ function o({ state: e, label: t, showLabel: i = !1, size: o = "md", className: s }) {
14
+ let c = t ?? a[e];
15
+ return /* @__PURE__ */ r("span", {
16
+ "data-state": e,
17
+ className: [
18
+ "agent-status",
19
+ `agent-status--${o}`,
20
+ s
21
+ ].filter(Boolean).join(" "),
22
+ children: [/* @__PURE__ */ n("span", {
23
+ className: "agent-status__dot",
24
+ "aria-hidden": "true"
25
+ }), /* @__PURE__ */ n("span", {
26
+ className: i ? "agent-status__label" : "agent-status__label agent-sr-only",
27
+ children: c
28
+ })]
29
+ });
30
+ }
31
+ //#endregion
32
+ //#region src/components/SessionStrip/SessionStrip.tsx
33
+ function s(e) {
34
+ return e.state === "needsYou" || e.state === "error";
35
+ }
36
+ function c(e) {
37
+ let t = (t) => e.filter((e) => t.includes(e.state)).length, n = [], r = t(["working", "running"]), i = t(["done"]), a = t(["idle"]);
38
+ return r > 0 && n.push(`${r} running`), i > 0 && n.push(`${i} done`), a > 0 && n.push(`${a} idle`), n.join(", ");
39
+ }
40
+ function l(e) {
41
+ let t = e.filter(s);
42
+ if (t.length === 0) return "";
43
+ let n = t.map((e) => e.name).join(", "), r = t.length === 1 ? "session needs" : "sessions need";
44
+ return `${t.length} ${r} you: ${n}.`;
45
+ }
46
+ function u({ sessions: e, maxQuiet: i = 4, onSelect: a, label: u = "Sessions", className: d }) {
47
+ if (e.length === 0) return /* @__PURE__ */ n("p", {
48
+ className: "agent-strip__announce agent-sr-only",
49
+ role: "status"
50
+ });
51
+ let f = e.filter(s), p = e.filter((e) => !s(e)), m = p.slice(0, Math.max(0, i)), h = p.slice(m.length), g = [...f, ...m], _ = c(h);
52
+ return /* @__PURE__ */ r("section", {
53
+ "aria-label": u,
54
+ className: ["agent-strip", d].filter(Boolean).join(" "),
55
+ children: [
56
+ /* @__PURE__ */ n("ul", {
57
+ className: "agent-strip__list",
58
+ children: g.map((e) => {
59
+ let i = /* @__PURE__ */ r(t, { children: [
60
+ /* @__PURE__ */ n(o, {
61
+ state: e.state,
62
+ showLabel: s(e),
63
+ className: "agent-strip__status"
64
+ }),
65
+ /* @__PURE__ */ n("span", {
66
+ className: "agent-strip__name",
67
+ children: e.name
68
+ }),
69
+ e.elapsed ? /* @__PURE__ */ n("span", {
70
+ className: "agent-strip__elapsed",
71
+ children: e.elapsed
72
+ }) : null
73
+ ] });
74
+ return /* @__PURE__ */ n("li", {
75
+ className: "agent-strip__item",
76
+ "data-state": e.state,
77
+ "data-actionable": s(e) ? "true" : void 0,
78
+ children: a ? /* @__PURE__ */ n("button", {
79
+ type: "button",
80
+ className: "agent-strip__row agent-strip__row--button",
81
+ onClick: () => a(e.id),
82
+ children: i
83
+ }) : /* @__PURE__ */ n("span", {
84
+ className: "agent-strip__row",
85
+ children: i
86
+ })
87
+ }, e.id);
88
+ })
89
+ }),
90
+ _ ? /* @__PURE__ */ r("p", {
91
+ className: "agent-strip__summary",
92
+ children: [/* @__PURE__ */ n("span", {
93
+ className: "agent-sr-only",
94
+ children: "Not listed: "
95
+ }), _]
96
+ }) : null,
97
+ /* @__PURE__ */ n("p", {
98
+ className: "agent-strip__announce agent-sr-only",
99
+ role: "status",
100
+ children: l(e)
101
+ })
102
+ ]
103
+ });
104
+ }
105
+ //#endregion
106
+ //#region src/components/ActivityTrail/ActivityTrail.tsx
107
+ function d(e) {
108
+ return e === "ask" || e === "error";
109
+ }
110
+ var f = {
111
+ read: ["file", "files"],
112
+ edit: ["file", "files"],
113
+ run: ["command", "commands"],
114
+ ask: ["question", "questions"],
115
+ error: ["failure", "failures"],
116
+ note: ["note", "notes"]
117
+ }, p = {
118
+ read: "Read",
119
+ edit: "Edited",
120
+ run: "Ran",
121
+ ask: "Asked",
122
+ error: "Failed",
123
+ note: "Noted"
124
+ };
125
+ function m(e, t) {
126
+ let [n, r] = f[e];
127
+ return `${p[e]} ${t} ${t === 1 ? n : r}`;
128
+ }
129
+ function h(e) {
130
+ let t = [];
131
+ for (let n of e) {
132
+ let e = t[t.length - 1];
133
+ if (d(n.kind)) {
134
+ t.push({
135
+ type: "single",
136
+ key: n.id,
137
+ activity: n
138
+ });
139
+ continue;
140
+ }
141
+ if (e?.type === "group" && e.kind === n.kind) {
142
+ e.activities.push(n);
143
+ continue;
144
+ }
145
+ if (e?.type === "single" && e.activity.kind === n.kind) {
146
+ t[t.length - 1] = {
147
+ type: "group",
148
+ key: e.activity.id,
149
+ kind: n.kind,
150
+ activities: [e.activity, n]
151
+ };
152
+ continue;
153
+ }
154
+ t.push({
155
+ type: "single",
156
+ key: n.id,
157
+ activity: n
158
+ });
159
+ }
160
+ return t;
161
+ }
162
+ function g({ activities: e, maxVisible: t = 8, summarise: a = m, label: o = "Activity", className: s }) {
163
+ let [c, l] = i([]);
164
+ if (e.length === 0) return null;
165
+ let u = h(e), d = Math.max(0, t), f = Math.max(0, u.length - d), p = f > 0 ? u.slice(f) : u, g = u.slice(0, f).reduce((e, t) => e + (t.type === "group" ? t.activities.length : 1), 0), _ = (e) => l((t) => t.includes(e) ? t.filter((t) => t !== e) : [...t, e]);
166
+ return /* @__PURE__ */ r("section", {
167
+ "aria-label": o,
168
+ className: ["agent-trail", s].filter(Boolean).join(" "),
169
+ children: [g > 0 ? /* @__PURE__ */ r("p", {
170
+ className: "agent-trail__earlier",
171
+ children: [g, " earlier steps"]
172
+ }) : null, /* @__PURE__ */ n("ol", {
173
+ className: "agent-trail__list",
174
+ children: p.map((e) => {
175
+ if (e.type === "single") {
176
+ let { activity: t } = e;
177
+ return /* @__PURE__ */ r("li", {
178
+ className: "agent-trail__item",
179
+ "data-kind": t.kind,
180
+ children: [
181
+ /* @__PURE__ */ n("span", {
182
+ className: "agent-trail__node",
183
+ "aria-hidden": "true"
184
+ }),
185
+ /* @__PURE__ */ r("span", {
186
+ className: "agent-trail__body",
187
+ children: [/* @__PURE__ */ n("span", {
188
+ className: "agent-trail__summary",
189
+ children: t.summary
190
+ }), t.detail ? /* @__PURE__ */ n("code", {
191
+ className: "agent-trail__detail",
192
+ children: t.detail
193
+ }) : null]
194
+ }),
195
+ t.at ? /* @__PURE__ */ n("span", {
196
+ className: "agent-trail__at",
197
+ children: t.at
198
+ }) : null
199
+ ]
200
+ }, e.key);
201
+ }
202
+ let t = c.includes(e.key);
203
+ return /* @__PURE__ */ r("li", {
204
+ className: "agent-trail__item",
205
+ "data-kind": e.kind,
206
+ children: [/* @__PURE__ */ n("span", {
207
+ className: "agent-trail__node",
208
+ "aria-hidden": "true"
209
+ }), /* @__PURE__ */ r("span", {
210
+ className: "agent-trail__body",
211
+ children: [/* @__PURE__ */ r("button", {
212
+ type: "button",
213
+ className: "agent-trail__group",
214
+ "aria-expanded": t,
215
+ onClick: () => _(e.key),
216
+ children: [/* @__PURE__ */ n("span", {
217
+ className: "agent-trail__summary",
218
+ children: a(e.kind, e.activities.length)
219
+ }), /* @__PURE__ */ n("span", {
220
+ className: "agent-trail__group-hint",
221
+ children: t ? "Hide" : "Show"
222
+ })]
223
+ }), t ? /* @__PURE__ */ n("ol", {
224
+ className: "agent-trail__sublist",
225
+ children: e.activities.map((e) => /* @__PURE__ */ r("li", {
226
+ className: "agent-trail__subitem",
227
+ children: [/* @__PURE__ */ n("span", {
228
+ className: "agent-trail__summary",
229
+ children: e.summary
230
+ }), e.detail ? /* @__PURE__ */ n("code", {
231
+ className: "agent-trail__detail",
232
+ children: e.detail
233
+ }) : null]
234
+ }, e.id))
235
+ }) : null]
236
+ })]
237
+ }, e.key);
238
+ })
239
+ })]
240
+ });
241
+ }
242
+ //#endregion
243
+ export { g as ActivityTrail, e as ApprovalGate, u as SessionStrip, o as StatusIndicator };
244
+
245
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/components/StatusIndicator/StatusIndicator.tsx","../src/components/SessionStrip/SessionStrip.tsx","../src/components/ActivityTrail/ActivityTrail.tsx"],"sourcesContent":["import type { SessionState, StatusIndicatorProps } from './types'\nimport '../sr-only.css'\nimport './StatusIndicator.css'\n\n/**\n * The wording each state shows by default. Pass `label` to replace it.\n *\n * \"Writing\" and \"Running a command\" say who is doing the work. The earlier\n * pair, \"Active\" and \"Running\", were synonyms in English and nobody could hold\n * them apart, including the person who designed them.\n */\nconst DEFAULT_LABEL: Record<SessionState, string> = {\n idle: 'Idle',\n working: 'Writing',\n running: 'Running a command',\n needsYou: 'Waiting for you',\n error: 'Error',\n done: 'Done',\n}\n\n/**\n * One session's state, as a dot with an optional label.\n *\n * Motion means state, colour means exception. Only `running` moves, because\n * only `running` is the case of being alive without progressing, and a breath\n * is what that looks like. `error` is told apart by shape rather than hue, so\n * it still reads for anyone who cannot separate red from amber.\n *\n * This component reports. It is never a control. It has no click handler and\n * takes no children on purpose: the moment a status can be clicked it has\n * become an approval, and approvals are ApprovalGate's job.\n */\nexport function StatusIndicator({\n state,\n label,\n showLabel = false,\n size = 'md',\n className,\n}: StatusIndicatorProps) {\n const text = label ?? DEFAULT_LABEL[state]\n\n return (\n <span\n // data-state is here so you can style any state from your own CSS\n // without this component needing to expose a prop for every colour.\n data-state={state}\n className={['agent-status', `agent-status--${size}`, className]\n .filter(Boolean)\n .join(' ')}\n >\n <span className=\"agent-status__dot\" aria-hidden=\"true\" />\n <span className={showLabel ? 'agent-status__label' : 'agent-status__label agent-sr-only'}>\n {text}\n </span>\n </span>\n )\n}\n","import { StatusIndicator } from '../StatusIndicator/StatusIndicator'\nimport type { SessionState } from '../StatusIndicator/types'\nimport type { Session, SessionStripProps } from './types'\nimport '../sr-only.css'\nimport './SessionStrip.css'\n\n/** A session the person can actually do something about right now. */\nfunction isActionable(session: Session) {\n return session.state === 'needsYou' || session.state === 'error'\n}\n\n/**\n * \"3 running, 1 done, 2 idle\". Weather, not telemetry.\n *\n * Writing and Running a command are counted together here on purpose. At a\n * glance the useful fact is how many are busy. Which kind of busy matters when\n * you look at a single session, not when you are counting them.\n */\nfunction summarise(sessions: Session[]) {\n const count = (states: SessionState[]) =>\n sessions.filter((session) => states.includes(session.state)).length\n\n const parts: string[] = []\n const running = count(['working', 'running'])\n const done = count(['done'])\n const idle = count(['idle'])\n\n if (running > 0) parts.push(`${running} running`)\n if (done > 0) parts.push(`${done} done`)\n if (idle > 0) parts.push(`${idle} idle`)\n return parts.join(', ')\n}\n\n/**\n * The one sentence this component says out loud.\n *\n * Compress the ordinary, never compress the exception, applied to sound. A\n * session that starts writing is ordinary and says nothing, because a screen\n * reader narrating twelve rows changing state is the telemetry this component\n * exists to avoid. A session that stops dead waiting for an answer is the\n * exception, and it is named.\n *\n * Derived from what is true now rather than from a transition, so there is no\n * timer, no stored history, and nothing to leak. A polite region is not read on\n * arrival, only when it changes, which is exactly the difference between the\n * state and an event.\n */\nfunction speak(sessions: Session[]) {\n const waiting = sessions.filter(isActionable)\n if (waiting.length === 0) return ''\n\n const names = waiting.map((session) => session.name).join(', ')\n const verb = waiting.length === 1 ? 'session needs' : 'sessions need'\n return `${waiting.length} ${verb} you: ${names}.`\n}\n\n/**\n * Many parallel sessions, read at a glance.\n *\n * Two rules hold this together. Sessions that need a person come first and are\n * never folded away, however many there are. Everything else is allowed to\n * collapse into a single line of counts, because a list of twelve calm rows is\n * telemetry, and nobody reads telemetry.\n *\n * Rows do not animate when they reorder. A session that starts needing you\n * jumps to the top immediately, with no transition, matching the menu bar.\n * Motion belongs to a single dot describing itself, not to a list rearranging\n * under someone's eyes.\n */\nexport function SessionStrip({\n sessions,\n maxQuiet = 4,\n onSelect,\n label = 'Sessions',\n className,\n}: SessionStripProps) {\n /* Empty, but not absent.\n *\n * Returning null took the live region out of the page with it, and a region\n * that arrives already carrying its sentence is not announced. So a dashboard\n * that renders this with nothing running, then has one session stop dead\n * waiting for an answer, said nothing at all to anyone listening. The shell\n * draws nothing and takes no space; it only keeps the announcer where it has\n * to be to work. */\n if (sessions.length === 0) {\n return <p className=\"agent-strip__announce agent-sr-only\" role=\"status\" />\n }\n\n const actionable = sessions.filter(isActionable)\n const quiet = sessions.filter((session) => !isActionable(session))\n\n const shownQuiet = quiet.slice(0, Math.max(0, maxQuiet))\n const foldedQuiet = quiet.slice(shownQuiet.length)\n\n const rows = [...actionable, ...shownQuiet]\n const summary = summarise(foldedQuiet)\n\n return (\n <section\n aria-label={label}\n className={['agent-strip', className].filter(Boolean).join(' ')}\n >\n <ul className=\"agent-strip__list\">\n {rows.map((session) => {\n // One StatusIndicator per row, never two. A second one would read the\n // state out twice to a screen reader.\n //\n // The word is shown only on rows that need a person. On a calm row the\n // dot is enough, and twelve rows each spelling out \"Idle\" is the\n // telemetry this component exists to avoid.\n const content = (\n <>\n <StatusIndicator\n state={session.state}\n showLabel={isActionable(session)}\n className=\"agent-strip__status\"\n />\n <span className=\"agent-strip__name\">{session.name}</span>\n {session.elapsed ? (\n <span className=\"agent-strip__elapsed\">{session.elapsed}</span>\n ) : null}\n </>\n )\n\n return (\n <li\n key={session.id}\n className=\"agent-strip__item\"\n data-state={session.state}\n data-actionable={isActionable(session) ? 'true' : undefined}\n >\n {onSelect ? (\n <button\n type=\"button\"\n className=\"agent-strip__row agent-strip__row--button\"\n onClick={() => onSelect(session.id)}\n >\n {content}\n </button>\n ) : (\n <span className=\"agent-strip__row\">{content}</span>\n )}\n </li>\n )\n })}\n </ul>\n\n {summary ? (\n <p className=\"agent-strip__summary\">\n {/* Read on its own this line is five numbers with no subject. The\n prefix says whose counts these are, and only to a screen reader,\n because on screen the rule above the line already says it. */}\n <span className=\"agent-sr-only\">Not listed: </span>\n {summary}\n </p>\n ) : null}\n\n {/* Named, not counted. See speak() above. */}\n <p className=\"agent-strip__announce agent-sr-only\" role=\"status\">\n {speak(sessions)}\n </p>\n </section>\n )\n}\n","import { useState } from 'react'\nimport type { Activity, ActivityKind, ActivityTrailProps } from './types'\nimport './ActivityTrail.css'\n\n/**\n * Exceptions never fold. A question the agent asked and a thing that failed are\n * the two events a person came here to find, so they always stand on their own,\n * however many of them there are.\n */\nfunction isException(kind: ActivityKind) {\n return kind === 'ask' || kind === 'error'\n}\n\nconst PLURAL: Record<ActivityKind, [one: string, many: string]> = {\n read: ['file', 'files'],\n edit: ['file', 'files'],\n run: ['command', 'commands'],\n ask: ['question', 'questions'],\n error: ['failure', 'failures'],\n note: ['note', 'notes'],\n}\n\nconst VERB: Record<ActivityKind, string> = {\n read: 'Read',\n edit: 'Edited',\n run: 'Ran',\n ask: 'Asked',\n error: 'Failed',\n note: 'Noted',\n}\n\nfunction defaultSummarise(kind: ActivityKind, count: number) {\n const [one, many] = PLURAL[kind]\n return `${VERB[kind]} ${count} ${count === 1 ? one : many}`\n}\n\ntype Row =\n | { type: 'single'; key: string; activity: Activity }\n | { type: 'group'; key: string; kind: ActivityKind; activities: Activity[] }\n\n/**\n * Runs of the same ordinary thing become one row. Runs are consecutive only,\n * because the order is the story: five reads, then a command, then five more\n * reads is three rows, not two.\n */\nfunction toRows(activities: Activity[]): Row[] {\n const rows: Row[] = []\n\n for (const activity of activities) {\n const previous = rows[rows.length - 1]\n\n if (isException(activity.kind)) {\n rows.push({ type: 'single', key: activity.id, activity })\n continue\n }\n\n if (previous?.type === 'group' && previous.kind === activity.kind) {\n previous.activities.push(activity)\n continue\n }\n\n if (previous?.type === 'single' && previous.activity.kind === activity.kind) {\n rows[rows.length - 1] = {\n type: 'group',\n key: previous.activity.id,\n kind: activity.kind,\n activities: [previous.activity, activity],\n }\n continue\n }\n\n rows.push({ type: 'single', key: activity.id, activity })\n }\n\n return rows\n}\n\n/**\n * What the agent did, compressed and scannable.\n *\n * A log dump is a list of everything that happened, in the order it happened,\n * at the same volume. Nobody reads one. This is the same information with the\n * repetition folded up and the two things worth finding, the questions and the\n * failures, left standing at full size.\n *\n * It is a record. There is nothing to click except opening a folded group.\n */\nexport function ActivityTrail({\n activities,\n maxVisible = 8,\n summarise = defaultSummarise,\n label = 'Activity',\n className,\n}: ActivityTrailProps) {\n const [openKeys, setOpenKeys] = useState<string[]>([])\n\n if (activities.length === 0) return null\n\n const rows = toRows(activities)\n const limit = Math.max(0, maxVisible)\n const hidden = Math.max(0, rows.length - limit)\n const shown = hidden > 0 ? rows.slice(hidden) : rows\n\n const earlier = rows\n .slice(0, hidden)\n .reduce((total, row) => total + (row.type === 'group' ? row.activities.length : 1), 0)\n\n const toggle = (key: string) =>\n setOpenKeys((open) =>\n open.includes(key) ? open.filter((each) => each !== key) : [...open, key],\n )\n\n return (\n <section\n aria-label={label}\n className={['agent-trail', className].filter(Boolean).join(' ')}\n >\n {earlier > 0 ? (\n <p className=\"agent-trail__earlier\">{earlier} earlier steps</p>\n ) : null}\n\n <ol className=\"agent-trail__list\">\n {shown.map((row) => {\n if (row.type === 'single') {\n const { activity } = row\n return (\n <li\n key={row.key}\n className=\"agent-trail__item\"\n data-kind={activity.kind}\n >\n <span className=\"agent-trail__node\" aria-hidden=\"true\" />\n <span className=\"agent-trail__body\">\n <span className=\"agent-trail__summary\">{activity.summary}</span>\n {activity.detail ? (\n <code className=\"agent-trail__detail\">{activity.detail}</code>\n ) : null}\n </span>\n {activity.at ? <span className=\"agent-trail__at\">{activity.at}</span> : null}\n </li>\n )\n }\n\n const isOpen = openKeys.includes(row.key)\n return (\n <li key={row.key} className=\"agent-trail__item\" data-kind={row.kind}>\n <span className=\"agent-trail__node\" aria-hidden=\"true\" />\n <span className=\"agent-trail__body\">\n <button\n type=\"button\"\n className=\"agent-trail__group\"\n aria-expanded={isOpen}\n onClick={() => toggle(row.key)}\n >\n <span className=\"agent-trail__summary\">\n {summarise(row.kind, row.activities.length)}\n </span>\n <span className=\"agent-trail__group-hint\">{isOpen ? 'Hide' : 'Show'}</span>\n </button>\n\n {isOpen ? (\n <ol className=\"agent-trail__sublist\">\n {row.activities.map((activity) => (\n <li key={activity.id} className=\"agent-trail__subitem\">\n <span className=\"agent-trail__summary\">{activity.summary}</span>\n {activity.detail ? (\n <code className=\"agent-trail__detail\">{activity.detail}</code>\n ) : null}\n </li>\n ))}\n </ol>\n ) : null}\n </span>\n </li>\n )\n })}\n </ol>\n </section>\n )\n}\n"],"mappings":";;;;AAWA,IAAM,IAA8C;CAClD,MAAM;CACN,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,MAAM;AACR;AAcA,SAAgB,EAAgB,EAC9B,UACA,UACA,eAAY,IACZ,UAAO,MACP,gBACuB;CACvB,IAAM,IAAO,KAAS,EAAc;CAEpC,OACE,kBAAC,QAAD;EAGE,cAAY;EACZ,WAAW;GAAC;GAAgB,iBAAiB;GAAQ;EAAS,CAAC,CAC5D,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EANb,UAAA,CAQE,kBAAC,QAAD;GAAM,WAAU;GAAoB,eAAY;EAAQ,CAAA,GACxD,kBAAC,QAAD;GAAM,WAAW,IAAY,wBAAwB;GAClD,UAAA;EACG,CAAA,CACF;;AAEV;;;ACjDA,SAAS,EAAa,GAAkB;CACtC,OAAO,EAAQ,UAAU,cAAc,EAAQ,UAAU;AAC3D;AASA,SAAS,EAAU,GAAqB;CACtC,IAAM,KAAS,MACb,EAAS,QAAQ,MAAY,EAAO,SAAS,EAAQ,KAAK,CAAC,CAAC,CAAC,QAEzD,IAAkB,CAAC,GACnB,IAAU,EAAM,CAAC,WAAW,SAAS,CAAC,GACtC,IAAO,EAAM,CAAC,MAAM,CAAC,GACrB,IAAO,EAAM,CAAC,MAAM,CAAC;CAK3B,OAHI,IAAU,KAAG,EAAM,KAAK,GAAG,EAAQ,SAAS,GAC5C,IAAO,KAAG,EAAM,KAAK,GAAG,EAAK,MAAM,GACnC,IAAO,KAAG,EAAM,KAAK,GAAG,EAAK,MAAM,GAChC,EAAM,KAAK,IAAI;AACxB;AAgBA,SAAS,EAAM,GAAqB;CAClC,IAAM,IAAU,EAAS,OAAO,CAAY;CAC5C,IAAI,EAAQ,WAAW,GAAG,OAAO;CAEjC,IAAM,IAAQ,EAAQ,KAAK,MAAY,EAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,GACxD,IAAO,EAAQ,WAAW,IAAI,kBAAkB;CACtD,OAAO,GAAG,EAAQ,OAAO,GAAG,EAAK,QAAQ,EAAM;AACjD;AAeA,SAAgB,EAAa,EAC3B,aACA,cAAW,GACX,aACA,WAAQ,YACR,gBACoB;CASpB,IAAI,EAAS,WAAW,GACtB,OAAO,kBAAC,KAAD;EAAG,WAAU;EAAsC,MAAK;CAAU,CAAA;CAG3E,IAAM,IAAa,EAAS,OAAO,CAAY,GACzC,IAAQ,EAAS,QAAQ,MAAY,CAAC,EAAa,CAAO,CAAC,GAE3D,IAAa,EAAM,MAAM,GAAG,KAAK,IAAI,GAAG,CAAQ,CAAC,GACjD,IAAc,EAAM,MAAM,EAAW,MAAM,GAE3C,IAAO,CAAC,GAAG,GAAY,GAAG,CAAU,GACpC,IAAU,EAAU,CAAW;CAErC,OACE,kBAAC,WAAD;EACE,cAAY;EACZ,WAAW,CAAC,eAAe,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAFhE,UAAA;GAIE,kBAAC,MAAD;IAAI,WAAU;IACX,UAAA,EAAK,KAAK,MAAY;KAOrB,IAAM,IACJ,kBAAA,GAAA,EAAA,UAAA;MACE,kBAAC,GAAD;OACE,OAAO,EAAQ;OACf,WAAW,EAAa,CAAO;OAC/B,WAAU;MACX,CAAA;MACD,kBAAC,QAAD;OAAM,WAAU;OAAqB,UAAA,EAAQ;MAAW,CAAA;MACvD,EAAQ,UACP,kBAAC,QAAD;OAAM,WAAU;OAAwB,UAAA,EAAQ;MAAc,CAAA,IAC5D;KACJ,EAAA,CAAA;KAGJ,OACE,kBAAC,MAAD;MAEE,WAAU;MACV,cAAY,EAAQ;MACpB,mBAAiB,EAAa,CAAO,IAAI,SAAS,KAAA;MAEjD,UAAA,IACC,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,eAAe,EAAS,EAAQ,EAAE;OAEjC,UAAA;MACK,CAAA,IAER,kBAAC,QAAD;OAAM,WAAU;OAAoB,UAAA;MAAc,CAAA;KAElD,GAhBG,EAAQ,EAgBX;IAER,CAAC;GACC,CAAA;GAEH,IACC,kBAAC,KAAD;IAAG,WAAU;IAAb,UAAA,CAIE,kBAAC,QAAD;KAAM,WAAU;KAAgB,UAAA;IAAkB,CAAA,GACjD,CACA;GACD,CAAA,IAAA;GAGJ,kBAAC,KAAD;IAAG,WAAU;IAAsC,MAAK;IACrD,UAAA,EAAM,CAAQ;GACd,CAAA;EACI;;AAEb;;;AC1JA,SAAS,EAAY,GAAoB;CACvC,OAAO,MAAS,SAAS,MAAS;AACpC;AAEA,IAAM,IAA4D;CAChE,MAAM,CAAC,QAAQ,OAAO;CACtB,MAAM,CAAC,QAAQ,OAAO;CACtB,KAAK,CAAC,WAAW,UAAU;CAC3B,KAAK,CAAC,YAAY,WAAW;CAC7B,OAAO,CAAC,WAAW,UAAU;CAC7B,MAAM,CAAC,QAAQ,OAAO;AACxB,GAEM,IAAqC;CACzC,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;CACL,OAAO;CACP,MAAM;AACR;AAEA,SAAS,EAAiB,GAAoB,GAAe;CAC3D,IAAM,CAAC,GAAK,KAAQ,EAAO;CAC3B,OAAO,GAAG,EAAK,GAAM,GAAG,EAAM,GAAG,MAAU,IAAI,IAAM;AACvD;AAWA,SAAS,EAAO,GAA+B;CAC7C,IAAM,IAAc,CAAC;CAErB,KAAK,IAAM,KAAY,GAAY;EACjC,IAAM,IAAW,EAAK,EAAK,SAAS;EAEpC,IAAI,EAAY,EAAS,IAAI,GAAG;GAC9B,EAAK,KAAK;IAAE,MAAM;IAAU,KAAK,EAAS;IAAI;GAAS,CAAC;GACxD;EACF;EAEA,IAAI,GAAU,SAAS,WAAW,EAAS,SAAS,EAAS,MAAM;GACjE,EAAS,WAAW,KAAK,CAAQ;GACjC;EACF;EAEA,IAAI,GAAU,SAAS,YAAY,EAAS,SAAS,SAAS,EAAS,MAAM;GAC3E,EAAK,EAAK,SAAS,KAAK;IACtB,MAAM;IACN,KAAK,EAAS,SAAS;IACvB,MAAM,EAAS;IACf,YAAY,CAAC,EAAS,UAAU,CAAQ;GAC1C;GACA;EACF;EAEA,EAAK,KAAK;GAAE,MAAM;GAAU,KAAK,EAAS;GAAI;EAAS,CAAC;CAC1D;CAEA,OAAO;AACT;AAYA,SAAgB,EAAc,EAC5B,eACA,gBAAa,GACb,eAAY,GACZ,WAAQ,YACR,gBACqB;CACrB,IAAM,CAAC,GAAU,KAAe,EAAmB,CAAC,CAAC;CAErD,IAAI,EAAW,WAAW,GAAG,OAAO;CAEpC,IAAM,IAAO,EAAO,CAAU,GACxB,IAAQ,KAAK,IAAI,GAAG,CAAU,GAC9B,IAAS,KAAK,IAAI,GAAG,EAAK,SAAS,CAAK,GACxC,IAAQ,IAAS,IAAI,EAAK,MAAM,CAAM,IAAI,GAE1C,IAAU,EACb,MAAM,GAAG,CAAM,CAAC,CAChB,QAAQ,GAAO,MAAQ,KAAS,EAAI,SAAS,UAAU,EAAI,WAAW,SAAS,IAAI,CAAC,GAEjF,KAAU,MACd,GAAa,MACX,EAAK,SAAS,CAAG,IAAI,EAAK,QAAQ,MAAS,MAAS,CAAG,IAAI,CAAC,GAAG,GAAM,CAAG,CAC1E;CAEF,OACE,kBAAC,WAAD;EACE,cAAY;EACZ,WAAW,CAAC,eAAe,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAFhE,UAAA,CAIG,IAAU,IACT,kBAAC,KAAD;GAAG,WAAU;GAAb,UAAA,CAAqC,GAAQ,gBAAiB;EAC5D,CAAA,IAAA,MAEJ,kBAAC,MAAD;GAAI,WAAU;GACX,UAAA,EAAM,KAAK,MAAQ;IAClB,IAAI,EAAI,SAAS,UAAU;KACzB,IAAM,EAAE,gBAAa;KACrB,OACE,kBAAC,MAAD;MAEE,WAAU;MACV,aAAW,EAAS;MAHtB,UAAA;OAKE,kBAAC,QAAD;QAAM,WAAU;QAAoB,eAAY;OAAQ,CAAA;OACxD,kBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;SAAwB,UAAA,EAAS;QAAc,CAAA,GAC9D,EAAS,SACR,kBAAC,QAAD;SAAM,WAAU;SAAuB,UAAA,EAAS;QAAa,CAAA,IAC3D,IACA;;OACL,EAAS,KAAK,kBAAC,QAAD;QAAM,WAAU;QAAmB,UAAA,EAAS;OAAS,CAAA,IAAI;MACtE;KAZG,GAAA,EAAI,GAYP;IAER;IAEA,IAAM,IAAS,EAAS,SAAS,EAAI,GAAG;IACxC,OACE,kBAAC,MAAD;KAAkB,WAAU;KAAoB,aAAW,EAAI;KAA/D,UAAA,CACE,kBAAC,QAAD;MAAM,WAAU;MAAoB,eAAY;KAAQ,CAAA,GACxD,kBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,iBAAe;OACf,eAAe,EAAO,EAAI,GAAG;OAJ/B,UAAA,CAME,kBAAC,QAAD;QAAM,WAAU;QACb,UAAA,EAAU,EAAI,MAAM,EAAI,WAAW,MAAM;OACtC,CAAA,GACN,kBAAC,QAAD;QAAM,WAAU;QAA2B,UAAA,IAAS,SAAS;OAAa,CAAA,CACpE;MAEP,CAAA,GAAA,IACC,kBAAC,MAAD;OAAI,WAAU;OACX,UAAA,EAAI,WAAW,KAAK,MACnB,kBAAC,MAAD;QAAsB,WAAU;QAAhC,UAAA,CACE,kBAAC,QAAD;SAAM,WAAU;SAAwB,UAAA,EAAS;QAAc,CAAA,GAC9D,EAAS,SACR,kBAAC,QAAD;SAAM,WAAU;SAAuB,UAAA,EAAS;QAAa,CAAA,IAC3D,IACF;OALK,GAAA,EAAS,EAKd,CACL;MACC,CAAA,IACF,IACA;KACJ,CAAA,CAAA;IA5BK,GAAA,EAAI,GA4BT;GAER,CAAC;EACC,CAAA,CACG;;AAEb"}
@@ -0,0 +1,209 @@
1
+ /* agent-approvals/theme-macos.css
2
+ *
3
+ * Optional. The package ships neutral on purpose: it inherits your font and
4
+ * mixes its neutrals from your text colour, so it is correct dropped into any
5
+ * app without being configured. That is the right default, because a dark
6
+ * vibrancy panel landing in a light dashboard is not a good default, it is a
7
+ * bug.
8
+ *
9
+ * This is the look from the documentation site, offered rather than imposed.
10
+ * One import and the components become macOS surfaces: system font, vibrancy
11
+ * material, exact capsules, concentric radii, and one accent colour used for
12
+ * one job.
13
+ *
14
+ * import 'agent-approvals/styles.css'
15
+ * import 'agent-approvals/theme-macos.css'
16
+ *
17
+ * Load it after the base stylesheet. Every value below is a custom property on
18
+ * the component itself rather than on :root, so nothing leaks into the rest of
19
+ * your app and you can still override any single value afterwards.
20
+ */
21
+
22
+ .agent-gate,
23
+ .agent-strip,
24
+ .agent-trail {
25
+ --am-bg: rgba(38, 38, 44, 0.82);
26
+ --am-rule: rgba(255, 255, 255, 0.14);
27
+ --am-ink: #f2f2f7;
28
+ --am-detail: #98989f;
29
+ --am-inset: rgba(255, 255, 255, 0.06);
30
+ --am-fill: rgba(255, 255, 255, 0.16);
31
+ --am-fill-quiet: rgba(255, 255, 255, 0.12);
32
+ --am-fill-hover: rgba(255, 255, 255, 0.22);
33
+ --am-shadow: 0 24px 48px rgba(0, 0, 0, 0.55), inset 0 0.5px 0 rgba(255, 255, 255, 0.18);
34
+ --am-caution: rgba(235, 235, 245, 0.45);
35
+ --am-danger: #ff453a;
36
+ --am-blue: #0a84ff;
37
+ --am-system: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif;
38
+ --am-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, monospace;
39
+ }
40
+
41
+ @media (prefers-color-scheme: light) {
42
+ .agent-gate,
43
+ .agent-strip,
44
+ .agent-trail {
45
+ --am-bg: rgba(252, 252, 253, 0.86);
46
+ --am-rule: rgba(0, 0, 0, 0.12);
47
+ --am-ink: #1c1c1e;
48
+ --am-detail: #6c6c72;
49
+ --am-inset: rgba(0, 0, 0, 0.045);
50
+ --am-fill: rgba(0, 0, 0, 0.09);
51
+ --am-fill-quiet: rgba(0, 0, 0, 0.06);
52
+ --am-fill-hover: rgba(0, 0, 0, 0.14);
53
+ --am-shadow: 0 18px 40px rgba(0, 0, 0, 0.14), inset 0 0.5px 0 rgba(255, 255, 255, 0.9);
54
+ --am-caution: rgba(60, 60, 67, 0.5);
55
+ --am-danger: #d70015;
56
+ --am-blue: #0066cc;
57
+ }
58
+ }
59
+
60
+ /* ---------- the gate ---------- */
61
+
62
+ .agent-gate {
63
+ font-family: var(--am-system);
64
+ font-size: 13px;
65
+ line-height: 1.38;
66
+ letter-spacing: -0.005em;
67
+ max-width: 420px;
68
+ padding: 16px;
69
+ border-radius: 16px;
70
+ border: 0.5px solid var(--am-rule);
71
+ background: var(--am-bg);
72
+ backdrop-filter: blur(30px) saturate(180%);
73
+ -webkit-backdrop-filter: blur(30px) saturate(180%);
74
+ box-shadow: var(--am-shadow);
75
+ color: var(--am-ink);
76
+ }
77
+
78
+ .agent-gate__title {
79
+ font-size: 13px;
80
+ font-weight: 600;
81
+ }
82
+
83
+ /* Blue is for the one navigational control and nothing else. */
84
+ .agent-gate__reveal-hint {
85
+ color: var(--am-blue);
86
+ text-decoration: none;
87
+ }
88
+
89
+ .agent-gate__disclose {
90
+ color: var(--am-detail);
91
+ }
92
+
93
+ .agent-gate__disclose:hover {
94
+ background: var(--am-fill-quiet);
95
+ color: var(--am-ink);
96
+ }
97
+
98
+ .agent-gate__waiting {
99
+ font-size: 13px;
100
+ color: var(--am-ink);
101
+ }
102
+
103
+ .agent-gate__waiting-held {
104
+ color: var(--am-danger);
105
+ }
106
+
107
+ .agent-gate__note[data-tone='alarm'] {
108
+ color: var(--am-danger);
109
+ font-size: 11px;
110
+ font-weight: 500;
111
+ }
112
+
113
+ .agent-gate__note[data-tone='caution'] {
114
+ color: var(--am-caution);
115
+ font-size: 11px;
116
+ font-weight: 500;
117
+ }
118
+
119
+ /* Quiet while nothing is open. The colour belongs next to the control that
120
+ takes the risk, not to a card sitting at rest. */
121
+ .agent-gate__note[data-resting] {
122
+ color: var(--am-detail);
123
+ font-weight: 500;
124
+ }
125
+
126
+ /* Bold and left aligned at the moment of the decision. */
127
+ .agent-gate__consequence {
128
+ font-size: 13px;
129
+ font-weight: 600;
130
+ }
131
+
132
+ /* Concentric: the card is 16, this sits 8 in, so it is 8. */
133
+ .agent-gate__revealed {
134
+ margin-top: 10px;
135
+ padding: 10px 12px;
136
+ border-left: 0;
137
+ border-radius: 8px;
138
+ background: var(--am-inset);
139
+ }
140
+
141
+ .agent-gate__detail {
142
+ font-family: var(--am-mono);
143
+ color: var(--am-detail);
144
+ font-size: 11.5px;
145
+ }
146
+
147
+ /* Exact capsules: 28px tall, 14px radius. */
148
+ .agent-gate__approve,
149
+ .agent-gate__approve-one,
150
+ .agent-gate__deny,
151
+ .agent-gate__hold {
152
+ height: 28px;
153
+ padding: 0 14px;
154
+ border-radius: 14px;
155
+ font-size: 13px;
156
+ font-weight: 500;
157
+ display: inline-flex;
158
+ align-items: center;
159
+ }
160
+
161
+ .agent-gate__approve {
162
+ background: var(--am-fill);
163
+ border: 0;
164
+ color: var(--am-ink);
165
+ font-weight: 600;
166
+ }
167
+
168
+ .agent-gate__deny {
169
+ background: var(--am-fill-quiet);
170
+ border: 0;
171
+ color: var(--am-ink);
172
+ }
173
+
174
+ .agent-gate__approve:hover,
175
+ .agent-gate__deny:hover {
176
+ background: var(--am-fill-hover);
177
+ }
178
+
179
+ .agent-gate__hold,
180
+ .agent-gate__approve-one {
181
+ background: transparent;
182
+ border: 1px solid color-mix(in srgb, var(--am-danger) 60%, transparent);
183
+ color: var(--am-danger);
184
+ }
185
+
186
+ .agent-gate__hold-fill {
187
+ background: color-mix(in srgb, var(--am-danger) 28%, transparent);
188
+ }
189
+
190
+ /* ---------- the strip and the trail ---------- */
191
+
192
+ .agent-strip,
193
+ .agent-trail {
194
+ font-family: var(--am-system);
195
+ font-size: 13px;
196
+ color: var(--am-ink);
197
+ }
198
+
199
+ .agent-strip__name {
200
+ font-family: var(--am-mono);
201
+ font-size: 12px;
202
+ }
203
+
204
+ .agent-trail__detail,
205
+ .agent-strip__elapsed,
206
+ .agent-trail__at {
207
+ font-family: var(--am-mono);
208
+ color: var(--am-detail);
209
+ }
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "agent-approvals",
3
+ "version": "0.7.0",
4
+ "description": "When an agent needs a person, this is the interface. React approval UI for when an agent spends money, sends messages or reaches private data. Works with Vercel's AI SDK out of the box.",
5
+ "keywords": [
6
+ "ai-sdk",
7
+ "vercel-ai-sdk",
8
+ "approval",
9
+ "human-in-the-loop",
10
+ "hitl",
11
+ "tool-approval",
12
+ "agent-ui",
13
+ "agentic",
14
+ "agent",
15
+ "agent-state",
16
+ "ai",
17
+ "llm",
18
+ "react",
19
+ "react-components",
20
+ "components",
21
+ "status-indicator",
22
+ "ui",
23
+ "typescript"
24
+ ],
25
+ "license": "MIT",
26
+ "author": "Ladan Johari",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/ladanjohari/agent-approvals.git"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/ladanjohari/agent-approvals/issues"
33
+ },
34
+ "homepage": "https://ladanjohari.com/agent-approvals/",
35
+ "type": "module",
36
+ "sideEffects": [
37
+ "**/*.css"
38
+ ],
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.ts",
48
+ "import": "./dist/index.js"
49
+ },
50
+ "./ai-sdk": {
51
+ "types": "./dist/ai-sdk/index.d.ts",
52
+ "import": "./dist/ai-sdk.js"
53
+ },
54
+ "./styles.css": "./dist/agent-approvals.css",
55
+ "./theme-macos.css": "./dist/theme-macos.css",
56
+ "./package.json": "./package.json"
57
+ },
58
+ "scripts": {
59
+ "dev": "vite",
60
+ "dev:docs": "vite --config vite.docs.config.ts",
61
+ "build": "vite build && tsc -p tsconfig.build.json && cp src/theme-macos.css dist/theme-macos.css",
62
+ "build:docs": "vite build --config vite.docs.config.ts",
63
+ "typecheck": "tsc -b",
64
+ "lint": "oxlint",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "preview": "vite preview",
68
+ "prepublishOnly": "npm run build",
69
+ "rebuild": "node rebuild/next.mjs"
70
+ },
71
+ "peerDependencies": {
72
+ "react": "^18.0.0 || ^19.0.0",
73
+ "react-dom": "^18.0.0 || ^19.0.0"
74
+ },
75
+ "devDependencies": {
76
+ "@testing-library/jest-dom": "^7.0.0",
77
+ "@testing-library/react": "^16.3.2",
78
+ "@testing-library/user-event": "^14.6.3",
79
+ "@types/node": "^24.13.3",
80
+ "@types/react": "^19.2.18",
81
+ "@types/react-dom": "^19.2.4",
82
+ "@vitejs/plugin-react": "^6.0.5",
83
+ "jsdom": "^30.0.1",
84
+ "oxlint": "^1.77.0",
85
+ "react": "^19.2.8",
86
+ "react-dom": "^19.2.8",
87
+ "typescript": "~6.0.3",
88
+ "vite": "^8.2.1",
89
+ "vitest": "^4.1.10"
90
+ },
91
+ "engines": {
92
+ "node": ">=20"
93
+ },
94
+ "publishConfig": {
95
+ "access": "public"
96
+ }
97
+ }