@athenaintel/react 0.24.0 → 0.24.1

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 (61) hide show
  1. package/README.md +11 -4
  2. package/dist/chat/StatewireApprovalCard.cjs +31 -6
  3. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  4. package/dist/chat/StatewireApprovalCard.d.ts +34 -4
  5. package/dist/chat/StatewireApprovalCard.js +33 -8
  6. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  7. package/dist/chat-ui/index.d.ts +1 -1
  8. package/dist/chat-ui/statewire.d.ts +1 -1
  9. package/dist/index.cjs +1 -0
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +2 -1
  13. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
  14. package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
  15. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +1 -0
  16. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  17. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +1 -0
  18. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  19. package/dist/packages/agent-runtime/src/interrupts.cjs +22 -2
  20. package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
  21. package/dist/packages/agent-runtime/src/interrupts.js +22 -2
  22. package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
  23. package/dist/packages/agent-runtime/src/messages.cjs +2 -1
  24. package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
  25. package/dist/packages/agent-runtime/src/messages.js +2 -1
  26. package/dist/packages/agent-runtime/src/messages.js.map +1 -1
  27. package/dist/packages/agent-runtime/src/voice-presentation.cjs +6 -0
  28. package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
  29. package/dist/packages/agent-runtime/src/voice-presentation.js +6 -0
  30. package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
  31. package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs +68 -0
  32. package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs.map +1 -0
  33. package/dist/packages/agent-runtime/src/voice-turn-grouping.js +68 -0
  34. package/dist/packages/agent-runtime/src/voice-turn-grouping.js.map +1 -0
  35. package/dist/packages/react/package.json.cjs +1 -1
  36. package/dist/packages/react/package.json.js +1 -1
  37. package/dist/provider/AthenaProvider.cjs +2 -1
  38. package/dist/provider/AthenaProvider.cjs.map +1 -1
  39. package/dist/provider/AthenaProvider.js +2 -1
  40. package/dist/provider/AthenaProvider.js.map +1 -1
  41. package/dist/provider/theme-style-context.cjs +12 -0
  42. package/dist/provider/theme-style-context.cjs.map +1 -0
  43. package/dist/provider/theme-style-context.d.ts +8 -0
  44. package/dist/provider/theme-style-context.js +12 -0
  45. package/dist/provider/theme-style-context.js.map +1 -0
  46. package/dist/styles.css +1 -1
  47. package/dist/threads/SessionList.cjs +87 -16
  48. package/dist/threads/SessionList.cjs.map +1 -1
  49. package/dist/threads/SessionList.js +88 -17
  50. package/dist/threads/SessionList.js.map +1 -1
  51. package/dist/threads/session-list-model.cjs +13 -5
  52. package/dist/threads/session-list-model.cjs.map +1 -1
  53. package/dist/threads/session-list-model.d.ts +10 -3
  54. package/dist/threads/session-list-model.js +13 -5
  55. package/dist/threads/session-list-model.js.map +1 -1
  56. package/dist/ui/popover.cjs +46 -0
  57. package/dist/ui/popover.cjs.map +1 -0
  58. package/dist/ui/popover.d.ts +6 -0
  59. package/dist/ui/popover.js +46 -0
  60. package/dist/ui/popover.js.map +1 -0
  61. package/package.json +2 -2
package/README.md CHANGED
@@ -266,9 +266,12 @@ Same visual language as `ThreadList` (Tailwind via `cn`, lucide icons), plus:
266
266
  unread (and not needs-you) a blue dot, `watching` a clock, otherwise none;
267
267
  - a second line — `nextAction` coloured by attention (needs-you orange, ready
268
268
  green, error red), else the muted relative time of the last update;
269
- - one pull-request chip (`#123`, or `3 PRs`) with the state icon of the
270
- primary pull request (open / draft / merged / closed) linking to it — a
271
- sibling of the row button, so a click on it never selects the row;
269
+ - one pull-request chip with the state icon of the primary pull request
270
+ (open / draft / merged / closed): `#123` links to that pull request;
271
+ `3 PRs` opens a list of all of them (in flight first, then the most
272
+ recently updated), each a link to GitHub with its number, state and checks
273
+ (a repository shared by all of them is named once in the list's header) —
274
+ a sibling of the row button, so a click on it never selects the row;
272
275
  - an origin badge from `appId` / `startChannel` / `triggerType` (Chrome,
273
276
  Word, Computer, GitHub, API, …);
274
277
  - a trailing action cluster per row, revealed on hover / focus (a pinned pin
@@ -343,7 +346,7 @@ The provider asks for pull-request state by default in thread-list mode
343
346
  (`threadListIncludePullRequests`, default `true`; turn it off in a host that
344
347
  never shows the chips to spare the server the lookup). The pure rules behind
345
348
  the rows — `groupSessions`, `sessionIndicator`, `sessionSecondaryLine`,
346
- `pullRequestChip`, `sessionOriginLabel` — are exported for hosts drawing their
349
+ `pullRequestChip`, `orderedPullRequests`, `sessionOriginLabel` — are exported for hosts drawing their
347
350
  own rows; `useAthenaStatewireThreadList().setFavorited({ threadId,
348
351
  isFavorited })` is the toggle behind them.
349
352
 
@@ -459,6 +462,10 @@ Both use the same bearer / API key as session creation. The full recipe
459
462
  template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
460
463
  exported if you hold the bridge state yourself.
461
464
 
465
+ ## What's new in 0.24.1
466
+
467
+ **See every pull request a session links.** In `SessionList`, a chip that counts several pull requests (`3 PRs`) is now a button. It opens a list of all of them, in flight first and then the most recently updated. Each row shows the title with the number, state and checks beneath it, and links to GitHub in a new tab. When every PR is in one repository, the header names it once; otherwise each row names its repository. A single pull request (`#123`) is still a direct link. New export: `orderedPullRequests`, the order the chip and the list share (its first entry is the one the chip stands for). A session now links a pull request only when it opens it, comments on it, or is asked to work on it; reading one no longer links it (#37792).
468
+
462
469
  ## What's new in 0.24.0
463
470
 
464
471
  **Dictation is transcribed by Athena.** The composer mic now records with `MediaRecorder` and, on Stop or Send, posts the clip to agora's `POST /api/transcription/upload`, which runs Deepgram Nova-3 and returns the text; the strip shows "Transcribing…" until the words land. Accuracy is Deepgram's, every language Deepgram supports works (the session `lang` is forwarded), and the mic is available in Firefox. There are no live interim captions. A recording stops on its own after 5 minutes and is transcribed as if Stop had been pressed (`createRecordedDictationEngine({ maxDurationMs })` to change it); agora caps a clip at 10 MB and rate-limits uploads per user (20 a minute by default). Pass `dictation={{ engine: createWebSpeechDictationEngine() }}` to keep the browser's live Web Speech engine. Needs an agora that serves `/api/transcription/upload` (staging from 2026-09-24; production with the next release train).
@@ -21,6 +21,7 @@ const StatewireApprovalCardInner = ({
21
21
  pendingResumeGraceMs,
22
22
  isHandledElsewhere,
23
23
  renderDetails,
24
+ renderEditor,
24
25
  readOnly = false,
25
26
  readOnlyNotice = "Waiting for someone with edit access to respond.",
26
27
  nonHitlNotice = "This request needs an answer this surface cannot collect.",
@@ -39,7 +40,11 @@ const StatewireApprovalCardInner = ({
39
40
  pending,
40
41
  resume,
41
42
  sendLaneDecisions
42
- } = useStatewireApprovalRequest.useStatewireApprovalRequest({ interruptPredicate, pendingResumeGraceMs, onResumeRejected });
43
+ } = useStatewireApprovalRequest.useStatewireApprovalRequest({
44
+ interruptPredicate,
45
+ pendingResumeGraceMs,
46
+ onResumeRejected
47
+ });
43
48
  const [dismissedId, setDismissedId] = react$1.useState();
44
49
  const [detailsOverride, setDetailsOverride] = react$1.useState();
45
50
  const [liveSentKey, setLiveSentKey] = react$1.useState();
@@ -48,6 +53,7 @@ const StatewireApprovalCardInner = ({
48
53
  const actionRequests = isLive ? liveActionRequests : parkedActionRequests;
49
54
  const laneIds = interrupts.laneInterruptIds(actionRequests);
50
55
  const undecidedLaneIds = (laneIds == null ? void 0 : laneIds.filter((id) => !(id in queuedLaneDecisions))) ?? null;
56
+ const positional = laneIds === null || !isLive && laneIds.length <= 1;
51
57
  const liveKey = isLive ? (laneIds == null ? void 0 : laneIds.join("|")) ?? null : null;
52
58
  const viewKey = requestId ?? liveKey ?? void 0;
53
59
  const prevViewKeyRef = react$1.useRef(viewKey);
@@ -58,7 +64,7 @@ const StatewireApprovalCardInner = ({
58
64
  }
59
65
  const decide = react$1.useCallback(
60
66
  (value) => {
61
- if (laneIds === null || undecidedLaneIds === null || !isLive && laneIds.length <= 1) {
67
+ if (positional || undecidedLaneIds === null) {
62
68
  resume(value);
63
69
  return;
64
70
  }
@@ -74,8 +80,18 @@ const StatewireApprovalCardInner = ({
74
80
  onResumeRejected == null ? void 0 : onResumeRejected(error, undecidedLaneIds[0] ?? "");
75
81
  });
76
82
  },
77
- [laneIds, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected]
83
+ [positional, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected]
78
84
  );
85
+ const decideEach = react$1.useMemo(() => {
86
+ if (interrupts.actionRequestToolCallIds(actionRequests) === null || !positional) return null;
87
+ return (decision) => {
88
+ const decisions = {};
89
+ for (const request2 of actionRequests) {
90
+ if (request2.toolCallId !== void 0) decisions[request2.toolCallId] = decision(request2);
91
+ }
92
+ resume(interrupts.hitlResumeActionDecisions(decisions));
93
+ };
94
+ }, [actionRequests, positional, resume]);
79
95
  if (!request && !isLive) {
80
96
  if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;
81
97
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -112,6 +128,15 @@ const StatewireApprovalCardInner = ({
112
128
  const sessionApprovalOffered = hitl !== null && interrupts.canApproveForSession(actionRequests);
113
129
  const laneDecided = undecidedLaneIds !== null && laneIds !== null && (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join("|"));
114
130
  const locked = pending || laneDecided;
131
+ const editor = !readOnly && hitl !== null && renderEditor ? renderEditor({
132
+ ...context,
133
+ locked,
134
+ sessionApprovalOffered,
135
+ approve: () => decide(interrupts.hitlResumeApprove()),
136
+ approveForSession: () => decide(interrupts.hitlResumeApproveForSession()),
137
+ reject: (reason) => decide(interrupts.hitlResumeReject(reason)),
138
+ decideEach
139
+ }) : void 0;
115
140
  return /* @__PURE__ */ jsxRuntime.jsxs(
116
141
  "section",
117
142
  {
@@ -136,7 +161,7 @@ const StatewireApprovalCardInner = ({
136
161
  " requires approval before it runs."
137
162
  ] }) : message }),
138
163
  isLive ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pt-1 text-amber-700 text-xs", "data-testid": "statewire-approval-live", children: laneDecided ? "Decision sent — it applies as soon as this step pauses." : "Other work keeps running while you decide." }) : null,
139
- hasDetails ? /* @__PURE__ */ jsxRuntime.jsxs(
164
+ hasDetails && editor == null ? /* @__PURE__ */ jsxRuntime.jsxs(
140
165
  "button",
141
166
  {
142
167
  type: "button",
@@ -154,10 +179,10 @@ const StatewireApprovalCardInner = ({
154
179
  ]
155
180
  }
156
181
  ) : null,
157
- showDetails ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn("mt-2 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: renderDetails ? renderDetails(context) : /* @__PURE__ */ jsxRuntime.jsx(DefaultApprovalDetails, { actionRequests }) }) : null
182
+ editor != null ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn("mt-3 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: editor }) : showDetails ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn("mt-2 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: renderDetails ? renderDetails(context) : /* @__PURE__ */ jsxRuntime.jsx(DefaultApprovalDetails, { actionRequests }) }) : null
158
183
  ] })
159
184
  ] }),
160
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn("flex flex-wrap items-center gap-2 pt-3", classNames == null ? void 0 : classNames.actions), children: readOnly ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-amber-800 text-sm", "data-testid": "statewire-approval-read-only", children: readOnlyNotice }) : hitl ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
185
+ editor != null ? null : /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn("flex flex-wrap items-center gap-2 pt-3", classNames == null ? void 0 : classNames.actions), children: readOnly ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-amber-800 text-sm", "data-testid": "statewire-approval-read-only", children: readOnlyNotice }) : hitl ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
161
186
  /* @__PURE__ */ jsxRuntime.jsxs(
162
187
  button.Button,
163
188
  {
@@ -1 +1 @@
1
- {"version":3,"file":"StatewireApprovalCard.cjs","sources":["../../src/chat/StatewireApprovalCard.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n canApproveForSession,\n fanLaneDecision,\n hasStatewireThreadExtras,\n type HitlApprovalPayload,\n hitlResumeApprove,\n hitlResumeApproveForSession,\n hitlResumeReject,\n interruptResumeDecline,\n laneInterruptIds,\n type PendingActionRequest,\n readInterruptMessage,\n} from '@athenaintel/agent-runtime';\nimport {\n CheckCheckIcon,\n CheckIcon,\n ChevronDownIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react';\nimport { type FC, type ReactNode, useCallback, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type StatewireInterruptPredicate,\n useStatewireApprovalRequest,\n} from './use-statewire-approval-request';\n\n/**\n * Human-in-the-loop approval card for the statewire transport — the one\n * approval UI every Athena chat surface mounts (the SDK's `AthenaChat`, the\n * Chrome side panel, the Office task pane, Olympus).\n *\n * When the deep-agent runtime hits an approval-gated tool call, the backend\n * parks the run on an `interrupt` input request. This card reads that request\n * through `useStatewireApprovalRequest` and answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. Without an answer the\n * run stays parked forever, so this card must be mounted on every statewire\n * chat surface. On legacy (non-statewire) threads it renders nothing.\n *\n * A gated call raised inside a delegated subagent parks only its lane: the\n * host mirrors that interrupt while the sibling lanes keep streaming, before\n * any input request exists. The card renders the mirrored payload the same\n * way and answers it with `run/lane-input` keyed by the lane's langgraph\n * interrupt id (`laneInterruptIds`); the host queues the decision and applies\n * it the moment the run parks, so the approver never waits on the siblings.\n * Once parked, a payload whose calls all carry interrupt ids resumes as a\n * `lane_decisions` map too — one positional `run/input` can only answer the\n * first of several parked lanes.\n *\n * Host-specific review UIs (subagent cards, email summaries) slot in through\n * `renderDetails`; requests another surface answers (typed interrupt\n * families, per-lane subagent approvals) stand down through\n * `isHandledElsewhere`; viewers without edit rights see `readOnlyNotice`.\n */\n\n/** The pending request a host slot renders or judges. */\nexport interface StatewireApprovalRequestContext {\n /** The parked input request — `undefined` while the run still streams (a live lane approval). */\n request: StatewireThread.CustomInputRequest | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null` for a generic park. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n}\n\nexport interface StatewireApprovalCardProps {\n /** Surface-styling overrides merged onto the default classes. */\n classNames?: {\n /** The approval card container. */\n root?: string;\n /** The \"approval was cancelled\" notice container. */\n abandoned?: string;\n /** The disclosed details block. */\n details?: string;\n /** The decision row. */\n actions?: string;\n };\n /** Copy shown when an interrupt payload carries no message. */\n fallbackMessage?: string;\n /**\n * Which pending interrupt this card answers. Defaults to\n * `isApprovalCardInterrupt` (client-tool parks excluded — keep it that way\n * unless the surface handles those itself).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a clicked decision keeps the buttons locked when no resume run\n * ever starts (a `run/input` the host rejected before any run began).\n * Defaults to `HITL_PENDING_RESUME_GRACE_MS`.\n */\n pendingResumeGraceMs?: number;\n /**\n * Return true when another surface answers this request (a typed Athena\n * interrupt family, per-lane subagent approval cards). The card renders\n * nothing for it — two surfaces must never answer the same input request.\n */\n isHandledElsewhere?: (context: StatewireApprovalRequestContext) => boolean;\n /**\n * Replace the default argument disclosure (one JSON block per gated call)\n * with a host-specific review UI. Rendered inside the collapsible details\n * region; the decision buttons stay with the card.\n */\n renderDetails?: (context: StatewireApprovalRequestContext) => ReactNode;\n /**\n * The viewer may see the request (it explains why the run is parked) but\n * cannot decide it — answering steers the shared run. Renders\n * `readOnlyNotice` in place of the buttons.\n */\n readOnly?: boolean;\n readOnlyNotice?: ReactNode;\n /**\n * Copy for a park that is not a HITL approval — a typed Athena interrupt\n * this surface has no card for. The only honest action is to decline it\n * (`{ action: 'reject' }`, which every typed raiser treats as a clean user\n * decline); a generic \"Continue\" would be rejected by the raiser's resume\n * schema and surface as a tool error.\n */\n nonHitlNotice?: ReactNode;\n /**\n * The host refused a decision (or the transport lost it). The optimistic\n * fold has rolled back and the request is on screen again with its actions\n * re-enabled; this is where a host tells the user why (a toast, a log line).\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n}\n\nexport const StatewireApprovalCard: FC<StatewireApprovalCardProps> = (props) => {\n const extras = useAuiState((s) => s.thread.extras);\n if (!hasStatewireThreadExtras(extras)) return null;\n return <StatewireApprovalCardInner {...props} />;\n};\n\nconst StatewireApprovalCardInner: FC<StatewireApprovalCardProps> = ({\n classNames,\n fallbackMessage,\n interruptPredicate,\n pendingResumeGraceMs,\n isHandledElsewhere,\n renderDetails,\n readOnly = false,\n readOnlyNotice = 'Waiting for someone with edit access to respond.',\n nonHitlNotice = 'This request needs an answer this surface cannot collect.',\n onResumeRejected,\n}) => {\n const {\n request,\n requestId,\n hitl: parkedHitl,\n actionRequests: parkedActionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n } = useStatewireApprovalRequest({ interruptPredicate, pendingResumeGraceMs, onResumeRejected });\n const [dismissedId, setDismissedId] = useState<string>();\n // The card shares the chat column with the thread. A lone gated call is\n // small enough to show inline, but a fan-out of a dozen calls would swallow\n // the conversation, so a batch starts collapsed until someone opens it.\n const [detailsOverride, setDetailsOverride] = useState<boolean>();\n // The live lanes a decision was sent for (joined ids); the buttons stay\n // locked until the host echoes them in `queuedLaneDecisions` or refuses.\n const [liveSentKey, setLiveSentKey] = useState<string>();\n\n const isLive = request === undefined && liveHitl !== null;\n const hitl = isLive ? liveHitl : parkedHitl;\n const actionRequests = isLive ? liveActionRequests : parkedActionRequests;\n const laneIds = laneInterruptIds(actionRequests);\n const undecidedLaneIds = laneIds?.filter((id) => !(id in queuedLaneDecisions)) ?? null;\n const liveKey = isLive ? (laneIds?.join('|') ?? null) : null;\n const viewKey = requestId ?? liveKey ?? undefined;\n\n // A fresh approval gets the default for ITS batch size, not the disclosure\n // the previous one was left in. Reset during render via React's \"previous\n // value\" pattern.\n const prevViewKeyRef = useRef(viewKey);\n if (prevViewKeyRef.current !== viewKey) {\n prevViewKeyRef.current = viewKey;\n if (detailsOverride !== undefined) setDetailsOverride(undefined);\n if (liveSentKey !== undefined) setLiveSentKey(undefined);\n }\n\n const decide = useCallback(\n (value: Record<string, string>) => {\n // Several parked lanes (or a live one) answer by interrupt id: a\n // positional `run/input` would resume only the first and re-park the\n // rest. A lone parked lane keeps the positional resume every host\n // already speaks.\n if (laneIds === null || undecidedLaneIds === null || (!isLive && laneIds.length <= 1)) {\n resume(value);\n return;\n }\n const lanes = fanLaneDecision({ interruptIds: undecidedLaneIds, decision: value });\n if (!isLive) {\n sendLaneDecisions(lanes);\n return;\n }\n const key = undecidedLaneIds.join('|');\n setLiveSentKey(key);\n Promise.resolve(sendLaneDecisions(lanes)).catch((error: unknown) => {\n setLiveSentKey((current) => (current === key ? undefined : current));\n onResumeRejected?.(error, undecidedLaneIds[0] ?? '');\n });\n },\n [laneIds, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected],\n );\n\n if (!request && !isLive) {\n if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;\n return (\n <div\n className={cn(\n 'aui-approval-abandoned flex w-full items-center justify-between gap-3 rounded-xl border border-amber-300 bg-amber-50 px-3 py-2 text-amber-800 text-xs',\n classNames?.abandoned,\n )}\n >\n <p>Approval was cancelled — the run moved on.</p>\n <button\n type=\"button\"\n aria-label=\"Dismiss cancelled approval\"\n onClick={() => setDismissedId(abandonedInterrupt.id)}\n className=\"shrink-0 rounded-md p-1 hover:bg-amber-100\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n );\n }\n\n const context: StatewireApprovalRequestContext = { request, hitl, actionRequests };\n if (isHandledElsewhere?.(context)) return null;\n // A live mirror without lane stamps cannot be answered before the run\n // parks; the parked request renders it then.\n if (isLive && laneIds === null) return null;\n\n const message = readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);\n const toolName = hitl?.context?.tool_name ?? hitl?.context?.tool_id;\n const pendingCount = hitl?.context?.pending_action_count ?? 1;\n const hasDetails = hitl !== null && actionRequests.length > 0;\n const showDetails = hasDetails && (detailsOverride ?? actionRequests.length <= 1);\n const sessionApprovalOffered = hitl !== null && canApproveForSession(actionRequests);\n const laneDecided =\n undecidedLaneIds !== null &&\n laneIds !== null &&\n (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join('|'));\n const locked = pending || laneDecided;\n\n return (\n <section\n aria-label={hitl ? 'Approval required' : 'Input required'}\n className={cn(\n 'aui-approval-card w-full rounded-2xl border border-amber-300 bg-amber-50 px-4 py-3 text-amber-900',\n classNames?.root,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"flex items-center gap-2 font-semibold text-sm\">\n <span>{hitl ? 'Approval required' : 'Waiting for input'}</span>\n {hitl && pendingCount > 1 ? (\n <span className=\"shrink-0 font-normal text-amber-800 text-xs\">\n {pendingCount} tool calls\n </span>\n ) : null}\n </p>\n <p className=\"pt-1 text-amber-800 text-sm leading-snug\">\n {hitl && pendingCount === 1 && toolName ? (\n <>\n <span className=\"font-mono\">{toolName}</span> requires approval before it runs.\n </>\n ) : (\n message\n )}\n </p>\n {isLive ? (\n <p className=\"pt-1 text-amber-700 text-xs\" data-testid=\"statewire-approval-live\">\n {laneDecided\n ? 'Decision sent — it applies as soon as this step pauses.'\n : 'Other work keeps running while you decide.'}\n </p>\n ) : null}\n {hasDetails ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOverride(!showDetails)}\n aria-expanded={showDetails}\n className=\"mt-2 flex items-center gap-1 rounded px-1 py-0.5 text-amber-800 text-xs transition-colors hover:text-amber-950\"\n >\n <ChevronDownIcon\n className={cn('size-3 transition-transform', showDetails && 'rotate-180')}\n />\n {showDetails ? 'Hide details' : 'Review details'}\n </button>\n ) : null}\n {/* EVERY pending gated call renders, in order — one decision\n authorizes the whole batch, so nothing may hide behind a host's\n richer cards. All read-only: this runtime resumes through\n `hitl_response_to_decisions`, which has no `tool_args` channel —\n an editable composer would let someone rewrite an email and then\n watch the original go out. */}\n {showDetails ? (\n <div className={cn('mt-2 flex flex-col gap-2', classNames?.details)}>\n {renderDetails ? (\n renderDetails(context)\n ) : (\n <DefaultApprovalDetails actionRequests={actionRequests} />\n )}\n </div>\n ) : null}\n </div>\n </div>\n <div className={cn('flex flex-wrap items-center gap-2 pt-3', classNames?.actions)}>\n {readOnly ? (\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-read-only\">\n {readOnlyNotice}\n </p>\n ) : hitl ? (\n <>\n <Button\n size=\"sm\"\n disabled={locked}\n onClick={() => decide(hitlResumeApprove())}\n className=\"bg-green-600 text-white hover:bg-green-700\"\n >\n <CheckIcon />\n Approve\n </Button>\n {sessionApprovalOffered ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeApproveForSession())}\n data-testid=\"statewire-approval-approve-session\"\n >\n <CheckCheckIcon />\n Approve all for this session\n </Button>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeReject())}\n >\n <XIcon />\n Reject\n </Button>\n </>\n ) : (\n <>\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-non-hitl\">\n {nonHitlNotice}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={pending}\n onClick={() => resume(interruptResumeDecline())}\n >\n <XIcon />\n Decline\n </Button>\n </>\n )}\n </div>\n </section>\n );\n};\n\n/** One block per gated call: its name (when the batch has several) and its arguments as JSON. */\nconst DefaultApprovalDetails: FC<{ actionRequests: PendingActionRequest[] }> = ({\n actionRequests,\n}) => (\n <>\n {actionRequests.map((action, index) => (\n <div\n key={`${action.name}-${index}`}\n className=\"rounded-lg bg-amber-100 p-2 text-amber-900 text-xs leading-relaxed\"\n >\n {actionRequests.length > 1 ? (\n <p className=\"font-mono font-semibold\">{action.name}</p>\n ) : null}\n {Object.keys(action.args).length > 0 ? (\n <pre className=\"mt-1 max-h-32 overflow-auto whitespace-pre-wrap break-words rounded bg-white/60 p-1.5 font-mono text-[11px]\">\n {JSON.stringify(action.args, null, 2)}\n </pre>\n ) : (\n <p className=\"text-amber-800\">No arguments.</p>\n )}\n </div>\n ))}\n </>\n);\n"],"names":["useAuiState","hasStatewireThreadExtras","jsx","useStatewireApprovalRequest","useState","laneInterruptIds","useRef","useCallback","fanLaneDecision","jsxs","cn","XIcon","readInterruptMessage","canApproveForSession","TriangleAlertIcon","Fragment","ChevronDownIcon","Button","hitlResumeApprove","CheckIcon","hitlResumeApproveForSession","CheckCheckIcon","hitlResumeReject","interruptResumeDecline"],"mappings":"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"}
1
+ {"version":3,"file":"StatewireApprovalCard.cjs","sources":["../../src/chat/StatewireApprovalCard.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n actionRequestToolCallIds,\n canApproveForSession,\n fanLaneDecision,\n hasStatewireThreadExtras,\n type HitlActionDecision,\n type HitlApprovalPayload,\n hitlResumeActionDecisions,\n hitlResumeApprove,\n hitlResumeApproveForSession,\n hitlResumeReject,\n interruptResumeDecline,\n laneInterruptIds,\n type PendingActionRequest,\n readInterruptMessage,\n} from '@athenaintel/agent-runtime';\nimport {\n CheckCheckIcon,\n CheckIcon,\n ChevronDownIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react';\nimport { type FC, type ReactNode, useCallback, useMemo, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type StatewireInterruptPredicate,\n useStatewireApprovalRequest,\n} from './use-statewire-approval-request';\n\n/**\n * Human-in-the-loop approval card for the statewire transport — the one\n * approval UI every Athena chat surface mounts (the SDK's `AthenaChat`, the\n * Chrome side panel, the Office task pane, Olympus).\n *\n * When the deep-agent runtime hits an approval-gated tool call, the backend\n * parks the run on an `interrupt` input request. This card reads that request\n * through `useStatewireApprovalRequest` and answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. Without an answer the\n * run stays parked forever, so this card must be mounted on every statewire\n * chat surface. On legacy (non-statewire) threads it renders nothing.\n *\n * A gated call raised inside a delegated subagent parks only its lane: the\n * host mirrors that interrupt while the sibling lanes keep streaming, before\n * any input request exists. The card renders the mirrored payload the same\n * way and answers it with `run/lane-input` keyed by the lane's langgraph\n * interrupt id (`laneInterruptIds`); the host queues the decision and applies\n * it the moment the run parks, so the approver never waits on the siblings.\n * Once parked, a payload whose calls all carry interrupt ids resumes as a\n * `lane_decisions` map too — one positional `run/input` can only answer the\n * first of several parked lanes.\n *\n * Host-specific review UIs (subagent cards, email summaries) slot in through\n * `renderDetails`; a host composer that must also OWN the decision (an\n * editable email whose edits have to reach the backend) takes over the\n * details and the decision row through `renderEditor`; requests another\n * surface answers (typed interrupt families, per-lane subagent approvals)\n * stand down through `isHandledElsewhere`; viewers without edit rights see\n * `readOnlyNotice`.\n */\n\n/** The pending request a host slot renders or judges. */\nexport interface StatewireApprovalRequestContext {\n /** The parked input request — `undefined` while the run still streams (a live lane approval). */\n request: StatewireThread.CustomInputRequest | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null` for a generic park. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n}\n\n/** The request a host editor renders, plus the decisions it may send for it. */\nexport interface StatewireApprovalEditorContext extends StatewireApprovalRequestContext {\n /** A decision is in flight (or a live lane already answered); the editor must not send another. */\n locked: boolean;\n /** Whether \"approve all for this session\" may be offered for this batch. */\n sessionApprovalOffered: boolean;\n approve: () => void;\n approveForSession: () => void;\n reject: (reason?: string) => void;\n /**\n * Answer each gated call on its own — the only way an EDITED call reaches\n * the backend. `decision` is asked once per call, in order, so the answer\n * always names exactly the parked calls (a partial map is refused\n * server-side and nothing runs). `null` when the batch cannot take\n * per-action answers: a call without a `tool_call_id`, or a live / multi-\n * lane approval, which resumes per lane and carries no edit.\n */\n decideEach: ((decision: (request: PendingActionRequest) => HitlActionDecision) => void) | null;\n}\n\nexport interface StatewireApprovalCardProps {\n /** Surface-styling overrides merged onto the default classes. */\n classNames?: {\n /** The approval card container. */\n root?: string;\n /** The \"approval was cancelled\" notice container. */\n abandoned?: string;\n /** The disclosed details block. */\n details?: string;\n /** The decision row. */\n actions?: string;\n };\n /** Copy shown when an interrupt payload carries no message. */\n fallbackMessage?: string;\n /**\n * Which pending interrupt this card answers. Defaults to\n * `isApprovalCardInterrupt` (client-tool parks excluded — keep it that way\n * unless the surface handles those itself).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a clicked decision keeps the buttons locked when no resume run\n * ever starts (a `run/input` the host rejected before any run began).\n * Defaults to `HITL_PENDING_RESUME_GRACE_MS`.\n */\n pendingResumeGraceMs?: number;\n /**\n * Return true when another surface answers this request (a typed Athena\n * interrupt family, per-lane subagent approval cards). The card renders\n * nothing for it — two surfaces must never answer the same input request.\n */\n isHandledElsewhere?: (context: StatewireApprovalRequestContext) => boolean;\n /**\n * Replace the default argument disclosure (one JSON block per gated call)\n * with a host-specific review UI. Rendered inside the collapsible details\n * region; the decision buttons stay with the card.\n */\n renderDetails?: (context: StatewireApprovalRequestContext) => ReactNode;\n /**\n * Replace BOTH the details region and the decision row with a host\n * composer that decides the request itself through the context's\n * callbacks (an editable email whose edits must be what runs). Return\n * `null`/`undefined` to keep the default card for this request. Not\n * consulted for read-only viewers or non-HITL parks.\n */\n renderEditor?: (context: StatewireApprovalEditorContext) => ReactNode;\n /**\n * The viewer may see the request (it explains why the run is parked) but\n * cannot decide it — answering steers the shared run. Renders\n * `readOnlyNotice` in place of the buttons.\n */\n readOnly?: boolean;\n readOnlyNotice?: ReactNode;\n /**\n * Copy for a park that is not a HITL approval — a typed Athena interrupt\n * this surface has no card for. The only honest action is to decline it\n * (`{ action: 'reject' }`, which every typed raiser treats as a clean user\n * decline); a generic \"Continue\" would be rejected by the raiser's resume\n * schema and surface as a tool error.\n */\n nonHitlNotice?: ReactNode;\n /**\n * The host refused a decision (or the transport lost it). The optimistic\n * fold has rolled back and the request is on screen again with its actions\n * re-enabled; this is where a host tells the user why (a toast, a log line).\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n}\n\nexport const StatewireApprovalCard: FC<StatewireApprovalCardProps> = (props) => {\n const extras = useAuiState((s) => s.thread.extras);\n if (!hasStatewireThreadExtras(extras)) return null;\n return <StatewireApprovalCardInner {...props} />;\n};\n\nconst StatewireApprovalCardInner: FC<StatewireApprovalCardProps> = ({\n classNames,\n fallbackMessage,\n interruptPredicate,\n pendingResumeGraceMs,\n isHandledElsewhere,\n renderDetails,\n renderEditor,\n readOnly = false,\n readOnlyNotice = 'Waiting for someone with edit access to respond.',\n nonHitlNotice = 'This request needs an answer this surface cannot collect.',\n onResumeRejected,\n}) => {\n const {\n request,\n requestId,\n hitl: parkedHitl,\n actionRequests: parkedActionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n } = useStatewireApprovalRequest<StatewireThread.InputResponse>({\n interruptPredicate,\n pendingResumeGraceMs,\n onResumeRejected,\n });\n const [dismissedId, setDismissedId] = useState<string>();\n // The card shares the chat column with the thread. A lone gated call is\n // small enough to show inline, but a fan-out of a dozen calls would swallow\n // the conversation, so a batch starts collapsed until someone opens it.\n const [detailsOverride, setDetailsOverride] = useState<boolean>();\n // The live lanes a decision was sent for (joined ids); the buttons stay\n // locked until the host echoes them in `queuedLaneDecisions` or refuses.\n const [liveSentKey, setLiveSentKey] = useState<string>();\n\n const isLive = request === undefined && liveHitl !== null;\n const hitl = isLive ? liveHitl : parkedHitl;\n const actionRequests = isLive ? liveActionRequests : parkedActionRequests;\n const laneIds = laneInterruptIds(actionRequests);\n const undecidedLaneIds = laneIds?.filter((id) => !(id in queuedLaneDecisions)) ?? null;\n // A lone parked lane (or an unstamped batch) answers with one positional\n // `run/input`; that is the only resume the backend binds per-action answers\n // to, so it is the only place an edited call can go.\n const positional = laneIds === null || (!isLive && laneIds.length <= 1);\n const liveKey = isLive ? (laneIds?.join('|') ?? null) : null;\n const viewKey = requestId ?? liveKey ?? undefined;\n\n // A fresh approval gets the default for ITS batch size, not the disclosure\n // the previous one was left in. Reset during render via React's \"previous\n // value\" pattern.\n const prevViewKeyRef = useRef(viewKey);\n if (prevViewKeyRef.current !== viewKey) {\n prevViewKeyRef.current = viewKey;\n if (detailsOverride !== undefined) setDetailsOverride(undefined);\n if (liveSentKey !== undefined) setLiveSentKey(undefined);\n }\n\n const decide = useCallback(\n (value: Record<string, string>) => {\n // Several parked lanes (or a live one) answer by interrupt id: a\n // positional `run/input` would resume only the first and re-park the\n // rest. A lone parked lane keeps the positional resume every host\n // already speaks.\n if (positional || undecidedLaneIds === null) {\n resume(value);\n return;\n }\n const lanes = fanLaneDecision({ interruptIds: undecidedLaneIds, decision: value });\n if (!isLive) {\n sendLaneDecisions(lanes);\n return;\n }\n const key = undecidedLaneIds.join('|');\n setLiveSentKey(key);\n Promise.resolve(sendLaneDecisions(lanes)).catch((error: unknown) => {\n setLiveSentKey((current) => (current === key ? undefined : current));\n onResumeRejected?.(error, undecidedLaneIds[0] ?? '');\n });\n },\n [positional, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected],\n );\n\n const decideEach = useMemo<StatewireApprovalEditorContext['decideEach']>(() => {\n if (actionRequestToolCallIds(actionRequests) === null || !positional) return null;\n return (decision) => {\n const decisions: Record<string, HitlActionDecision> = {};\n for (const request of actionRequests) {\n if (request.toolCallId !== undefined) decisions[request.toolCallId] = decision(request);\n }\n // The edited argument set comes from the wire as JSON; the resume value\n // is that JSON going back.\n resume(hitlResumeActionDecisions(decisions) as StatewireThread.InputResponse);\n };\n }, [actionRequests, positional, resume]);\n\n if (!request && !isLive) {\n if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;\n return (\n <div\n className={cn(\n 'aui-approval-abandoned flex w-full items-center justify-between gap-3 rounded-xl border border-amber-300 bg-amber-50 px-3 py-2 text-amber-800 text-xs',\n classNames?.abandoned,\n )}\n >\n <p>Approval was cancelled — the run moved on.</p>\n <button\n type=\"button\"\n aria-label=\"Dismiss cancelled approval\"\n onClick={() => setDismissedId(abandonedInterrupt.id)}\n className=\"shrink-0 rounded-md p-1 hover:bg-amber-100\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n );\n }\n\n const context: StatewireApprovalRequestContext = { request, hitl, actionRequests };\n if (isHandledElsewhere?.(context)) return null;\n // A live mirror without lane stamps cannot be answered before the run\n // parks; the parked request renders it then.\n if (isLive && laneIds === null) return null;\n\n const message = readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);\n const toolName = hitl?.context?.tool_name ?? hitl?.context?.tool_id;\n const pendingCount = hitl?.context?.pending_action_count ?? 1;\n const hasDetails = hitl !== null && actionRequests.length > 0;\n const showDetails = hasDetails && (detailsOverride ?? actionRequests.length <= 1);\n const sessionApprovalOffered = hitl !== null && canApproveForSession(actionRequests);\n const laneDecided =\n undecidedLaneIds !== null &&\n laneIds !== null &&\n (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join('|'));\n const locked = pending || laneDecided;\n\n const editor =\n !readOnly && hitl !== null && renderEditor\n ? renderEditor({\n ...context,\n locked,\n sessionApprovalOffered,\n approve: () => decide(hitlResumeApprove()),\n approveForSession: () => decide(hitlResumeApproveForSession()),\n reject: (reason) => decide(hitlResumeReject(reason)),\n decideEach,\n })\n : undefined;\n\n return (\n <section\n aria-label={hitl ? 'Approval required' : 'Input required'}\n className={cn(\n 'aui-approval-card w-full rounded-2xl border border-amber-300 bg-amber-50 px-4 py-3 text-amber-900',\n classNames?.root,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"flex items-center gap-2 font-semibold text-sm\">\n <span>{hitl ? 'Approval required' : 'Waiting for input'}</span>\n {hitl && pendingCount > 1 ? (\n <span className=\"shrink-0 font-normal text-amber-800 text-xs\">\n {pendingCount} tool calls\n </span>\n ) : null}\n </p>\n <p className=\"pt-1 text-amber-800 text-sm leading-snug\">\n {hitl && pendingCount === 1 && toolName ? (\n <>\n <span className=\"font-mono\">{toolName}</span> requires approval before it runs.\n </>\n ) : (\n message\n )}\n </p>\n {isLive ? (\n <p className=\"pt-1 text-amber-700 text-xs\" data-testid=\"statewire-approval-live\">\n {laneDecided\n ? 'Decision sent — it applies as soon as this step pauses.'\n : 'Other work keeps running while you decide.'}\n </p>\n ) : null}\n {hasDetails && editor == null ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOverride(!showDetails)}\n aria-expanded={showDetails}\n className=\"mt-2 flex items-center gap-1 rounded px-1 py-0.5 text-amber-800 text-xs transition-colors hover:text-amber-950\"\n >\n <ChevronDownIcon\n className={cn('size-3 transition-transform', showDetails && 'rotate-180')}\n />\n {showDetails ? 'Hide details' : 'Review details'}\n </button>\n ) : null}\n {/* EVERY pending gated call renders, in order — one decision\n authorizes the whole batch, so nothing may hide behind a host's\n richer cards. All read-only: this runtime resumes through\n `hitl_response_to_decisions`, which has no `tool_args` channel —\n an editable composer would let someone rewrite an email and then\n watch the original go out — unless the host takes over the\n decision too (`renderEditor`), which sends the edit per call. */}\n {editor != null ? (\n <div className={cn('mt-3 flex flex-col gap-2', classNames?.details)}>{editor}</div>\n ) : showDetails ? (\n <div className={cn('mt-2 flex flex-col gap-2', classNames?.details)}>\n {renderDetails ? (\n renderDetails(context)\n ) : (\n <DefaultApprovalDetails actionRequests={actionRequests} />\n )}\n </div>\n ) : null}\n </div>\n </div>\n {editor != null ? null : (\n <div className={cn('flex flex-wrap items-center gap-2 pt-3', classNames?.actions)}>\n {readOnly ? (\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-read-only\">\n {readOnlyNotice}\n </p>\n ) : hitl ? (\n <>\n <Button\n size=\"sm\"\n disabled={locked}\n onClick={() => decide(hitlResumeApprove())}\n className=\"bg-green-600 text-white hover:bg-green-700\"\n >\n <CheckIcon />\n Approve\n </Button>\n {sessionApprovalOffered ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeApproveForSession())}\n data-testid=\"statewire-approval-approve-session\"\n >\n <CheckCheckIcon />\n Approve all for this session\n </Button>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeReject())}\n >\n <XIcon />\n Reject\n </Button>\n </>\n ) : (\n <>\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-non-hitl\">\n {nonHitlNotice}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={pending}\n onClick={() => resume(interruptResumeDecline())}\n >\n <XIcon />\n Decline\n </Button>\n </>\n )}\n </div>\n )}\n </section>\n );\n};\n\n/** One block per gated call: its name (when the batch has several) and its arguments as JSON. */\nconst DefaultApprovalDetails: FC<{ actionRequests: PendingActionRequest[] }> = ({\n actionRequests,\n}) => (\n <>\n {actionRequests.map((action, index) => (\n <div\n key={`${action.name}-${index}`}\n className=\"rounded-lg bg-amber-100 p-2 text-amber-900 text-xs leading-relaxed\"\n >\n {actionRequests.length > 1 ? (\n <p className=\"font-mono font-semibold\">{action.name}</p>\n ) : null}\n {Object.keys(action.args).length > 0 ? (\n <pre className=\"mt-1 max-h-32 overflow-auto whitespace-pre-wrap break-words rounded bg-white/60 p-1.5 font-mono text-[11px]\">\n {JSON.stringify(action.args, null, 2)}\n </pre>\n ) : (\n <p className=\"text-amber-800\">No arguments.</p>\n )}\n </div>\n ))}\n </>\n);\n"],"names":["useAuiState","hasStatewireThreadExtras","jsx","useStatewireApprovalRequest","useState","laneInterruptIds","useRef","useCallback","fanLaneDecision","useMemo","actionRequestToolCallIds","request","hitlResumeActionDecisions","jsxs","cn","XIcon","readInterruptMessage","canApproveForSession","hitlResumeApprove","hitlResumeApproveForSession","hitlResumeReject","TriangleAlertIcon","Fragment","ChevronDownIcon","Button","CheckIcon","CheckCheckIcon","interruptResumeDecline"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { StatewireThread } from '@assistant-ui/react-statewire';
2
- import { type HitlApprovalPayload, type PendingActionRequest } from '@athenaintel/agent-runtime';
2
+ import { type HitlActionDecision, type HitlApprovalPayload, type PendingActionRequest } from '@athenaintel/agent-runtime';
3
3
  import { type FC, type ReactNode } from 'react';
4
4
  import { type StatewireInterruptPredicate } from './use-statewire-approval-request';
5
5
  /**
@@ -26,9 +26,12 @@ import { type StatewireInterruptPredicate } from './use-statewire-approval-reque
26
26
  * first of several parked lanes.
27
27
  *
28
28
  * Host-specific review UIs (subagent cards, email summaries) slot in through
29
- * `renderDetails`; requests another surface answers (typed interrupt
30
- * families, per-lane subagent approvals) stand down through
31
- * `isHandledElsewhere`; viewers without edit rights see `readOnlyNotice`.
29
+ * `renderDetails`; a host composer that must also OWN the decision (an
30
+ * editable email whose edits have to reach the backend) takes over the
31
+ * details and the decision row through `renderEditor`; requests another
32
+ * surface answers (typed interrupt families, per-lane subagent approvals)
33
+ * stand down through `isHandledElsewhere`; viewers without edit rights see
34
+ * `readOnlyNotice`.
32
35
  */
33
36
  /** The pending request a host slot renders or judges. */
34
37
  export interface StatewireApprovalRequestContext {
@@ -39,6 +42,25 @@ export interface StatewireApprovalRequestContext {
39
42
  /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */
40
43
  actionRequests: PendingActionRequest[];
41
44
  }
45
+ /** The request a host editor renders, plus the decisions it may send for it. */
46
+ export interface StatewireApprovalEditorContext extends StatewireApprovalRequestContext {
47
+ /** A decision is in flight (or a live lane already answered); the editor must not send another. */
48
+ locked: boolean;
49
+ /** Whether "approve all for this session" may be offered for this batch. */
50
+ sessionApprovalOffered: boolean;
51
+ approve: () => void;
52
+ approveForSession: () => void;
53
+ reject: (reason?: string) => void;
54
+ /**
55
+ * Answer each gated call on its own — the only way an EDITED call reaches
56
+ * the backend. `decision` is asked once per call, in order, so the answer
57
+ * always names exactly the parked calls (a partial map is refused
58
+ * server-side and nothing runs). `null` when the batch cannot take
59
+ * per-action answers: a call without a `tool_call_id`, or a live / multi-
60
+ * lane approval, which resumes per lane and carries no edit.
61
+ */
62
+ decideEach: ((decision: (request: PendingActionRequest) => HitlActionDecision) => void) | null;
63
+ }
42
64
  export interface StatewireApprovalCardProps {
43
65
  /** Surface-styling overrides merged onto the default classes. */
44
66
  classNames?: {
@@ -77,6 +99,14 @@ export interface StatewireApprovalCardProps {
77
99
  * region; the decision buttons stay with the card.
78
100
  */
79
101
  renderDetails?: (context: StatewireApprovalRequestContext) => ReactNode;
102
+ /**
103
+ * Replace BOTH the details region and the decision row with a host
104
+ * composer that decides the request itself through the context's
105
+ * callbacks (an editable email whose edits must be what runs). Return
106
+ * `null`/`undefined` to keep the default card for this request. Not
107
+ * consulted for read-only viewers or non-HITL parks.
108
+ */
109
+ renderEditor?: (context: StatewireApprovalEditorContext) => ReactNode;
80
110
  /**
81
111
  * The viewer may see the request (it explains why the run is parked) but
82
112
  * cannot decide it — answering steers the shared run. Renders
@@ -1,11 +1,11 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useAuiState } from "@assistant-ui/react";
3
3
  import { XIcon, TriangleAlertIcon, ChevronDownIcon, CheckIcon, CheckCheckIcon } from "lucide-react";
4
- import { useState, useRef, useCallback } from "react";
4
+ import { useState, useRef, useCallback, useMemo } from "react";
5
5
  import { Button } from "../ui/button.js";
6
6
  import { useStatewireApprovalRequest } from "./use-statewire-approval-request.js";
7
7
  import { hasStatewireThreadExtras } from "../packages/agent-runtime/src/thread-extras.js";
8
- import { laneInterruptIds, fanLaneDecision, readInterruptMessage, canApproveForSession, hitlResumeApprove, hitlResumeApproveForSession, hitlResumeReject, interruptResumeDecline } from "../packages/agent-runtime/src/interrupts.js";
8
+ import { laneInterruptIds, fanLaneDecision, actionRequestToolCallIds, hitlResumeActionDecisions, readInterruptMessage, canApproveForSession, hitlResumeReject, hitlResumeApproveForSession, hitlResumeApprove, interruptResumeDecline } from "../packages/agent-runtime/src/interrupts.js";
9
9
  import { cn } from "cnfast";
10
10
  const StatewireApprovalCard = (props) => {
11
11
  const extras = useAuiState((s) => s.thread.extras);
@@ -19,6 +19,7 @@ const StatewireApprovalCardInner = ({
19
19
  pendingResumeGraceMs,
20
20
  isHandledElsewhere,
21
21
  renderDetails,
22
+ renderEditor,
22
23
  readOnly = false,
23
24
  readOnlyNotice = "Waiting for someone with edit access to respond.",
24
25
  nonHitlNotice = "This request needs an answer this surface cannot collect.",
@@ -37,7 +38,11 @@ const StatewireApprovalCardInner = ({
37
38
  pending,
38
39
  resume,
39
40
  sendLaneDecisions
40
- } = useStatewireApprovalRequest({ interruptPredicate, pendingResumeGraceMs, onResumeRejected });
41
+ } = useStatewireApprovalRequest({
42
+ interruptPredicate,
43
+ pendingResumeGraceMs,
44
+ onResumeRejected
45
+ });
41
46
  const [dismissedId, setDismissedId] = useState();
42
47
  const [detailsOverride, setDetailsOverride] = useState();
43
48
  const [liveSentKey, setLiveSentKey] = useState();
@@ -46,6 +51,7 @@ const StatewireApprovalCardInner = ({
46
51
  const actionRequests = isLive ? liveActionRequests : parkedActionRequests;
47
52
  const laneIds = laneInterruptIds(actionRequests);
48
53
  const undecidedLaneIds = (laneIds == null ? void 0 : laneIds.filter((id) => !(id in queuedLaneDecisions))) ?? null;
54
+ const positional = laneIds === null || !isLive && laneIds.length <= 1;
49
55
  const liveKey = isLive ? (laneIds == null ? void 0 : laneIds.join("|")) ?? null : null;
50
56
  const viewKey = requestId ?? liveKey ?? void 0;
51
57
  const prevViewKeyRef = useRef(viewKey);
@@ -56,7 +62,7 @@ const StatewireApprovalCardInner = ({
56
62
  }
57
63
  const decide = useCallback(
58
64
  (value) => {
59
- if (laneIds === null || undecidedLaneIds === null || !isLive && laneIds.length <= 1) {
65
+ if (positional || undecidedLaneIds === null) {
60
66
  resume(value);
61
67
  return;
62
68
  }
@@ -72,8 +78,18 @@ const StatewireApprovalCardInner = ({
72
78
  onResumeRejected == null ? void 0 : onResumeRejected(error, undecidedLaneIds[0] ?? "");
73
79
  });
74
80
  },
75
- [laneIds, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected]
81
+ [positional, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected]
76
82
  );
83
+ const decideEach = useMemo(() => {
84
+ if (actionRequestToolCallIds(actionRequests) === null || !positional) return null;
85
+ return (decision) => {
86
+ const decisions = {};
87
+ for (const request2 of actionRequests) {
88
+ if (request2.toolCallId !== void 0) decisions[request2.toolCallId] = decision(request2);
89
+ }
90
+ resume(hitlResumeActionDecisions(decisions));
91
+ };
92
+ }, [actionRequests, positional, resume]);
77
93
  if (!request && !isLive) {
78
94
  if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;
79
95
  return /* @__PURE__ */ jsxs(
@@ -110,6 +126,15 @@ const StatewireApprovalCardInner = ({
110
126
  const sessionApprovalOffered = hitl !== null && canApproveForSession(actionRequests);
111
127
  const laneDecided = undecidedLaneIds !== null && laneIds !== null && (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join("|"));
112
128
  const locked = pending || laneDecided;
129
+ const editor = !readOnly && hitl !== null && renderEditor ? renderEditor({
130
+ ...context,
131
+ locked,
132
+ sessionApprovalOffered,
133
+ approve: () => decide(hitlResumeApprove()),
134
+ approveForSession: () => decide(hitlResumeApproveForSession()),
135
+ reject: (reason) => decide(hitlResumeReject(reason)),
136
+ decideEach
137
+ }) : void 0;
113
138
  return /* @__PURE__ */ jsxs(
114
139
  "section",
115
140
  {
@@ -134,7 +159,7 @@ const StatewireApprovalCardInner = ({
134
159
  " requires approval before it runs."
135
160
  ] }) : message }),
136
161
  isLive ? /* @__PURE__ */ jsx("p", { className: "pt-1 text-amber-700 text-xs", "data-testid": "statewire-approval-live", children: laneDecided ? "Decision sent — it applies as soon as this step pauses." : "Other work keeps running while you decide." }) : null,
137
- hasDetails ? /* @__PURE__ */ jsxs(
162
+ hasDetails && editor == null ? /* @__PURE__ */ jsxs(
138
163
  "button",
139
164
  {
140
165
  type: "button",
@@ -152,10 +177,10 @@ const StatewireApprovalCardInner = ({
152
177
  ]
153
178
  }
154
179
  ) : null,
155
- showDetails ? /* @__PURE__ */ jsx("div", { className: cn("mt-2 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: renderDetails ? renderDetails(context) : /* @__PURE__ */ jsx(DefaultApprovalDetails, { actionRequests }) }) : null
180
+ editor != null ? /* @__PURE__ */ jsx("div", { className: cn("mt-3 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: editor }) : showDetails ? /* @__PURE__ */ jsx("div", { className: cn("mt-2 flex flex-col gap-2", classNames == null ? void 0 : classNames.details), children: renderDetails ? renderDetails(context) : /* @__PURE__ */ jsx(DefaultApprovalDetails, { actionRequests }) }) : null
156
181
  ] })
157
182
  ] }),
158
- /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-2 pt-3", classNames == null ? void 0 : classNames.actions), children: readOnly ? /* @__PURE__ */ jsx("p", { className: "text-amber-800 text-sm", "data-testid": "statewire-approval-read-only", children: readOnlyNotice }) : hitl ? /* @__PURE__ */ jsxs(Fragment, { children: [
183
+ editor != null ? null : /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-2 pt-3", classNames == null ? void 0 : classNames.actions), children: readOnly ? /* @__PURE__ */ jsx("p", { className: "text-amber-800 text-sm", "data-testid": "statewire-approval-read-only", children: readOnlyNotice }) : hitl ? /* @__PURE__ */ jsxs(Fragment, { children: [
159
184
  /* @__PURE__ */ jsxs(
160
185
  Button,
161
186
  {
@@ -1 +1 @@
1
- {"version":3,"file":"StatewireApprovalCard.js","sources":["../../src/chat/StatewireApprovalCard.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n canApproveForSession,\n fanLaneDecision,\n hasStatewireThreadExtras,\n type HitlApprovalPayload,\n hitlResumeApprove,\n hitlResumeApproveForSession,\n hitlResumeReject,\n interruptResumeDecline,\n laneInterruptIds,\n type PendingActionRequest,\n readInterruptMessage,\n} from '@athenaintel/agent-runtime';\nimport {\n CheckCheckIcon,\n CheckIcon,\n ChevronDownIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react';\nimport { type FC, type ReactNode, useCallback, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type StatewireInterruptPredicate,\n useStatewireApprovalRequest,\n} from './use-statewire-approval-request';\n\n/**\n * Human-in-the-loop approval card for the statewire transport — the one\n * approval UI every Athena chat surface mounts (the SDK's `AthenaChat`, the\n * Chrome side panel, the Office task pane, Olympus).\n *\n * When the deep-agent runtime hits an approval-gated tool call, the backend\n * parks the run on an `interrupt` input request. This card reads that request\n * through `useStatewireApprovalRequest` and answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. Without an answer the\n * run stays parked forever, so this card must be mounted on every statewire\n * chat surface. On legacy (non-statewire) threads it renders nothing.\n *\n * A gated call raised inside a delegated subagent parks only its lane: the\n * host mirrors that interrupt while the sibling lanes keep streaming, before\n * any input request exists. The card renders the mirrored payload the same\n * way and answers it with `run/lane-input` keyed by the lane's langgraph\n * interrupt id (`laneInterruptIds`); the host queues the decision and applies\n * it the moment the run parks, so the approver never waits on the siblings.\n * Once parked, a payload whose calls all carry interrupt ids resumes as a\n * `lane_decisions` map too — one positional `run/input` can only answer the\n * first of several parked lanes.\n *\n * Host-specific review UIs (subagent cards, email summaries) slot in through\n * `renderDetails`; requests another surface answers (typed interrupt\n * families, per-lane subagent approvals) stand down through\n * `isHandledElsewhere`; viewers without edit rights see `readOnlyNotice`.\n */\n\n/** The pending request a host slot renders or judges. */\nexport interface StatewireApprovalRequestContext {\n /** The parked input request — `undefined` while the run still streams (a live lane approval). */\n request: StatewireThread.CustomInputRequest | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null` for a generic park. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n}\n\nexport interface StatewireApprovalCardProps {\n /** Surface-styling overrides merged onto the default classes. */\n classNames?: {\n /** The approval card container. */\n root?: string;\n /** The \"approval was cancelled\" notice container. */\n abandoned?: string;\n /** The disclosed details block. */\n details?: string;\n /** The decision row. */\n actions?: string;\n };\n /** Copy shown when an interrupt payload carries no message. */\n fallbackMessage?: string;\n /**\n * Which pending interrupt this card answers. Defaults to\n * `isApprovalCardInterrupt` (client-tool parks excluded — keep it that way\n * unless the surface handles those itself).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a clicked decision keeps the buttons locked when no resume run\n * ever starts (a `run/input` the host rejected before any run began).\n * Defaults to `HITL_PENDING_RESUME_GRACE_MS`.\n */\n pendingResumeGraceMs?: number;\n /**\n * Return true when another surface answers this request (a typed Athena\n * interrupt family, per-lane subagent approval cards). The card renders\n * nothing for it — two surfaces must never answer the same input request.\n */\n isHandledElsewhere?: (context: StatewireApprovalRequestContext) => boolean;\n /**\n * Replace the default argument disclosure (one JSON block per gated call)\n * with a host-specific review UI. Rendered inside the collapsible details\n * region; the decision buttons stay with the card.\n */\n renderDetails?: (context: StatewireApprovalRequestContext) => ReactNode;\n /**\n * The viewer may see the request (it explains why the run is parked) but\n * cannot decide it — answering steers the shared run. Renders\n * `readOnlyNotice` in place of the buttons.\n */\n readOnly?: boolean;\n readOnlyNotice?: ReactNode;\n /**\n * Copy for a park that is not a HITL approval — a typed Athena interrupt\n * this surface has no card for. The only honest action is to decline it\n * (`{ action: 'reject' }`, which every typed raiser treats as a clean user\n * decline); a generic \"Continue\" would be rejected by the raiser's resume\n * schema and surface as a tool error.\n */\n nonHitlNotice?: ReactNode;\n /**\n * The host refused a decision (or the transport lost it). The optimistic\n * fold has rolled back and the request is on screen again with its actions\n * re-enabled; this is where a host tells the user why (a toast, a log line).\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n}\n\nexport const StatewireApprovalCard: FC<StatewireApprovalCardProps> = (props) => {\n const extras = useAuiState((s) => s.thread.extras);\n if (!hasStatewireThreadExtras(extras)) return null;\n return <StatewireApprovalCardInner {...props} />;\n};\n\nconst StatewireApprovalCardInner: FC<StatewireApprovalCardProps> = ({\n classNames,\n fallbackMessage,\n interruptPredicate,\n pendingResumeGraceMs,\n isHandledElsewhere,\n renderDetails,\n readOnly = false,\n readOnlyNotice = 'Waiting for someone with edit access to respond.',\n nonHitlNotice = 'This request needs an answer this surface cannot collect.',\n onResumeRejected,\n}) => {\n const {\n request,\n requestId,\n hitl: parkedHitl,\n actionRequests: parkedActionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n } = useStatewireApprovalRequest({ interruptPredicate, pendingResumeGraceMs, onResumeRejected });\n const [dismissedId, setDismissedId] = useState<string>();\n // The card shares the chat column with the thread. A lone gated call is\n // small enough to show inline, but a fan-out of a dozen calls would swallow\n // the conversation, so a batch starts collapsed until someone opens it.\n const [detailsOverride, setDetailsOverride] = useState<boolean>();\n // The live lanes a decision was sent for (joined ids); the buttons stay\n // locked until the host echoes them in `queuedLaneDecisions` or refuses.\n const [liveSentKey, setLiveSentKey] = useState<string>();\n\n const isLive = request === undefined && liveHitl !== null;\n const hitl = isLive ? liveHitl : parkedHitl;\n const actionRequests = isLive ? liveActionRequests : parkedActionRequests;\n const laneIds = laneInterruptIds(actionRequests);\n const undecidedLaneIds = laneIds?.filter((id) => !(id in queuedLaneDecisions)) ?? null;\n const liveKey = isLive ? (laneIds?.join('|') ?? null) : null;\n const viewKey = requestId ?? liveKey ?? undefined;\n\n // A fresh approval gets the default for ITS batch size, not the disclosure\n // the previous one was left in. Reset during render via React's \"previous\n // value\" pattern.\n const prevViewKeyRef = useRef(viewKey);\n if (prevViewKeyRef.current !== viewKey) {\n prevViewKeyRef.current = viewKey;\n if (detailsOverride !== undefined) setDetailsOverride(undefined);\n if (liveSentKey !== undefined) setLiveSentKey(undefined);\n }\n\n const decide = useCallback(\n (value: Record<string, string>) => {\n // Several parked lanes (or a live one) answer by interrupt id: a\n // positional `run/input` would resume only the first and re-park the\n // rest. A lone parked lane keeps the positional resume every host\n // already speaks.\n if (laneIds === null || undecidedLaneIds === null || (!isLive && laneIds.length <= 1)) {\n resume(value);\n return;\n }\n const lanes = fanLaneDecision({ interruptIds: undecidedLaneIds, decision: value });\n if (!isLive) {\n sendLaneDecisions(lanes);\n return;\n }\n const key = undecidedLaneIds.join('|');\n setLiveSentKey(key);\n Promise.resolve(sendLaneDecisions(lanes)).catch((error: unknown) => {\n setLiveSentKey((current) => (current === key ? undefined : current));\n onResumeRejected?.(error, undecidedLaneIds[0] ?? '');\n });\n },\n [laneIds, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected],\n );\n\n if (!request && !isLive) {\n if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;\n return (\n <div\n className={cn(\n 'aui-approval-abandoned flex w-full items-center justify-between gap-3 rounded-xl border border-amber-300 bg-amber-50 px-3 py-2 text-amber-800 text-xs',\n classNames?.abandoned,\n )}\n >\n <p>Approval was cancelled — the run moved on.</p>\n <button\n type=\"button\"\n aria-label=\"Dismiss cancelled approval\"\n onClick={() => setDismissedId(abandonedInterrupt.id)}\n className=\"shrink-0 rounded-md p-1 hover:bg-amber-100\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n );\n }\n\n const context: StatewireApprovalRequestContext = { request, hitl, actionRequests };\n if (isHandledElsewhere?.(context)) return null;\n // A live mirror without lane stamps cannot be answered before the run\n // parks; the parked request renders it then.\n if (isLive && laneIds === null) return null;\n\n const message = readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);\n const toolName = hitl?.context?.tool_name ?? hitl?.context?.tool_id;\n const pendingCount = hitl?.context?.pending_action_count ?? 1;\n const hasDetails = hitl !== null && actionRequests.length > 0;\n const showDetails = hasDetails && (detailsOverride ?? actionRequests.length <= 1);\n const sessionApprovalOffered = hitl !== null && canApproveForSession(actionRequests);\n const laneDecided =\n undecidedLaneIds !== null &&\n laneIds !== null &&\n (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join('|'));\n const locked = pending || laneDecided;\n\n return (\n <section\n aria-label={hitl ? 'Approval required' : 'Input required'}\n className={cn(\n 'aui-approval-card w-full rounded-2xl border border-amber-300 bg-amber-50 px-4 py-3 text-amber-900',\n classNames?.root,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"flex items-center gap-2 font-semibold text-sm\">\n <span>{hitl ? 'Approval required' : 'Waiting for input'}</span>\n {hitl && pendingCount > 1 ? (\n <span className=\"shrink-0 font-normal text-amber-800 text-xs\">\n {pendingCount} tool calls\n </span>\n ) : null}\n </p>\n <p className=\"pt-1 text-amber-800 text-sm leading-snug\">\n {hitl && pendingCount === 1 && toolName ? (\n <>\n <span className=\"font-mono\">{toolName}</span> requires approval before it runs.\n </>\n ) : (\n message\n )}\n </p>\n {isLive ? (\n <p className=\"pt-1 text-amber-700 text-xs\" data-testid=\"statewire-approval-live\">\n {laneDecided\n ? 'Decision sent — it applies as soon as this step pauses.'\n : 'Other work keeps running while you decide.'}\n </p>\n ) : null}\n {hasDetails ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOverride(!showDetails)}\n aria-expanded={showDetails}\n className=\"mt-2 flex items-center gap-1 rounded px-1 py-0.5 text-amber-800 text-xs transition-colors hover:text-amber-950\"\n >\n <ChevronDownIcon\n className={cn('size-3 transition-transform', showDetails && 'rotate-180')}\n />\n {showDetails ? 'Hide details' : 'Review details'}\n </button>\n ) : null}\n {/* EVERY pending gated call renders, in order — one decision\n authorizes the whole batch, so nothing may hide behind a host's\n richer cards. All read-only: this runtime resumes through\n `hitl_response_to_decisions`, which has no `tool_args` channel —\n an editable composer would let someone rewrite an email and then\n watch the original go out. */}\n {showDetails ? (\n <div className={cn('mt-2 flex flex-col gap-2', classNames?.details)}>\n {renderDetails ? (\n renderDetails(context)\n ) : (\n <DefaultApprovalDetails actionRequests={actionRequests} />\n )}\n </div>\n ) : null}\n </div>\n </div>\n <div className={cn('flex flex-wrap items-center gap-2 pt-3', classNames?.actions)}>\n {readOnly ? (\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-read-only\">\n {readOnlyNotice}\n </p>\n ) : hitl ? (\n <>\n <Button\n size=\"sm\"\n disabled={locked}\n onClick={() => decide(hitlResumeApprove())}\n className=\"bg-green-600 text-white hover:bg-green-700\"\n >\n <CheckIcon />\n Approve\n </Button>\n {sessionApprovalOffered ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeApproveForSession())}\n data-testid=\"statewire-approval-approve-session\"\n >\n <CheckCheckIcon />\n Approve all for this session\n </Button>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeReject())}\n >\n <XIcon />\n Reject\n </Button>\n </>\n ) : (\n <>\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-non-hitl\">\n {nonHitlNotice}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={pending}\n onClick={() => resume(interruptResumeDecline())}\n >\n <XIcon />\n Decline\n </Button>\n </>\n )}\n </div>\n </section>\n );\n};\n\n/** One block per gated call: its name (when the batch has several) and its arguments as JSON. */\nconst DefaultApprovalDetails: FC<{ actionRequests: PendingActionRequest[] }> = ({\n actionRequests,\n}) => (\n <>\n {actionRequests.map((action, index) => (\n <div\n key={`${action.name}-${index}`}\n className=\"rounded-lg bg-amber-100 p-2 text-amber-900 text-xs leading-relaxed\"\n >\n {actionRequests.length > 1 ? (\n <p className=\"font-mono font-semibold\">{action.name}</p>\n ) : null}\n {Object.keys(action.args).length > 0 ? (\n <pre className=\"mt-1 max-h-32 overflow-auto whitespace-pre-wrap break-words rounded bg-white/60 p-1.5 font-mono text-[11px]\">\n {JSON.stringify(action.args, null, 2)}\n </pre>\n ) : (\n <p className=\"text-amber-800\">No arguments.</p>\n )}\n </div>\n ))}\n </>\n);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StatewireApprovalCard.js","sources":["../../src/chat/StatewireApprovalCard.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n actionRequestToolCallIds,\n canApproveForSession,\n fanLaneDecision,\n hasStatewireThreadExtras,\n type HitlActionDecision,\n type HitlApprovalPayload,\n hitlResumeActionDecisions,\n hitlResumeApprove,\n hitlResumeApproveForSession,\n hitlResumeReject,\n interruptResumeDecline,\n laneInterruptIds,\n type PendingActionRequest,\n readInterruptMessage,\n} from '@athenaintel/agent-runtime';\nimport {\n CheckCheckIcon,\n CheckIcon,\n ChevronDownIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react';\nimport { type FC, type ReactNode, useCallback, useMemo, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type StatewireInterruptPredicate,\n useStatewireApprovalRequest,\n} from './use-statewire-approval-request';\n\n/**\n * Human-in-the-loop approval card for the statewire transport — the one\n * approval UI every Athena chat surface mounts (the SDK's `AthenaChat`, the\n * Chrome side panel, the Office task pane, Olympus).\n *\n * When the deep-agent runtime hits an approval-gated tool call, the backend\n * parks the run on an `interrupt` input request. This card reads that request\n * through `useStatewireApprovalRequest` and answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. Without an answer the\n * run stays parked forever, so this card must be mounted on every statewire\n * chat surface. On legacy (non-statewire) threads it renders nothing.\n *\n * A gated call raised inside a delegated subagent parks only its lane: the\n * host mirrors that interrupt while the sibling lanes keep streaming, before\n * any input request exists. The card renders the mirrored payload the same\n * way and answers it with `run/lane-input` keyed by the lane's langgraph\n * interrupt id (`laneInterruptIds`); the host queues the decision and applies\n * it the moment the run parks, so the approver never waits on the siblings.\n * Once parked, a payload whose calls all carry interrupt ids resumes as a\n * `lane_decisions` map too — one positional `run/input` can only answer the\n * first of several parked lanes.\n *\n * Host-specific review UIs (subagent cards, email summaries) slot in through\n * `renderDetails`; a host composer that must also OWN the decision (an\n * editable email whose edits have to reach the backend) takes over the\n * details and the decision row through `renderEditor`; requests another\n * surface answers (typed interrupt families, per-lane subagent approvals)\n * stand down through `isHandledElsewhere`; viewers without edit rights see\n * `readOnlyNotice`.\n */\n\n/** The pending request a host slot renders or judges. */\nexport interface StatewireApprovalRequestContext {\n /** The parked input request — `undefined` while the run still streams (a live lane approval). */\n request: StatewireThread.CustomInputRequest | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null` for a generic park. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n}\n\n/** The request a host editor renders, plus the decisions it may send for it. */\nexport interface StatewireApprovalEditorContext extends StatewireApprovalRequestContext {\n /** A decision is in flight (or a live lane already answered); the editor must not send another. */\n locked: boolean;\n /** Whether \"approve all for this session\" may be offered for this batch. */\n sessionApprovalOffered: boolean;\n approve: () => void;\n approveForSession: () => void;\n reject: (reason?: string) => void;\n /**\n * Answer each gated call on its own — the only way an EDITED call reaches\n * the backend. `decision` is asked once per call, in order, so the answer\n * always names exactly the parked calls (a partial map is refused\n * server-side and nothing runs). `null` when the batch cannot take\n * per-action answers: a call without a `tool_call_id`, or a live / multi-\n * lane approval, which resumes per lane and carries no edit.\n */\n decideEach: ((decision: (request: PendingActionRequest) => HitlActionDecision) => void) | null;\n}\n\nexport interface StatewireApprovalCardProps {\n /** Surface-styling overrides merged onto the default classes. */\n classNames?: {\n /** The approval card container. */\n root?: string;\n /** The \"approval was cancelled\" notice container. */\n abandoned?: string;\n /** The disclosed details block. */\n details?: string;\n /** The decision row. */\n actions?: string;\n };\n /** Copy shown when an interrupt payload carries no message. */\n fallbackMessage?: string;\n /**\n * Which pending interrupt this card answers. Defaults to\n * `isApprovalCardInterrupt` (client-tool parks excluded — keep it that way\n * unless the surface handles those itself).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a clicked decision keeps the buttons locked when no resume run\n * ever starts (a `run/input` the host rejected before any run began).\n * Defaults to `HITL_PENDING_RESUME_GRACE_MS`.\n */\n pendingResumeGraceMs?: number;\n /**\n * Return true when another surface answers this request (a typed Athena\n * interrupt family, per-lane subagent approval cards). The card renders\n * nothing for it — two surfaces must never answer the same input request.\n */\n isHandledElsewhere?: (context: StatewireApprovalRequestContext) => boolean;\n /**\n * Replace the default argument disclosure (one JSON block per gated call)\n * with a host-specific review UI. Rendered inside the collapsible details\n * region; the decision buttons stay with the card.\n */\n renderDetails?: (context: StatewireApprovalRequestContext) => ReactNode;\n /**\n * Replace BOTH the details region and the decision row with a host\n * composer that decides the request itself through the context's\n * callbacks (an editable email whose edits must be what runs). Return\n * `null`/`undefined` to keep the default card for this request. Not\n * consulted for read-only viewers or non-HITL parks.\n */\n renderEditor?: (context: StatewireApprovalEditorContext) => ReactNode;\n /**\n * The viewer may see the request (it explains why the run is parked) but\n * cannot decide it — answering steers the shared run. Renders\n * `readOnlyNotice` in place of the buttons.\n */\n readOnly?: boolean;\n readOnlyNotice?: ReactNode;\n /**\n * Copy for a park that is not a HITL approval — a typed Athena interrupt\n * this surface has no card for. The only honest action is to decline it\n * (`{ action: 'reject' }`, which every typed raiser treats as a clean user\n * decline); a generic \"Continue\" would be rejected by the raiser's resume\n * schema and surface as a tool error.\n */\n nonHitlNotice?: ReactNode;\n /**\n * The host refused a decision (or the transport lost it). The optimistic\n * fold has rolled back and the request is on screen again with its actions\n * re-enabled; this is where a host tells the user why (a toast, a log line).\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n}\n\nexport const StatewireApprovalCard: FC<StatewireApprovalCardProps> = (props) => {\n const extras = useAuiState((s) => s.thread.extras);\n if (!hasStatewireThreadExtras(extras)) return null;\n return <StatewireApprovalCardInner {...props} />;\n};\n\nconst StatewireApprovalCardInner: FC<StatewireApprovalCardProps> = ({\n classNames,\n fallbackMessage,\n interruptPredicate,\n pendingResumeGraceMs,\n isHandledElsewhere,\n renderDetails,\n renderEditor,\n readOnly = false,\n readOnlyNotice = 'Waiting for someone with edit access to respond.',\n nonHitlNotice = 'This request needs an answer this surface cannot collect.',\n onResumeRejected,\n}) => {\n const {\n request,\n requestId,\n hitl: parkedHitl,\n actionRequests: parkedActionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n } = useStatewireApprovalRequest<StatewireThread.InputResponse>({\n interruptPredicate,\n pendingResumeGraceMs,\n onResumeRejected,\n });\n const [dismissedId, setDismissedId] = useState<string>();\n // The card shares the chat column with the thread. A lone gated call is\n // small enough to show inline, but a fan-out of a dozen calls would swallow\n // the conversation, so a batch starts collapsed until someone opens it.\n const [detailsOverride, setDetailsOverride] = useState<boolean>();\n // The live lanes a decision was sent for (joined ids); the buttons stay\n // locked until the host echoes them in `queuedLaneDecisions` or refuses.\n const [liveSentKey, setLiveSentKey] = useState<string>();\n\n const isLive = request === undefined && liveHitl !== null;\n const hitl = isLive ? liveHitl : parkedHitl;\n const actionRequests = isLive ? liveActionRequests : parkedActionRequests;\n const laneIds = laneInterruptIds(actionRequests);\n const undecidedLaneIds = laneIds?.filter((id) => !(id in queuedLaneDecisions)) ?? null;\n // A lone parked lane (or an unstamped batch) answers with one positional\n // `run/input`; that is the only resume the backend binds per-action answers\n // to, so it is the only place an edited call can go.\n const positional = laneIds === null || (!isLive && laneIds.length <= 1);\n const liveKey = isLive ? (laneIds?.join('|') ?? null) : null;\n const viewKey = requestId ?? liveKey ?? undefined;\n\n // A fresh approval gets the default for ITS batch size, not the disclosure\n // the previous one was left in. Reset during render via React's \"previous\n // value\" pattern.\n const prevViewKeyRef = useRef(viewKey);\n if (prevViewKeyRef.current !== viewKey) {\n prevViewKeyRef.current = viewKey;\n if (detailsOverride !== undefined) setDetailsOverride(undefined);\n if (liveSentKey !== undefined) setLiveSentKey(undefined);\n }\n\n const decide = useCallback(\n (value: Record<string, string>) => {\n // Several parked lanes (or a live one) answer by interrupt id: a\n // positional `run/input` would resume only the first and re-park the\n // rest. A lone parked lane keeps the positional resume every host\n // already speaks.\n if (positional || undecidedLaneIds === null) {\n resume(value);\n return;\n }\n const lanes = fanLaneDecision({ interruptIds: undecidedLaneIds, decision: value });\n if (!isLive) {\n sendLaneDecisions(lanes);\n return;\n }\n const key = undecidedLaneIds.join('|');\n setLiveSentKey(key);\n Promise.resolve(sendLaneDecisions(lanes)).catch((error: unknown) => {\n setLiveSentKey((current) => (current === key ? undefined : current));\n onResumeRejected?.(error, undecidedLaneIds[0] ?? '');\n });\n },\n [positional, undecidedLaneIds, isLive, resume, sendLaneDecisions, onResumeRejected],\n );\n\n const decideEach = useMemo<StatewireApprovalEditorContext['decideEach']>(() => {\n if (actionRequestToolCallIds(actionRequests) === null || !positional) return null;\n return (decision) => {\n const decisions: Record<string, HitlActionDecision> = {};\n for (const request of actionRequests) {\n if (request.toolCallId !== undefined) decisions[request.toolCallId] = decision(request);\n }\n // The edited argument set comes from the wire as JSON; the resume value\n // is that JSON going back.\n resume(hitlResumeActionDecisions(decisions) as StatewireThread.InputResponse);\n };\n }, [actionRequests, positional, resume]);\n\n if (!request && !isLive) {\n if (!abandonedInterrupt || abandonedInterrupt.id === dismissedId) return null;\n return (\n <div\n className={cn(\n 'aui-approval-abandoned flex w-full items-center justify-between gap-3 rounded-xl border border-amber-300 bg-amber-50 px-3 py-2 text-amber-800 text-xs',\n classNames?.abandoned,\n )}\n >\n <p>Approval was cancelled — the run moved on.</p>\n <button\n type=\"button\"\n aria-label=\"Dismiss cancelled approval\"\n onClick={() => setDismissedId(abandonedInterrupt.id)}\n className=\"shrink-0 rounded-md p-1 hover:bg-amber-100\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n );\n }\n\n const context: StatewireApprovalRequestContext = { request, hitl, actionRequests };\n if (isHandledElsewhere?.(context)) return null;\n // A live mirror without lane stamps cannot be answered before the run\n // parks; the parked request renders it then.\n if (isLive && laneIds === null) return null;\n\n const message = readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);\n const toolName = hitl?.context?.tool_name ?? hitl?.context?.tool_id;\n const pendingCount = hitl?.context?.pending_action_count ?? 1;\n const hasDetails = hitl !== null && actionRequests.length > 0;\n const showDetails = hasDetails && (detailsOverride ?? actionRequests.length <= 1);\n const sessionApprovalOffered = hitl !== null && canApproveForSession(actionRequests);\n const laneDecided =\n undecidedLaneIds !== null &&\n laneIds !== null &&\n (undecidedLaneIds.length === 0 || liveSentKey === undecidedLaneIds.join('|'));\n const locked = pending || laneDecided;\n\n const editor =\n !readOnly && hitl !== null && renderEditor\n ? renderEditor({\n ...context,\n locked,\n sessionApprovalOffered,\n approve: () => decide(hitlResumeApprove()),\n approveForSession: () => decide(hitlResumeApproveForSession()),\n reject: (reason) => decide(hitlResumeReject(reason)),\n decideEach,\n })\n : undefined;\n\n return (\n <section\n aria-label={hitl ? 'Approval required' : 'Input required'}\n className={cn(\n 'aui-approval-card w-full rounded-2xl border border-amber-300 bg-amber-50 px-4 py-3 text-amber-900',\n classNames?.root,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"flex items-center gap-2 font-semibold text-sm\">\n <span>{hitl ? 'Approval required' : 'Waiting for input'}</span>\n {hitl && pendingCount > 1 ? (\n <span className=\"shrink-0 font-normal text-amber-800 text-xs\">\n {pendingCount} tool calls\n </span>\n ) : null}\n </p>\n <p className=\"pt-1 text-amber-800 text-sm leading-snug\">\n {hitl && pendingCount === 1 && toolName ? (\n <>\n <span className=\"font-mono\">{toolName}</span> requires approval before it runs.\n </>\n ) : (\n message\n )}\n </p>\n {isLive ? (\n <p className=\"pt-1 text-amber-700 text-xs\" data-testid=\"statewire-approval-live\">\n {laneDecided\n ? 'Decision sent — it applies as soon as this step pauses.'\n : 'Other work keeps running while you decide.'}\n </p>\n ) : null}\n {hasDetails && editor == null ? (\n <button\n type=\"button\"\n onClick={() => setDetailsOverride(!showDetails)}\n aria-expanded={showDetails}\n className=\"mt-2 flex items-center gap-1 rounded px-1 py-0.5 text-amber-800 text-xs transition-colors hover:text-amber-950\"\n >\n <ChevronDownIcon\n className={cn('size-3 transition-transform', showDetails && 'rotate-180')}\n />\n {showDetails ? 'Hide details' : 'Review details'}\n </button>\n ) : null}\n {/* EVERY pending gated call renders, in order — one decision\n authorizes the whole batch, so nothing may hide behind a host's\n richer cards. All read-only: this runtime resumes through\n `hitl_response_to_decisions`, which has no `tool_args` channel —\n an editable composer would let someone rewrite an email and then\n watch the original go out — unless the host takes over the\n decision too (`renderEditor`), which sends the edit per call. */}\n {editor != null ? (\n <div className={cn('mt-3 flex flex-col gap-2', classNames?.details)}>{editor}</div>\n ) : showDetails ? (\n <div className={cn('mt-2 flex flex-col gap-2', classNames?.details)}>\n {renderDetails ? (\n renderDetails(context)\n ) : (\n <DefaultApprovalDetails actionRequests={actionRequests} />\n )}\n </div>\n ) : null}\n </div>\n </div>\n {editor != null ? null : (\n <div className={cn('flex flex-wrap items-center gap-2 pt-3', classNames?.actions)}>\n {readOnly ? (\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-read-only\">\n {readOnlyNotice}\n </p>\n ) : hitl ? (\n <>\n <Button\n size=\"sm\"\n disabled={locked}\n onClick={() => decide(hitlResumeApprove())}\n className=\"bg-green-600 text-white hover:bg-green-700\"\n >\n <CheckIcon />\n Approve\n </Button>\n {sessionApprovalOffered ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeApproveForSession())}\n data-testid=\"statewire-approval-approve-session\"\n >\n <CheckCheckIcon />\n Approve all for this session\n </Button>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => decide(hitlResumeReject())}\n >\n <XIcon />\n Reject\n </Button>\n </>\n ) : (\n <>\n <p className=\"text-amber-800 text-sm\" data-testid=\"statewire-approval-non-hitl\">\n {nonHitlNotice}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={pending}\n onClick={() => resume(interruptResumeDecline())}\n >\n <XIcon />\n Decline\n </Button>\n </>\n )}\n </div>\n )}\n </section>\n );\n};\n\n/** One block per gated call: its name (when the batch has several) and its arguments as JSON. */\nconst DefaultApprovalDetails: FC<{ actionRequests: PendingActionRequest[] }> = ({\n actionRequests,\n}) => (\n <>\n {actionRequests.map((action, index) => (\n <div\n key={`${action.name}-${index}`}\n className=\"rounded-lg bg-amber-100 p-2 text-amber-900 text-xs leading-relaxed\"\n >\n {actionRequests.length > 1 ? (\n <p className=\"font-mono font-semibold\">{action.name}</p>\n ) : null}\n {Object.keys(action.args).length > 0 ? (\n <pre className=\"mt-1 max-h-32 overflow-auto whitespace-pre-wrap break-words rounded bg-white/60 p-1.5 font-mono text-[11px]\">\n {JSON.stringify(action.args, null, 2)}\n </pre>\n ) : (\n <p className=\"text-amber-800\">No arguments.</p>\n )}\n </div>\n ))}\n </>\n);\n"],"names":["request"],"mappings":"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"}
@@ -44,7 +44,7 @@ export { WakeMarkerCard, type WakeMarkerCardProps, WakeMarkerMessage, } from "./
44
44
  export { VoiceDelegationCard, type VoiceDelegationCardProps, VoiceDelegationMessage, } from "./messages/voice-delegation-card";
45
45
  export { ContinuePill } from "./messages/continue-pill";
46
46
  export { type ReadStatewireTailEnding, StatewireContinueRunAffordance, StatewireContinueRunButton, type StatewireContinueRunButtonProps, StatewireRunEndedFooter, type StatewireRunEndedFooterActionsContext, type StatewireRunEndedFooterProps, type StatewireTailEnding, } from "./messages/statewire-continue-run";
47
- export { StatewireApprovalCard, type StatewireApprovalCardProps, type StatewireApprovalRequestContext, } from "../chat/StatewireApprovalCard";
47
+ export { StatewireApprovalCard, type StatewireApprovalCardProps, type StatewireApprovalEditorContext, type StatewireApprovalRequestContext, } from "../chat/StatewireApprovalCard";
48
48
  export { type StatewireApprovalRequest, type StatewireInterruptPredicate, type UseStatewireApprovalRequestOptions, useStatewireApprovalRequest, } from "../chat/use-statewire-approval-request";
49
49
  export { StatewireQueuedMessages, type StatewireQueueEditorContext, type StatewireQueueEvent, type StatewireQueueRowContentContext, type StatewireQueuedMessagesProps, } from "../chat/StatewireQueuedMessages";
50
50
  export { WaitingOnYouBanner, type WaitingOnYouBannerProps, WaitingOnYouBannerView, type WaitingOnYouBannerViewProps, } from "../chat/WaitingOnYouBanner";
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Published as the `@athenaintel/react/chat-ui/statewire` sub-path.
10
10
  */
11
- export { StatewireApprovalCard, type StatewireApprovalCardProps, type StatewireApprovalRequestContext, } from "../chat/StatewireApprovalCard";
11
+ export { StatewireApprovalCard, type StatewireApprovalCardProps, type StatewireApprovalEditorContext, type StatewireApprovalRequestContext, } from "../chat/StatewireApprovalCard";
12
12
  export { type StatewireApprovalRequest, type StatewireInterruptPredicate, type UseStatewireApprovalRequestOptions, useStatewireApprovalRequest, } from "../chat/use-statewire-approval-request";
13
13
  export { StatewireQueuedMessages, type StatewireQueueEditorContext, type StatewireQueueEvent, type StatewireQueueRowContentContext, type StatewireQueuedMessagesProps, } from "../chat/StatewireQueuedMessages";
14
14
  export { HistoryEdgeRow, type HistoryEdgeRowProps, StatewireHistoryEdge, type StatewireHistoryEdgeProps, } from "./thread/statewire-history-edge";
package/dist/index.cjs CHANGED
@@ -247,6 +247,7 @@ exports.ThreadList = ThreadList.ThreadList;
247
247
  exports.SessionList = SessionList.SessionList;
248
248
  exports.useAthenaSessionAttention = useAthenaSessionAttention.useAthenaSessionAttention;
249
249
  exports.groupSessions = sessionListModel.groupSessions;
250
+ exports.orderedPullRequests = sessionListModel.orderedPullRequests;
250
251
  exports.pullRequestChip = sessionListModel.pullRequestChip;
251
252
  exports.pullRequestChipState = sessionListModel.pullRequestChipState;
252
253
  exports.sessionIndicator = sessionListModel.sessionIndicator;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}