@athenaintel/react 0.59.0 → 0.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +124 -20
  2. package/dist/agent-runtime/src/client-tool-bridge.cjs +20 -2
  3. package/dist/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
  4. package/dist/agent-runtime/src/client-tool-bridge.d.ts +9 -4
  5. package/dist/agent-runtime/src/client-tool-bridge.js +20 -2
  6. package/dist/agent-runtime/src/client-tool-bridge.js.map +1 -1
  7. package/dist/agent-runtime/src/client-tools.cjs.map +1 -1
  8. package/dist/agent-runtime/src/client-tools.d.ts +9 -0
  9. package/dist/agent-runtime/src/client-tools.js.map +1 -1
  10. package/dist/agent-runtime/src/composer-markdown.cjs +0 -16
  11. package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
  12. package/dist/agent-runtime/src/composer-markdown.js +1 -17
  13. package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
  14. package/dist/agent-runtime/src/named-agents.js +1 -1
  15. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  16. package/dist/agent-runtime/src/offline-status.d.ts +27 -0
  17. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  18. package/dist/agent-runtime/src/tool-metadata.cjs +7 -0
  19. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  20. package/dist/agent-runtime/src/tool-metadata.js +7 -0
  21. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  22. package/dist/chat/AthenaChat.cjs +17 -5
  23. package/dist/chat/AthenaChat.cjs.map +1 -1
  24. package/dist/chat/AthenaChat.js +18 -6
  25. package/dist/chat/AthenaChat.js.map +1 -1
  26. package/dist/chat/StatewireClientToolBridge.cjs +69 -1
  27. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  28. package/dist/chat/StatewireClientToolBridge.d.ts +14 -1
  29. package/dist/chat/StatewireClientToolBridge.js +70 -2
  30. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  31. package/dist/chat/StatewireClientToolConsentCard.cjs +65 -0
  32. package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -0
  33. package/dist/chat/StatewireClientToolConsentCard.d.ts +24 -0
  34. package/dist/chat/StatewireClientToolConsentCard.js +65 -0
  35. package/dist/chat/StatewireClientToolConsentCard.js.map +1 -0
  36. package/dist/chat/client-tool-write-consent.cjs +111 -0
  37. package/dist/chat/client-tool-write-consent.cjs.map +1 -0
  38. package/dist/chat/client-tool-write-consent.d.ts +71 -0
  39. package/dist/chat/client-tool-write-consent.js +111 -0
  40. package/dist/chat/client-tool-write-consent.js.map +1 -0
  41. package/dist/chat/statewire-client-tools.cjs +30 -5
  42. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  43. package/dist/chat/statewire-client-tools.d.ts +34 -2
  44. package/dist/chat/statewire-client-tools.js +30 -5
  45. package/dist/chat/statewire-client-tools.js.map +1 -1
  46. package/dist/chat-ui/index.cjs +2 -0
  47. package/dist/chat-ui/index.cjs.map +1 -1
  48. package/dist/chat-ui/index.d.ts +1 -0
  49. package/dist/chat-ui/index.js +2 -0
  50. package/dist/chat-ui/index.js.map +1 -1
  51. package/dist/chat-ui/mentions/mention-chip.cjs +9 -3
  52. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  53. package/dist/chat-ui/mentions/mention-chip.js +9 -3
  54. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  55. package/dist/chat-ui/mentions/suggestions/components/mention-icon.cjs +18 -2
  56. package/dist/chat-ui/mentions/suggestions/components/mention-icon.cjs.map +1 -1
  57. package/dist/chat-ui/mentions/suggestions/components/mention-icon.d.ts +10 -1
  58. package/dist/chat-ui/mentions/suggestions/components/mention-icon.js +19 -3
  59. package/dist/chat-ui/mentions/suggestions/components/mention-icon.js.map +1 -1
  60. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +9 -0
  61. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  62. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +6 -0
  63. package/dist/chat-ui/mentions/suggestions/data/api-client.js +9 -0
  64. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  65. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.cjs +89 -0
  66. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.cjs.map +1 -0
  67. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.d.ts +8 -0
  68. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.js +89 -0
  69. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.js.map +1 -0
  70. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +153 -116
  71. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
  72. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.d.ts +58 -12
  73. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +154 -117
  74. package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
  75. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +38 -6
  76. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
  77. package/dist/chat-ui/mentions/suggestions/data/sources/index.js +40 -8
  78. package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
  79. package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs +13 -0
  80. package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs.map +1 -0
  81. package/dist/chat-ui/mentions/use-asset-icon-app-url.d.ts +9 -0
  82. package/dist/chat-ui/mentions/use-asset-icon-app-url.js +13 -0
  83. package/dist/chat-ui/mentions/use-asset-icon-app-url.js.map +1 -0
  84. package/dist/chat-ui/statewire.cjs +4 -0
  85. package/dist/chat-ui/statewire.cjs.map +1 -1
  86. package/dist/chat-ui/statewire.d.ts +2 -0
  87. package/dist/chat-ui/statewire.js +4 -0
  88. package/dist/chat-ui/statewire.js.map +1 -1
  89. package/dist/collab/client.cjs +24 -1
  90. package/dist/collab/client.cjs.map +1 -1
  91. package/dist/collab/client.d.ts +16 -0
  92. package/dist/collab/client.js +24 -1
  93. package/dist/collab/client.js.map +1 -1
  94. package/dist/composer/attached-references.js +1 -1
  95. package/dist/composer/staged-message-metadata.cjs +12 -0
  96. package/dist/composer/staged-message-metadata.cjs.map +1 -0
  97. package/dist/composer/staged-message-metadata.d.ts +15 -0
  98. package/dist/composer/staged-message-metadata.js +12 -0
  99. package/dist/composer/staged-message-metadata.js.map +1 -0
  100. package/dist/data/athena-request.cjs +2 -0
  101. package/dist/data/athena-request.cjs.map +1 -1
  102. package/dist/data/athena-request.js +2 -0
  103. package/dist/data/athena-request.js.map +1 -1
  104. package/dist/index.cjs +5 -0
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.d.ts +6 -2
  107. package/dist/index.js +6 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/provider/AthenaProvider.cjs +26 -6
  110. package/dist/provider/AthenaProvider.cjs.map +1 -1
  111. package/dist/provider/AthenaProvider.d.ts +8 -1
  112. package/dist/provider/AthenaProvider.js +27 -7
  113. package/dist/provider/AthenaProvider.js.map +1 -1
  114. package/dist/react/package.json.cjs +1 -1
  115. package/dist/react/package.json.js +1 -1
  116. package/dist/references/src/anchors/parse.cjs +11 -1
  117. package/dist/references/src/anchors/parse.cjs.map +1 -1
  118. package/dist/references/src/anchors/parse.js +11 -1
  119. package/dist/references/src/anchors/parse.js.map +1 -1
  120. package/dist/references/src/anchors/schemas.d.ts +1 -0
  121. package/dist/references/src/anchors/serialize.cjs +1 -0
  122. package/dist/references/src/anchors/serialize.cjs.map +1 -1
  123. package/dist/references/src/anchors/serialize.js +1 -0
  124. package/dist/references/src/anchors/serialize.js.map +1 -1
  125. package/dist/references/src/anchors/types.cjs.map +1 -1
  126. package/dist/references/src/anchors/types.d.ts +9 -0
  127. package/dist/references/src/anchors/types.js.map +1 -1
  128. package/dist/references/src/schemas.d.ts +2 -0
  129. package/dist/runtime/useAthenaStatewireRuntime.cjs +6 -8
  130. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  131. package/dist/runtime/useAthenaStatewireRuntime.js +6 -8
  132. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  133. package/dist/threads/statewire-thread-list.cjs +14 -2
  134. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  135. package/dist/threads/statewire-thread-list.d.ts +11 -2
  136. package/dist/threads/statewire-thread-list.js +14 -2
  137. package/dist/threads/statewire-thread-list.js.map +1 -1
  138. package/dist/tools/drive-tool-uis.cjs +8 -4
  139. package/dist/tools/drive-tool-uis.cjs.map +1 -1
  140. package/dist/tools/drive-tool-uis.d.ts +5 -4
  141. package/dist/tools/drive-tool-uis.js +9 -5
  142. package/dist/tools/drive-tool-uis.js.map +1 -1
  143. package/dist/version.cjs +4 -0
  144. package/dist/version.cjs.map +1 -1
  145. package/dist/version.d.ts +9 -0
  146. package/dist/version.js +4 -0
  147. package/dist/version.js.map +1 -1
  148. package/dist/workbench/live-desktop-api.cjs +2 -1
  149. package/dist/workbench/live-desktop-api.cjs.map +1 -1
  150. package/dist/workbench/live-desktop-api.js +2 -1
  151. package/dist/workbench/live-desktop-api.js.map +1 -1
  152. package/dist/workbench/session-workspace-api.cjs +7 -2
  153. package/dist/workbench/session-workspace-api.cjs.map +1 -1
  154. package/dist/workbench/session-workspace-api.d.ts +10 -0
  155. package/dist/workbench/session-workspace-api.js +7 -2
  156. package/dist/workbench/session-workspace-api.js.map +1 -1
  157. package/package.json +1 -1
@@ -2,15 +2,20 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const reactStatewire = require("@assistant-ui/react-statewire");
4
4
  const require$$0 = require("react");
5
+ const clientToolWriteConsent = require("./client-tool-write-consent.cjs");
5
6
  const clientToolBridge = require("../agent-runtime/src/client-tool-bridge.cjs");
6
7
  const clientTools = require("../agent-runtime/src/client-tools.cjs");
7
8
  const clientToolRequests = clientTools.createClientToolRequestTracker();
9
+ const SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE = "The person did not allow this. Do not retry it; ask them how they want to proceed.";
8
10
  function StatewireClientToolBridge({
9
11
  tools,
10
- threadId
12
+ threadId,
13
+ writeConsentSummaries,
14
+ confirmWrites: hostConfirmWrites
11
15
  }) {
12
16
  const sendCommand = reactStatewire.useStatewireSendCommand();
13
17
  const inputRequests = reactStatewire.useStatewireRuns().inputRequests;
18
+ const consentStore = require$$0.useContext(clientToolWriteConsent.ClientToolWriteConsentContext);
14
19
  const bridgeTools = require$$0.useMemo(
15
20
  () => tools.map((tool) => ({
16
21
  name: tool.definition.name,
@@ -19,11 +24,73 @@ function StatewireClientToolBridge({
19
24
  })),
20
25
  [tools]
21
26
  );
27
+ const askingRef = require$$0.useRef(null);
28
+ require$$0.useEffect(() => {
29
+ const asking = askingRef.current;
30
+ if (!asking) return;
31
+ if ((inputRequests ?? []).some((request) => request.id === asking.requestId)) return;
32
+ askingRef.current = null;
33
+ asking.abandon();
34
+ }, [inputRequests]);
35
+ require$$0.useEffect(
36
+ () => () => {
37
+ var _a;
38
+ (_a = askingRef.current) == null ? void 0 : _a.abandon();
39
+ askingRef.current = null;
40
+ },
41
+ []
42
+ );
43
+ const summariesRef = require$$0.useRef(writeConsentSummaries);
44
+ summariesRef.current = writeConsentSummaries;
45
+ const hostConfirmRef = require$$0.useRef(hostConfirmWrites);
46
+ hostConfirmRef.current = hostConfirmWrites;
47
+ const confirmWrites = require$$0.useCallback(
48
+ async ({
49
+ requestId,
50
+ writeToolNames
51
+ }) => {
52
+ var _a, _b;
53
+ if (consentStore == null ? void 0 : consentStore.isGranted(threadId)) return "proceed";
54
+ const consentTools = Array.from(new Set(writeToolNames)).map(
55
+ (name) => {
56
+ var _a2;
57
+ const summary = (_a2 = summariesRef.current) == null ? void 0 : _a2[name];
58
+ return summary ? { name, summary } : { name };
59
+ }
60
+ );
61
+ const request = { requestId, threadId, tools: consentTools };
62
+ const hostConfirm = hostConfirmRef.current;
63
+ if (hostConfirm) {
64
+ const controller = new AbortController();
65
+ askingRef.current = { requestId, abandon: () => controller.abort() };
66
+ const abandoned = new Promise((resolve) => {
67
+ controller.signal.addEventListener("abort", () => resolve("abandoned"), { once: true });
68
+ });
69
+ const answered = Promise.resolve().then(() => hostConfirm({ ...request, signal: controller.signal })).then(
70
+ (allowed) => allowed === true,
71
+ () => false
72
+ );
73
+ const outcome = await Promise.race([answered, abandoned]);
74
+ if (((_a = askingRef.current) == null ? void 0 : _a.requestId) === requestId) askingRef.current = null;
75
+ if (outcome === "abandoned" || controller.signal.aborted) return "abandoned";
76
+ if (outcome) consentStore == null ? void 0 : consentStore.grant(threadId);
77
+ return outcome ? "proceed" : "declined";
78
+ }
79
+ if (!consentStore) return "declined";
80
+ askingRef.current = { requestId, abandon: () => consentStore.abandon(requestId) };
81
+ const decision = await consentStore.ask(request);
82
+ if (((_b = askingRef.current) == null ? void 0 : _b.requestId) === requestId) askingRef.current = null;
83
+ return decision;
84
+ },
85
+ [consentStore, threadId]
86
+ );
22
87
  clientToolBridge.useStatewireClientToolBridge({
23
88
  threadId,
24
89
  tools: bridgeTools,
25
90
  tracker: clientToolRequests,
26
91
  inputRequests,
92
+ confirmWrites,
93
+ writeDeclinedMessage: SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE,
27
94
  sendResume: (requestId, value) => {
28
95
  return sendCommand({
29
96
  type: "run/input",
@@ -34,5 +101,6 @@ function StatewireClientToolBridge({
34
101
  });
35
102
  return null;
36
103
  }
104
+ exports.SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE = SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE;
37
105
  exports.StatewireClientToolBridge = StatewireClientToolBridge;
38
106
  //# sourceMappingURL=StatewireClientToolBridge.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatewireClientToolBridge.cjs","sources":["../../src/chat/StatewireClientToolBridge.tsx"],"sourcesContent":["import { useStatewireRuns, useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type ClientToolBridgeTool,\n createClientToolRequestTracker,\n type RegisteredClientTool,\n useStatewireClientToolBridge,\n} from '@athenaintel/agent-runtime';\nimport type { ReadonlyJSONObject } from 'assistant-stream/utils';\nimport { useMemo } from 'react';\n\n// Shared across every mount: the same thread can be attached from two mounted\n// surfaces, and a parked client-tool call must be executed and resumed by\n// exactly one of them. Whichever bridge claims the request first owns it.\nconst clientToolRequests = createClientToolRequestTracker();\n\n/**\n * Executes the deep agent's frontend tool calls in statewire mode.\n *\n * Tools declared through `runConfig.custom.client_tools` have no backend\n * implementation: a call parks the run and surfaces here as a statewire\n * input request. The bridge runs the matching local handler and resumes the\n * run with a per-call result envelope, so a missing tool or a thrown handler\n * still frees the run with an error the model can act on instead of hanging.\n *\n * The claim lifecycle, liveness guards, and wire-safety live in\n * `@athenaintel/agent-runtime`'s `useStatewireClientToolBridge`; this wrapper\n * only adapts the shared `RegisteredClientTool` shape and the SDK's transport.\n */\nexport function StatewireClientToolBridge({\n tools,\n threadId,\n}: {\n tools: readonly RegisteredClientTool[];\n threadId: string;\n}): null {\n const sendCommand = useStatewireSendCommand();\n const inputRequests = useStatewireRuns().inputRequests;\n\n const bridgeTools = useMemo<readonly ClientToolBridgeTool[]>(\n () =>\n tools.map((tool) => ({\n name: tool.definition.name,\n requiresWrite: tool.definition.requiresWrite,\n run: (args, context) => tool.handler(args, context),\n })),\n [tools],\n );\n\n useStatewireClientToolBridge({\n threadId,\n tools: bridgeTools,\n tracker: clientToolRequests,\n inputRequests,\n sendResume: (requestId, value) => {\n // Every envelope value is already wire-safe (per-call JSON round-trip in\n // the shared batch engine), so the batch only needs the transport's\n // JSON typing.\n return sendCommand({\n type: 'run/input',\n requestId,\n response: { type: 'resume', value: value as ReadonlyJSONObject },\n });\n },\n });\n\n return null;\n}\n"],"names":["createClientToolRequestTracker","useStatewireSendCommand","useStatewireRuns","useMemo","useStatewireClientToolBridge"],"mappings":";;;;;;AAaA,MAAM,qBAAqBA,YAAAA,+BAAA;AAepB,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGS;AACP,QAAM,cAAcC,eAAAA,wBAAA;AACpB,QAAM,gBAAgBC,eAAAA,mBAAmB;AAEzC,QAAM,cAAcC,WAAAA;AAAAA,IAClB,MACE,MAAM,IAAI,CAAC,UAAU;AAAA,MACnB,MAAM,KAAK,WAAW;AAAA,MACtB,eAAe,KAAK,WAAW;AAAA,MAC/B,KAAK,CAAC,MAAM,YAAY,KAAK,QAAQ,MAAM,OAAO;AAAA,IAAA,EAClD;AAAA,IACJ,CAAC,KAAK;AAAA,EAAA;AAGRC,gDAA6B;AAAA,IAC3B;AAAA,IACA,OAAO;AAAA,IACP,SAAS;AAAA,IACT;AAAA,IACA,YAAY,CAAC,WAAW,UAAU;AAIhC,aAAO,YAAY;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,QACA,UAAU,EAAE,MAAM,UAAU,MAAA;AAAA,MAAmC,CAChE;AAAA,IACH;AAAA,EAAA,CACD;AAED,SAAO;AACT;;"}
1
+ {"version":3,"file":"StatewireClientToolBridge.cjs","sources":["../../src/chat/StatewireClientToolBridge.tsx"],"sourcesContent":["import { useStatewireRuns, useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type ClientToolBridgeTool,\n type ClientToolWriteConsentDecision,\n createClientToolRequestTracker,\n type RegisteredClientTool,\n useStatewireClientToolBridge,\n} from '@athenaintel/agent-runtime';\nimport type { ReadonlyJSONObject } from 'assistant-stream/utils';\nimport { useCallback, useContext, useEffect, useMemo, useRef } from 'react';\nimport {\n type ClientToolWriteConfirmer,\n ClientToolWriteConsentContext,\n type ClientToolWriteConsentTool,\n} from './client-tool-write-consent';\n\n// Shared across every mount: the same thread can be attached from two mounted\n// surfaces, and a parked client-tool call must be executed and resumed by\n// exactly one of them. Whichever bridge claims the request first owns it.\nconst clientToolRequests = createClientToolRequestTracker();\n\n/** Answers the model for a write the person did not allow. */\nexport const SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE =\n 'The person did not allow this. Do not retry it; ask them how they want to proceed.';\n\n/**\n * Executes the deep agent's frontend tool calls in statewire mode.\n *\n * Tools declared through `runConfig.custom.client_tools` have no backend\n * implementation: a call parks the run and surfaces here as a statewire\n * input request. The bridge runs the matching local handler and resumes the\n * run with a per-call result envelope, so a missing tool or a thrown handler\n * still frees the run with an error the model can act on instead of hanging.\n *\n * A batch holding a tool marked `requiresWriteConsent` first asks the person\n * (once per conversation): through the host's `confirmWrites` when it gave\n * one, else through the consent store the nearest `AthenaProvider` holds,\n * which `StatewireClientToolConsentCard` draws. With neither there is nobody\n * to ask, so the writes are declined.\n *\n * The claim lifecycle, liveness guards, and wire-safety live in\n * `@athenaintel/agent-runtime`'s `useStatewireClientToolBridge`; this wrapper\n * only adapts the shared `RegisteredClientTool` shape and the SDK's transport.\n */\nexport function StatewireClientToolBridge({\n tools,\n threadId,\n writeConsentSummaries,\n confirmWrites: hostConfirmWrites,\n}: {\n tools: readonly RegisteredClientTool[];\n threadId: string;\n /** Each write tool's own words for what it does, by tool name. */\n writeConsentSummaries?: Readonly<Record<string, string>>;\n /** The host's own consent UI, in place of the SDK's card. */\n confirmWrites?: ClientToolWriteConfirmer;\n}): null {\n const sendCommand = useStatewireSendCommand();\n const inputRequests = useStatewireRuns().inputRequests;\n const consentStore = useContext(ClientToolWriteConsentContext);\n\n const bridgeTools = useMemo<readonly ClientToolBridgeTool[]>(\n () =>\n tools.map((tool) => ({\n name: tool.definition.name,\n requiresWrite: tool.definition.requiresWrite,\n run: (args, context) => tool.handler(args, context),\n })),\n [tools],\n );\n\n // The question on screen belongs to one parked request. When that request\n // goes away (stop, an answer from elsewhere, another conversation), close\n // the question instead of leaving it up for a call that no longer exists.\n const askingRef = useRef<{ requestId: string; abandon: () => void } | null>(null);\n useEffect(() => {\n const asking = askingRef.current;\n if (!asking) return;\n if ((inputRequests ?? []).some((request) => request.id === asking.requestId)) return;\n askingRef.current = null;\n asking.abandon();\n }, [inputRequests]);\n useEffect(\n () => () => {\n askingRef.current?.abandon();\n askingRef.current = null;\n },\n [],\n );\n\n const summariesRef = useRef(writeConsentSummaries);\n summariesRef.current = writeConsentSummaries;\n const hostConfirmRef = useRef(hostConfirmWrites);\n hostConfirmRef.current = hostConfirmWrites;\n\n const confirmWrites = useCallback(\n async ({\n requestId,\n writeToolNames,\n }: {\n requestId: string;\n writeToolNames: readonly string[];\n }): Promise<ClientToolWriteConsentDecision> => {\n if (consentStore?.isGranted(threadId)) return 'proceed';\n const consentTools: ClientToolWriteConsentTool[] = Array.from(new Set(writeToolNames)).map(\n (name) => {\n const summary = summariesRef.current?.[name];\n return summary ? { name, summary } : { name };\n },\n );\n const request = { requestId, threadId, tools: consentTools };\n\n const hostConfirm = hostConfirmRef.current;\n if (hostConfirm) {\n const controller = new AbortController();\n askingRef.current = { requestId, abandon: () => controller.abort() };\n // The question ends when the person answers or when its call goes\n // away, whichever is first: a host dialog that closes on the signal\n // without settling its promise must not hold the request's claim.\n const abandoned = new Promise<'abandoned'>((resolve) => {\n controller.signal.addEventListener('abort', () => resolve('abandoned'), { once: true });\n });\n const answered = Promise.resolve()\n .then(() => hostConfirm({ ...request, signal: controller.signal }))\n // A consent UI that failed is not a yes.\n .then(\n (allowed) => allowed === true,\n () => false,\n );\n const outcome = await Promise.race([answered, abandoned]);\n if (askingRef.current?.requestId === requestId) askingRef.current = null;\n // An answer that arrives after the call went away is discarded.\n if (outcome === 'abandoned' || controller.signal.aborted) return 'abandoned';\n if (outcome) consentStore?.grant(threadId);\n return outcome ? 'proceed' : 'declined';\n }\n\n if (!consentStore) return 'declined';\n askingRef.current = { requestId, abandon: () => consentStore.abandon(requestId) };\n const decision = await consentStore.ask(request);\n if (askingRef.current?.requestId === requestId) askingRef.current = null;\n return decision;\n },\n [consentStore, threadId],\n );\n\n useStatewireClientToolBridge({\n threadId,\n tools: bridgeTools,\n tracker: clientToolRequests,\n inputRequests,\n confirmWrites,\n writeDeclinedMessage: SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE,\n sendResume: (requestId, value) => {\n // Every envelope value is already wire-safe (per-call JSON round-trip in\n // the shared batch engine), so the batch only needs the transport's\n // JSON typing.\n return sendCommand({\n type: 'run/input',\n requestId,\n response: { type: 'resume', value: value as ReadonlyJSONObject },\n });\n },\n });\n\n return null;\n}\n"],"names":["createClientToolRequestTracker","useStatewireSendCommand","useStatewireRuns","useContext","ClientToolWriteConsentContext","useMemo","useRef","useEffect","useCallback","_a","useStatewireClientToolBridge"],"mappings":"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"}
@@ -1,4 +1,7 @@
1
1
  import { type RegisteredClientTool } from '../agent-runtime/src/index';
2
+ import { type ClientToolWriteConfirmer } from './client-tool-write-consent';
3
+ /** Answers the model for a write the person did not allow. */
4
+ export declare const SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE = "The person did not allow this. Do not retry it; ask them how they want to proceed.";
2
5
  /**
3
6
  * Executes the deep agent's frontend tool calls in statewire mode.
4
7
  *
@@ -8,11 +11,21 @@ import { type RegisteredClientTool } from '../agent-runtime/src/index';
8
11
  * run with a per-call result envelope, so a missing tool or a thrown handler
9
12
  * still frees the run with an error the model can act on instead of hanging.
10
13
  *
14
+ * A batch holding a tool marked `requiresWriteConsent` first asks the person
15
+ * (once per conversation): through the host's `confirmWrites` when it gave
16
+ * one, else through the consent store the nearest `AthenaProvider` holds,
17
+ * which `StatewireClientToolConsentCard` draws. With neither there is nobody
18
+ * to ask, so the writes are declined.
19
+ *
11
20
  * The claim lifecycle, liveness guards, and wire-safety live in
12
21
  * `@athenaintel/agent-runtime`'s `useStatewireClientToolBridge`; this wrapper
13
22
  * only adapts the shared `RegisteredClientTool` shape and the SDK's transport.
14
23
  */
15
- export declare function StatewireClientToolBridge({ tools, threadId, }: {
24
+ export declare function StatewireClientToolBridge({ tools, threadId, writeConsentSummaries, confirmWrites: hostConfirmWrites, }: {
16
25
  tools: readonly RegisteredClientTool[];
17
26
  threadId: string;
27
+ /** Each write tool's own words for what it does, by tool name. */
28
+ writeConsentSummaries?: Readonly<Record<string, string>>;
29
+ /** The host's own consent UI, in place of the SDK's card. */
30
+ confirmWrites?: ClientToolWriteConfirmer;
18
31
  }): null;
@@ -1,14 +1,19 @@
1
1
  import { useStatewireSendCommand, useStatewireRuns } from "@assistant-ui/react-statewire";
2
- import { useMemo } from "react";
2
+ import { useContext, useMemo, useRef, useEffect, useCallback } from "react";
3
+ import { ClientToolWriteConsentContext } from "./client-tool-write-consent.js";
3
4
  import { useStatewireClientToolBridge } from "../agent-runtime/src/client-tool-bridge.js";
4
5
  import { createClientToolRequestTracker } from "../agent-runtime/src/client-tools.js";
5
6
  const clientToolRequests = createClientToolRequestTracker();
7
+ const SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE = "The person did not allow this. Do not retry it; ask them how they want to proceed.";
6
8
  function StatewireClientToolBridge({
7
9
  tools,
8
- threadId
10
+ threadId,
11
+ writeConsentSummaries,
12
+ confirmWrites: hostConfirmWrites
9
13
  }) {
10
14
  const sendCommand = useStatewireSendCommand();
11
15
  const inputRequests = useStatewireRuns().inputRequests;
16
+ const consentStore = useContext(ClientToolWriteConsentContext);
12
17
  const bridgeTools = useMemo(
13
18
  () => tools.map((tool) => ({
14
19
  name: tool.definition.name,
@@ -17,11 +22,73 @@ function StatewireClientToolBridge({
17
22
  })),
18
23
  [tools]
19
24
  );
25
+ const askingRef = useRef(null);
26
+ useEffect(() => {
27
+ const asking = askingRef.current;
28
+ if (!asking) return;
29
+ if ((inputRequests ?? []).some((request) => request.id === asking.requestId)) return;
30
+ askingRef.current = null;
31
+ asking.abandon();
32
+ }, [inputRequests]);
33
+ useEffect(
34
+ () => () => {
35
+ var _a;
36
+ (_a = askingRef.current) == null ? void 0 : _a.abandon();
37
+ askingRef.current = null;
38
+ },
39
+ []
40
+ );
41
+ const summariesRef = useRef(writeConsentSummaries);
42
+ summariesRef.current = writeConsentSummaries;
43
+ const hostConfirmRef = useRef(hostConfirmWrites);
44
+ hostConfirmRef.current = hostConfirmWrites;
45
+ const confirmWrites = useCallback(
46
+ async ({
47
+ requestId,
48
+ writeToolNames
49
+ }) => {
50
+ var _a, _b;
51
+ if (consentStore == null ? void 0 : consentStore.isGranted(threadId)) return "proceed";
52
+ const consentTools = Array.from(new Set(writeToolNames)).map(
53
+ (name) => {
54
+ var _a2;
55
+ const summary = (_a2 = summariesRef.current) == null ? void 0 : _a2[name];
56
+ return summary ? { name, summary } : { name };
57
+ }
58
+ );
59
+ const request = { requestId, threadId, tools: consentTools };
60
+ const hostConfirm = hostConfirmRef.current;
61
+ if (hostConfirm) {
62
+ const controller = new AbortController();
63
+ askingRef.current = { requestId, abandon: () => controller.abort() };
64
+ const abandoned = new Promise((resolve) => {
65
+ controller.signal.addEventListener("abort", () => resolve("abandoned"), { once: true });
66
+ });
67
+ const answered = Promise.resolve().then(() => hostConfirm({ ...request, signal: controller.signal })).then(
68
+ (allowed) => allowed === true,
69
+ () => false
70
+ );
71
+ const outcome = await Promise.race([answered, abandoned]);
72
+ if (((_a = askingRef.current) == null ? void 0 : _a.requestId) === requestId) askingRef.current = null;
73
+ if (outcome === "abandoned" || controller.signal.aborted) return "abandoned";
74
+ if (outcome) consentStore == null ? void 0 : consentStore.grant(threadId);
75
+ return outcome ? "proceed" : "declined";
76
+ }
77
+ if (!consentStore) return "declined";
78
+ askingRef.current = { requestId, abandon: () => consentStore.abandon(requestId) };
79
+ const decision = await consentStore.ask(request);
80
+ if (((_b = askingRef.current) == null ? void 0 : _b.requestId) === requestId) askingRef.current = null;
81
+ return decision;
82
+ },
83
+ [consentStore, threadId]
84
+ );
20
85
  useStatewireClientToolBridge({
21
86
  threadId,
22
87
  tools: bridgeTools,
23
88
  tracker: clientToolRequests,
24
89
  inputRequests,
90
+ confirmWrites,
91
+ writeDeclinedMessage: SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE,
25
92
  sendResume: (requestId, value) => {
26
93
  return sendCommand({
27
94
  type: "run/input",
@@ -33,6 +100,7 @@ function StatewireClientToolBridge({
33
100
  return null;
34
101
  }
35
102
  export {
103
+ SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE,
36
104
  StatewireClientToolBridge
37
105
  };
38
106
  //# sourceMappingURL=StatewireClientToolBridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatewireClientToolBridge.js","sources":["../../src/chat/StatewireClientToolBridge.tsx"],"sourcesContent":["import { useStatewireRuns, useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type ClientToolBridgeTool,\n createClientToolRequestTracker,\n type RegisteredClientTool,\n useStatewireClientToolBridge,\n} from '@athenaintel/agent-runtime';\nimport type { ReadonlyJSONObject } from 'assistant-stream/utils';\nimport { useMemo } from 'react';\n\n// Shared across every mount: the same thread can be attached from two mounted\n// surfaces, and a parked client-tool call must be executed and resumed by\n// exactly one of them. Whichever bridge claims the request first owns it.\nconst clientToolRequests = createClientToolRequestTracker();\n\n/**\n * Executes the deep agent's frontend tool calls in statewire mode.\n *\n * Tools declared through `runConfig.custom.client_tools` have no backend\n * implementation: a call parks the run and surfaces here as a statewire\n * input request. The bridge runs the matching local handler and resumes the\n * run with a per-call result envelope, so a missing tool or a thrown handler\n * still frees the run with an error the model can act on instead of hanging.\n *\n * The claim lifecycle, liveness guards, and wire-safety live in\n * `@athenaintel/agent-runtime`'s `useStatewireClientToolBridge`; this wrapper\n * only adapts the shared `RegisteredClientTool` shape and the SDK's transport.\n */\nexport function StatewireClientToolBridge({\n tools,\n threadId,\n}: {\n tools: readonly RegisteredClientTool[];\n threadId: string;\n}): null {\n const sendCommand = useStatewireSendCommand();\n const inputRequests = useStatewireRuns().inputRequests;\n\n const bridgeTools = useMemo<readonly ClientToolBridgeTool[]>(\n () =>\n tools.map((tool) => ({\n name: tool.definition.name,\n requiresWrite: tool.definition.requiresWrite,\n run: (args, context) => tool.handler(args, context),\n })),\n [tools],\n );\n\n useStatewireClientToolBridge({\n threadId,\n tools: bridgeTools,\n tracker: clientToolRequests,\n inputRequests,\n sendResume: (requestId, value) => {\n // Every envelope value is already wire-safe (per-call JSON round-trip in\n // the shared batch engine), so the batch only needs the transport's\n // JSON typing.\n return sendCommand({\n type: 'run/input',\n requestId,\n response: { type: 'resume', value: value as ReadonlyJSONObject },\n });\n },\n });\n\n return null;\n}\n"],"names":[],"mappings":";;;;AAaA,MAAM,qBAAqB,+BAAA;AAepB,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGS;AACP,QAAM,cAAc,wBAAA;AACpB,QAAM,gBAAgB,mBAAmB;AAEzC,QAAM,cAAc;AAAA,IAClB,MACE,MAAM,IAAI,CAAC,UAAU;AAAA,MACnB,MAAM,KAAK,WAAW;AAAA,MACtB,eAAe,KAAK,WAAW;AAAA,MAC/B,KAAK,CAAC,MAAM,YAAY,KAAK,QAAQ,MAAM,OAAO;AAAA,IAAA,EAClD;AAAA,IACJ,CAAC,KAAK;AAAA,EAAA;AAGR,+BAA6B;AAAA,IAC3B;AAAA,IACA,OAAO;AAAA,IACP,SAAS;AAAA,IACT;AAAA,IACA,YAAY,CAAC,WAAW,UAAU;AAIhC,aAAO,YAAY;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,QACA,UAAU,EAAE,MAAM,UAAU,MAAA;AAAA,MAAmC,CAChE;AAAA,IACH;AAAA,EAAA,CACD;AAED,SAAO;AACT;"}
1
+ {"version":3,"file":"StatewireClientToolBridge.js","sources":["../../src/chat/StatewireClientToolBridge.tsx"],"sourcesContent":["import { useStatewireRuns, useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type ClientToolBridgeTool,\n type ClientToolWriteConsentDecision,\n createClientToolRequestTracker,\n type RegisteredClientTool,\n useStatewireClientToolBridge,\n} from '@athenaintel/agent-runtime';\nimport type { ReadonlyJSONObject } from 'assistant-stream/utils';\nimport { useCallback, useContext, useEffect, useMemo, useRef } from 'react';\nimport {\n type ClientToolWriteConfirmer,\n ClientToolWriteConsentContext,\n type ClientToolWriteConsentTool,\n} from './client-tool-write-consent';\n\n// Shared across every mount: the same thread can be attached from two mounted\n// surfaces, and a parked client-tool call must be executed and resumed by\n// exactly one of them. Whichever bridge claims the request first owns it.\nconst clientToolRequests = createClientToolRequestTracker();\n\n/** Answers the model for a write the person did not allow. */\nexport const SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE =\n 'The person did not allow this. Do not retry it; ask them how they want to proceed.';\n\n/**\n * Executes the deep agent's frontend tool calls in statewire mode.\n *\n * Tools declared through `runConfig.custom.client_tools` have no backend\n * implementation: a call parks the run and surfaces here as a statewire\n * input request. The bridge runs the matching local handler and resumes the\n * run with a per-call result envelope, so a missing tool or a thrown handler\n * still frees the run with an error the model can act on instead of hanging.\n *\n * A batch holding a tool marked `requiresWriteConsent` first asks the person\n * (once per conversation): through the host's `confirmWrites` when it gave\n * one, else through the consent store the nearest `AthenaProvider` holds,\n * which `StatewireClientToolConsentCard` draws. With neither there is nobody\n * to ask, so the writes are declined.\n *\n * The claim lifecycle, liveness guards, and wire-safety live in\n * `@athenaintel/agent-runtime`'s `useStatewireClientToolBridge`; this wrapper\n * only adapts the shared `RegisteredClientTool` shape and the SDK's transport.\n */\nexport function StatewireClientToolBridge({\n tools,\n threadId,\n writeConsentSummaries,\n confirmWrites: hostConfirmWrites,\n}: {\n tools: readonly RegisteredClientTool[];\n threadId: string;\n /** Each write tool's own words for what it does, by tool name. */\n writeConsentSummaries?: Readonly<Record<string, string>>;\n /** The host's own consent UI, in place of the SDK's card. */\n confirmWrites?: ClientToolWriteConfirmer;\n}): null {\n const sendCommand = useStatewireSendCommand();\n const inputRequests = useStatewireRuns().inputRequests;\n const consentStore = useContext(ClientToolWriteConsentContext);\n\n const bridgeTools = useMemo<readonly ClientToolBridgeTool[]>(\n () =>\n tools.map((tool) => ({\n name: tool.definition.name,\n requiresWrite: tool.definition.requiresWrite,\n run: (args, context) => tool.handler(args, context),\n })),\n [tools],\n );\n\n // The question on screen belongs to one parked request. When that request\n // goes away (stop, an answer from elsewhere, another conversation), close\n // the question instead of leaving it up for a call that no longer exists.\n const askingRef = useRef<{ requestId: string; abandon: () => void } | null>(null);\n useEffect(() => {\n const asking = askingRef.current;\n if (!asking) return;\n if ((inputRequests ?? []).some((request) => request.id === asking.requestId)) return;\n askingRef.current = null;\n asking.abandon();\n }, [inputRequests]);\n useEffect(\n () => () => {\n askingRef.current?.abandon();\n askingRef.current = null;\n },\n [],\n );\n\n const summariesRef = useRef(writeConsentSummaries);\n summariesRef.current = writeConsentSummaries;\n const hostConfirmRef = useRef(hostConfirmWrites);\n hostConfirmRef.current = hostConfirmWrites;\n\n const confirmWrites = useCallback(\n async ({\n requestId,\n writeToolNames,\n }: {\n requestId: string;\n writeToolNames: readonly string[];\n }): Promise<ClientToolWriteConsentDecision> => {\n if (consentStore?.isGranted(threadId)) return 'proceed';\n const consentTools: ClientToolWriteConsentTool[] = Array.from(new Set(writeToolNames)).map(\n (name) => {\n const summary = summariesRef.current?.[name];\n return summary ? { name, summary } : { name };\n },\n );\n const request = { requestId, threadId, tools: consentTools };\n\n const hostConfirm = hostConfirmRef.current;\n if (hostConfirm) {\n const controller = new AbortController();\n askingRef.current = { requestId, abandon: () => controller.abort() };\n // The question ends when the person answers or when its call goes\n // away, whichever is first: a host dialog that closes on the signal\n // without settling its promise must not hold the request's claim.\n const abandoned = new Promise<'abandoned'>((resolve) => {\n controller.signal.addEventListener('abort', () => resolve('abandoned'), { once: true });\n });\n const answered = Promise.resolve()\n .then(() => hostConfirm({ ...request, signal: controller.signal }))\n // A consent UI that failed is not a yes.\n .then(\n (allowed) => allowed === true,\n () => false,\n );\n const outcome = await Promise.race([answered, abandoned]);\n if (askingRef.current?.requestId === requestId) askingRef.current = null;\n // An answer that arrives after the call went away is discarded.\n if (outcome === 'abandoned' || controller.signal.aborted) return 'abandoned';\n if (outcome) consentStore?.grant(threadId);\n return outcome ? 'proceed' : 'declined';\n }\n\n if (!consentStore) return 'declined';\n askingRef.current = { requestId, abandon: () => consentStore.abandon(requestId) };\n const decision = await consentStore.ask(request);\n if (askingRef.current?.requestId === requestId) askingRef.current = null;\n return decision;\n },\n [consentStore, threadId],\n );\n\n useStatewireClientToolBridge({\n threadId,\n tools: bridgeTools,\n tracker: clientToolRequests,\n inputRequests,\n confirmWrites,\n writeDeclinedMessage: SDK_CLIENT_TOOL_WRITE_DECLINED_MESSAGE,\n sendResume: (requestId, value) => {\n // Every envelope value is already wire-safe (per-call JSON round-trip in\n // the shared batch engine), so the batch only needs the transport's\n // JSON typing.\n return sendCommand({\n type: 'run/input',\n requestId,\n response: { type: 'resume', value: value as ReadonlyJSONObject },\n });\n },\n });\n\n return null;\n}\n"],"names":["_a"],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const lucideReact = require("lucide-react");
5
+ const button = require("../ui/button.cjs");
6
+ const clientToolWriteConsent = require("./client-tool-write-consent.cjs");
7
+ const cnfast = require("cnfast");
8
+ function clientToolConsentSentence({
9
+ agentName,
10
+ request
11
+ }) {
12
+ const summaries = Array.from(
13
+ new Set(request.tools.flatMap((tool) => tool.summary ? [tool.summary] : []))
14
+ );
15
+ if (summaries.length > 0 && summaries.length === request.tools.length) {
16
+ return `${agentName} wants to ${joinWords(summaries)}.`;
17
+ }
18
+ const names = request.tools.map((tool) => tool.name);
19
+ return `${agentName} wants to run ${joinWords(names)}, which ${names.length === 1 ? "changes" : "change"} something here.`;
20
+ }
21
+ function joinWords(words) {
22
+ if (words.length <= 1) return words[0] ?? "";
23
+ return `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
24
+ }
25
+ const StatewireClientToolConsentCard = ({
26
+ className,
27
+ agentName = "The agent"
28
+ }) => {
29
+ const { request, allow, decline } = clientToolWriteConsent.useClientToolWriteConsent();
30
+ if (!request) return null;
31
+ return /* @__PURE__ */ jsxRuntime.jsxs(
32
+ "section",
33
+ {
34
+ "aria-label": "Permission required",
35
+ "data-testid": "client-tool-consent-card",
36
+ className: cnfast.cn(
37
+ "aui-client-tool-consent-card w-full rounded-2xl border border-border bg-card px-4 py-3 text-foreground shadow-xs",
38
+ className
39
+ ),
40
+ children: [
41
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
42
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointerClickIcon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
43
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
44
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-sm", children: "Allow this?" }),
45
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "break-words pt-1 text-foreground text-sm leading-snug", children: clientToolConsentSentence({ agentName, request }) }),
46
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pt-1 text-muted-foreground text-sm leading-snug", children: "Allowing covers the rest of this conversation." })
47
+ ] })
48
+ ] }),
49
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-3", children: [
50
+ /* @__PURE__ */ jsxRuntime.jsxs(button.Button, { size: "sm", onClick: allow, children: [
51
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, {}),
52
+ "Allow"
53
+ ] }),
54
+ /* @__PURE__ */ jsxRuntime.jsxs(button.Button, { size: "sm", variant: "outline", onClick: decline, children: [
55
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}),
56
+ "Decline"
57
+ ] })
58
+ ] })
59
+ ]
60
+ }
61
+ );
62
+ };
63
+ exports.StatewireClientToolConsentCard = StatewireClientToolConsentCard;
64
+ exports.clientToolConsentSentence = clientToolConsentSentence;
65
+ //# sourceMappingURL=StatewireClientToolConsentCard.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatewireClientToolConsentCard.cjs","sources":["../../src/chat/StatewireClientToolConsentCard.tsx"],"sourcesContent":["'use client';\n\nimport { CheckIcon, MousePointerClickIcon, XIcon } from 'lucide-react';\nimport type { FC } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type ClientToolWriteConsentRequest,\n useClientToolWriteConsent,\n} from './client-tool-write-consent';\n\nexport interface StatewireClientToolConsentCardProps {\n /** Applied to the card's section. */\n className?: string;\n /**\n * Who is asking, as the card names them (\"Jichard wants to …\"). Defaults to\n * \"The agent\".\n */\n agentName?: string;\n}\n\n/** What the batch does, in the card's sentence: its tools' own words, else their names. */\nexport function clientToolConsentSentence({\n agentName,\n request,\n}: {\n agentName: string;\n request: ClientToolWriteConsentRequest;\n}): string {\n const summaries = Array.from(\n new Set(request.tools.flatMap((tool) => (tool.summary ? [tool.summary] : []))),\n );\n if (summaries.length > 0 && summaries.length === request.tools.length) {\n return `${agentName} wants to ${joinWords(summaries)}.`;\n }\n const names = request.tools.map((tool) => tool.name);\n return `${agentName} wants to run ${joinWords(names)}, which ${names.length === 1 ? 'changes' : 'change'} something here.`;\n}\n\nfunction joinWords(words: readonly string[]): string {\n if (words.length <= 1) return words[0] ?? '';\n return `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`;\n}\n\n/**\n * The question a frontend tool marked `requiresWriteConsent` puts to the\n * person before its first call in a conversation runs: allow (which covers\n * the rest of the conversation) or decline. `AthenaChat` renders it above the\n * composer; a host with its own chat mounts it there, or builds its own on\n * `useClientToolWriteConsent`. Renders nothing while no call is waiting.\n */\nexport const StatewireClientToolConsentCard: FC<StatewireClientToolConsentCardProps> = ({\n className,\n agentName = 'The agent',\n}) => {\n const { request, allow, decline } = useClientToolWriteConsent();\n if (!request) return null;\n return (\n <section\n aria-label=\"Permission required\"\n data-testid=\"client-tool-consent-card\"\n className={cn(\n 'aui-client-tool-consent-card w-full rounded-2xl border border-border bg-card px-4 py-3 text-foreground shadow-xs',\n className,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <MousePointerClickIcon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-sm\">Allow this?</p>\n <p className=\"break-words pt-1 text-foreground text-sm leading-snug\">\n {clientToolConsentSentence({ agentName, request })}\n </p>\n <p className=\"pt-1 text-muted-foreground text-sm leading-snug\">\n Allowing covers the rest of this conversation.\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2 pt-3\">\n <Button size=\"sm\" onClick={allow}>\n <CheckIcon />\n Allow\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={decline}>\n <XIcon />\n Decline\n </Button>\n </div>\n </section>\n );\n};\n"],"names":["useClientToolWriteConsent","jsxs","cn","jsx","MousePointerClickIcon","Button","CheckIcon","XIcon"],"mappings":";;;;;;;AAsBO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGW;AACT,QAAM,YAAY,MAAM;AAAA,IACtB,IAAI,IAAI,QAAQ,MAAM,QAAQ,CAAC,SAAU,KAAK,UAAU,CAAC,KAAK,OAAO,IAAI,CAAA,CAAG,CAAC;AAAA,EAAA;AAE/E,MAAI,UAAU,SAAS,KAAK,UAAU,WAAW,QAAQ,MAAM,QAAQ;AACrE,WAAO,GAAG,SAAS,aAAa,UAAU,SAAS,CAAC;AAAA,EACtD;AACA,QAAM,QAAQ,QAAQ,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI;AACnD,SAAO,GAAG,SAAS,iBAAiB,UAAU,KAAK,CAAC,WAAW,MAAM,WAAW,IAAI,YAAY,QAAQ;AAC1G;AAEA,SAAS,UAAU,OAAkC;AACnD,MAAI,MAAM,UAAU,EAAG,QAAO,MAAM,CAAC,KAAK;AAC1C,SAAO,GAAG,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,QAAQ,MAAM,MAAM,SAAS,CAAC,CAAC;AACxE;AASO,MAAM,iCAA0E,CAAC;AAAA,EACtF;AAAA,EACA,YAAY;AACd,MAAM;AACJ,QAAM,EAAE,SAAS,OAAO,QAAA,IAAYA,uBAAAA,0BAAA;AACpC,MAAI,CAAC,QAAS,QAAO;AACrB,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,cAAW;AAAA,MACX,eAAY;AAAA,MACZ,WAAWC,OAAAA;AAAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,0BACb,UAAA;AAAA,UAAAE,2BAAAA,IAACC,YAAAA,uBAAA,EAAsB,WAAU,+CAAA,CAA+C;AAAA,UAChFH,2BAAAA,KAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,YAAAE,2BAAAA,IAAC,KAAA,EAAE,WAAU,yBAAwB,UAAA,eAAW;AAAA,YAChDA,2BAAAA,IAAC,OAAE,WAAU,yDACV,oCAA0B,EAAE,WAAW,QAAA,CAAS,GACnD;AAAA,YACAA,2BAAAA,IAAC,KAAA,EAAE,WAAU,mDAAkD,UAAA,iDAAA,CAE/D;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,GACF;AAAA,QACAF,2BAAAA,KAAC,OAAA,EAAI,WAAU,0CACb,UAAA;AAAA,UAAAA,2BAAAA,KAACI,OAAAA,QAAA,EAAO,MAAK,MAAK,SAAS,OACzB,UAAA;AAAA,YAAAF,2BAAAA,IAACG,YAAAA,WAAA,EAAU;AAAA,YAAE;AAAA,UAAA,GAEf;AAAA,0CACCD,OAAAA,QAAA,EAAO,MAAK,MAAK,SAAQ,WAAU,SAAS,SAC3C,UAAA;AAAA,YAAAF,2BAAAA,IAACI,YAAAA,OAAA,EAAM;AAAA,YAAE;AAAA,UAAA,EAAA,CAEX;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;;;"}
@@ -0,0 +1,24 @@
1
+ import type { FC } from 'react';
2
+ import { type ClientToolWriteConsentRequest } from './client-tool-write-consent';
3
+ export interface StatewireClientToolConsentCardProps {
4
+ /** Applied to the card's section. */
5
+ className?: string;
6
+ /**
7
+ * Who is asking, as the card names them ("Jichard wants to …"). Defaults to
8
+ * "The agent".
9
+ */
10
+ agentName?: string;
11
+ }
12
+ /** What the batch does, in the card's sentence: its tools' own words, else their names. */
13
+ export declare function clientToolConsentSentence({ agentName, request, }: {
14
+ agentName: string;
15
+ request: ClientToolWriteConsentRequest;
16
+ }): string;
17
+ /**
18
+ * The question a frontend tool marked `requiresWriteConsent` puts to the
19
+ * person before its first call in a conversation runs: allow (which covers
20
+ * the rest of the conversation) or decline. `AthenaChat` renders it above the
21
+ * composer; a host with its own chat mounts it there, or builds its own on
22
+ * `useClientToolWriteConsent`. Renders nothing while no call is waiting.
23
+ */
24
+ export declare const StatewireClientToolConsentCard: FC<StatewireClientToolConsentCardProps>;
@@ -0,0 +1,65 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { MousePointerClickIcon, CheckIcon, XIcon } from "lucide-react";
3
+ import { Button } from "../ui/button.js";
4
+ import { useClientToolWriteConsent } from "./client-tool-write-consent.js";
5
+ import { cn } from "cnfast";
6
+ function clientToolConsentSentence({
7
+ agentName,
8
+ request
9
+ }) {
10
+ const summaries = Array.from(
11
+ new Set(request.tools.flatMap((tool) => tool.summary ? [tool.summary] : []))
12
+ );
13
+ if (summaries.length > 0 && summaries.length === request.tools.length) {
14
+ return `${agentName} wants to ${joinWords(summaries)}.`;
15
+ }
16
+ const names = request.tools.map((tool) => tool.name);
17
+ return `${agentName} wants to run ${joinWords(names)}, which ${names.length === 1 ? "changes" : "change"} something here.`;
18
+ }
19
+ function joinWords(words) {
20
+ if (words.length <= 1) return words[0] ?? "";
21
+ return `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
22
+ }
23
+ const StatewireClientToolConsentCard = ({
24
+ className,
25
+ agentName = "The agent"
26
+ }) => {
27
+ const { request, allow, decline } = useClientToolWriteConsent();
28
+ if (!request) return null;
29
+ return /* @__PURE__ */ jsxs(
30
+ "section",
31
+ {
32
+ "aria-label": "Permission required",
33
+ "data-testid": "client-tool-consent-card",
34
+ className: cn(
35
+ "aui-client-tool-consent-card w-full rounded-2xl border border-border bg-card px-4 py-3 text-foreground shadow-xs",
36
+ className
37
+ ),
38
+ children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
40
+ /* @__PURE__ */ jsx(MousePointerClickIcon, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }),
41
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
42
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-sm", children: "Allow this?" }),
43
+ /* @__PURE__ */ jsx("p", { className: "break-words pt-1 text-foreground text-sm leading-snug", children: clientToolConsentSentence({ agentName, request }) }),
44
+ /* @__PURE__ */ jsx("p", { className: "pt-1 text-muted-foreground text-sm leading-snug", children: "Allowing covers the rest of this conversation." })
45
+ ] })
46
+ ] }),
47
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-3", children: [
48
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: allow, children: [
49
+ /* @__PURE__ */ jsx(CheckIcon, {}),
50
+ "Allow"
51
+ ] }),
52
+ /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: decline, children: [
53
+ /* @__PURE__ */ jsx(XIcon, {}),
54
+ "Decline"
55
+ ] })
56
+ ] })
57
+ ]
58
+ }
59
+ );
60
+ };
61
+ export {
62
+ StatewireClientToolConsentCard,
63
+ clientToolConsentSentence
64
+ };
65
+ //# sourceMappingURL=StatewireClientToolConsentCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatewireClientToolConsentCard.js","sources":["../../src/chat/StatewireClientToolConsentCard.tsx"],"sourcesContent":["'use client';\n\nimport { CheckIcon, MousePointerClickIcon, XIcon } from 'lucide-react';\nimport type { FC } from 'react';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport {\n type ClientToolWriteConsentRequest,\n useClientToolWriteConsent,\n} from './client-tool-write-consent';\n\nexport interface StatewireClientToolConsentCardProps {\n /** Applied to the card's section. */\n className?: string;\n /**\n * Who is asking, as the card names them (\"Jichard wants to …\"). Defaults to\n * \"The agent\".\n */\n agentName?: string;\n}\n\n/** What the batch does, in the card's sentence: its tools' own words, else their names. */\nexport function clientToolConsentSentence({\n agentName,\n request,\n}: {\n agentName: string;\n request: ClientToolWriteConsentRequest;\n}): string {\n const summaries = Array.from(\n new Set(request.tools.flatMap((tool) => (tool.summary ? [tool.summary] : []))),\n );\n if (summaries.length > 0 && summaries.length === request.tools.length) {\n return `${agentName} wants to ${joinWords(summaries)}.`;\n }\n const names = request.tools.map((tool) => tool.name);\n return `${agentName} wants to run ${joinWords(names)}, which ${names.length === 1 ? 'changes' : 'change'} something here.`;\n}\n\nfunction joinWords(words: readonly string[]): string {\n if (words.length <= 1) return words[0] ?? '';\n return `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`;\n}\n\n/**\n * The question a frontend tool marked `requiresWriteConsent` puts to the\n * person before its first call in a conversation runs: allow (which covers\n * the rest of the conversation) or decline. `AthenaChat` renders it above the\n * composer; a host with its own chat mounts it there, or builds its own on\n * `useClientToolWriteConsent`. Renders nothing while no call is waiting.\n */\nexport const StatewireClientToolConsentCard: FC<StatewireClientToolConsentCardProps> = ({\n className,\n agentName = 'The agent',\n}) => {\n const { request, allow, decline } = useClientToolWriteConsent();\n if (!request) return null;\n return (\n <section\n aria-label=\"Permission required\"\n data-testid=\"client-tool-consent-card\"\n className={cn(\n 'aui-client-tool-consent-card w-full rounded-2xl border border-border bg-card px-4 py-3 text-foreground shadow-xs',\n className,\n )}\n >\n <div className=\"flex items-start gap-3\">\n <MousePointerClickIcon className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-sm\">Allow this?</p>\n <p className=\"break-words pt-1 text-foreground text-sm leading-snug\">\n {clientToolConsentSentence({ agentName, request })}\n </p>\n <p className=\"pt-1 text-muted-foreground text-sm leading-snug\">\n Allowing covers the rest of this conversation.\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2 pt-3\">\n <Button size=\"sm\" onClick={allow}>\n <CheckIcon />\n Allow\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={decline}>\n <XIcon />\n Decline\n </Button>\n </div>\n </section>\n );\n};\n"],"names":[],"mappings":";;;;;AAsBO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AACF,GAGW;AACT,QAAM,YAAY,MAAM;AAAA,IACtB,IAAI,IAAI,QAAQ,MAAM,QAAQ,CAAC,SAAU,KAAK,UAAU,CAAC,KAAK,OAAO,IAAI,CAAA,CAAG,CAAC;AAAA,EAAA;AAE/E,MAAI,UAAU,SAAS,KAAK,UAAU,WAAW,QAAQ,MAAM,QAAQ;AACrE,WAAO,GAAG,SAAS,aAAa,UAAU,SAAS,CAAC;AAAA,EACtD;AACA,QAAM,QAAQ,QAAQ,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI;AACnD,SAAO,GAAG,SAAS,iBAAiB,UAAU,KAAK,CAAC,WAAW,MAAM,WAAW,IAAI,YAAY,QAAQ;AAC1G;AAEA,SAAS,UAAU,OAAkC;AACnD,MAAI,MAAM,UAAU,EAAG,QAAO,MAAM,CAAC,KAAK;AAC1C,SAAO,GAAG,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,QAAQ,MAAM,MAAM,SAAS,CAAC,CAAC;AACxE;AASO,MAAM,iCAA0E,CAAC;AAAA,EACtF;AAAA,EACA,YAAY;AACd,MAAM;AACJ,QAAM,EAAE,SAAS,OAAO,QAAA,IAAY,0BAAA;AACpC,MAAI,CAAC,QAAS,QAAO;AACrB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,cAAW;AAAA,MACX,eAAY;AAAA,MACZ,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,0BACb,UAAA;AAAA,UAAA,oBAAC,uBAAA,EAAsB,WAAU,+CAAA,CAA+C;AAAA,UAChF,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,YAAA,oBAAC,KAAA,EAAE,WAAU,yBAAwB,UAAA,eAAW;AAAA,YAChD,oBAAC,OAAE,WAAU,yDACV,oCAA0B,EAAE,WAAW,QAAA,CAAS,GACnD;AAAA,YACA,oBAAC,KAAA,EAAE,WAAU,mDAAkD,UAAA,iDAAA,CAE/D;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,GACF;AAAA,QACA,qBAAC,OAAA,EAAI,WAAU,0CACb,UAAA;AAAA,UAAA,qBAAC,QAAA,EAAO,MAAK,MAAK,SAAS,OACzB,UAAA;AAAA,YAAA,oBAAC,WAAA,EAAU;AAAA,YAAE;AAAA,UAAA,GAEf;AAAA,+BACC,QAAA,EAAO,MAAK,MAAK,SAAQ,WAAU,SAAS,SAC3C,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAM;AAAA,YAAE;AAAA,UAAA,EAAA,CAEX;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require$$0 = require("react");
4
+ const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
5
+ const errors = require("../diagnostics/errors.cjs");
6
+ function createClientToolWriteConsentStore() {
7
+ let pending = null;
8
+ let cards = 0;
9
+ const granted = /* @__PURE__ */ new Set();
10
+ const listeners = /* @__PURE__ */ new Set();
11
+ const notify = () => {
12
+ for (const listener of Array.from(listeners)) listener();
13
+ };
14
+ const settle = (decision) => {
15
+ const settled = pending;
16
+ pending = null;
17
+ settled == null ? void 0 : settled.resolve(decision);
18
+ notify();
19
+ };
20
+ return {
21
+ getPending: () => (pending == null ? void 0 : pending.request) ?? null,
22
+ subscribe(listener) {
23
+ listeners.add(listener);
24
+ return () => {
25
+ listeners.delete(listener);
26
+ };
27
+ },
28
+ isGranted: (threadId) => granted.has(threadId),
29
+ ask(request) {
30
+ if (granted.has(request.threadId)) return Promise.resolve("proceed");
31
+ if (pending) settle("abandoned");
32
+ if (cards === 0) {
33
+ athenaDiagnostics.athenaDiagnostics.emit("sdk.config.warning", {
34
+ level: "warn",
35
+ error: new errors.AthenaSdkError({
36
+ code: "config_invalid",
37
+ message: "A frontend tool is waiting for the person to allow it, and nothing on screen asks them.",
38
+ hint: "Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.",
39
+ context: { tools: request.tools.map((tool) => tool.name) }
40
+ }),
41
+ data: { tools: request.tools.map((tool) => tool.name) }
42
+ });
43
+ }
44
+ return new Promise((resolve) => {
45
+ pending = { request, resolve };
46
+ notify();
47
+ });
48
+ },
49
+ answer({ requestId, allow }) {
50
+ if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
51
+ if (allow) granted.add(pending.request.threadId);
52
+ settle(allow ? "proceed" : "declined");
53
+ },
54
+ abandon(requestId) {
55
+ if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
56
+ settle("abandoned");
57
+ },
58
+ grant(threadId) {
59
+ granted.add(threadId);
60
+ },
61
+ registerCard() {
62
+ cards += 1;
63
+ return () => {
64
+ cards -= 1;
65
+ };
66
+ },
67
+ dispose() {
68
+ granted.clear();
69
+ if (pending) settle("abandoned");
70
+ }
71
+ };
72
+ }
73
+ function useClientToolWriteConsentStore(scope) {
74
+ const [held, setHeld] = require$$0.useState(() => ({ scope, store: createClientToolWriteConsentStore() }));
75
+ let current = held;
76
+ if (held.scope !== scope) {
77
+ current = { scope, store: createClientToolWriteConsentStore() };
78
+ setHeld(current);
79
+ }
80
+ const { store } = current;
81
+ require$$0.useEffect(() => () => store.dispose(), [store]);
82
+ return store;
83
+ }
84
+ const ClientToolWriteConsentContext = require$$0.createContext(
85
+ null
86
+ );
87
+ const NO_SUBSCRIPTION = () => () => {
88
+ };
89
+ const NO_PENDING = () => null;
90
+ function useClientToolWriteConsent() {
91
+ const store = require$$0.useContext(ClientToolWriteConsentContext);
92
+ const request = require$$0.useSyncExternalStore(
93
+ (store == null ? void 0 : store.subscribe) ?? NO_SUBSCRIPTION,
94
+ (store == null ? void 0 : store.getPending) ?? NO_PENDING,
95
+ NO_PENDING
96
+ );
97
+ require$$0.useEffect(() => store == null ? void 0 : store.registerCard(), [store]);
98
+ const requestId = request == null ? void 0 : request.requestId;
99
+ const allow = require$$0.useCallback(() => {
100
+ if (requestId !== void 0) store == null ? void 0 : store.answer({ requestId, allow: true });
101
+ }, [requestId, store]);
102
+ const decline = require$$0.useCallback(() => {
103
+ if (requestId !== void 0) store == null ? void 0 : store.answer({ requestId, allow: false });
104
+ }, [requestId, store]);
105
+ return { request, allow, decline };
106
+ }
107
+ exports.ClientToolWriteConsentContext = ClientToolWriteConsentContext;
108
+ exports.createClientToolWriteConsentStore = createClientToolWriteConsentStore;
109
+ exports.useClientToolWriteConsent = useClientToolWriteConsent;
110
+ exports.useClientToolWriteConsentStore = useClientToolWriteConsentStore;
111
+ //# sourceMappingURL=client-tool-write-consent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-tool-write-consent.cjs","sources":["../../src/chat/client-tool-write-consent.ts"],"sourcesContent":["'use client';\n\nimport type { ClientToolWriteConsentDecision } from '@athenaintel/agent-runtime';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/*\n * Write consent for the app's own frontend tools. A tool marked with\n * `requiresWriteConsent` parks its first call in a conversation until the\n * person answers; this store holds that question between the client-tool\n * bridge (which asks) and whatever draws it (`StatewireClientToolConsentCard`\n * in `AthenaChat`, or a host's own card on `useClientToolWriteConsent`).\n *\n * Allowing covers the rest of the conversation, as Athena's own apps do, for\n * the person who allowed it: a store belongs to one signed-in person on one\n * Athena (`useClientToolWriteConsentStore`), so someone else signing in to the\n * same mounted chat is asked for themselves. Nothing is remembered across a\n * reload: the next call asks again.\n */\n\n/** One write tool of a batch waiting on the person. */\nexport interface ClientToolWriteConsentTool {\n /** The tool's name, as the model calls it. */\n name: string;\n /** What it does, when the tool said (`requiresWriteConsent(…, { summary })`). */\n summary?: string;\n}\n\n/** The question a parked batch of write calls puts to the person. */\nexport interface ClientToolWriteConsentRequest {\n /** The parked request the answer resumes. */\n requestId: string;\n /** The conversation the answer covers. */\n threadId: string;\n /** The batch's write tools, each once. */\n tools: readonly ClientToolWriteConsentTool[];\n}\n\n/**\n * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).\n * Resolve `true` to allow the conversation's writes, `false` to decline this\n * batch. `signal` aborts when the question no longer has a call to answer\n * (the run was stopped): close the dialog then; the value is ignored.\n */\nexport type ClientToolWriteConfirmer = (\n request: ClientToolWriteConsentRequest & { signal: AbortSignal },\n) => Promise<boolean>;\n\nexport interface ClientToolWriteConsentStore {\n /** The question on screen, or `null`. */\n getPending(): ClientToolWriteConsentRequest | null;\n subscribe(listener: () => void): () => void;\n /** Whether the person already allowed writes in this conversation. */\n isGranted(threadId: string): boolean;\n /** Put the question to the person; resolves with their answer, or `abandoned`. */\n ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;\n /** The person's answer to the pending question. */\n answer(args: { requestId: string; allow: boolean }): void;\n /** The pending question lost its call (stop, another conversation): close it. */\n abandon(requestId: string): void;\n /** Record a consent the host's own UI collected. */\n grant(threadId: string): void;\n /** A mounted card registers itself, so a question nobody can see is reported. */\n registerCard(): () => void;\n /** The store's person is gone: close the pending question and forget every yes. */\n dispose(): void;\n}\n\ninterface PendingConsent {\n request: ClientToolWriteConsentRequest;\n resolve: (decision: ClientToolWriteConsentDecision) => void;\n}\n\nexport function createClientToolWriteConsentStore(): ClientToolWriteConsentStore {\n let pending: PendingConsent | null = null;\n let cards = 0;\n const granted = new Set<string>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of Array.from(listeners)) listener();\n };\n const settle = (decision: ClientToolWriteConsentDecision) => {\n const settled = pending;\n pending = null;\n settled?.resolve(decision);\n notify();\n };\n\n return {\n getPending: () => pending?.request ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isGranted: (threadId) => granted.has(threadId),\n ask(request) {\n if (granted.has(request.threadId)) return Promise.resolve('proceed');\n // One question at a time: a newer batch replaces a question whose call\n // the bridge has already given up on.\n if (pending) settle('abandoned');\n if (cards === 0) {\n athenaDiagnostics.emit('sdk.config.warning', {\n level: 'warn',\n error: new AthenaSdkError({\n code: 'config_invalid',\n message:\n 'A frontend tool is waiting for the person to allow it, and nothing on screen asks them.',\n hint: 'Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.',\n context: { tools: request.tools.map((tool) => tool.name) },\n }),\n data: { tools: request.tools.map((tool) => tool.name) },\n });\n }\n return new Promise<ClientToolWriteConsentDecision>((resolve) => {\n pending = { request, resolve };\n notify();\n });\n },\n answer({ requestId, allow }) {\n if (pending?.request.requestId !== requestId) return;\n if (allow) granted.add(pending.request.threadId);\n settle(allow ? 'proceed' : 'declined');\n },\n abandon(requestId) {\n if (pending?.request.requestId !== requestId) return;\n settle('abandoned');\n },\n grant(threadId) {\n granted.add(threadId);\n },\n registerCard() {\n cards += 1;\n return () => {\n cards -= 1;\n };\n },\n dispose() {\n granted.clear();\n if (pending) settle('abandoned');\n },\n };\n}\n\n/**\n * The consent store for one signed-in person on one Athena. `scope` names\n * both (the provider passes the session principal and the sync host): when it\n * changes, the previous person's store is disposed (its question closed, its\n * yeses forgotten) and the next person starts with none.\n */\nexport function useClientToolWriteConsentStore(scope: string): ClientToolWriteConsentStore {\n const [held, setHeld] = useState(() => ({ scope, store: createClientToolWriteConsentStore() }));\n let current = held;\n if (held.scope !== scope) {\n // Replaced during render, so no commit ever pairs the new person with the\n // previous person's store.\n current = { scope, store: createClientToolWriteConsentStore() };\n setHeld(current);\n }\n const { store } = current;\n useEffect(() => () => store.dispose(), [store]);\n return store;\n}\n\n/** Provided by `AthenaProvider`; `null` outside one. */\nexport const ClientToolWriteConsentContext = createContext<ClientToolWriteConsentStore | null>(\n null,\n);\n\nconst NO_SUBSCRIPTION = () => () => {};\nconst NO_PENDING = () => null;\n\nexport interface ClientToolWriteConsent {\n /** The question waiting on the person, or `null`. */\n request: ClientToolWriteConsentRequest | null;\n /** Allow the conversation's writes and run the waiting calls. */\n allow: () => void;\n /** Decline the waiting calls; the agent is told, and a later call asks again. */\n decline: () => void;\n}\n\n/**\n * The write-consent question for the chat the nearest `AthenaProvider` shows,\n * for a host that draws its own card. Calling it marks the question as shown.\n */\nexport function useClientToolWriteConsent(): ClientToolWriteConsent {\n const store = useContext(ClientToolWriteConsentContext);\n const request = useSyncExternalStore(\n store?.subscribe ?? NO_SUBSCRIPTION,\n store?.getPending ?? NO_PENDING,\n NO_PENDING,\n );\n useEffect(() => store?.registerCard(), [store]);\n const requestId = request?.requestId;\n const allow = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: true });\n }, [requestId, store]);\n const decline = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: false });\n }, [requestId, store]);\n return { request, allow, decline };\n}\n"],"names":["athenaDiagnostics","AthenaSdkError","useState","useEffect","createContext","useContext","useSyncExternalStore","useCallback"],"mappings":"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"}