@athenaintel/react 0.53.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +37 -1
  2. package/dist/agent-runtime/src/call-recap.cjs +2 -3
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -1
  4. package/dist/agent-runtime/src/call-recap.d.ts +1 -2
  5. package/dist/agent-runtime/src/call-recap.js +1 -2
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -1
  7. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +7 -3
  8. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  9. package/dist/agent-runtime/src/deep-agent-transport-recipe.d.ts +10 -2
  10. package/dist/agent-runtime/src/deep-agent-transport-recipe.js +8 -4
  11. package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  12. package/dist/agent-runtime/src/dictation-session.d.ts +158 -0
  13. package/dist/agent-runtime/src/edit-command.cjs +15 -3
  14. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  15. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  16. package/dist/agent-runtime/src/edit-command.js +15 -3
  17. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  18. package/dist/agent-runtime/src/generated/runconfig-contract.cjs +10 -0
  19. package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  20. package/dist/agent-runtime/src/generated/runconfig-contract.d.ts +25 -1
  21. package/dist/agent-runtime/src/generated/runconfig-contract.js +10 -0
  22. package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  23. package/dist/agent-runtime/src/history-pages.cjs +279 -0
  24. package/dist/agent-runtime/src/history-pages.cjs.map +1 -0
  25. package/dist/agent-runtime/src/history-pages.d.ts +252 -0
  26. package/dist/agent-runtime/src/history-pages.js +279 -0
  27. package/dist/agent-runtime/src/history-pages.js.map +1 -0
  28. package/dist/agent-runtime/src/index.d.ts +8 -7
  29. package/dist/agent-runtime/src/message-window.cjs +111 -7
  30. package/dist/agent-runtime/src/message-window.cjs.map +1 -1
  31. package/dist/agent-runtime/src/message-window.d.ts +66 -6
  32. package/dist/agent-runtime/src/message-window.js +112 -8
  33. package/dist/agent-runtime/src/message-window.js.map +1 -1
  34. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  35. package/dist/agent-runtime/src/offline-status.d.ts +64 -2
  36. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  37. package/dist/agent-runtime/src/runtime.d.ts +4 -3
  38. package/dist/agent-runtime/src/subagent-transcripts.cjs +23 -1
  39. package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
  40. package/dist/agent-runtime/src/subagent-transcripts.js +25 -3
  41. package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
  42. package/dist/agent-runtime/src/subagents.cjs +112 -0
  43. package/dist/agent-runtime/src/subagents.cjs.map +1 -1
  44. package/dist/agent-runtime/src/subagents.d.ts +103 -0
  45. package/dist/agent-runtime/src/subagents.js +113 -1
  46. package/dist/agent-runtime/src/subagents.js.map +1 -1
  47. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +37 -9
  48. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  49. package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
  50. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  51. package/dist/agent-runtime/src/tool-metadata.js +8 -0
  52. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  53. package/dist/agent-runtime/src/use-deep-agent-thread.cjs +22 -2
  54. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  55. package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +9 -1
  56. package/dist/agent-runtime/src/use-deep-agent-thread.js +23 -3
  57. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  58. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +106 -25
  59. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  60. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +41 -6
  61. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +109 -28
  62. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  63. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +5 -9
  64. package/dist/agent-runtime/src/voice-presentation.cjs +34 -0
  65. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  66. package/dist/agent-runtime/src/voice-presentation.d.ts +35 -0
  67. package/dist/agent-runtime/src/voice-presentation.js +34 -0
  68. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  69. package/dist/assets/AthenaAssetEmbed.cjs +4 -2
  70. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  71. package/dist/assets/AthenaAssetEmbed.d.ts +17 -8
  72. package/dist/assets/AthenaAssetEmbed.js +4 -2
  73. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  74. package/dist/assets/use-asset-embed.cjs.map +1 -1
  75. package/dist/assets/use-asset-embed.d.ts +7 -5
  76. package/dist/assets/use-asset-embed.js.map +1 -1
  77. package/dist/auth/account-identity.cjs +69 -0
  78. package/dist/auth/account-identity.cjs.map +1 -0
  79. package/dist/auth/account-identity.d.ts +57 -0
  80. package/dist/auth/account-identity.js +69 -0
  81. package/dist/auth/account-identity.js.map +1 -0
  82. package/dist/chat/AthenaChat.cjs +1 -1
  83. package/dist/chat/AthenaChat.cjs.map +1 -1
  84. package/dist/chat/AthenaChat.js +1 -1
  85. package/dist/chat/AthenaChat.js.map +1 -1
  86. package/dist/chat/super-grouping/SuperGroupingCard.cjs +51 -43
  87. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  88. package/dist/chat/super-grouping/SuperGroupingCard.js +51 -43
  89. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  90. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +9 -4
  91. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  92. package/dist/chat/super-grouping/SuperGroupingTranscript.js +9 -4
  93. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  94. package/dist/chat/super-grouping/activity-timeline.cjs +7 -4
  95. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  96. package/dist/chat/super-grouping/activity-timeline.d.ts +10 -1
  97. package/dist/chat/super-grouping/activity-timeline.js +7 -4
  98. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  99. package/dist/chat/super-grouping/earlier-steps-button.cjs +50 -0
  100. package/dist/chat/super-grouping/earlier-steps-button.cjs.map +1 -0
  101. package/dist/chat/super-grouping/earlier-steps-button.d.ts +11 -0
  102. package/dist/chat/super-grouping/earlier-steps-button.js +50 -0
  103. package/dist/chat/super-grouping/earlier-steps-button.js.map +1 -0
  104. package/dist/chat/super-grouping/toolkit-map.cjs +1 -0
  105. package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
  106. package/dist/chat/super-grouping/toolkit-map.js +1 -0
  107. package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
  108. package/dist/chat/super-grouping/use-turn-steps.cjs +28 -0
  109. package/dist/chat/super-grouping/use-turn-steps.cjs.map +1 -0
  110. package/dist/chat/super-grouping/use-turn-steps.d.ts +23 -0
  111. package/dist/chat/super-grouping/use-turn-steps.js +28 -0
  112. package/dist/chat/super-grouping/use-turn-steps.js.map +1 -0
  113. package/dist/chat/use-statewire-approval-request.cjs +5 -5
  114. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  115. package/dist/chat/use-statewire-approval-request.d.ts +7 -0
  116. package/dist/chat/use-statewire-approval-request.js +5 -5
  117. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  118. package/dist/chat-ui/mentions/use-account-workspace-id.cjs +18 -58
  119. package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
  120. package/dist/chat-ui/mentions/use-account-workspace-id.js +18 -58
  121. package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
  122. package/dist/chat-ui/messages/voice-delegation-card.cjs +93 -25
  123. package/dist/chat-ui/messages/voice-delegation-card.cjs.map +1 -1
  124. package/dist/chat-ui/messages/voice-delegation-card.d.ts +16 -7
  125. package/dist/chat-ui/messages/voice-delegation-card.js +95 -27
  126. package/dist/chat-ui/messages/voice-delegation-card.js.map +1 -1
  127. package/dist/chat-ui/statewire.cjs +6 -0
  128. package/dist/chat-ui/statewire.cjs.map +1 -1
  129. package/dist/chat-ui/statewire.d.ts +3 -1
  130. package/dist/chat-ui/statewire.js +7 -1
  131. package/dist/chat-ui/statewire.js.map +1 -1
  132. package/dist/chat-ui/thread/handoff-cursor.cjs +2 -2
  133. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -1
  134. package/dist/chat-ui/thread/handoff-cursor.js +3 -3
  135. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -1
  136. package/dist/chat-ui/thread/statewire-history-edge.cjs +4 -0
  137. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  138. package/dist/chat-ui/thread/statewire-history-edge.d.ts +11 -1
  139. package/dist/chat-ui/thread/statewire-history-edge.js +5 -1
  140. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  141. package/dist/collab/client.cjs +134 -7
  142. package/dist/collab/client.cjs.map +1 -1
  143. package/dist/collab/client.d.ts +44 -0
  144. package/dist/collab/client.js +134 -7
  145. package/dist/collab/client.js.map +1 -1
  146. package/dist/collab/index.d.ts +1 -1
  147. package/dist/composer/QuoteContext.cjs +3 -3
  148. package/dist/composer/QuoteContext.cjs.map +1 -1
  149. package/dist/composer/QuoteContext.js +3 -3
  150. package/dist/composer/QuoteContext.js.map +1 -1
  151. package/dist/keryx-http/src/initial-sync.cjs +121 -0
  152. package/dist/keryx-http/src/initial-sync.cjs.map +1 -0
  153. package/dist/keryx-http/src/initial-sync.d.ts +77 -0
  154. package/dist/keryx-http/src/initial-sync.js +104 -0
  155. package/dist/keryx-http/src/initial-sync.js.map +1 -0
  156. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +8 -0
  157. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -1
  158. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +6 -0
  159. package/dist/keryx-http/src/unconfirmed-update-attributions.js +8 -0
  160. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -1
  161. package/dist/provider/AthenaProvider.cjs +11 -3
  162. package/dist/provider/AthenaProvider.cjs.map +1 -1
  163. package/dist/provider/AthenaProvider.js +12 -4
  164. package/dist/provider/AthenaProvider.js.map +1 -1
  165. package/dist/provider/statewire-window-context.cjs +6 -0
  166. package/dist/provider/statewire-window-context.cjs.map +1 -1
  167. package/dist/provider/statewire-window-context.d.ts +10 -1
  168. package/dist/provider/statewire-window-context.js +6 -0
  169. package/dist/provider/statewire-window-context.js.map +1 -1
  170. package/dist/react/package.json.cjs +1 -1
  171. package/dist/react/package.json.js +1 -1
  172. package/dist/runtime/useAthenaStatewireRuntime.cjs +2 -0
  173. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  174. package/dist/runtime/useAthenaStatewireRuntime.d.ts +7 -1
  175. package/dist/runtime/useAthenaStatewireRuntime.js +2 -0
  176. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  177. package/dist/styles.css +1 -1
  178. package/dist/threads/api.cjs +1 -2
  179. package/dist/threads/api.cjs.map +1 -1
  180. package/dist/threads/api.d.ts +3 -2
  181. package/dist/threads/api.js +1 -2
  182. package/dist/threads/api.js.map +1 -1
  183. package/dist/threads/sessions-query.cjs +2 -0
  184. package/dist/threads/sessions-query.cjs.map +1 -1
  185. package/dist/threads/sessions-query.js +2 -0
  186. package/dist/threads/sessions-query.js.map +1 -1
  187. package/dist/threads/statewire-thread-list.cjs +5 -7
  188. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  189. package/dist/threads/statewire-thread-list.js +7 -9
  190. package/dist/threads/statewire-thread-list.js.map +1 -1
  191. package/dist/tools/subagent-task-tool-ui.cjs +15 -5
  192. package/dist/tools/subagent-task-tool-ui.cjs.map +1 -1
  193. package/dist/tools/subagent-task-tool-ui.d.ts +5 -4
  194. package/dist/tools/subagent-task-tool-ui.js +16 -6
  195. package/dist/tools/subagent-task-tool-ui.js.map +1 -1
  196. package/package.json +1 -1
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react = require("@assistant-ui/react");
4
+ const require$$0 = require("react");
5
+ const historyPages = require("../../agent-runtime/src/history-pages.cjs");
6
+ function rawTurnStepsMarker(message) {
7
+ var _a;
8
+ for (const source of react.getExternalStoreMessages(message)) {
9
+ const marker = (_a = source == null ? void 0 : source.additional_kwargs) == null ? void 0 : _a[historyPages.DEEP_AGENT_TURN_STEPS_KEY];
10
+ if (marker !== void 0) return marker;
11
+ }
12
+ return void 0;
13
+ }
14
+ function useTurnStepsMarker() {
15
+ const raw = react.useAuiState((s) => rawTurnStepsMarker(s.message));
16
+ return require$$0.useMemo(
17
+ () => raw === void 0 ? void 0 : historyPages.readTurnStepsMarker({ additional_kwargs: { [historyPages.DEEP_AGENT_TURN_STEPS_KEY]: raw } }),
18
+ [raw]
19
+ );
20
+ }
21
+ const TurnStepsControlContext = require$$0.createContext(null);
22
+ function useTurnStepsControl() {
23
+ return require$$0.useContext(TurnStepsControlContext);
24
+ }
25
+ exports.TurnStepsControlContext = TurnStepsControlContext;
26
+ exports.useTurnStepsControl = useTurnStepsControl;
27
+ exports.useTurnStepsMarker = useTurnStepsMarker;
28
+ //# sourceMappingURL=use-turn-steps.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-turn-steps.cjs","sources":["../../../src/chat/super-grouping/use-turn-steps.ts"],"sourcesContent":["import { getExternalStoreMessages, useAuiState } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_TURN_STEPS_KEY,\n type DeepAgentTurnStepsMarker,\n readTurnStepsMarker,\n} from '@athenaintel/agent-runtime';\nimport { type ComponentType, createContext, useContext, useMemo } from 'react';\n\n/**\n * The raw marker object a history join put on one of this turn's source\n * messages: the same reference for as long as the join reuses its marked\n * copy, so it is a stable selector value. Read off the bound source messages\n * because assistant-ui copies `metadata.custom` from a turn's first assistant\n * message only, and the marked one need not be first.\n */\nfunction rawTurnStepsMarker(message: Parameters<typeof getExternalStoreMessages>[0]): unknown {\n for (const source of getExternalStoreMessages<{ additional_kwargs?: Record<string, unknown> }>(message)) {\n const marker = source?.additional_kwargs?.[DEEP_AGENT_TURN_STEPS_KEY];\n if (marker !== undefined) return marker;\n }\n return undefined;\n}\n\n/**\n * What a summarized turn of older history (`history/page`) left out: its\n * exact hidden counts, read off the turn's messages, or `undefined` for a turn\n * loaded whole. Inside the thread's message scope.\n */\nexport function useTurnStepsMarker(): DeepAgentTurnStepsMarker | undefined {\n const raw = useAuiState((s) => rawTurnStepsMarker(s.message));\n return useMemo(\n () => (raw === undefined ? undefined : readTurnStepsMarker({ additional_kwargs: { [DEEP_AGENT_TURN_STEPS_KEY]: raw } })),\n [raw],\n );\n}\n\n/** Props of the control a turn renders to show the steps its page left out. */\nexport interface TurnStepsControlProps {\n marker: DeepAgentTurnStepsMarker;\n /** Fetch the newest steps by itself, once per turn (an opened card). */\n autoLoad?: boolean;\n}\n\n/**\n * The control a summarized turn renders to show its steps\n * (`EarlierStepsButton`). `AthenaProvider` provides it with its history-page\n * store; a host that mounts the runtime itself provides both. Without one a\n * turn reads its exact counts and offers no control, and the surface bundles\n * none of the loading code.\n */\nexport const TurnStepsControlContext = createContext<ComponentType<TurnStepsControlProps> | null>(null);\n\nexport function useTurnStepsControl(): ComponentType<TurnStepsControlProps> | null {\n return useContext(TurnStepsControlContext);\n}\n"],"names":["getExternalStoreMessages","DEEP_AGENT_TURN_STEPS_KEY","useAuiState","useMemo","readTurnStepsMarker","createContext","useContext"],"mappings":";;;;;AAeA,SAAS,mBAAmB,SAAkE;;AAC5F,aAAW,UAAUA,+BAA0E,OAAO,GAAG;AACvG,UAAM,UAAS,sCAAQ,sBAAR,mBAA4BC;AAC3C,QAAI,WAAW,OAAW,QAAO;AAAA,EACnC;AACA,SAAO;AACT;AAOO,SAAS,qBAA2D;AACzE,QAAM,MAAMC,MAAAA,YAAY,CAAC,MAAM,mBAAmB,EAAE,OAAO,CAAC;AAC5D,SAAOC,WAAAA;AAAAA,IACL,MAAO,QAAQ,SAAY,SAAYC,aAAAA,oBAAoB,EAAE,mBAAmB,EAAE,CAACH,aAAAA,yBAAyB,GAAG,IAAA,GAAO;AAAA,IACtH,CAAC,GAAG;AAAA,EAAA;AAER;AAgBO,MAAM,0BAA0BI,WAAAA,cAA2D,IAAI;AAE/F,SAAS,sBAAmE;AACjF,SAAOC,WAAAA,WAAW,uBAAuB;AAC3C;;;;"}
@@ -0,0 +1,23 @@
1
+ import { type DeepAgentTurnStepsMarker } from '../../agent-runtime/src/index';
2
+ import { type ComponentType } from 'react';
3
+ /**
4
+ * What a summarized turn of older history (`history/page`) left out: its
5
+ * exact hidden counts, read off the turn's messages, or `undefined` for a turn
6
+ * loaded whole. Inside the thread's message scope.
7
+ */
8
+ export declare function useTurnStepsMarker(): DeepAgentTurnStepsMarker | undefined;
9
+ /** Props of the control a turn renders to show the steps its page left out. */
10
+ export interface TurnStepsControlProps {
11
+ marker: DeepAgentTurnStepsMarker;
12
+ /** Fetch the newest steps by itself, once per turn (an opened card). */
13
+ autoLoad?: boolean;
14
+ }
15
+ /**
16
+ * The control a summarized turn renders to show its steps
17
+ * (`EarlierStepsButton`). `AthenaProvider` provides it with its history-page
18
+ * store; a host that mounts the runtime itself provides both. Without one a
19
+ * turn reads its exact counts and offers no control, and the surface bundles
20
+ * none of the loading code.
21
+ */
22
+ export declare const TurnStepsControlContext: import("react").Context<ComponentType<TurnStepsControlProps> | null>;
23
+ export declare function useTurnStepsControl(): ComponentType<TurnStepsControlProps> | null;
@@ -0,0 +1,28 @@
1
+ import { useAuiState, getExternalStoreMessages } from "@assistant-ui/react";
2
+ import { createContext, useMemo, useContext } from "react";
3
+ import { readTurnStepsMarker, DEEP_AGENT_TURN_STEPS_KEY } from "../../agent-runtime/src/history-pages.js";
4
+ function rawTurnStepsMarker(message) {
5
+ var _a;
6
+ for (const source of getExternalStoreMessages(message)) {
7
+ const marker = (_a = source == null ? void 0 : source.additional_kwargs) == null ? void 0 : _a[DEEP_AGENT_TURN_STEPS_KEY];
8
+ if (marker !== void 0) return marker;
9
+ }
10
+ return void 0;
11
+ }
12
+ function useTurnStepsMarker() {
13
+ const raw = useAuiState((s) => rawTurnStepsMarker(s.message));
14
+ return useMemo(
15
+ () => raw === void 0 ? void 0 : readTurnStepsMarker({ additional_kwargs: { [DEEP_AGENT_TURN_STEPS_KEY]: raw } }),
16
+ [raw]
17
+ );
18
+ }
19
+ const TurnStepsControlContext = createContext(null);
20
+ function useTurnStepsControl() {
21
+ return useContext(TurnStepsControlContext);
22
+ }
23
+ export {
24
+ TurnStepsControlContext,
25
+ useTurnStepsControl,
26
+ useTurnStepsMarker
27
+ };
28
+ //# sourceMappingURL=use-turn-steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-turn-steps.js","sources":["../../../src/chat/super-grouping/use-turn-steps.ts"],"sourcesContent":["import { getExternalStoreMessages, useAuiState } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_TURN_STEPS_KEY,\n type DeepAgentTurnStepsMarker,\n readTurnStepsMarker,\n} from '@athenaintel/agent-runtime';\nimport { type ComponentType, createContext, useContext, useMemo } from 'react';\n\n/**\n * The raw marker object a history join put on one of this turn's source\n * messages: the same reference for as long as the join reuses its marked\n * copy, so it is a stable selector value. Read off the bound source messages\n * because assistant-ui copies `metadata.custom` from a turn's first assistant\n * message only, and the marked one need not be first.\n */\nfunction rawTurnStepsMarker(message: Parameters<typeof getExternalStoreMessages>[0]): unknown {\n for (const source of getExternalStoreMessages<{ additional_kwargs?: Record<string, unknown> }>(message)) {\n const marker = source?.additional_kwargs?.[DEEP_AGENT_TURN_STEPS_KEY];\n if (marker !== undefined) return marker;\n }\n return undefined;\n}\n\n/**\n * What a summarized turn of older history (`history/page`) left out: its\n * exact hidden counts, read off the turn's messages, or `undefined` for a turn\n * loaded whole. Inside the thread's message scope.\n */\nexport function useTurnStepsMarker(): DeepAgentTurnStepsMarker | undefined {\n const raw = useAuiState((s) => rawTurnStepsMarker(s.message));\n return useMemo(\n () => (raw === undefined ? undefined : readTurnStepsMarker({ additional_kwargs: { [DEEP_AGENT_TURN_STEPS_KEY]: raw } })),\n [raw],\n );\n}\n\n/** Props of the control a turn renders to show the steps its page left out. */\nexport interface TurnStepsControlProps {\n marker: DeepAgentTurnStepsMarker;\n /** Fetch the newest steps by itself, once per turn (an opened card). */\n autoLoad?: boolean;\n}\n\n/**\n * The control a summarized turn renders to show its steps\n * (`EarlierStepsButton`). `AthenaProvider` provides it with its history-page\n * store; a host that mounts the runtime itself provides both. Without one a\n * turn reads its exact counts and offers no control, and the surface bundles\n * none of the loading code.\n */\nexport const TurnStepsControlContext = createContext<ComponentType<TurnStepsControlProps> | null>(null);\n\nexport function useTurnStepsControl(): ComponentType<TurnStepsControlProps> | null {\n return useContext(TurnStepsControlContext);\n}\n"],"names":[],"mappings":";;;AAeA,SAAS,mBAAmB,SAAkE;;AAC5F,aAAW,UAAU,yBAA0E,OAAO,GAAG;AACvG,UAAM,UAAS,sCAAQ,sBAAR,mBAA4B;AAC3C,QAAI,WAAW,OAAW,QAAO;AAAA,EACnC;AACA,SAAO;AACT;AAOO,SAAS,qBAA2D;AACzE,QAAM,MAAM,YAAY,CAAC,MAAM,mBAAmB,EAAE,OAAO,CAAC;AAC5D,SAAO;AAAA,IACL,MAAO,QAAQ,SAAY,SAAY,oBAAoB,EAAE,mBAAmB,EAAE,CAAC,yBAAyB,GAAG,IAAA,GAAO;AAAA,IACtH,CAAC,GAAG;AAAA,EAAA;AAER;AAgBO,MAAM,0BAA0B,cAA2D,IAAI;AAE/F,SAAS,sBAAmE;AACjF,SAAO,WAAW,uBAAuB;AAC3C;"}
@@ -175,13 +175,13 @@ function toolCallIdSet(key) {
175
175
  function useStatewireApprovalWait() {
176
176
  const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } = useStatewireApprovalRequest();
177
177
  const runParked = requestId !== void 0;
178
- const ids = interrupts.toolCallIdsAwaitingApproval({
179
- actionRequests: runParked ? actionRequests : liveActionRequests,
180
- queuedLaneDecisions
181
- });
178
+ const requests = runParked ? actionRequests : liveActionRequests;
179
+ const ids = interrupts.toolCallIdsAwaitingApproval({ actionRequests: requests, queuedLaneDecisions });
182
180
  const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);
181
+ const requestsRef = require$$0.useRef(requests);
182
+ requestsRef.current = requests;
183
183
  return require$$0.useMemo(
184
- () => key === "" && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked },
184
+ () => key === "" && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked, actionRequests: requestsRef.current },
185
185
  [key, runParked]
186
186
  );
187
187
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-statewire-approval-request.cjs","sources":["../../src/chat/use-statewire-approval-request.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n useStatewireRuns,\n useStatewireSendCommand,\n useStatewireState,\n} from '@assistant-ui/react-statewire';\nimport {\n type AbandonedInputRequest,\n asHitlApproval,\n findAbandonedInterrupt,\n HITL_PENDING_RESUME_GRACE_MS,\n type HitlApprovalPayload,\n interruptMirrorPayload,\n isApprovalCardInterrupt,\n laneInterruptIds,\n type PendingActionRequest,\n pendingActionRequests,\n readAbandonedInputRequests,\n readInterruptMirror,\n readQueuedLaneDecisions,\n type ResumeExtras,\n toolCallIdsAwaitingApproval,\n useApprovalResumeLock,\n} from '@athenaintel/agent-runtime';\nimport {\n createContext,\n createElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport { deviceOperationStamp, useDeviceOperationReader } from './device-operation';\nimport { useAthenaRunLive } from './use-athena-run-live';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /**\n * Decide per-dispatch subagent lanes while the parent run is still\n * streaming (`DeepAgentThread.run_lane_input`). Keys are the lanes'\n * langgraph interrupt ids; the host matches them against the lanes its\n * mirrored interrupt currently parks, answers at once when the run is\n * already parked, and otherwise holds them until it parks — stale or\n * unknown ids are ignored, never carried to a later interrupt.\n */\n runLaneInput: {\n readonly type: 'run/lane-input';\n readonly laneDecisions: Readonly<Record<string, StatewireThread.InputResponse>>;\n /** The passkey decision check covering these lanes (identity 0b.9). */\n readonly decisionCheck?: string;\n /** The device an agent was operating as these were sent (identity v7, D11). */\n readonly deviceOperation?: string;\n };\n /**\n * `run/input` sent while the host's device runs an agent's action\n * (`DeviceOperationProvider`, identity v7 D11): agora refuses it when it\n * approves anything, and reads a reject as before.\n */\n runInputFromOperatedDevice: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck?: string;\n readonly deviceOperation: string;\n };\n /**\n * `run/input` carrying the passkey decision check a person minted for\n * exactly this answer (identity 0b.9). The run manager reads only\n * `requestId` and `response`; the host classifies and spends the check\n * once the answer is recorded.\n */\n runInputWithDecisionCheck: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck: string;\n };\n /**\n * Re-send the lane decisions the host still holds queued as their\n * approver's resume of the parked request (`DeepAgentThread.run_lane_replay`).\n * The host resumes under the meta the approver's `run/lane-input`\n * recorded and refuses any other caller — every attached client reads\n * the same queue, and only the one that queued it may turn it into a\n * resume. Carries no decisions: the host replays what it recorded.\n */\n runLaneReplay: {\n readonly type: 'run/lane-replay';\n readonly requestId: string;\n };\n }\n }\n}\n\nconst NO_QUEUED_LANE_DECISIONS: Readonly<Record<string, unknown>> = Object.freeze({});\nconst NO_ACTION_REQUESTS: PendingActionRequest[] = [];\n\n/**\n * Lane ids of the parked requests this client resumed, keyed by request id\n * and shared by every hook instance on the page (approval card,\n * waiting-on-you banner, host editors) so all of them treat a lingering\n * mirror the same way. Request ids are unique per parked interrupt, so an\n * instance only ever consults the record of the request it saw parked: two\n * chats mounted side by side cannot suppress or clear each other's. A record\n * is retired once the host drops that request's mirror (the resume run\n * started), the surface sees a different request park, or it unmounts.\n */\nconst answeredRequests = new Map<string, readonly string[]>();\nconst answeredListeners = new Set<() => void>();\nlet answeredVersion = 0;\nfunction notifyAnswered(): void {\n answeredVersion += 1;\n for (const listener of answeredListeners) listener();\n}\nfunction recordAnswered(requestId: string, laneIds: readonly string[]): void {\n answeredRequests.set(requestId, laneIds);\n notifyAnswered();\n}\nfunction forgetAnswered(requestId: string): void {\n if (answeredRequests.delete(requestId)) notifyAnswered();\n}\nfunction subscribeAnswered(listener: () => void): () => void {\n answeredListeners.add(listener);\n return () => {\n answeredListeners.delete(listener);\n };\n}\nconst readAnsweredVersion = (): number => answeredVersion;\n\n/**\n * The lanes a resume of the parked request settles: every lane of the request\n * (a resume answers the whole park — lanes already queued through\n * `run/lane-input` ride along) plus any lane named in the `lane_decisions` map.\n */\nfunction answeredLaneIds(value: unknown, actionRequests: readonly PendingActionRequest[]): string[] {\n const ids = laneInterruptIds(actionRequests) ?? [];\n if (typeof value === 'object' && value !== null && 'lane_decisions' in value) {\n const lanes = (value as { lane_decisions: unknown }).lane_decisions;\n if (typeof lanes === 'object' && lanes !== null) {\n for (const id of Object.keys(lanes)) if (!ids.includes(id)) ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Whether the host's mirror still shows an interrupt this client already\n * answered. After `run/input` the host drops the parked request at once but\n * keeps `__interrupt__` until the resume run starts and clears it (seconds on\n * a cold checkpoint); without this check the card would re-offer the SAME\n * approval from the mirror, buttons live, and then fold it away again.\n */\nfunction mirrorAlreadyAnswered(\n answeredLanes: readonly string[] | undefined,\n liveActionRequests: readonly PendingActionRequest[],\n): boolean {\n if (answeredLanes === undefined) return false;\n const ids = laneInterruptIds(liveActionRequests);\n if (ids === null) return false;\n return ids.every((id) => answeredLanes.includes(id));\n}\n\n/** Selects which pending interrupt an approval surface answers. */\nexport type StatewireInterruptPredicate = (\n request: StatewireThread.InputRequestState,\n) => request is StatewireThread.CustomInputRequest;\n\nexport interface UseStatewireApprovalRequestOptions {\n /**\n * Which pending interrupt to answer. Defaults to `isApprovalCardInterrupt`\n * (client-tool parks excluded — the client-tool bridge owns those, and a\n * generic resume would corrupt the parked tool call).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a sent decision keeps the actions locked when no resume run ever\n * starts. Defaults to {@link HITL_PENDING_RESUME_GRACE_MS}.\n */\n pendingResumeGraceMs?: number;\n /**\n * The host refused the `run/input` (or the transport lost it): the\n * optimistic fold rolls back, the request is on screen again with its\n * actions re-enabled, and this is the surface's one chance to say why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /** See {@link UseApprovalResumeLockOptions.onResumeRetry}. */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n}\n\nexport interface StatewireApprovalRequest<TValue> {\n /** The pending interrupt this surface answers, if any. */\n request: StatewireThread.CustomInputRequest | undefined;\n requestId: string | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null`. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n /** An interrupt a non-resume dispatch dropped — the \"approval was cancelled\" notice. */\n abandonedInterrupt: AbandonedInputRequest | undefined;\n /**\n * The mirrored interrupt's HITL payload while the run is still live and no\n * input request is answerable: per-dispatch `task` lanes parked on their\n * gates while sibling lanes stream. `null` once the run parks (the same\n * lanes are then `hitl` / `actionRequests`) and when nothing is parked.\n */\n liveHitl: HitlApprovalPayload | null;\n /** Every gated call `liveHitl` covers (empty when it is `null`). */\n liveActionRequests: PendingActionRequest[];\n /**\n * The decisions the host already holds from a `run/lane-input`, keyed by\n * langgraph interrupt id, to be answered when the run parks — a queued lane\n * reads as decided on every attached client, not just the one that sent it.\n */\n queuedLaneDecisions: Readonly<Record<string, unknown>>;\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Answer the request with `{ type: 'run/input', requestId, response: { type: 'resume', value } }`,\n * plus `decisionCheck` when `extras` carries one.\n * Returns the dispatch promise (settles when the host accepts, rejects when\n * it refuses), or `undefined` when the lock dropped the send.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n /**\n * Decide per-dispatch lanes by langgraph interrupt id. With a pending\n * request this is the native `lane_decisions` resume through the lock;\n * without one (the run is still streaming siblings) it is `run/lane-input`,\n * which the host queues and answers on the park. Returns the dispatch\n * promise, or `undefined` when the lock dropped a resume.\n */\n sendLaneDecisions: (\n lanes: Readonly<Record<string, StatewireThread.InputResponse>>,\n extras?: ResumeExtras,\n ) => Promise<unknown> | undefined;\n /**\n * Ask the host to resume the parked `requestId` with the lane decisions it\n * still holds queued, as their approver (`run/lane-replay`). For the card\n * that queued them, after the host's own answerer has stayed silent past\n * its budget; the host refuses every other caller. Settles when the host\n * accepts, rejects when it refuses.\n */\n replayQueuedLanes: (requestId: string) => Promise<unknown>;\n}\n\n/**\n * The headless half of every statewire approval surface: selects the pending\n * interrupt from the runs projection, narrows its payload, reads the\n * host-published abandoned interrupts, and wires the shared optimistic resume\n * lock to the thread's `run/input` command. `StatewireApprovalCard` renders\n * over it; hosts with their own decision UI (per-lane subagent cards) build\n * on it directly so the selection and lock semantics cannot drift.\n *\n * Requires a statewire aui scope (throws outside one, like every\n * `useStatewire*` hook) — gate the mount with `hasStatewireThreadExtras`.\n */\nexport function useStatewireApprovalRequest<\n TValue extends StatewireThread.InputResponse = Record<string, string>,\n>(options: UseStatewireApprovalRequestOptions = {}): StatewireApprovalRequest<TValue> {\n const {\n interruptPredicate = isApprovalCardInterrupt,\n pendingResumeGraceMs = HITL_PENDING_RESUME_GRACE_MS,\n onResumeRejected,\n onResumeRetry,\n } = options;\n const sendCommand = useStatewireSendCommand();\n // Read at each send: the host's device may be operated by an agent now and\n // not a second later (identity v7, D11).\n const readDeviceOperation = useDeviceOperationReader();\n // The runs projection is memoized by the lib, so this derivation is\n // reference-stable between state updates.\n const request = useStatewireRuns().inputRequests?.find(interruptPredicate);\n const requestId = request?.id;\n\n // The host publishes the interrupts a non-resume dispatch dropped. Select\n // the raw array reference (pre-stream-safe) and derive inline; a `.find`\n // selector would return a fresh object every update.\n const abandoned = useStatewireState(readAbandonedInputRequests);\n const abandonedInterrupt = findAbandonedInterrupt(abandoned);\n\n const hitl = asHitlApproval(request?.payload);\n const actionRequests = useMemo(() => pendingActionRequests(hitl), [hitl]);\n\n const isRunning = useAthenaRunLive();\n // The host mirrors the interrupt into state as soon as a lane parks — while\n // the run manager still reports `running` (siblings streaming) and has no\n // request to answer. Raw reference selectors; derive inline.\n const mirror = useStatewireState(readInterruptMirror);\n const mirrorHitl =\n request === undefined && isRunning ? asHitlApproval(interruptMirrorPayload(mirror)) : null;\n const mirrorActionRequests = useMemo(() => pendingActionRequests(mirrorHitl), [mirrorHitl]);\n\n // The request this surface last saw parked: the only record it may read\n // or retire (the mirror it watches is the one that request's resume clears).\n const lastRequestIdRef = useRef<string>(undefined);\n if (requestId !== undefined) lastRequestIdRef.current = requestId;\n useSyncExternalStore(subscribeAnswered, readAnsweredVersion, readAnsweredVersion);\n const answeredLanes =\n lastRequestIdRef.current === undefined ? undefined : answeredRequests.get(lastRequestIdRef.current);\n const mirrorCleared = interruptMirrorPayload(mirror) === undefined;\n useEffect(() => {\n if (requestId === undefined) return;\n return () => {\n // A different request parks next (or this one unparks for good): the\n // record of this one is stale once it is not the one on screen.\n if (lastRequestIdRef.current !== requestId) forgetAnswered(requestId);\n };\n }, [requestId]);\n useEffect(() => {\n if (mirrorCleared && lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n }, [mirrorCleared]);\n useEffect(\n () => () => {\n if (lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n },\n [],\n );\n const liveHitl = mirrorAlreadyAnswered(answeredLanes, mirrorActionRequests) ? null : mirrorHitl;\n const liveActionRequests = liveHitl === mirrorHitl ? mirrorActionRequests : NO_ACTION_REQUESTS;\n const queuedLaneDecisions =\n useStatewireState(readQueuedLaneDecisions) ?? NO_QUEUED_LANE_DECISIONS;\n\n const { pending, resume: resumeLocked } = useApprovalResumeLock<TValue>({\n requestId,\n isRunning,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n // The dispatch promise is handed to the lock: a refusal releases it and\n // reaches `onResumeRejected`.\n sendResume: (id, value, extras) => {\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n if (deviceOperation !== undefined) {\n return sendCommand({\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n deviceOperation,\n });\n }\n return sendCommand(\n extras?.decisionCheck\n ? {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n decisionCheck: extras.decisionCheck,\n }\n : {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n },\n );\n },\n });\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n recordAnswered(requestId, answeredLaneIds(value, actionRequests));\n }\n return resumeLocked(value, extras);\n },\n [requestId, actionRequests, resumeLocked],\n );\n\n const sendLaneDecisions = useCallback(\n (lanes: Readonly<Record<string, StatewireThread.InputResponse>>, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n return resume({ lane_decisions: lanes } as unknown as TValue, extras);\n }\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n return sendCommand({\n type: 'run/lane-input',\n laneDecisions: lanes,\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n ...(deviceOperation !== undefined ? { deviceOperation } : {}),\n });\n },\n [requestId, resume, sendCommand, readDeviceOperation],\n );\n\n const replayQueuedLanes = useCallback(\n (id: string) => sendCommand({ type: 'run/lane-replay', requestId: id }),\n [sendCommand],\n );\n\n return {\n request,\n requestId,\n hitl,\n actionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n replayQueuedLanes,\n };\n}\n\n/**\n * What the run waits on a person's approval for: the one read every surface\n * that shows a parked step makes (the SDK chat's turns and subagent cards,\n * Olympus's lane cards and step lists), so they all agree with the approval\n * card. The join from an approval to the calls, lanes and steps it gates is\n * agent-runtime's (`toolCallIdsAwaitingApproval`, `subagentAwaitingApproval`,\n * `useTurnToolCallsAwaitingApproval`). Read-only: nothing here sends a command.\n */\nexport interface StatewireApprovalWait {\n /**\n * The gated calls still waiting on a decision, by `tool_call_id`: a step\n * holding one waits for approval, and so does a subagent lane whose own\n * dispatch, or a call it made, is one. Keeps its identity while its ids are\n * unchanged.\n */\n toolCallIds: ReadonlySet<string>;\n /**\n * The run is parked on a request a person answers (an approval, a\n * question): nothing runs until someone answers it, so no step's silence is\n * a stall.\n */\n runParked: boolean;\n}\n\nconst NO_WAITING_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/** Nothing waits on a person. */\nconst NOTHING_WAITS: StatewireApprovalWait = {\n toolCallIds: NO_WAITING_TOOL_CALL_IDS,\n runParked: false,\n};\n\n/** Joins call ids into one selector value; an id never holds it. */\nconst TOOL_CALL_ID_SEPARATOR = '\\n';\n\nfunction toolCallIdSet(key: string): ReadonlySet<string> {\n return key === '' ? NO_WAITING_TOOL_CALL_IDS : new Set(key.split(TOOL_CALL_ID_SEPARATOR));\n}\n\n/**\n * The run's approval wait: the gated calls of the approval on screen (the\n * parked request's, else the host's mirror of a lane parked while its\n * siblings still stream) that nobody has answered, less the lanes whose\n * decision the host already holds (`queuedLaneDecisions`: a lane decided live\n * waits for the run to park, not for anyone), and whether the run is parked.\n * It clears the moment the approval is answered.\n *\n * Requires a statewire aui scope (throws outside one), like\n * {@link useStatewireApprovalRequest}.\n */\nexport function useStatewireApprovalWait(): StatewireApprovalWait {\n const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } =\n useStatewireApprovalRequest();\n const runParked = requestId !== undefined;\n const ids = toolCallIdsAwaitingApproval({\n actionRequests: runParked ? actionRequests : liveActionRequests,\n queuedLaneDecisions,\n });\n // A fresh set per state update: key it, so a reader re-renders only when the ids change.\n const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);\n return useMemo(\n () => (key === '' && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked }),\n [key, runParked],\n );\n}\n\nconst StatewireApprovalWaitContext = createContext<StatewireApprovalWait>(NOTHING_WAITS);\n\n/**\n * Reads the run's approval wait once for everything below it: `AthenaChat`'s\n * turns and subagent cards. Needs a statewire aui scope.\n */\nexport function StatewireApprovalWaitProvider({ children }: { children?: ReactNode }) {\n return createElement(\n StatewireApprovalWaitContext.Provider,\n { value: useStatewireApprovalWait() },\n children,\n );\n}\n\n/**\n * The nearest {@link StatewireApprovalWaitProvider}'s wait. Without one (a\n * host's own thread), nothing waits, as before the SDK read it.\n */\nexport function useApprovalWaitInScope(): StatewireApprovalWait {\n return useContext(StatewireApprovalWaitContext);\n}\n"],"names":["laneInterruptIds","isApprovalCardInterrupt","HITL_PENDING_RESUME_GRACE_MS","useStatewireSendCommand","useDeviceOperationReader","useStatewireRuns","useStatewireState","readAbandonedInputRequests","findAbandonedInterrupt","asHitlApproval","useMemo","pendingActionRequests","useAthenaRunLive","readInterruptMirror","interruptMirrorPayload","useRef","useSyncExternalStore","useEffect","readQueuedLaneDecisions","useApprovalResumeLock","deviceOperation","deviceOperationStamp","useCallback","toolCallIdsAwaitingApproval","createContext","createElement","useContext"],"mappings":"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"}
1
+ {"version":3,"file":"use-statewire-approval-request.cjs","sources":["../../src/chat/use-statewire-approval-request.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n useStatewireRuns,\n useStatewireSendCommand,\n useStatewireState,\n} from '@assistant-ui/react-statewire';\nimport {\n type AbandonedInputRequest,\n asHitlApproval,\n findAbandonedInterrupt,\n HITL_PENDING_RESUME_GRACE_MS,\n type HitlApprovalPayload,\n interruptMirrorPayload,\n isApprovalCardInterrupt,\n laneInterruptIds,\n type PendingActionRequest,\n pendingActionRequests,\n readAbandonedInputRequests,\n readInterruptMirror,\n readQueuedLaneDecisions,\n type ResumeExtras,\n toolCallIdsAwaitingApproval,\n useApprovalResumeLock,\n} from '@athenaintel/agent-runtime';\nimport {\n createContext,\n createElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport { deviceOperationStamp, useDeviceOperationReader } from './device-operation';\nimport { useAthenaRunLive } from './use-athena-run-live';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /**\n * Decide per-dispatch subagent lanes while the parent run is still\n * streaming (`DeepAgentThread.run_lane_input`). Keys are the lanes'\n * langgraph interrupt ids; the host matches them against the lanes its\n * mirrored interrupt currently parks, answers at once when the run is\n * already parked, and otherwise holds them until it parks — stale or\n * unknown ids are ignored, never carried to a later interrupt.\n */\n runLaneInput: {\n readonly type: 'run/lane-input';\n readonly laneDecisions: Readonly<Record<string, StatewireThread.InputResponse>>;\n /** The passkey decision check covering these lanes (identity 0b.9). */\n readonly decisionCheck?: string;\n /** The device an agent was operating as these were sent (identity v7, D11). */\n readonly deviceOperation?: string;\n };\n /**\n * `run/input` sent while the host's device runs an agent's action\n * (`DeviceOperationProvider`, identity v7 D11): agora refuses it when it\n * approves anything, and reads a reject as before.\n */\n runInputFromOperatedDevice: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck?: string;\n readonly deviceOperation: string;\n };\n /**\n * `run/input` carrying the passkey decision check a person minted for\n * exactly this answer (identity 0b.9). The run manager reads only\n * `requestId` and `response`; the host classifies and spends the check\n * once the answer is recorded.\n */\n runInputWithDecisionCheck: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck: string;\n };\n /**\n * Re-send the lane decisions the host still holds queued as their\n * approver's resume of the parked request (`DeepAgentThread.run_lane_replay`).\n * The host resumes under the meta the approver's `run/lane-input`\n * recorded and refuses any other caller — every attached client reads\n * the same queue, and only the one that queued it may turn it into a\n * resume. Carries no decisions: the host replays what it recorded.\n */\n runLaneReplay: {\n readonly type: 'run/lane-replay';\n readonly requestId: string;\n };\n }\n }\n}\n\nconst NO_QUEUED_LANE_DECISIONS: Readonly<Record<string, unknown>> = Object.freeze({});\nconst NO_ACTION_REQUESTS: PendingActionRequest[] = [];\n\n/**\n * Lane ids of the parked requests this client resumed, keyed by request id\n * and shared by every hook instance on the page (approval card,\n * waiting-on-you banner, host editors) so all of them treat a lingering\n * mirror the same way. Request ids are unique per parked interrupt, so an\n * instance only ever consults the record of the request it saw parked: two\n * chats mounted side by side cannot suppress or clear each other's. A record\n * is retired once the host drops that request's mirror (the resume run\n * started), the surface sees a different request park, or it unmounts.\n */\nconst answeredRequests = new Map<string, readonly string[]>();\nconst answeredListeners = new Set<() => void>();\nlet answeredVersion = 0;\nfunction notifyAnswered(): void {\n answeredVersion += 1;\n for (const listener of answeredListeners) listener();\n}\nfunction recordAnswered(requestId: string, laneIds: readonly string[]): void {\n answeredRequests.set(requestId, laneIds);\n notifyAnswered();\n}\nfunction forgetAnswered(requestId: string): void {\n if (answeredRequests.delete(requestId)) notifyAnswered();\n}\nfunction subscribeAnswered(listener: () => void): () => void {\n answeredListeners.add(listener);\n return () => {\n answeredListeners.delete(listener);\n };\n}\nconst readAnsweredVersion = (): number => answeredVersion;\n\n/**\n * The lanes a resume of the parked request settles: every lane of the request\n * (a resume answers the whole park — lanes already queued through\n * `run/lane-input` ride along) plus any lane named in the `lane_decisions` map.\n */\nfunction answeredLaneIds(value: unknown, actionRequests: readonly PendingActionRequest[]): string[] {\n const ids = laneInterruptIds(actionRequests) ?? [];\n if (typeof value === 'object' && value !== null && 'lane_decisions' in value) {\n const lanes = (value as { lane_decisions: unknown }).lane_decisions;\n if (typeof lanes === 'object' && lanes !== null) {\n for (const id of Object.keys(lanes)) if (!ids.includes(id)) ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Whether the host's mirror still shows an interrupt this client already\n * answered. After `run/input` the host drops the parked request at once but\n * keeps `__interrupt__` until the resume run starts and clears it (seconds on\n * a cold checkpoint); without this check the card would re-offer the SAME\n * approval from the mirror, buttons live, and then fold it away again.\n */\nfunction mirrorAlreadyAnswered(\n answeredLanes: readonly string[] | undefined,\n liveActionRequests: readonly PendingActionRequest[],\n): boolean {\n if (answeredLanes === undefined) return false;\n const ids = laneInterruptIds(liveActionRequests);\n if (ids === null) return false;\n return ids.every((id) => answeredLanes.includes(id));\n}\n\n/** Selects which pending interrupt an approval surface answers. */\nexport type StatewireInterruptPredicate = (\n request: StatewireThread.InputRequestState,\n) => request is StatewireThread.CustomInputRequest;\n\nexport interface UseStatewireApprovalRequestOptions {\n /**\n * Which pending interrupt to answer. Defaults to `isApprovalCardInterrupt`\n * (client-tool parks excluded — the client-tool bridge owns those, and a\n * generic resume would corrupt the parked tool call).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a sent decision keeps the actions locked when no resume run ever\n * starts. Defaults to {@link HITL_PENDING_RESUME_GRACE_MS}.\n */\n pendingResumeGraceMs?: number;\n /**\n * The host refused the `run/input` (or the transport lost it): the\n * optimistic fold rolls back, the request is on screen again with its\n * actions re-enabled, and this is the surface's one chance to say why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /** See {@link UseApprovalResumeLockOptions.onResumeRetry}. */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n}\n\nexport interface StatewireApprovalRequest<TValue> {\n /** The pending interrupt this surface answers, if any. */\n request: StatewireThread.CustomInputRequest | undefined;\n requestId: string | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null`. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n /** An interrupt a non-resume dispatch dropped — the \"approval was cancelled\" notice. */\n abandonedInterrupt: AbandonedInputRequest | undefined;\n /**\n * The mirrored interrupt's HITL payload while the run is still live and no\n * input request is answerable: per-dispatch `task` lanes parked on their\n * gates while sibling lanes stream. `null` once the run parks (the same\n * lanes are then `hitl` / `actionRequests`) and when nothing is parked.\n */\n liveHitl: HitlApprovalPayload | null;\n /** Every gated call `liveHitl` covers (empty when it is `null`). */\n liveActionRequests: PendingActionRequest[];\n /**\n * The decisions the host already holds from a `run/lane-input`, keyed by\n * langgraph interrupt id, to be answered when the run parks — a queued lane\n * reads as decided on every attached client, not just the one that sent it.\n */\n queuedLaneDecisions: Readonly<Record<string, unknown>>;\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Answer the request with `{ type: 'run/input', requestId, response: { type: 'resume', value } }`,\n * plus `decisionCheck` when `extras` carries one.\n * Returns the dispatch promise (settles when the host accepts, rejects when\n * it refuses), or `undefined` when the lock dropped the send.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n /**\n * Decide per-dispatch lanes by langgraph interrupt id. With a pending\n * request this is the native `lane_decisions` resume through the lock;\n * without one (the run is still streaming siblings) it is `run/lane-input`,\n * which the host queues and answers on the park. Returns the dispatch\n * promise, or `undefined` when the lock dropped a resume.\n */\n sendLaneDecisions: (\n lanes: Readonly<Record<string, StatewireThread.InputResponse>>,\n extras?: ResumeExtras,\n ) => Promise<unknown> | undefined;\n /**\n * Ask the host to resume the parked `requestId` with the lane decisions it\n * still holds queued, as their approver (`run/lane-replay`). For the card\n * that queued them, after the host's own answerer has stayed silent past\n * its budget; the host refuses every other caller. Settles when the host\n * accepts, rejects when it refuses.\n */\n replayQueuedLanes: (requestId: string) => Promise<unknown>;\n}\n\n/**\n * The headless half of every statewire approval surface: selects the pending\n * interrupt from the runs projection, narrows its payload, reads the\n * host-published abandoned interrupts, and wires the shared optimistic resume\n * lock to the thread's `run/input` command. `StatewireApprovalCard` renders\n * over it; hosts with their own decision UI (per-lane subagent cards) build\n * on it directly so the selection and lock semantics cannot drift.\n *\n * Requires a statewire aui scope (throws outside one, like every\n * `useStatewire*` hook) — gate the mount with `hasStatewireThreadExtras`.\n */\nexport function useStatewireApprovalRequest<\n TValue extends StatewireThread.InputResponse = Record<string, string>,\n>(options: UseStatewireApprovalRequestOptions = {}): StatewireApprovalRequest<TValue> {\n const {\n interruptPredicate = isApprovalCardInterrupt,\n pendingResumeGraceMs = HITL_PENDING_RESUME_GRACE_MS,\n onResumeRejected,\n onResumeRetry,\n } = options;\n const sendCommand = useStatewireSendCommand();\n // Read at each send: the host's device may be operated by an agent now and\n // not a second later (identity v7, D11).\n const readDeviceOperation = useDeviceOperationReader();\n // The runs projection is memoized by the lib, so this derivation is\n // reference-stable between state updates.\n const request = useStatewireRuns().inputRequests?.find(interruptPredicate);\n const requestId = request?.id;\n\n // The host publishes the interrupts a non-resume dispatch dropped. Select\n // the raw array reference (pre-stream-safe) and derive inline; a `.find`\n // selector would return a fresh object every update.\n const abandoned = useStatewireState(readAbandonedInputRequests);\n const abandonedInterrupt = findAbandonedInterrupt(abandoned);\n\n const hitl = asHitlApproval(request?.payload);\n const actionRequests = useMemo(() => pendingActionRequests(hitl), [hitl]);\n\n const isRunning = useAthenaRunLive();\n // The host mirrors the interrupt into state as soon as a lane parks — while\n // the run manager still reports `running` (siblings streaming) and has no\n // request to answer. Raw reference selectors; derive inline.\n const mirror = useStatewireState(readInterruptMirror);\n const mirrorHitl =\n request === undefined && isRunning ? asHitlApproval(interruptMirrorPayload(mirror)) : null;\n const mirrorActionRequests = useMemo(() => pendingActionRequests(mirrorHitl), [mirrorHitl]);\n\n // The request this surface last saw parked: the only record it may read\n // or retire (the mirror it watches is the one that request's resume clears).\n const lastRequestIdRef = useRef<string>(undefined);\n if (requestId !== undefined) lastRequestIdRef.current = requestId;\n useSyncExternalStore(subscribeAnswered, readAnsweredVersion, readAnsweredVersion);\n const answeredLanes =\n lastRequestIdRef.current === undefined ? undefined : answeredRequests.get(lastRequestIdRef.current);\n const mirrorCleared = interruptMirrorPayload(mirror) === undefined;\n useEffect(() => {\n if (requestId === undefined) return;\n return () => {\n // A different request parks next (or this one unparks for good): the\n // record of this one is stale once it is not the one on screen.\n if (lastRequestIdRef.current !== requestId) forgetAnswered(requestId);\n };\n }, [requestId]);\n useEffect(() => {\n if (mirrorCleared && lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n }, [mirrorCleared]);\n useEffect(\n () => () => {\n if (lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n },\n [],\n );\n const liveHitl = mirrorAlreadyAnswered(answeredLanes, mirrorActionRequests) ? null : mirrorHitl;\n const liveActionRequests = liveHitl === mirrorHitl ? mirrorActionRequests : NO_ACTION_REQUESTS;\n const queuedLaneDecisions =\n useStatewireState(readQueuedLaneDecisions) ?? NO_QUEUED_LANE_DECISIONS;\n\n const { pending, resume: resumeLocked } = useApprovalResumeLock<TValue>({\n requestId,\n isRunning,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n // The dispatch promise is handed to the lock: a refusal releases it and\n // reaches `onResumeRejected`.\n sendResume: (id, value, extras) => {\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n if (deviceOperation !== undefined) {\n return sendCommand({\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n deviceOperation,\n });\n }\n return sendCommand(\n extras?.decisionCheck\n ? {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n decisionCheck: extras.decisionCheck,\n }\n : {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n },\n );\n },\n });\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n recordAnswered(requestId, answeredLaneIds(value, actionRequests));\n }\n return resumeLocked(value, extras);\n },\n [requestId, actionRequests, resumeLocked],\n );\n\n const sendLaneDecisions = useCallback(\n (lanes: Readonly<Record<string, StatewireThread.InputResponse>>, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n return resume({ lane_decisions: lanes } as unknown as TValue, extras);\n }\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n return sendCommand({\n type: 'run/lane-input',\n laneDecisions: lanes,\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n ...(deviceOperation !== undefined ? { deviceOperation } : {}),\n });\n },\n [requestId, resume, sendCommand, readDeviceOperation],\n );\n\n const replayQueuedLanes = useCallback(\n (id: string) => sendCommand({ type: 'run/lane-replay', requestId: id }),\n [sendCommand],\n );\n\n return {\n request,\n requestId,\n hitl,\n actionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n replayQueuedLanes,\n };\n}\n\n/**\n * What the run waits on a person's approval for: the one read every surface\n * that shows a parked step makes (the SDK chat's turns and subagent cards,\n * Olympus's lane cards and step lists), so they all agree with the approval\n * card. The join from an approval to the calls, lanes and steps it gates is\n * agent-runtime's (`toolCallIdsAwaitingApproval`, `subagentAwaitingApproval`,\n * `useTurnToolCallsAwaitingApproval`). Read-only: nothing here sends a command.\n */\nexport interface StatewireApprovalWait {\n /**\n * The gated calls still waiting on a decision, by `tool_call_id`: a step\n * holding one waits for approval, and so does a subagent lane whose own\n * dispatch, or a call it made, is one. Keeps its identity while its ids are\n * unchanged.\n */\n toolCallIds: ReadonlySet<string>;\n /**\n * The run is parked on a request a person answers (an approval, a\n * question): nothing runs until someone answers it, so no step's silence is\n * a stall.\n */\n runParked: boolean;\n /**\n * The approval's gated calls (tool name and arguments by `toolCallId`), so a\n * lane card can name a call its own steps don't show (a conversation opened\n * after its run parked: agent-runtime's `turnLaneApprovalWait`). Absent\n * while nothing waits.\n */\n actionRequests?: readonly PendingActionRequest[];\n}\n\nconst NO_WAITING_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/** Nothing waits on a person. */\nconst NOTHING_WAITS: StatewireApprovalWait = {\n toolCallIds: NO_WAITING_TOOL_CALL_IDS,\n runParked: false,\n};\n\n/** Joins call ids into one selector value; an id never holds it. */\nconst TOOL_CALL_ID_SEPARATOR = '\\n';\n\nfunction toolCallIdSet(key: string): ReadonlySet<string> {\n return key === '' ? NO_WAITING_TOOL_CALL_IDS : new Set(key.split(TOOL_CALL_ID_SEPARATOR));\n}\n\n/**\n * The run's approval wait: the gated calls of the approval on screen (the\n * parked request's, else the host's mirror of a lane parked while its\n * siblings still stream) that nobody has answered, less the lanes whose\n * decision the host already holds (`queuedLaneDecisions`: a lane decided live\n * waits for the run to park, not for anyone), and whether the run is parked.\n * It clears the moment the approval is answered.\n *\n * Requires a statewire aui scope (throws outside one), like\n * {@link useStatewireApprovalRequest}.\n */\nexport function useStatewireApprovalWait(): StatewireApprovalWait {\n const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } =\n useStatewireApprovalRequest();\n const runParked = requestId !== undefined;\n const requests = runParked ? actionRequests : liveActionRequests;\n const ids = toolCallIdsAwaitingApproval({ actionRequests: requests, queuedLaneDecisions });\n // A fresh set per state update: key it, so a reader re-renders only when the ids change.\n const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);\n // The requests a key names never change (a call's id names its tool and arguments), so the\n // ones read when the key last changed are current; held in a ref to keep the wait's identity.\n const requestsRef = useRef(requests);\n requestsRef.current = requests;\n return useMemo(\n () =>\n key === '' && !runParked\n ? NOTHING_WAITS\n : { toolCallIds: toolCallIdSet(key), runParked, actionRequests: requestsRef.current },\n [key, runParked],\n );\n}\n\nconst StatewireApprovalWaitContext = createContext<StatewireApprovalWait>(NOTHING_WAITS);\n\n/**\n * Reads the run's approval wait once for everything below it: `AthenaChat`'s\n * turns and subagent cards. Needs a statewire aui scope.\n */\nexport function StatewireApprovalWaitProvider({ children }: { children?: ReactNode }) {\n return createElement(\n StatewireApprovalWaitContext.Provider,\n { value: useStatewireApprovalWait() },\n children,\n );\n}\n\n/**\n * The nearest {@link StatewireApprovalWaitProvider}'s wait. Without one (a\n * host's own thread), nothing waits, as before the SDK read it.\n */\nexport function useApprovalWaitInScope(): StatewireApprovalWait {\n return useContext(StatewireApprovalWaitContext);\n}\n"],"names":["laneInterruptIds","isApprovalCardInterrupt","HITL_PENDING_RESUME_GRACE_MS","useStatewireSendCommand","useDeviceOperationReader","useStatewireRuns","useStatewireState","readAbandonedInputRequests","findAbandonedInterrupt","asHitlApproval","useMemo","pendingActionRequests","useAthenaRunLive","readInterruptMirror","interruptMirrorPayload","useRef","useSyncExternalStore","useEffect","readQueuedLaneDecisions","useApprovalResumeLock","deviceOperation","deviceOperationStamp","useCallback","toolCallIdsAwaitingApproval","createContext","createElement","useContext"],"mappings":"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"}
@@ -167,6 +167,13 @@ export interface StatewireApprovalWait {
167
167
  * a stall.
168
168
  */
169
169
  runParked: boolean;
170
+ /**
171
+ * The approval's gated calls (tool name and arguments by `toolCallId`), so a
172
+ * lane card can name a call its own steps don't show (a conversation opened
173
+ * after its run parked: agent-runtime's `turnLaneApprovalWait`). Absent
174
+ * while nothing waits.
175
+ */
176
+ actionRequests?: readonly PendingActionRequest[];
170
177
  }
171
178
  /**
172
179
  * The run's approval wait: the gated calls of the approval on screen (the
@@ -173,13 +173,13 @@ function toolCallIdSet(key) {
173
173
  function useStatewireApprovalWait() {
174
174
  const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } = useStatewireApprovalRequest();
175
175
  const runParked = requestId !== void 0;
176
- const ids = toolCallIdsAwaitingApproval({
177
- actionRequests: runParked ? actionRequests : liveActionRequests,
178
- queuedLaneDecisions
179
- });
176
+ const requests = runParked ? actionRequests : liveActionRequests;
177
+ const ids = toolCallIdsAwaitingApproval({ actionRequests: requests, queuedLaneDecisions });
180
178
  const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);
179
+ const requestsRef = useRef(requests);
180
+ requestsRef.current = requests;
181
181
  return useMemo(
182
- () => key === "" && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked },
182
+ () => key === "" && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked, actionRequests: requestsRef.current },
183
183
  [key, runParked]
184
184
  );
185
185
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-statewire-approval-request.js","sources":["../../src/chat/use-statewire-approval-request.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n useStatewireRuns,\n useStatewireSendCommand,\n useStatewireState,\n} from '@assistant-ui/react-statewire';\nimport {\n type AbandonedInputRequest,\n asHitlApproval,\n findAbandonedInterrupt,\n HITL_PENDING_RESUME_GRACE_MS,\n type HitlApprovalPayload,\n interruptMirrorPayload,\n isApprovalCardInterrupt,\n laneInterruptIds,\n type PendingActionRequest,\n pendingActionRequests,\n readAbandonedInputRequests,\n readInterruptMirror,\n readQueuedLaneDecisions,\n type ResumeExtras,\n toolCallIdsAwaitingApproval,\n useApprovalResumeLock,\n} from '@athenaintel/agent-runtime';\nimport {\n createContext,\n createElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport { deviceOperationStamp, useDeviceOperationReader } from './device-operation';\nimport { useAthenaRunLive } from './use-athena-run-live';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /**\n * Decide per-dispatch subagent lanes while the parent run is still\n * streaming (`DeepAgentThread.run_lane_input`). Keys are the lanes'\n * langgraph interrupt ids; the host matches them against the lanes its\n * mirrored interrupt currently parks, answers at once when the run is\n * already parked, and otherwise holds them until it parks — stale or\n * unknown ids are ignored, never carried to a later interrupt.\n */\n runLaneInput: {\n readonly type: 'run/lane-input';\n readonly laneDecisions: Readonly<Record<string, StatewireThread.InputResponse>>;\n /** The passkey decision check covering these lanes (identity 0b.9). */\n readonly decisionCheck?: string;\n /** The device an agent was operating as these were sent (identity v7, D11). */\n readonly deviceOperation?: string;\n };\n /**\n * `run/input` sent while the host's device runs an agent's action\n * (`DeviceOperationProvider`, identity v7 D11): agora refuses it when it\n * approves anything, and reads a reject as before.\n */\n runInputFromOperatedDevice: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck?: string;\n readonly deviceOperation: string;\n };\n /**\n * `run/input` carrying the passkey decision check a person minted for\n * exactly this answer (identity 0b.9). The run manager reads only\n * `requestId` and `response`; the host classifies and spends the check\n * once the answer is recorded.\n */\n runInputWithDecisionCheck: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck: string;\n };\n /**\n * Re-send the lane decisions the host still holds queued as their\n * approver's resume of the parked request (`DeepAgentThread.run_lane_replay`).\n * The host resumes under the meta the approver's `run/lane-input`\n * recorded and refuses any other caller — every attached client reads\n * the same queue, and only the one that queued it may turn it into a\n * resume. Carries no decisions: the host replays what it recorded.\n */\n runLaneReplay: {\n readonly type: 'run/lane-replay';\n readonly requestId: string;\n };\n }\n }\n}\n\nconst NO_QUEUED_LANE_DECISIONS: Readonly<Record<string, unknown>> = Object.freeze({});\nconst NO_ACTION_REQUESTS: PendingActionRequest[] = [];\n\n/**\n * Lane ids of the parked requests this client resumed, keyed by request id\n * and shared by every hook instance on the page (approval card,\n * waiting-on-you banner, host editors) so all of them treat a lingering\n * mirror the same way. Request ids are unique per parked interrupt, so an\n * instance only ever consults the record of the request it saw parked: two\n * chats mounted side by side cannot suppress or clear each other's. A record\n * is retired once the host drops that request's mirror (the resume run\n * started), the surface sees a different request park, or it unmounts.\n */\nconst answeredRequests = new Map<string, readonly string[]>();\nconst answeredListeners = new Set<() => void>();\nlet answeredVersion = 0;\nfunction notifyAnswered(): void {\n answeredVersion += 1;\n for (const listener of answeredListeners) listener();\n}\nfunction recordAnswered(requestId: string, laneIds: readonly string[]): void {\n answeredRequests.set(requestId, laneIds);\n notifyAnswered();\n}\nfunction forgetAnswered(requestId: string): void {\n if (answeredRequests.delete(requestId)) notifyAnswered();\n}\nfunction subscribeAnswered(listener: () => void): () => void {\n answeredListeners.add(listener);\n return () => {\n answeredListeners.delete(listener);\n };\n}\nconst readAnsweredVersion = (): number => answeredVersion;\n\n/**\n * The lanes a resume of the parked request settles: every lane of the request\n * (a resume answers the whole park — lanes already queued through\n * `run/lane-input` ride along) plus any lane named in the `lane_decisions` map.\n */\nfunction answeredLaneIds(value: unknown, actionRequests: readonly PendingActionRequest[]): string[] {\n const ids = laneInterruptIds(actionRequests) ?? [];\n if (typeof value === 'object' && value !== null && 'lane_decisions' in value) {\n const lanes = (value as { lane_decisions: unknown }).lane_decisions;\n if (typeof lanes === 'object' && lanes !== null) {\n for (const id of Object.keys(lanes)) if (!ids.includes(id)) ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Whether the host's mirror still shows an interrupt this client already\n * answered. After `run/input` the host drops the parked request at once but\n * keeps `__interrupt__` until the resume run starts and clears it (seconds on\n * a cold checkpoint); without this check the card would re-offer the SAME\n * approval from the mirror, buttons live, and then fold it away again.\n */\nfunction mirrorAlreadyAnswered(\n answeredLanes: readonly string[] | undefined,\n liveActionRequests: readonly PendingActionRequest[],\n): boolean {\n if (answeredLanes === undefined) return false;\n const ids = laneInterruptIds(liveActionRequests);\n if (ids === null) return false;\n return ids.every((id) => answeredLanes.includes(id));\n}\n\n/** Selects which pending interrupt an approval surface answers. */\nexport type StatewireInterruptPredicate = (\n request: StatewireThread.InputRequestState,\n) => request is StatewireThread.CustomInputRequest;\n\nexport interface UseStatewireApprovalRequestOptions {\n /**\n * Which pending interrupt to answer. Defaults to `isApprovalCardInterrupt`\n * (client-tool parks excluded — the client-tool bridge owns those, and a\n * generic resume would corrupt the parked tool call).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a sent decision keeps the actions locked when no resume run ever\n * starts. Defaults to {@link HITL_PENDING_RESUME_GRACE_MS}.\n */\n pendingResumeGraceMs?: number;\n /**\n * The host refused the `run/input` (or the transport lost it): the\n * optimistic fold rolls back, the request is on screen again with its\n * actions re-enabled, and this is the surface's one chance to say why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /** See {@link UseApprovalResumeLockOptions.onResumeRetry}. */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n}\n\nexport interface StatewireApprovalRequest<TValue> {\n /** The pending interrupt this surface answers, if any. */\n request: StatewireThread.CustomInputRequest | undefined;\n requestId: string | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null`. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n /** An interrupt a non-resume dispatch dropped — the \"approval was cancelled\" notice. */\n abandonedInterrupt: AbandonedInputRequest | undefined;\n /**\n * The mirrored interrupt's HITL payload while the run is still live and no\n * input request is answerable: per-dispatch `task` lanes parked on their\n * gates while sibling lanes stream. `null` once the run parks (the same\n * lanes are then `hitl` / `actionRequests`) and when nothing is parked.\n */\n liveHitl: HitlApprovalPayload | null;\n /** Every gated call `liveHitl` covers (empty when it is `null`). */\n liveActionRequests: PendingActionRequest[];\n /**\n * The decisions the host already holds from a `run/lane-input`, keyed by\n * langgraph interrupt id, to be answered when the run parks — a queued lane\n * reads as decided on every attached client, not just the one that sent it.\n */\n queuedLaneDecisions: Readonly<Record<string, unknown>>;\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Answer the request with `{ type: 'run/input', requestId, response: { type: 'resume', value } }`,\n * plus `decisionCheck` when `extras` carries one.\n * Returns the dispatch promise (settles when the host accepts, rejects when\n * it refuses), or `undefined` when the lock dropped the send.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n /**\n * Decide per-dispatch lanes by langgraph interrupt id. With a pending\n * request this is the native `lane_decisions` resume through the lock;\n * without one (the run is still streaming siblings) it is `run/lane-input`,\n * which the host queues and answers on the park. Returns the dispatch\n * promise, or `undefined` when the lock dropped a resume.\n */\n sendLaneDecisions: (\n lanes: Readonly<Record<string, StatewireThread.InputResponse>>,\n extras?: ResumeExtras,\n ) => Promise<unknown> | undefined;\n /**\n * Ask the host to resume the parked `requestId` with the lane decisions it\n * still holds queued, as their approver (`run/lane-replay`). For the card\n * that queued them, after the host's own answerer has stayed silent past\n * its budget; the host refuses every other caller. Settles when the host\n * accepts, rejects when it refuses.\n */\n replayQueuedLanes: (requestId: string) => Promise<unknown>;\n}\n\n/**\n * The headless half of every statewire approval surface: selects the pending\n * interrupt from the runs projection, narrows its payload, reads the\n * host-published abandoned interrupts, and wires the shared optimistic resume\n * lock to the thread's `run/input` command. `StatewireApprovalCard` renders\n * over it; hosts with their own decision UI (per-lane subagent cards) build\n * on it directly so the selection and lock semantics cannot drift.\n *\n * Requires a statewire aui scope (throws outside one, like every\n * `useStatewire*` hook) — gate the mount with `hasStatewireThreadExtras`.\n */\nexport function useStatewireApprovalRequest<\n TValue extends StatewireThread.InputResponse = Record<string, string>,\n>(options: UseStatewireApprovalRequestOptions = {}): StatewireApprovalRequest<TValue> {\n const {\n interruptPredicate = isApprovalCardInterrupt,\n pendingResumeGraceMs = HITL_PENDING_RESUME_GRACE_MS,\n onResumeRejected,\n onResumeRetry,\n } = options;\n const sendCommand = useStatewireSendCommand();\n // Read at each send: the host's device may be operated by an agent now and\n // not a second later (identity v7, D11).\n const readDeviceOperation = useDeviceOperationReader();\n // The runs projection is memoized by the lib, so this derivation is\n // reference-stable between state updates.\n const request = useStatewireRuns().inputRequests?.find(interruptPredicate);\n const requestId = request?.id;\n\n // The host publishes the interrupts a non-resume dispatch dropped. Select\n // the raw array reference (pre-stream-safe) and derive inline; a `.find`\n // selector would return a fresh object every update.\n const abandoned = useStatewireState(readAbandonedInputRequests);\n const abandonedInterrupt = findAbandonedInterrupt(abandoned);\n\n const hitl = asHitlApproval(request?.payload);\n const actionRequests = useMemo(() => pendingActionRequests(hitl), [hitl]);\n\n const isRunning = useAthenaRunLive();\n // The host mirrors the interrupt into state as soon as a lane parks — while\n // the run manager still reports `running` (siblings streaming) and has no\n // request to answer. Raw reference selectors; derive inline.\n const mirror = useStatewireState(readInterruptMirror);\n const mirrorHitl =\n request === undefined && isRunning ? asHitlApproval(interruptMirrorPayload(mirror)) : null;\n const mirrorActionRequests = useMemo(() => pendingActionRequests(mirrorHitl), [mirrorHitl]);\n\n // The request this surface last saw parked: the only record it may read\n // or retire (the mirror it watches is the one that request's resume clears).\n const lastRequestIdRef = useRef<string>(undefined);\n if (requestId !== undefined) lastRequestIdRef.current = requestId;\n useSyncExternalStore(subscribeAnswered, readAnsweredVersion, readAnsweredVersion);\n const answeredLanes =\n lastRequestIdRef.current === undefined ? undefined : answeredRequests.get(lastRequestIdRef.current);\n const mirrorCleared = interruptMirrorPayload(mirror) === undefined;\n useEffect(() => {\n if (requestId === undefined) return;\n return () => {\n // A different request parks next (or this one unparks for good): the\n // record of this one is stale once it is not the one on screen.\n if (lastRequestIdRef.current !== requestId) forgetAnswered(requestId);\n };\n }, [requestId]);\n useEffect(() => {\n if (mirrorCleared && lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n }, [mirrorCleared]);\n useEffect(\n () => () => {\n if (lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n },\n [],\n );\n const liveHitl = mirrorAlreadyAnswered(answeredLanes, mirrorActionRequests) ? null : mirrorHitl;\n const liveActionRequests = liveHitl === mirrorHitl ? mirrorActionRequests : NO_ACTION_REQUESTS;\n const queuedLaneDecisions =\n useStatewireState(readQueuedLaneDecisions) ?? NO_QUEUED_LANE_DECISIONS;\n\n const { pending, resume: resumeLocked } = useApprovalResumeLock<TValue>({\n requestId,\n isRunning,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n // The dispatch promise is handed to the lock: a refusal releases it and\n // reaches `onResumeRejected`.\n sendResume: (id, value, extras) => {\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n if (deviceOperation !== undefined) {\n return sendCommand({\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n deviceOperation,\n });\n }\n return sendCommand(\n extras?.decisionCheck\n ? {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n decisionCheck: extras.decisionCheck,\n }\n : {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n },\n );\n },\n });\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n recordAnswered(requestId, answeredLaneIds(value, actionRequests));\n }\n return resumeLocked(value, extras);\n },\n [requestId, actionRequests, resumeLocked],\n );\n\n const sendLaneDecisions = useCallback(\n (lanes: Readonly<Record<string, StatewireThread.InputResponse>>, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n return resume({ lane_decisions: lanes } as unknown as TValue, extras);\n }\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n return sendCommand({\n type: 'run/lane-input',\n laneDecisions: lanes,\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n ...(deviceOperation !== undefined ? { deviceOperation } : {}),\n });\n },\n [requestId, resume, sendCommand, readDeviceOperation],\n );\n\n const replayQueuedLanes = useCallback(\n (id: string) => sendCommand({ type: 'run/lane-replay', requestId: id }),\n [sendCommand],\n );\n\n return {\n request,\n requestId,\n hitl,\n actionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n replayQueuedLanes,\n };\n}\n\n/**\n * What the run waits on a person's approval for: the one read every surface\n * that shows a parked step makes (the SDK chat's turns and subagent cards,\n * Olympus's lane cards and step lists), so they all agree with the approval\n * card. The join from an approval to the calls, lanes and steps it gates is\n * agent-runtime's (`toolCallIdsAwaitingApproval`, `subagentAwaitingApproval`,\n * `useTurnToolCallsAwaitingApproval`). Read-only: nothing here sends a command.\n */\nexport interface StatewireApprovalWait {\n /**\n * The gated calls still waiting on a decision, by `tool_call_id`: a step\n * holding one waits for approval, and so does a subagent lane whose own\n * dispatch, or a call it made, is one. Keeps its identity while its ids are\n * unchanged.\n */\n toolCallIds: ReadonlySet<string>;\n /**\n * The run is parked on a request a person answers (an approval, a\n * question): nothing runs until someone answers it, so no step's silence is\n * a stall.\n */\n runParked: boolean;\n}\n\nconst NO_WAITING_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/** Nothing waits on a person. */\nconst NOTHING_WAITS: StatewireApprovalWait = {\n toolCallIds: NO_WAITING_TOOL_CALL_IDS,\n runParked: false,\n};\n\n/** Joins call ids into one selector value; an id never holds it. */\nconst TOOL_CALL_ID_SEPARATOR = '\\n';\n\nfunction toolCallIdSet(key: string): ReadonlySet<string> {\n return key === '' ? NO_WAITING_TOOL_CALL_IDS : new Set(key.split(TOOL_CALL_ID_SEPARATOR));\n}\n\n/**\n * The run's approval wait: the gated calls of the approval on screen (the\n * parked request's, else the host's mirror of a lane parked while its\n * siblings still stream) that nobody has answered, less the lanes whose\n * decision the host already holds (`queuedLaneDecisions`: a lane decided live\n * waits for the run to park, not for anyone), and whether the run is parked.\n * It clears the moment the approval is answered.\n *\n * Requires a statewire aui scope (throws outside one), like\n * {@link useStatewireApprovalRequest}.\n */\nexport function useStatewireApprovalWait(): StatewireApprovalWait {\n const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } =\n useStatewireApprovalRequest();\n const runParked = requestId !== undefined;\n const ids = toolCallIdsAwaitingApproval({\n actionRequests: runParked ? actionRequests : liveActionRequests,\n queuedLaneDecisions,\n });\n // A fresh set per state update: key it, so a reader re-renders only when the ids change.\n const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);\n return useMemo(\n () => (key === '' && !runParked ? NOTHING_WAITS : { toolCallIds: toolCallIdSet(key), runParked }),\n [key, runParked],\n );\n}\n\nconst StatewireApprovalWaitContext = createContext<StatewireApprovalWait>(NOTHING_WAITS);\n\n/**\n * Reads the run's approval wait once for everything below it: `AthenaChat`'s\n * turns and subagent cards. Needs a statewire aui scope.\n */\nexport function StatewireApprovalWaitProvider({ children }: { children?: ReactNode }) {\n return createElement(\n StatewireApprovalWaitContext.Provider,\n { value: useStatewireApprovalWait() },\n children,\n );\n}\n\n/**\n * The nearest {@link StatewireApprovalWaitProvider}'s wait. Without one (a\n * host's own thread), nothing waits, as before the SDK read it.\n */\nexport function useApprovalWaitInScope(): StatewireApprovalWait {\n return useContext(StatewireApprovalWaitContext);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-statewire-approval-request.js","sources":["../../src/chat/use-statewire-approval-request.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n useStatewireRuns,\n useStatewireSendCommand,\n useStatewireState,\n} from '@assistant-ui/react-statewire';\nimport {\n type AbandonedInputRequest,\n asHitlApproval,\n findAbandonedInterrupt,\n HITL_PENDING_RESUME_GRACE_MS,\n type HitlApprovalPayload,\n interruptMirrorPayload,\n isApprovalCardInterrupt,\n laneInterruptIds,\n type PendingActionRequest,\n pendingActionRequests,\n readAbandonedInputRequests,\n readInterruptMirror,\n readQueuedLaneDecisions,\n type ResumeExtras,\n toolCallIdsAwaitingApproval,\n useApprovalResumeLock,\n} from '@athenaintel/agent-runtime';\nimport {\n createContext,\n createElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport { deviceOperationStamp, useDeviceOperationReader } from './device-operation';\nimport { useAthenaRunLive } from './use-athena-run-live';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /**\n * Decide per-dispatch subagent lanes while the parent run is still\n * streaming (`DeepAgentThread.run_lane_input`). Keys are the lanes'\n * langgraph interrupt ids; the host matches them against the lanes its\n * mirrored interrupt currently parks, answers at once when the run is\n * already parked, and otherwise holds them until it parks — stale or\n * unknown ids are ignored, never carried to a later interrupt.\n */\n runLaneInput: {\n readonly type: 'run/lane-input';\n readonly laneDecisions: Readonly<Record<string, StatewireThread.InputResponse>>;\n /** The passkey decision check covering these lanes (identity 0b.9). */\n readonly decisionCheck?: string;\n /** The device an agent was operating as these were sent (identity v7, D11). */\n readonly deviceOperation?: string;\n };\n /**\n * `run/input` sent while the host's device runs an agent's action\n * (`DeviceOperationProvider`, identity v7 D11): agora refuses it when it\n * approves anything, and reads a reject as before.\n */\n runInputFromOperatedDevice: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck?: string;\n readonly deviceOperation: string;\n };\n /**\n * `run/input` carrying the passkey decision check a person minted for\n * exactly this answer (identity 0b.9). The run manager reads only\n * `requestId` and `response`; the host classifies and spends the check\n * once the answer is recorded.\n */\n runInputWithDecisionCheck: {\n readonly type: 'run/input';\n readonly requestId: string;\n readonly response: StatewireThread.InputResponse;\n readonly decisionCheck: string;\n };\n /**\n * Re-send the lane decisions the host still holds queued as their\n * approver's resume of the parked request (`DeepAgentThread.run_lane_replay`).\n * The host resumes under the meta the approver's `run/lane-input`\n * recorded and refuses any other caller — every attached client reads\n * the same queue, and only the one that queued it may turn it into a\n * resume. Carries no decisions: the host replays what it recorded.\n */\n runLaneReplay: {\n readonly type: 'run/lane-replay';\n readonly requestId: string;\n };\n }\n }\n}\n\nconst NO_QUEUED_LANE_DECISIONS: Readonly<Record<string, unknown>> = Object.freeze({});\nconst NO_ACTION_REQUESTS: PendingActionRequest[] = [];\n\n/**\n * Lane ids of the parked requests this client resumed, keyed by request id\n * and shared by every hook instance on the page (approval card,\n * waiting-on-you banner, host editors) so all of them treat a lingering\n * mirror the same way. Request ids are unique per parked interrupt, so an\n * instance only ever consults the record of the request it saw parked: two\n * chats mounted side by side cannot suppress or clear each other's. A record\n * is retired once the host drops that request's mirror (the resume run\n * started), the surface sees a different request park, or it unmounts.\n */\nconst answeredRequests = new Map<string, readonly string[]>();\nconst answeredListeners = new Set<() => void>();\nlet answeredVersion = 0;\nfunction notifyAnswered(): void {\n answeredVersion += 1;\n for (const listener of answeredListeners) listener();\n}\nfunction recordAnswered(requestId: string, laneIds: readonly string[]): void {\n answeredRequests.set(requestId, laneIds);\n notifyAnswered();\n}\nfunction forgetAnswered(requestId: string): void {\n if (answeredRequests.delete(requestId)) notifyAnswered();\n}\nfunction subscribeAnswered(listener: () => void): () => void {\n answeredListeners.add(listener);\n return () => {\n answeredListeners.delete(listener);\n };\n}\nconst readAnsweredVersion = (): number => answeredVersion;\n\n/**\n * The lanes a resume of the parked request settles: every lane of the request\n * (a resume answers the whole park — lanes already queued through\n * `run/lane-input` ride along) plus any lane named in the `lane_decisions` map.\n */\nfunction answeredLaneIds(value: unknown, actionRequests: readonly PendingActionRequest[]): string[] {\n const ids = laneInterruptIds(actionRequests) ?? [];\n if (typeof value === 'object' && value !== null && 'lane_decisions' in value) {\n const lanes = (value as { lane_decisions: unknown }).lane_decisions;\n if (typeof lanes === 'object' && lanes !== null) {\n for (const id of Object.keys(lanes)) if (!ids.includes(id)) ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Whether the host's mirror still shows an interrupt this client already\n * answered. After `run/input` the host drops the parked request at once but\n * keeps `__interrupt__` until the resume run starts and clears it (seconds on\n * a cold checkpoint); without this check the card would re-offer the SAME\n * approval from the mirror, buttons live, and then fold it away again.\n */\nfunction mirrorAlreadyAnswered(\n answeredLanes: readonly string[] | undefined,\n liveActionRequests: readonly PendingActionRequest[],\n): boolean {\n if (answeredLanes === undefined) return false;\n const ids = laneInterruptIds(liveActionRequests);\n if (ids === null) return false;\n return ids.every((id) => answeredLanes.includes(id));\n}\n\n/** Selects which pending interrupt an approval surface answers. */\nexport type StatewireInterruptPredicate = (\n request: StatewireThread.InputRequestState,\n) => request is StatewireThread.CustomInputRequest;\n\nexport interface UseStatewireApprovalRequestOptions {\n /**\n * Which pending interrupt to answer. Defaults to `isApprovalCardInterrupt`\n * (client-tool parks excluded — the client-tool bridge owns those, and a\n * generic resume would corrupt the parked tool call).\n */\n interruptPredicate?: StatewireInterruptPredicate;\n /**\n * How long a sent decision keeps the actions locked when no resume run ever\n * starts. Defaults to {@link HITL_PENDING_RESUME_GRACE_MS}.\n */\n pendingResumeGraceMs?: number;\n /**\n * The host refused the `run/input` (or the transport lost it): the\n * optimistic fold rolls back, the request is on screen again with its\n * actions re-enabled, and this is the surface's one chance to say why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /** See {@link UseApprovalResumeLockOptions.onResumeRetry}. */\n onResumeRetry?: (error: unknown, requestId: string, attempt: number) => void;\n}\n\nexport interface StatewireApprovalRequest<TValue> {\n /** The pending interrupt this surface answers, if any. */\n request: StatewireThread.CustomInputRequest | undefined;\n requestId: string | undefined;\n /** The payload narrowed to the Athena HITL approval shape, or `null`. */\n hitl: HitlApprovalPayload | null;\n /** Every gated call the approval covers, in decision order (empty for non-HITL parks). */\n actionRequests: PendingActionRequest[];\n /** An interrupt a non-resume dispatch dropped — the \"approval was cancelled\" notice. */\n abandonedInterrupt: AbandonedInputRequest | undefined;\n /**\n * The mirrored interrupt's HITL payload while the run is still live and no\n * input request is answerable: per-dispatch `task` lanes parked on their\n * gates while sibling lanes stream. `null` once the run parks (the same\n * lanes are then `hitl` / `actionRequests`) and when nothing is parked.\n */\n liveHitl: HitlApprovalPayload | null;\n /** Every gated call `liveHitl` covers (empty when it is `null`). */\n liveActionRequests: PendingActionRequest[];\n /**\n * The decisions the host already holds from a `run/lane-input`, keyed by\n * langgraph interrupt id, to be answered when the run parks — a queued lane\n * reads as decided on every attached client, not just the one that sent it.\n */\n queuedLaneDecisions: Readonly<Record<string, unknown>>;\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Answer the request with `{ type: 'run/input', requestId, response: { type: 'resume', value } }`,\n * plus `decisionCheck` when `extras` carries one.\n * Returns the dispatch promise (settles when the host accepts, rejects when\n * it refuses), or `undefined` when the lock dropped the send.\n */\n resume: (value: TValue, extras?: ResumeExtras) => Promise<unknown> | undefined;\n /**\n * Decide per-dispatch lanes by langgraph interrupt id. With a pending\n * request this is the native `lane_decisions` resume through the lock;\n * without one (the run is still streaming siblings) it is `run/lane-input`,\n * which the host queues and answers on the park. Returns the dispatch\n * promise, or `undefined` when the lock dropped a resume.\n */\n sendLaneDecisions: (\n lanes: Readonly<Record<string, StatewireThread.InputResponse>>,\n extras?: ResumeExtras,\n ) => Promise<unknown> | undefined;\n /**\n * Ask the host to resume the parked `requestId` with the lane decisions it\n * still holds queued, as their approver (`run/lane-replay`). For the card\n * that queued them, after the host's own answerer has stayed silent past\n * its budget; the host refuses every other caller. Settles when the host\n * accepts, rejects when it refuses.\n */\n replayQueuedLanes: (requestId: string) => Promise<unknown>;\n}\n\n/**\n * The headless half of every statewire approval surface: selects the pending\n * interrupt from the runs projection, narrows its payload, reads the\n * host-published abandoned interrupts, and wires the shared optimistic resume\n * lock to the thread's `run/input` command. `StatewireApprovalCard` renders\n * over it; hosts with their own decision UI (per-lane subagent cards) build\n * on it directly so the selection and lock semantics cannot drift.\n *\n * Requires a statewire aui scope (throws outside one, like every\n * `useStatewire*` hook) — gate the mount with `hasStatewireThreadExtras`.\n */\nexport function useStatewireApprovalRequest<\n TValue extends StatewireThread.InputResponse = Record<string, string>,\n>(options: UseStatewireApprovalRequestOptions = {}): StatewireApprovalRequest<TValue> {\n const {\n interruptPredicate = isApprovalCardInterrupt,\n pendingResumeGraceMs = HITL_PENDING_RESUME_GRACE_MS,\n onResumeRejected,\n onResumeRetry,\n } = options;\n const sendCommand = useStatewireSendCommand();\n // Read at each send: the host's device may be operated by an agent now and\n // not a second later (identity v7, D11).\n const readDeviceOperation = useDeviceOperationReader();\n // The runs projection is memoized by the lib, so this derivation is\n // reference-stable between state updates.\n const request = useStatewireRuns().inputRequests?.find(interruptPredicate);\n const requestId = request?.id;\n\n // The host publishes the interrupts a non-resume dispatch dropped. Select\n // the raw array reference (pre-stream-safe) and derive inline; a `.find`\n // selector would return a fresh object every update.\n const abandoned = useStatewireState(readAbandonedInputRequests);\n const abandonedInterrupt = findAbandonedInterrupt(abandoned);\n\n const hitl = asHitlApproval(request?.payload);\n const actionRequests = useMemo(() => pendingActionRequests(hitl), [hitl]);\n\n const isRunning = useAthenaRunLive();\n // The host mirrors the interrupt into state as soon as a lane parks — while\n // the run manager still reports `running` (siblings streaming) and has no\n // request to answer. Raw reference selectors; derive inline.\n const mirror = useStatewireState(readInterruptMirror);\n const mirrorHitl =\n request === undefined && isRunning ? asHitlApproval(interruptMirrorPayload(mirror)) : null;\n const mirrorActionRequests = useMemo(() => pendingActionRequests(mirrorHitl), [mirrorHitl]);\n\n // The request this surface last saw parked: the only record it may read\n // or retire (the mirror it watches is the one that request's resume clears).\n const lastRequestIdRef = useRef<string>(undefined);\n if (requestId !== undefined) lastRequestIdRef.current = requestId;\n useSyncExternalStore(subscribeAnswered, readAnsweredVersion, readAnsweredVersion);\n const answeredLanes =\n lastRequestIdRef.current === undefined ? undefined : answeredRequests.get(lastRequestIdRef.current);\n const mirrorCleared = interruptMirrorPayload(mirror) === undefined;\n useEffect(() => {\n if (requestId === undefined) return;\n return () => {\n // A different request parks next (or this one unparks for good): the\n // record of this one is stale once it is not the one on screen.\n if (lastRequestIdRef.current !== requestId) forgetAnswered(requestId);\n };\n }, [requestId]);\n useEffect(() => {\n if (mirrorCleared && lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n }, [mirrorCleared]);\n useEffect(\n () => () => {\n if (lastRequestIdRef.current !== undefined) forgetAnswered(lastRequestIdRef.current);\n },\n [],\n );\n const liveHitl = mirrorAlreadyAnswered(answeredLanes, mirrorActionRequests) ? null : mirrorHitl;\n const liveActionRequests = liveHitl === mirrorHitl ? mirrorActionRequests : NO_ACTION_REQUESTS;\n const queuedLaneDecisions =\n useStatewireState(readQueuedLaneDecisions) ?? NO_QUEUED_LANE_DECISIONS;\n\n const { pending, resume: resumeLocked } = useApprovalResumeLock<TValue>({\n requestId,\n isRunning,\n pendingResumeGraceMs,\n onResumeRejected,\n onResumeRetry,\n // The dispatch promise is handed to the lock: a refusal releases it and\n // reaches `onResumeRejected`.\n sendResume: (id, value, extras) => {\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n if (deviceOperation !== undefined) {\n return sendCommand({\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n deviceOperation,\n });\n }\n return sendCommand(\n extras?.decisionCheck\n ? {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n decisionCheck: extras.decisionCheck,\n }\n : {\n type: 'run/input',\n requestId: id,\n response: { type: 'resume', value },\n },\n );\n },\n });\n\n const resume = useCallback(\n (value: TValue, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n recordAnswered(requestId, answeredLaneIds(value, actionRequests));\n }\n return resumeLocked(value, extras);\n },\n [requestId, actionRequests, resumeLocked],\n );\n\n const sendLaneDecisions = useCallback(\n (lanes: Readonly<Record<string, StatewireThread.InputResponse>>, extras?: ResumeExtras) => {\n if (requestId !== undefined) {\n return resume({ lane_decisions: lanes } as unknown as TValue, extras);\n }\n const deviceOperation = deviceOperationStamp(readDeviceOperation);\n return sendCommand({\n type: 'run/lane-input',\n laneDecisions: lanes,\n ...(extras?.decisionCheck ? { decisionCheck: extras.decisionCheck } : {}),\n ...(deviceOperation !== undefined ? { deviceOperation } : {}),\n });\n },\n [requestId, resume, sendCommand, readDeviceOperation],\n );\n\n const replayQueuedLanes = useCallback(\n (id: string) => sendCommand({ type: 'run/lane-replay', requestId: id }),\n [sendCommand],\n );\n\n return {\n request,\n requestId,\n hitl,\n actionRequests,\n liveHitl,\n liveActionRequests,\n queuedLaneDecisions,\n abandonedInterrupt,\n pending,\n resume,\n sendLaneDecisions,\n replayQueuedLanes,\n };\n}\n\n/**\n * What the run waits on a person's approval for: the one read every surface\n * that shows a parked step makes (the SDK chat's turns and subagent cards,\n * Olympus's lane cards and step lists), so they all agree with the approval\n * card. The join from an approval to the calls, lanes and steps it gates is\n * agent-runtime's (`toolCallIdsAwaitingApproval`, `subagentAwaitingApproval`,\n * `useTurnToolCallsAwaitingApproval`). Read-only: nothing here sends a command.\n */\nexport interface StatewireApprovalWait {\n /**\n * The gated calls still waiting on a decision, by `tool_call_id`: a step\n * holding one waits for approval, and so does a subagent lane whose own\n * dispatch, or a call it made, is one. Keeps its identity while its ids are\n * unchanged.\n */\n toolCallIds: ReadonlySet<string>;\n /**\n * The run is parked on a request a person answers (an approval, a\n * question): nothing runs until someone answers it, so no step's silence is\n * a stall.\n */\n runParked: boolean;\n /**\n * The approval's gated calls (tool name and arguments by `toolCallId`), so a\n * lane card can name a call its own steps don't show (a conversation opened\n * after its run parked: agent-runtime's `turnLaneApprovalWait`). Absent\n * while nothing waits.\n */\n actionRequests?: readonly PendingActionRequest[];\n}\n\nconst NO_WAITING_TOOL_CALL_IDS: ReadonlySet<string> = new Set();\n\n/** Nothing waits on a person. */\nconst NOTHING_WAITS: StatewireApprovalWait = {\n toolCallIds: NO_WAITING_TOOL_CALL_IDS,\n runParked: false,\n};\n\n/** Joins call ids into one selector value; an id never holds it. */\nconst TOOL_CALL_ID_SEPARATOR = '\\n';\n\nfunction toolCallIdSet(key: string): ReadonlySet<string> {\n return key === '' ? NO_WAITING_TOOL_CALL_IDS : new Set(key.split(TOOL_CALL_ID_SEPARATOR));\n}\n\n/**\n * The run's approval wait: the gated calls of the approval on screen (the\n * parked request's, else the host's mirror of a lane parked while its\n * siblings still stream) that nobody has answered, less the lanes whose\n * decision the host already holds (`queuedLaneDecisions`: a lane decided live\n * waits for the run to park, not for anyone), and whether the run is parked.\n * It clears the moment the approval is answered.\n *\n * Requires a statewire aui scope (throws outside one), like\n * {@link useStatewireApprovalRequest}.\n */\nexport function useStatewireApprovalWait(): StatewireApprovalWait {\n const { requestId, actionRequests, liveActionRequests, queuedLaneDecisions } =\n useStatewireApprovalRequest();\n const runParked = requestId !== undefined;\n const requests = runParked ? actionRequests : liveActionRequests;\n const ids = toolCallIdsAwaitingApproval({ actionRequests: requests, queuedLaneDecisions });\n // A fresh set per state update: key it, so a reader re-renders only when the ids change.\n const key = [...ids].sort().join(TOOL_CALL_ID_SEPARATOR);\n // The requests a key names never change (a call's id names its tool and arguments), so the\n // ones read when the key last changed are current; held in a ref to keep the wait's identity.\n const requestsRef = useRef(requests);\n requestsRef.current = requests;\n return useMemo(\n () =>\n key === '' && !runParked\n ? NOTHING_WAITS\n : { toolCallIds: toolCallIdSet(key), runParked, actionRequests: requestsRef.current },\n [key, runParked],\n );\n}\n\nconst StatewireApprovalWaitContext = createContext<StatewireApprovalWait>(NOTHING_WAITS);\n\n/**\n * Reads the run's approval wait once for everything below it: `AthenaChat`'s\n * turns and subagent cards. Needs a statewire aui scope.\n */\nexport function StatewireApprovalWaitProvider({ children }: { children?: ReactNode }) {\n return createElement(\n StatewireApprovalWaitContext.Provider,\n { value: useStatewireApprovalWait() },\n children,\n );\n}\n\n/**\n * The nearest {@link StatewireApprovalWaitProvider}'s wait. Without one (a\n * host's own thread), nothing waits, as before the SDK read it.\n */\nexport function useApprovalWaitInScope(): StatewireApprovalWait {\n return useContext(StatewireApprovalWaitContext);\n}\n"],"names":[],"mappings":"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"}
@@ -1,66 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const reactQuery = require("@tanstack/react-query");
4
- const AthenaCredentialSourceContext = require("../../auth/AthenaCredentialSourceContext.cjs");
5
- const credentialSource = require("../../auth/credential-source.cjs");
3
+ const accountIdentity = require("../../auth/account-identity.cjs");
6
4
  const runtimeCatalog = require("../../catalog/runtime-catalog.cjs");
7
- const useAthenaRestQuery = require("../../lib/use-athena-rest-query.cjs");
8
- const credentialRenewal = require("../../runtime/credential-renewal.cjs");
9
- const api = require("../../threads/api.cjs");
10
- const tokenGetterIds = /* @__PURE__ */ new WeakMap();
11
- let lastTokenGetterId = 0;
12
- function tokenGetterId(getToken) {
13
- if (getToken === void 0) return "";
14
- let id = tokenGetterIds.get(getToken);
15
- if (id === void 0) {
16
- lastTokenGetterId += 1;
17
- id = lastTokenGetterId;
18
- tokenGetterIds.set(getToken, id);
19
- }
20
- return `getter:${id}`;
21
- }
22
- function heldTokenIdentity(token) {
23
- if (!token) return "";
24
- const subject = credentialSource.jwtSubject(token);
25
- if (subject === null) return `token:${useAthenaRestQuery.keyPartFingerprint(token)}`;
26
- const scope = credentialSource.jwtScope(token);
27
- return scope === null ? `sub:${useAthenaRestQuery.keyPartFingerprint(subject)}` : `sub:${useAthenaRestQuery.keyPartFingerprint(subject)}|scope:${useAthenaRestQuery.keyPartFingerprint(scope)}`;
28
- }
29
5
  function useAccountIdentityRead(auth, { enabled, tokenGetter }) {
30
- const queryClient = runtimeCatalog.useCatalogQueryClient();
31
- const identityEpoch = AthenaCredentialSourceContext.useAthenaCredentialIdentityEpoch();
32
- const query = reactQuery.useQuery(
33
- {
34
- queryKey: [
35
- "athena-sdk",
36
- "account",
37
- (auth == null ? void 0 : auth.backendUrl) ?? "",
38
- (auth == null ? void 0 : auth.userId) ?? "",
39
- (auth == null ? void 0 : auth.apiKey) ? useAthenaRestQuery.keyPartFingerprint(auth.apiKey) : "",
40
- tokenGetterId(tokenGetter),
41
- heldTokenIdentity(auth == null ? void 0 : auth.accessToken),
42
- identityEpoch
43
- ],
44
- queryFn: async ({ signal }) => {
45
- if (!auth) return null;
46
- return credentialRenewal.withCredentialRenewal(
47
- async ({ force } = {}) => {
48
- var _a, _b;
49
- return {
50
- token: (force ? await ((_a = auth.renewAccessToken) == null ? void 0 : _a.call(auth)) : await ((_b = auth.getAccessToken) == null ? void 0 : _b.call(auth))) ?? auth.accessToken ?? null,
51
- apiKey: auth.apiKey,
52
- credentials: auth.credentials
53
- };
54
- },
55
- (resolved) => api.fetchAccountIdentity(auth.backendUrl, resolved, { signal })
56
- );
57
- },
58
- enabled: auth !== null && enabled,
59
- retry: false,
60
- staleTime: 6e4
6
+ const query = accountIdentity.useAccountIdentityQuery({
7
+ credential: {
8
+ backendUrl: (auth == null ? void 0 : auth.backendUrl) ?? "",
9
+ principal: auth == null ? void 0 : auth.userId,
10
+ apiKey: auth == null ? void 0 : auth.apiKey,
11
+ heldToken: auth == null ? void 0 : auth.accessToken,
12
+ tokenGetter
13
+ },
14
+ resolveAuth: async ({ force } = {}) => {
15
+ var _a, _b;
16
+ return {
17
+ token: (force ? await ((_a = auth == null ? void 0 : auth.renewAccessToken) == null ? void 0 : _a.call(auth)) : await ((_b = auth == null ? void 0 : auth.getAccessToken) == null ? void 0 : _b.call(auth))) ?? (auth == null ? void 0 : auth.accessToken) ?? null,
18
+ apiKey: auth == null ? void 0 : auth.apiKey
19
+ };
61
20
  },
62
- queryClient
63
- );
21
+ enabled: auth !== null && enabled,
22
+ queryClient: runtimeCatalog.useCatalogQueryClient()
23
+ });
64
24
  return query.data;
65
25
  }
66
26
  function useAccountIdentity(auth, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-account-workspace-id.cjs","sources":["../../../src/chat-ui/mentions/use-account-workspace-id.ts"],"sourcesContent":["import { useQuery } from '@tanstack/react-query';\nimport { useAthenaCredentialIdentityEpoch } from '../../auth/AthenaCredentialSourceContext';\nimport { jwtScope, jwtSubject } from '../../auth/credential-source';\nimport { useCatalogQueryClient } from '../../catalog/runtime-catalog';\nimport { keyPartFingerprint } from '../../lib/use-athena-rest-query';\nimport { withCredentialRenewal } from '../../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../../runtime/statewire-auth-headers';\nimport { type AccountIdentity, fetchAccountIdentity } from '../../threads/api';\nimport type { MentionAuthContext } from './suggestions/data/api-client';\n\n/** The credential's agora identity, as far as it is known yet. */\nexport interface AccountIdentityState {\n /** Agora's user id (`user_…`) for the credential; unknown until `/me` answers. */\n userId: string | undefined;\n /** The host's workspace when it names one, else the credential's current workspace. */\n workspaceId: string | undefined;\n}\n\nexport interface AccountIdentityOptions {\n /**\n * The provider's token getter, which `auth.getAccessToken` wraps (`AthenaChat`\n * passes `useAthenaConfig().getToken`). Its identity keeps two providers\n * apart when only their getters know who they sign in.\n */\n tokenGetter?: AthenaStatewireTokenGetter;\n}\n\n/** One id per token getter, so the key can name a getter without holding it. */\nconst tokenGetterIds = new WeakMap<AthenaStatewireTokenGetter, number>();\nlet lastTokenGetterId = 0;\n\nfunction tokenGetterId(getToken: AthenaStatewireTokenGetter | undefined): string {\n if (getToken === undefined) return '';\n let id = tokenGetterIds.get(getToken);\n if (id === undefined) {\n lastTokenGetterId += 1;\n id = lastTokenGetterId;\n tokenGetterIds.set(getToken, id);\n }\n return `getter:${id}`;\n}\n\n/**\n * Who a held token acts as, and where, without the token: its JWT subject and\n * the claims scoping it to a workspace or org (`jwtScope`), or the token\n * itself when it is opaque, fingerprinted. A renewal for the same person and\n * scope keeps it, so a rotation is not a new identity; the same person's token\n * scoped to another workspace is.\n */\nfunction heldTokenIdentity(token: string | null | undefined): string {\n if (!token) return '';\n const subject = jwtSubject(token);\n if (subject === null) return `token:${keyPartFingerprint(token)}`;\n const scope = jwtScope(token);\n return scope === null\n ? `sub:${keyPartFingerprint(subject)}`\n : `sub:${keyPartFingerprint(subject)}|scope:${keyPartFingerprint(scope)}`;\n}\n\n/**\n * One `/me` read per credential, through the host's query cache (never a\n * process-wide user-id map), keyed by the credential the read is sent with and\n * never by the secret: the backend, the signed-in person, the API key's\n * fingerprint, the provider's token getter, who the held token acts as and\n * which workspace it is scoped to, and the provider's credential identity\n * epoch (`useAthenaCredentialIdentityEpoch`), which moves once the getter has\n * handed out a token that acts as someone else. A renewal for the same person\n * and scope keeps the key, so the answer stays put; an API-key switch, a held\n * token scoped to another workspace, or a getter now signing in someone else,\n * reads `/me` again under a new key, never a cached answer for the previous\n * credential. The epoch follows the person, not the scope: a host getter that\n * alone hands out the same person's token for another workspace is read again\n * once the answer goes stale. `enabled` is the caller's: a reader that only\n * needs the workspace skips the read when the host names one.\n */\nfunction useAccountIdentityRead(\n auth: MentionAuthContext | null,\n { enabled, tokenGetter }: { enabled: boolean } & AccountIdentityOptions\n): AccountIdentity | null | undefined {\n const queryClient = useCatalogQueryClient();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'account',\n auth?.backendUrl ?? '',\n auth?.userId ?? '',\n auth?.apiKey ? keyPartFingerprint(auth.apiKey) : '',\n tokenGetterId(tokenGetter),\n heldTokenIdentity(auth?.accessToken),\n identityEpoch,\n ],\n queryFn: async ({ signal }) => {\n if (!auth) return null;\n return withCredentialRenewal(\n async ({ force }: { force?: boolean } = {}) => ({\n token:\n (force ? await auth.renewAccessToken?.() : await auth.getAccessToken?.()) ??\n auth.accessToken ??\n null,\n apiKey: auth.apiKey,\n credentials: auth.credentials,\n }),\n (resolved) => fetchAccountIdentity(auth.backendUrl, resolved, { signal })\n );\n },\n enabled: auth !== null && enabled,\n retry: false,\n staleTime: 60_000,\n },\n queryClient\n );\n return query.data;\n}\n\n/** The workspace mentions search: the host's when it names one, else the credential's own. */\nexport function useAccountWorkspaceId(auth: MentionAuthContext | null): string | undefined {\n const identity = useAccountIdentityRead(auth, { enabled: !auth?.workspaceId });\n return auth?.workspaceId ?? identity?.workspaceId ?? undefined;\n}\n\n/**\n * The credential's agora user id and workspace from the same read. The auth\n * context carries the identity provider's user id, not agora's, so \"who is\n * the viewer\" (an answer label's \"for you\") needs this one, and it reads\n * `/me` even when the host names a workspace.\n */\nexport function useAccountIdentity(\n auth: MentionAuthContext | null,\n options: AccountIdentityOptions = {}\n): AccountIdentityState {\n const identity = useAccountIdentityRead(auth, { enabled: true, ...options });\n return {\n userId: identity?.userId,\n workspaceId: auth?.workspaceId ?? identity?.workspaceId ?? undefined,\n };\n}\n"],"names":["jwtSubject","keyPartFingerprint","jwtScope","useCatalogQueryClient","useAthenaCredentialIdentityEpoch","useQuery","withCredentialRenewal","fetchAccountIdentity"],"mappings":";;;;;;;;;AA4BA,MAAM,qCAAqB,QAAA;AAC3B,IAAI,oBAAoB;AAExB,SAAS,cAAc,UAA0D;AAC/E,MAAI,aAAa,OAAW,QAAO;AACnC,MAAI,KAAK,eAAe,IAAI,QAAQ;AACpC,MAAI,OAAO,QAAW;AACpB,yBAAqB;AACrB,SAAK;AACL,mBAAe,IAAI,UAAU,EAAE;AAAA,EACjC;AACA,SAAO,UAAU,EAAE;AACrB;AASA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAUA,iBAAAA,WAAW,KAAK;AAChC,MAAI,YAAY,KAAM,QAAO,SAASC,mBAAAA,mBAAmB,KAAK,CAAC;AAC/D,QAAM,QAAQC,iBAAAA,SAAS,KAAK;AAC5B,SAAO,UAAU,OACb,OAAOD,mBAAAA,mBAAmB,OAAO,CAAC,KAClC,OAAOA,mBAAAA,mBAAmB,OAAO,CAAC,UAAUA,mBAAAA,mBAAmB,KAAK,CAAC;AAC3E;AAkBA,SAAS,uBACP,MACA,EAAE,SAAS,eACyB;AACpC,QAAM,cAAcE,eAAAA,sBAAA;AACpB,QAAM,gBAAgBC,8BAAAA,iCAAA;AACtB,QAAM,QAAQC,WAAAA;AAAAA,IACZ;AAAA,MACE,UAAU;AAAA,QACR;AAAA,QACA;AAAA,SACA,6BAAM,eAAc;AAAA,SACpB,6BAAM,WAAU;AAAA,SAChB,6BAAM,UAASJ,mBAAAA,mBAAmB,KAAK,MAAM,IAAI;AAAA,QACjD,cAAc,WAAW;AAAA,QACzB,kBAAkB,6BAAM,WAAW;AAAA,QACnC;AAAA,MAAA;AAAA,MAEF,SAAS,OAAO,EAAE,aAAa;AAC7B,YAAI,CAAC,KAAM,QAAO;AAClB,eAAOK,kBAAAA;AAAAA,UACL,OAAO,EAAE,MAAA,IAA+B,OAAC;;AAAO;AAAA,cAC9C,QACG,QAAQ,QAAM,UAAK,qBAAL,iCAA4B,QAAM,UAAK,mBAAL,mCACjD,KAAK,eACL;AAAA,cACF,QAAQ,KAAK;AAAA,cACb,aAAa,KAAK;AAAA,YAAA;AAAA;AAAA,UAEpB,CAAC,aAAaC,IAAAA,qBAAqB,KAAK,YAAY,UAAU,EAAE,QAAQ;AAAA,QAAA;AAAA,MAE5E;AAAA,MACA,SAAS,SAAS,QAAQ;AAAA,MAC1B,OAAO;AAAA,MACP,WAAW;AAAA,IAAA;AAAA,IAEb;AAAA,EAAA;AAEF,SAAO,MAAM;AACf;AAcO,SAAS,mBACd,MACA,UAAkC,IACZ;AACtB,QAAM,WAAW,uBAAuB,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS;AAC3E,SAAO;AAAA,IACL,QAAQ,qCAAU;AAAA,IAClB,cAAa,6BAAM,iBAAe,qCAAU,gBAAe;AAAA,EAAA;AAE/D;;"}
1
+ {"version":3,"file":"use-account-workspace-id.cjs","sources":["../../../src/chat-ui/mentions/use-account-workspace-id.ts"],"sourcesContent":["import { useAccountIdentityQuery } from '../../auth/account-identity';\nimport { useCatalogQueryClient } from '../../catalog/runtime-catalog';\nimport type { AthenaStatewireTokenGetter } from '../../runtime/statewire-auth-headers';\nimport type { AccountIdentity } from '../../threads/api';\nimport type { MentionAuthContext } from './suggestions/data/api-client';\n\n/** The credential's agora identity, as far as it is known yet. */\nexport interface AccountIdentityState {\n /** Agora's user id (`user_…`) for the credential; unknown until `/me` answers. */\n userId: string | undefined;\n /** The host's workspace when it names one, else the credential's current workspace. */\n workspaceId: string | undefined;\n}\n\nexport interface AccountIdentityOptions {\n /**\n * The provider's token getter, which `auth.getAccessToken` wraps (`AthenaChat`\n * passes `useAthenaConfig().getToken`). Its identity keeps two providers\n * apart when only their getters know who they sign in.\n */\n tokenGetter?: AthenaStatewireTokenGetter;\n}\n\n/**\n * The chat's read of the credential's identity (`useAccountIdentityQuery`),\n * through the host's query cache: inside a provider with a session list, the\n * list's own read under the same key, so the page sends one `/me`. `enabled`\n * is the caller's: a reader that only needs the workspace skips the read when\n * the host names one.\n */\nfunction useAccountIdentityRead(\n auth: MentionAuthContext | null,\n { enabled, tokenGetter }: { enabled: boolean } & AccountIdentityOptions\n): AccountIdentity | undefined {\n const query = useAccountIdentityQuery({\n credential: {\n backendUrl: auth?.backendUrl ?? '',\n principal: auth?.userId,\n apiKey: auth?.apiKey,\n heldToken: auth?.accessToken,\n tokenGetter,\n },\n resolveAuth: async ({ force }: { force?: boolean } = {}) => ({\n token:\n (force ? await auth?.renewAccessToken?.() : await auth?.getAccessToken?.()) ??\n auth?.accessToken ??\n null,\n apiKey: auth?.apiKey,\n }),\n enabled: auth !== null && enabled,\n queryClient: useCatalogQueryClient(),\n });\n return query.data;\n}\n\n/** The workspace mentions search: the host's when it names one, else the credential's own. */\nexport function useAccountWorkspaceId(auth: MentionAuthContext | null): string | undefined {\n const identity = useAccountIdentityRead(auth, { enabled: !auth?.workspaceId });\n return auth?.workspaceId ?? identity?.workspaceId ?? undefined;\n}\n\n/**\n * The credential's agora user id and workspace from the same read. The auth\n * context carries the identity provider's user id, not agora's, so \"who is\n * the viewer\" (an answer label's \"for you\") needs this one, and it reads\n * `/me` even when the host names a workspace.\n */\nexport function useAccountIdentity(\n auth: MentionAuthContext | null,\n options: AccountIdentityOptions = {}\n): AccountIdentityState {\n const identity = useAccountIdentityRead(auth, { enabled: true, ...options });\n return {\n userId: identity?.userId,\n workspaceId: auth?.workspaceId ?? identity?.workspaceId ?? undefined,\n };\n}\n"],"names":["useAccountIdentityQuery","useCatalogQueryClient"],"mappings":";;;;AA8BA,SAAS,uBACP,MACA,EAAE,SAAS,eACkB;AAC7B,QAAM,QAAQA,gBAAAA,wBAAwB;AAAA,IACpC,YAAY;AAAA,MACV,aAAY,6BAAM,eAAc;AAAA,MAChC,WAAW,6BAAM;AAAA,MACjB,QAAQ,6BAAM;AAAA,MACd,WAAW,6BAAM;AAAA,MACjB;AAAA,IAAA;AAAA,IAEF,aAAa,OAAO,EAAE,MAAA,IAA+B,OAAC;;AAAO;AAAA,QAC3D,QACG,QAAQ,QAAM,kCAAM,qBAAN,iCAA6B,QAAM,kCAAM,mBAAN,oCAClD,6BAAM,gBACN;AAAA,QACF,QAAQ,6BAAM;AAAA,MAAA;AAAA;AAAA,IAEhB,SAAS,SAAS,QAAQ;AAAA,IAC1B,aAAaC,eAAAA,sBAAA;AAAA,EAAsB,CACpC;AACD,SAAO,MAAM;AACf;AAcO,SAAS,mBACd,MACA,UAAkC,IACZ;AACtB,QAAM,WAAW,uBAAuB,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS;AAC3E,SAAO;AAAA,IACL,QAAQ,qCAAU;AAAA,IAClB,cAAa,6BAAM,iBAAe,qCAAU,gBAAe;AAAA,EAAA;AAE/D;;"}