@athenaintel/react 0.24.1 → 0.26.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 (186) hide show
  1. package/README.md +24 -0
  2. package/dist/assets/AssetPanel.cjs +4 -3
  3. package/dist/assets/AssetPanel.cjs.map +1 -1
  4. package/dist/assets/AssetPanel.js +4 -3
  5. package/dist/assets/AssetPanel.js.map +1 -1
  6. package/dist/assets/AthenaAssetEmbed.cjs +2 -1
  7. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  8. package/dist/assets/AthenaAssetEmbed.js +2 -1
  9. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  10. package/dist/assets/use-asset-embed.cjs +22 -23
  11. package/dist/assets/use-asset-embed.cjs.map +1 -1
  12. package/dist/assets/use-asset-embed.d.ts +9 -0
  13. package/dist/assets/use-asset-embed.js +22 -23
  14. package/dist/assets/use-asset-embed.js.map +1 -1
  15. package/dist/auth/AthenaAuthProvider.cjs +32 -4
  16. package/dist/auth/AthenaAuthProvider.cjs.map +1 -1
  17. package/dist/auth/AthenaAuthProvider.d.ts +21 -0
  18. package/dist/auth/AthenaAuthProvider.js +33 -5
  19. package/dist/auth/AthenaAuthProvider.js.map +1 -1
  20. package/dist/auth/AthenaCredentialSourceContext.cjs +21 -0
  21. package/dist/auth/AthenaCredentialSourceContext.cjs.map +1 -0
  22. package/dist/auth/AthenaCredentialSourceContext.d.ts +14 -0
  23. package/dist/auth/AthenaCredentialSourceContext.js +21 -0
  24. package/dist/auth/AthenaCredentialSourceContext.js.map +1 -0
  25. package/dist/auth/AthenaSSOProvider.cjs +71 -47
  26. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  27. package/dist/auth/AthenaSSOProvider.js +71 -47
  28. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  29. package/dist/auth/credential-source.cjs +120 -0
  30. package/dist/auth/credential-source.cjs.map +1 -0
  31. package/dist/auth/credential-source.d.ts +68 -0
  32. package/dist/auth/credential-source.js +120 -0
  33. package/dist/auth/credential-source.js.map +1 -0
  34. package/dist/auth/types.d.ts +9 -0
  35. package/dist/chat/AthenaChat.cjs +8 -0
  36. package/dist/chat/AthenaChat.cjs.map +1 -1
  37. package/dist/chat/AthenaChat.js +8 -0
  38. package/dist/chat/AthenaChat.js.map +1 -1
  39. package/dist/chat-ui/composer/run-input-rules-on-enter.cjs +14 -0
  40. package/dist/chat-ui/composer/run-input-rules-on-enter.cjs.map +1 -0
  41. package/dist/chat-ui/composer/run-input-rules-on-enter.d.ts +10 -0
  42. package/dist/chat-ui/composer/run-input-rules-on-enter.js +14 -0
  43. package/dist/chat-ui/composer/run-input-rules-on-enter.js.map +1 -0
  44. package/dist/chat-ui/composer/tiptap-composer.cjs +7 -4
  45. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  46. package/dist/chat-ui/composer/tiptap-composer.js +7 -4
  47. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  48. package/dist/chat-ui/markdown-storage.cjs +16 -0
  49. package/dist/chat-ui/markdown-storage.cjs.map +1 -0
  50. package/dist/chat-ui/markdown-storage.d.ts +21 -0
  51. package/dist/chat-ui/markdown-storage.js +16 -0
  52. package/dist/chat-ui/markdown-storage.js.map +1 -0
  53. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +20 -11
  54. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  55. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +11 -2
  56. package/dist/chat-ui/mentions/suggestions/data/api-client.js +20 -11
  57. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  58. package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
  59. package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
  60. package/dist/chat-ui/rich-text/chat-text-schema.cjs +15 -10
  61. package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
  62. package/dist/chat-ui/rich-text/chat-text-schema.js +11 -6
  63. package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
  64. package/dist/collab/mint.cjs +6 -15
  65. package/dist/collab/mint.cjs.map +1 -1
  66. package/dist/collab/mint.d.ts +8 -0
  67. package/dist/collab/mint.js +6 -15
  68. package/dist/collab/mint.js.map +1 -1
  69. package/dist/collab/presence-mint.cjs +20 -16
  70. package/dist/collab/presence-mint.cjs.map +1 -1
  71. package/dist/collab/presence-mint.js +21 -17
  72. package/dist/collab/presence-mint.js.map +1 -1
  73. package/dist/collab/presence-react.cjs +12 -5
  74. package/dist/collab/presence-react.cjs.map +1 -1
  75. package/dist/collab/presence-react.d.ts +3 -1
  76. package/dist/collab/presence-react.js +12 -5
  77. package/dist/collab/presence-react.js.map +1 -1
  78. package/dist/collab/react.cjs +5 -2
  79. package/dist/collab/react.cjs.map +1 -1
  80. package/dist/collab/react.js +5 -2
  81. package/dist/collab/react.js.map +1 -1
  82. package/dist/dictation/use-athena-dictation-engine.cjs +12 -11
  83. package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -1
  84. package/dist/dictation/use-athena-dictation-engine.d.ts +4 -3
  85. package/dist/dictation/use-athena-dictation-engine.js +12 -11
  86. package/dist/dictation/use-athena-dictation-engine.js.map +1 -1
  87. package/dist/index.d.ts +1 -1
  88. package/dist/packages/agent-runtime/src/conversation-list.cjs +3 -0
  89. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
  90. package/dist/packages/agent-runtime/src/conversation-list.js +3 -0
  91. package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
  92. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -2
  93. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  94. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -2
  95. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  96. package/dist/packages/agent-runtime/src/use-conversation-list.cjs +1 -0
  97. package/dist/packages/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  98. package/dist/packages/agent-runtime/src/use-conversation-list.js +1 -0
  99. package/dist/packages/agent-runtime/src/use-conversation-list.js.map +1 -1
  100. package/dist/packages/react/package.json.cjs +1 -1
  101. package/dist/packages/react/package.json.js +1 -1
  102. package/dist/provider/AthenaContext.cjs.map +1 -1
  103. package/dist/provider/AthenaContext.d.ts +7 -1
  104. package/dist/provider/AthenaContext.js.map +1 -1
  105. package/dist/provider/AthenaProvider.cjs +23 -2
  106. package/dist/provider/AthenaProvider.cjs.map +1 -1
  107. package/dist/provider/AthenaProvider.js +23 -2
  108. package/dist/provider/AthenaProvider.js.map +1 -1
  109. package/dist/runtime/auth-refusal-restart.cjs +66 -0
  110. package/dist/runtime/auth-refusal-restart.cjs.map +1 -0
  111. package/dist/runtime/auth-refusal-restart.d.ts +35 -0
  112. package/dist/runtime/auth-refusal-restart.js +66 -0
  113. package/dist/runtime/auth-refusal-restart.js.map +1 -0
  114. package/dist/runtime/auth.cjs +58 -21
  115. package/dist/runtime/auth.cjs.map +1 -1
  116. package/dist/runtime/auth.d.ts +11 -1
  117. package/dist/runtime/auth.js +59 -22
  118. package/dist/runtime/auth.js.map +1 -1
  119. package/dist/runtime/credential-renewal.cjs +29 -0
  120. package/dist/runtime/credential-renewal.cjs.map +1 -0
  121. package/dist/runtime/credential-renewal.d.ts +35 -0
  122. package/dist/runtime/credential-renewal.js +29 -0
  123. package/dist/runtime/credential-renewal.js.map +1 -0
  124. package/dist/runtime/statewire-run-config.cjs +1 -1
  125. package/dist/runtime/statewire-run-config.cjs.map +1 -1
  126. package/dist/runtime/statewire-run-config.d.ts +1 -1
  127. package/dist/runtime/statewire-run-config.js +1 -1
  128. package/dist/runtime/statewire-run-config.js.map +1 -1
  129. package/dist/search/useAthenaRepoSearch.cjs +3 -5
  130. package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
  131. package/dist/search/useAthenaRepoSearch.d.ts +7 -7
  132. package/dist/search/useAthenaRepoSearch.js +3 -5
  133. package/dist/search/useAthenaRepoSearch.js.map +1 -1
  134. package/dist/threads/api.cjs +5 -1
  135. package/dist/threads/api.cjs.map +1 -1
  136. package/dist/threads/api.js +5 -1
  137. package/dist/threads/api.js.map +1 -1
  138. package/dist/threads/conversation-list-fetcher.cjs +5 -5
  139. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  140. package/dist/threads/conversation-list-fetcher.d.ts +6 -5
  141. package/dist/threads/conversation-list-fetcher.js +5 -5
  142. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  143. package/dist/threads/statewire-thread-list.cjs +13 -14
  144. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  145. package/dist/threads/statewire-thread-list.js +13 -14
  146. package/dist/threads/statewire-thread-list.js.map +1 -1
  147. package/dist/threads/use-rest-conversation-history.cjs +6 -2
  148. package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
  149. package/dist/threads/use-rest-conversation-history.d.ts +4 -1
  150. package/dist/threads/use-rest-conversation-history.js +7 -3
  151. package/dist/threads/use-rest-conversation-history.js.map +1 -1
  152. package/dist/threads/use-statewire-conversation-history.cjs +9 -2
  153. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  154. package/dist/threads/use-statewire-conversation-history.d.ts +5 -1
  155. package/dist/threads/use-statewire-conversation-history.js +10 -3
  156. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  157. package/dist/threads/use-thread-archive.cjs +2 -1
  158. package/dist/threads/use-thread-archive.cjs.map +1 -1
  159. package/dist/threads/use-thread-archive.d.ts +3 -1
  160. package/dist/threads/use-thread-archive.js +2 -1
  161. package/dist/threads/use-thread-archive.js.map +1 -1
  162. package/dist/threads/use-thread-favorite.cjs.map +1 -1
  163. package/dist/threads/use-thread-favorite.d.ts +3 -1
  164. package/dist/threads/use-thread-favorite.js.map +1 -1
  165. package/dist/threads/use-thread-field-override.cjs +2 -1
  166. package/dist/threads/use-thread-field-override.cjs.map +1 -1
  167. package/dist/threads/use-thread-field-override.d.ts +3 -1
  168. package/dist/threads/use-thread-field-override.js +2 -1
  169. package/dist/threads/use-thread-field-override.js.map +1 -1
  170. package/dist/threads/use-thread-rename.cjs.map +1 -1
  171. package/dist/threads/use-thread-rename.d.ts +3 -1
  172. package/dist/threads/use-thread-rename.js.map +1 -1
  173. package/dist/tools/append-document-tool-ui.cjs +5 -0
  174. package/dist/tools/append-document-tool-ui.cjs.map +1 -1
  175. package/dist/tools/append-document-tool-ui.js +5 -0
  176. package/dist/tools/append-document-tool-ui.js.map +1 -1
  177. package/dist/tools/tool-uis.cjs +11 -15
  178. package/dist/tools/tool-uis.cjs.map +1 -1
  179. package/dist/tools/tool-uis.js +11 -15
  180. package/dist/tools/tool-uis.js.map +1 -1
  181. package/dist/voice/use-athena-voice-session.cjs +17 -5
  182. package/dist/voice/use-athena-voice-session.cjs.map +1 -1
  183. package/dist/voice/use-athena-voice-session.d.ts +2 -2
  184. package/dist/voice/use-athena-voice-session.js +17 -5
  185. package/dist/voice/use-athena-voice-session.js.map +1 -1
  186. package/package.json +13 -16
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const credentialRenewal = require("../runtime/credential-renewal.cjs");
3
4
  const mint = require("./mint.cjs");
4
5
  function workspaceUrl({
5
6
  auth,
@@ -10,17 +11,17 @@ function workspaceUrl({
10
11
  return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;
11
12
  }
12
13
  async function mintWorkspacePresenceToken(args) {
13
- const response = await fetch(
14
- workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence-token" }),
15
- {
14
+ const response = await credentialRenewal.sendWithCredentialRenewal(
15
+ args.auth,
16
+ (headers) => fetch(workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence-token" }), {
16
17
  method: "POST",
17
18
  headers: {
18
19
  "Content-Type": "application/json",
19
- ...mint.getCollabAuthHeaders(args.auth)
20
+ ...headers
20
21
  },
21
22
  body: JSON.stringify({}),
22
23
  signal: args.signal
23
- }
24
+ })
24
25
  );
25
26
  if (!response.ok) {
26
27
  throw await mint.collabErrorFromResponse(
@@ -34,17 +35,20 @@ function arrayOrEmpty(value) {
34
35
  return Array.isArray(value) ? value : [];
35
36
  }
36
37
  async function resolveWorkspacePresence(args) {
37
- const response = await fetch(
38
- workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence/resolve" }),
39
- {
40
- method: "POST",
41
- headers: {
42
- "Content-Type": "application/json",
43
- ...mint.getCollabAuthHeaders(args.auth)
44
- },
45
- body: JSON.stringify(args.request),
46
- signal: args.signal
47
- }
38
+ const response = await credentialRenewal.sendWithCredentialRenewal(
39
+ args.auth,
40
+ (headers) => fetch(
41
+ workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence/resolve" }),
42
+ {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ ...headers
47
+ },
48
+ body: JSON.stringify(args.request),
49
+ signal: args.signal
50
+ }
51
+ )
48
52
  );
49
53
  if (!response.ok) {
50
54
  throw await mint.collabErrorFromResponse(
@@ -1 +1 @@
1
- {"version":3,"file":"presence-mint.cjs","sources":["../../src/collab/presence-mint.ts"],"sourcesContent":["/**\n * Public-API clients for the workspace presence feed.\n *\n * Two endpoints, both admin-only and both under the workspace:\n *\n * - `POST /api/v0/workspaces/{workspaceId}/presence-token` mints a\n * **read-only** Keryx token for the workspace awareness room\n * (`<workspaceId>/awareness`) — the same per-recipient-filtered presence\n * feed the Olympus People page renders. The holder can receive presence and\n * broadcast nothing; `connectWorkspacePresence` re-mints before expiry.\n * - `POST /api/v0/workspaces/{workspaceId}/presence/resolve` turns the opaque\n * ids carried in awareness contexts (asset ids, session thread ids, project\n * ids) into titles the caller may see. Only things the caller may open come\n * back; missing ids are simply absent.\n *\n * Wire shapes are snake_case, matching `CollabTokenResponse`.\n */\n\nimport {\n type CollabAuth,\n CollabTokenError,\n collabErrorFromResponse,\n getAthenaApiBaseUrl,\n getCollabAuthHeaders,\n} from './mint';\n\n/** Grant for the workspace awareness room. Always read-only (`access_type: 'r'`). */\nexport interface WorkspacePresenceTokenResponse {\n token: string;\n access_type: 'r' | 'rw';\n expires_at_ms: number;\n ws_url: string;\n org: string;\n doc_id: string;\n branch: string;\n}\n\nexport interface WorkspacePresenceResolveRequest {\n asset_ids?: string[];\n /** Thread ids, as published in `session` awareness contexts. */\n session_ids?: string[];\n project_ids?: string[];\n include_projects?: boolean;\n include_members?: boolean;\n}\n\nexport interface WorkspacePresenceResolvedAsset {\n id: string;\n title: string;\n asset_type: string;\n}\n\nexport interface WorkspacePresenceResolvedSession {\n thread_id: string;\n asset_id: string;\n title: string;\n}\n\nexport interface WorkspacePresenceResolvedProject {\n id: string;\n title: string;\n assets: WorkspacePresenceResolvedAsset[];\n}\n\nexport interface WorkspacePresenceResolvedMember {\n id: string;\n name: string;\n email: string;\n avatar_url: string | null;\n}\n\nexport interface WorkspacePresenceResolveResponse {\n assets: WorkspacePresenceResolvedAsset[];\n sessions: WorkspacePresenceResolvedSession[];\n projects: WorkspacePresenceResolvedProject[];\n members: WorkspacePresenceResolvedMember[];\n}\n\nfunction workspaceUrl({\n auth,\n workspaceId,\n path,\n}: {\n auth: CollabAuth;\n workspaceId: string;\n path: string;\n}): string {\n const base = getAthenaApiBaseUrl(auth.backendUrl);\n return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;\n}\n\n/**\n * Mint a read-only token for the workspace awareness room.\n *\n * Rejects with `CollabTokenError` on 401 (bad credential) and 403 (admin\n * required, roster not enabled for this workspace, or a sandbox credential\n * that may not read presence), carrying the server's `detail`.\n */\nexport async function mintWorkspacePresenceToken(args: {\n auth: CollabAuth;\n workspaceId: string;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceTokenResponse> {\n const response = await fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence-token' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...getCollabAuthHeaders(args.auth),\n },\n body: JSON.stringify({}),\n signal: args.signal,\n }\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence token mint failed with status ${response.status}`\n );\n }\n return (await response.json()) as WorkspacePresenceTokenResponse;\n}\n\nfunction arrayOrEmpty<T>(value: T[] | undefined | null): T[] {\n return Array.isArray(value) ? value : [];\n}\n\n/**\n * Resolve awareness ids to titles. Every section of the response is always\n * present on the returned object (an omitted section reads as empty), so\n * callers can index without guarding. Failures reject with `CollabTokenError`\n * — the same status + detail shape as the mint.\n */\nexport async function resolveWorkspacePresence(args: {\n auth: CollabAuth;\n workspaceId: string;\n request: WorkspacePresenceResolveRequest;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceResolveResponse> {\n const response = await fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence/resolve' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...getCollabAuthHeaders(args.auth),\n },\n body: JSON.stringify(args.request),\n signal: args.signal,\n }\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence resolve failed with status ${response.status}`\n );\n }\n const body = (await response.json()) as Partial<WorkspacePresenceResolveResponse> | null;\n if (!body || typeof body !== 'object') {\n throw new CollabTokenError(response.status, 'Workspace presence resolve returned no body');\n }\n return {\n assets: arrayOrEmpty(body.assets),\n sessions: arrayOrEmpty(body.sessions),\n projects: arrayOrEmpty(body.projects),\n members: arrayOrEmpty(body.members),\n };\n}\n"],"names":["getAthenaApiBaseUrl","getCollabAuthHeaders","collabErrorFromResponse","CollabTokenError"],"mappings":";;;AA8EA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,OAAOA,KAAAA,oBAAoB,KAAK,UAAU;AAChD,SAAO,GAAG,IAAI,sBAAsB,mBAAmB,WAAW,CAAC,IAAI,IAAI;AAC7E;AASA,eAAsB,2BAA2B,MAIL;AAC1C,QAAM,WAAW,MAAM;AAAA,IACrB,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,kBAAkB;AAAA,IACvF;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAGC,KAAAA,qBAAqB,KAAK,IAAI;AAAA,MAAA;AAAA,MAEnC,MAAM,KAAK,UAAU,EAAE;AAAA,MACvB,QAAQ,KAAK;AAAA,IAAA;AAAA,EACf;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAMC,KAAAA;AAAAA,MACV;AAAA,MACA,oDAAoD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEvE;AACA,SAAQ,MAAM,SAAS,KAAA;AACzB;AAEA,SAAS,aAAgB,OAAoC;AAC3D,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA;AACxC;AAQA,eAAsB,yBAAyB,MAKD;AAC5C,QAAM,WAAW,MAAM;AAAA,IACrB,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,oBAAoB;AAAA,IACzF;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAGD,KAAAA,qBAAqB,KAAK,IAAI;AAAA,MAAA;AAAA,MAEnC,MAAM,KAAK,UAAU,KAAK,OAAO;AAAA,MACjC,QAAQ,KAAK;AAAA,IAAA;AAAA,EACf;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAMC,KAAAA;AAAAA,MACV;AAAA,MACA,iDAAiD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEpE;AACA,QAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAIC,KAAAA,iBAAiB,SAAS,QAAQ,6CAA6C;AAAA,EAC3F;AACA,SAAO;AAAA,IACL,QAAQ,aAAa,KAAK,MAAM;AAAA,IAChC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,SAAS,aAAa,KAAK,OAAO;AAAA,EAAA;AAEtC;;;"}
1
+ {"version":3,"file":"presence-mint.cjs","sources":["../../src/collab/presence-mint.ts"],"sourcesContent":["/**\n * Public-API clients for the workspace presence feed.\n *\n * Two endpoints, both admin-only and both under the workspace:\n *\n * - `POST /api/v0/workspaces/{workspaceId}/presence-token` mints a\n * **read-only** Keryx token for the workspace awareness room\n * (`<workspaceId>/awareness`) — the same per-recipient-filtered presence\n * feed the Olympus People page renders. The holder can receive presence and\n * broadcast nothing; `connectWorkspacePresence` re-mints before expiry.\n * - `POST /api/v0/workspaces/{workspaceId}/presence/resolve` turns the opaque\n * ids carried in awareness contexts (asset ids, session thread ids, project\n * ids) into titles the caller may see. Only things the caller may open come\n * back; missing ids are simply absent.\n *\n * Wire shapes are snake_case, matching `CollabTokenResponse`.\n */\n\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport {\n type CollabAuth,\n CollabTokenError,\n collabErrorFromResponse,\n getAthenaApiBaseUrl,\n} from './mint';\n\n/** Grant for the workspace awareness room. Always read-only (`access_type: 'r'`). */\nexport interface WorkspacePresenceTokenResponse {\n token: string;\n access_type: 'r' | 'rw';\n expires_at_ms: number;\n ws_url: string;\n org: string;\n doc_id: string;\n branch: string;\n}\n\nexport interface WorkspacePresenceResolveRequest {\n asset_ids?: string[];\n /** Thread ids, as published in `session` awareness contexts. */\n session_ids?: string[];\n project_ids?: string[];\n include_projects?: boolean;\n include_members?: boolean;\n}\n\nexport interface WorkspacePresenceResolvedAsset {\n id: string;\n title: string;\n asset_type: string;\n}\n\nexport interface WorkspacePresenceResolvedSession {\n thread_id: string;\n asset_id: string;\n title: string;\n}\n\nexport interface WorkspacePresenceResolvedProject {\n id: string;\n title: string;\n assets: WorkspacePresenceResolvedAsset[];\n}\n\nexport interface WorkspacePresenceResolvedMember {\n id: string;\n name: string;\n email: string;\n avatar_url: string | null;\n}\n\nexport interface WorkspacePresenceResolveResponse {\n assets: WorkspacePresenceResolvedAsset[];\n sessions: WorkspacePresenceResolvedSession[];\n projects: WorkspacePresenceResolvedProject[];\n members: WorkspacePresenceResolvedMember[];\n}\n\nfunction workspaceUrl({\n auth,\n workspaceId,\n path,\n}: {\n auth: CollabAuth;\n workspaceId: string;\n path: string;\n}): string {\n const base = getAthenaApiBaseUrl(auth.backendUrl);\n return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;\n}\n\n/**\n * Mint a read-only token for the workspace awareness room.\n *\n * Rejects with `CollabTokenError` on 401 (bad credential) and 403 (admin\n * required, roster not enabled for this workspace, or a sandbox credential\n * that may not read presence), carrying the server's `detail`.\n */\nexport async function mintWorkspacePresenceToken(args: {\n auth: CollabAuth;\n workspaceId: string;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceTokenResponse> {\n const response = await sendWithCredentialRenewal(args.auth, (headers) =>\n fetch(workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence-token' }), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify({}),\n signal: args.signal,\n })\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence token mint failed with status ${response.status}`\n );\n }\n return (await response.json()) as WorkspacePresenceTokenResponse;\n}\n\nfunction arrayOrEmpty<T>(value: T[] | undefined | null): T[] {\n return Array.isArray(value) ? value : [];\n}\n\n/**\n * Resolve awareness ids to titles. Every section of the response is always\n * present on the returned object (an omitted section reads as empty), so\n * callers can index without guarding. Failures reject with `CollabTokenError`\n * — the same status + detail shape as the mint.\n */\nexport async function resolveWorkspacePresence(args: {\n auth: CollabAuth;\n workspaceId: string;\n request: WorkspacePresenceResolveRequest;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceResolveResponse> {\n const response = await sendWithCredentialRenewal(args.auth, (headers) =>\n fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence/resolve' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(args.request),\n signal: args.signal,\n }\n )\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence resolve failed with status ${response.status}`\n );\n }\n const body = (await response.json()) as Partial<WorkspacePresenceResolveResponse> | null;\n if (!body || typeof body !== 'object') {\n throw new CollabTokenError(response.status, 'Workspace presence resolve returned no body');\n }\n return {\n assets: arrayOrEmpty(body.assets),\n sessions: arrayOrEmpty(body.sessions),\n projects: arrayOrEmpty(body.projects),\n members: arrayOrEmpty(body.members),\n };\n}\n"],"names":["getAthenaApiBaseUrl","sendWithCredentialRenewal","collabErrorFromResponse","CollabTokenError"],"mappings":";;;;AA8EA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,OAAOA,KAAAA,oBAAoB,KAAK,UAAU;AAChD,SAAO,GAAG,IAAI,sBAAsB,mBAAmB,WAAW,CAAC,IAAI,IAAI;AAC7E;AASA,eAAsB,2BAA2B,MAIL;AAC1C,QAAM,WAAW,MAAMC,kBAAAA;AAAAA,IAA0B,KAAK;AAAA,IAAM,CAAC,YAC3D,MAAM,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,iBAAA,CAAkB,GAAG;AAAA,MAC9F,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG;AAAA,MAAA;AAAA,MAEL,MAAM,KAAK,UAAU,EAAE;AAAA,MACvB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAEH,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAMC,KAAAA;AAAAA,MACV;AAAA,MACA,oDAAoD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEvE;AACA,SAAQ,MAAM,SAAS,KAAA;AACzB;AAEA,SAAS,aAAgB,OAAoC;AAC3D,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA;AACxC;AAQA,eAAsB,yBAAyB,MAKD;AAC5C,QAAM,WAAW,MAAMD,kBAAAA;AAAAA,IAA0B,KAAK;AAAA,IAAM,CAAC,YAC3D;AAAA,MACE,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,oBAAoB;AAAA,MACzF;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAG;AAAA,QAAA;AAAA,QAEL,MAAM,KAAK,UAAU,KAAK,OAAO;AAAA,QACjC,QAAQ,KAAK;AAAA,MAAA;AAAA,IACf;AAAA,EACF;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAMC,KAAAA;AAAAA,MACV;AAAA,MACA,iDAAiD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEpE;AACA,QAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAIC,KAAAA,iBAAiB,SAAS,QAAQ,6CAA6C;AAAA,EAC3F;AACA,SAAO;AAAA,IACL,QAAQ,aAAa,KAAK,MAAM;AAAA,IAChC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,SAAS,aAAa,KAAK,OAAO;AAAA,EAAA;AAEtC;;;"}
@@ -1,4 +1,5 @@
1
- import { getCollabAuthHeaders, collabErrorFromResponse, CollabTokenError, getAthenaApiBaseUrl } from "./mint.js";
1
+ import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
2
+ import { collabErrorFromResponse, CollabTokenError, getAthenaApiBaseUrl } from "./mint.js";
2
3
  function workspaceUrl({
3
4
  auth,
4
5
  workspaceId,
@@ -8,17 +9,17 @@ function workspaceUrl({
8
9
  return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;
9
10
  }
10
11
  async function mintWorkspacePresenceToken(args) {
11
- const response = await fetch(
12
- workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence-token" }),
13
- {
12
+ const response = await sendWithCredentialRenewal(
13
+ args.auth,
14
+ (headers) => fetch(workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence-token" }), {
14
15
  method: "POST",
15
16
  headers: {
16
17
  "Content-Type": "application/json",
17
- ...getCollabAuthHeaders(args.auth)
18
+ ...headers
18
19
  },
19
20
  body: JSON.stringify({}),
20
21
  signal: args.signal
21
- }
22
+ })
22
23
  );
23
24
  if (!response.ok) {
24
25
  throw await collabErrorFromResponse(
@@ -32,17 +33,20 @@ function arrayOrEmpty(value) {
32
33
  return Array.isArray(value) ? value : [];
33
34
  }
34
35
  async function resolveWorkspacePresence(args) {
35
- const response = await fetch(
36
- workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence/resolve" }),
37
- {
38
- method: "POST",
39
- headers: {
40
- "Content-Type": "application/json",
41
- ...getCollabAuthHeaders(args.auth)
42
- },
43
- body: JSON.stringify(args.request),
44
- signal: args.signal
45
- }
36
+ const response = await sendWithCredentialRenewal(
37
+ args.auth,
38
+ (headers) => fetch(
39
+ workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: "presence/resolve" }),
40
+ {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ ...headers
45
+ },
46
+ body: JSON.stringify(args.request),
47
+ signal: args.signal
48
+ }
49
+ )
46
50
  );
47
51
  if (!response.ok) {
48
52
  throw await collabErrorFromResponse(
@@ -1 +1 @@
1
- {"version":3,"file":"presence-mint.js","sources":["../../src/collab/presence-mint.ts"],"sourcesContent":["/**\n * Public-API clients for the workspace presence feed.\n *\n * Two endpoints, both admin-only and both under the workspace:\n *\n * - `POST /api/v0/workspaces/{workspaceId}/presence-token` mints a\n * **read-only** Keryx token for the workspace awareness room\n * (`<workspaceId>/awareness`) — the same per-recipient-filtered presence\n * feed the Olympus People page renders. The holder can receive presence and\n * broadcast nothing; `connectWorkspacePresence` re-mints before expiry.\n * - `POST /api/v0/workspaces/{workspaceId}/presence/resolve` turns the opaque\n * ids carried in awareness contexts (asset ids, session thread ids, project\n * ids) into titles the caller may see. Only things the caller may open come\n * back; missing ids are simply absent.\n *\n * Wire shapes are snake_case, matching `CollabTokenResponse`.\n */\n\nimport {\n type CollabAuth,\n CollabTokenError,\n collabErrorFromResponse,\n getAthenaApiBaseUrl,\n getCollabAuthHeaders,\n} from './mint';\n\n/** Grant for the workspace awareness room. Always read-only (`access_type: 'r'`). */\nexport interface WorkspacePresenceTokenResponse {\n token: string;\n access_type: 'r' | 'rw';\n expires_at_ms: number;\n ws_url: string;\n org: string;\n doc_id: string;\n branch: string;\n}\n\nexport interface WorkspacePresenceResolveRequest {\n asset_ids?: string[];\n /** Thread ids, as published in `session` awareness contexts. */\n session_ids?: string[];\n project_ids?: string[];\n include_projects?: boolean;\n include_members?: boolean;\n}\n\nexport interface WorkspacePresenceResolvedAsset {\n id: string;\n title: string;\n asset_type: string;\n}\n\nexport interface WorkspacePresenceResolvedSession {\n thread_id: string;\n asset_id: string;\n title: string;\n}\n\nexport interface WorkspacePresenceResolvedProject {\n id: string;\n title: string;\n assets: WorkspacePresenceResolvedAsset[];\n}\n\nexport interface WorkspacePresenceResolvedMember {\n id: string;\n name: string;\n email: string;\n avatar_url: string | null;\n}\n\nexport interface WorkspacePresenceResolveResponse {\n assets: WorkspacePresenceResolvedAsset[];\n sessions: WorkspacePresenceResolvedSession[];\n projects: WorkspacePresenceResolvedProject[];\n members: WorkspacePresenceResolvedMember[];\n}\n\nfunction workspaceUrl({\n auth,\n workspaceId,\n path,\n}: {\n auth: CollabAuth;\n workspaceId: string;\n path: string;\n}): string {\n const base = getAthenaApiBaseUrl(auth.backendUrl);\n return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;\n}\n\n/**\n * Mint a read-only token for the workspace awareness room.\n *\n * Rejects with `CollabTokenError` on 401 (bad credential) and 403 (admin\n * required, roster not enabled for this workspace, or a sandbox credential\n * that may not read presence), carrying the server's `detail`.\n */\nexport async function mintWorkspacePresenceToken(args: {\n auth: CollabAuth;\n workspaceId: string;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceTokenResponse> {\n const response = await fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence-token' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...getCollabAuthHeaders(args.auth),\n },\n body: JSON.stringify({}),\n signal: args.signal,\n }\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence token mint failed with status ${response.status}`\n );\n }\n return (await response.json()) as WorkspacePresenceTokenResponse;\n}\n\nfunction arrayOrEmpty<T>(value: T[] | undefined | null): T[] {\n return Array.isArray(value) ? value : [];\n}\n\n/**\n * Resolve awareness ids to titles. Every section of the response is always\n * present on the returned object (an omitted section reads as empty), so\n * callers can index without guarding. Failures reject with `CollabTokenError`\n * — the same status + detail shape as the mint.\n */\nexport async function resolveWorkspacePresence(args: {\n auth: CollabAuth;\n workspaceId: string;\n request: WorkspacePresenceResolveRequest;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceResolveResponse> {\n const response = await fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence/resolve' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...getCollabAuthHeaders(args.auth),\n },\n body: JSON.stringify(args.request),\n signal: args.signal,\n }\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence resolve failed with status ${response.status}`\n );\n }\n const body = (await response.json()) as Partial<WorkspacePresenceResolveResponse> | null;\n if (!body || typeof body !== 'object') {\n throw new CollabTokenError(response.status, 'Workspace presence resolve returned no body');\n }\n return {\n assets: arrayOrEmpty(body.assets),\n sessions: arrayOrEmpty(body.sessions),\n projects: arrayOrEmpty(body.projects),\n members: arrayOrEmpty(body.members),\n };\n}\n"],"names":[],"mappings":";AA8EA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,OAAO,oBAAoB,KAAK,UAAU;AAChD,SAAO,GAAG,IAAI,sBAAsB,mBAAmB,WAAW,CAAC,IAAI,IAAI;AAC7E;AASA,eAAsB,2BAA2B,MAIL;AAC1C,QAAM,WAAW,MAAM;AAAA,IACrB,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,kBAAkB;AAAA,IACvF;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,qBAAqB,KAAK,IAAI;AAAA,MAAA;AAAA,MAEnC,MAAM,KAAK,UAAU,EAAE;AAAA,MACvB,QAAQ,KAAK;AAAA,IAAA;AAAA,EACf;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM;AAAA,MACV;AAAA,MACA,oDAAoD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEvE;AACA,SAAQ,MAAM,SAAS,KAAA;AACzB;AAEA,SAAS,aAAgB,OAAoC;AAC3D,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA;AACxC;AAQA,eAAsB,yBAAyB,MAKD;AAC5C,QAAM,WAAW,MAAM;AAAA,IACrB,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,oBAAoB;AAAA,IACzF;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG,qBAAqB,KAAK,IAAI;AAAA,MAAA;AAAA,MAEnC,MAAM,KAAK,UAAU,KAAK,OAAO;AAAA,MACjC,QAAQ,KAAK;AAAA,IAAA;AAAA,EACf;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM;AAAA,MACV;AAAA,MACA,iDAAiD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEpE;AACA,QAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,iBAAiB,SAAS,QAAQ,6CAA6C;AAAA,EAC3F;AACA,SAAO;AAAA,IACL,QAAQ,aAAa,KAAK,MAAM;AAAA,IAChC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,SAAS,aAAa,KAAK,OAAO;AAAA,EAAA;AAEtC;"}
1
+ {"version":3,"file":"presence-mint.js","sources":["../../src/collab/presence-mint.ts"],"sourcesContent":["/**\n * Public-API clients for the workspace presence feed.\n *\n * Two endpoints, both admin-only and both under the workspace:\n *\n * - `POST /api/v0/workspaces/{workspaceId}/presence-token` mints a\n * **read-only** Keryx token for the workspace awareness room\n * (`<workspaceId>/awareness`) — the same per-recipient-filtered presence\n * feed the Olympus People page renders. The holder can receive presence and\n * broadcast nothing; `connectWorkspacePresence` re-mints before expiry.\n * - `POST /api/v0/workspaces/{workspaceId}/presence/resolve` turns the opaque\n * ids carried in awareness contexts (asset ids, session thread ids, project\n * ids) into titles the caller may see. Only things the caller may open come\n * back; missing ids are simply absent.\n *\n * Wire shapes are snake_case, matching `CollabTokenResponse`.\n */\n\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport {\n type CollabAuth,\n CollabTokenError,\n collabErrorFromResponse,\n getAthenaApiBaseUrl,\n} from './mint';\n\n/** Grant for the workspace awareness room. Always read-only (`access_type: 'r'`). */\nexport interface WorkspacePresenceTokenResponse {\n token: string;\n access_type: 'r' | 'rw';\n expires_at_ms: number;\n ws_url: string;\n org: string;\n doc_id: string;\n branch: string;\n}\n\nexport interface WorkspacePresenceResolveRequest {\n asset_ids?: string[];\n /** Thread ids, as published in `session` awareness contexts. */\n session_ids?: string[];\n project_ids?: string[];\n include_projects?: boolean;\n include_members?: boolean;\n}\n\nexport interface WorkspacePresenceResolvedAsset {\n id: string;\n title: string;\n asset_type: string;\n}\n\nexport interface WorkspacePresenceResolvedSession {\n thread_id: string;\n asset_id: string;\n title: string;\n}\n\nexport interface WorkspacePresenceResolvedProject {\n id: string;\n title: string;\n assets: WorkspacePresenceResolvedAsset[];\n}\n\nexport interface WorkspacePresenceResolvedMember {\n id: string;\n name: string;\n email: string;\n avatar_url: string | null;\n}\n\nexport interface WorkspacePresenceResolveResponse {\n assets: WorkspacePresenceResolvedAsset[];\n sessions: WorkspacePresenceResolvedSession[];\n projects: WorkspacePresenceResolvedProject[];\n members: WorkspacePresenceResolvedMember[];\n}\n\nfunction workspaceUrl({\n auth,\n workspaceId,\n path,\n}: {\n auth: CollabAuth;\n workspaceId: string;\n path: string;\n}): string {\n const base = getAthenaApiBaseUrl(auth.backendUrl);\n return `${base}/api/v0/workspaces/${encodeURIComponent(workspaceId)}/${path}`;\n}\n\n/**\n * Mint a read-only token for the workspace awareness room.\n *\n * Rejects with `CollabTokenError` on 401 (bad credential) and 403 (admin\n * required, roster not enabled for this workspace, or a sandbox credential\n * that may not read presence), carrying the server's `detail`.\n */\nexport async function mintWorkspacePresenceToken(args: {\n auth: CollabAuth;\n workspaceId: string;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceTokenResponse> {\n const response = await sendWithCredentialRenewal(args.auth, (headers) =>\n fetch(workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence-token' }), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify({}),\n signal: args.signal,\n })\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence token mint failed with status ${response.status}`\n );\n }\n return (await response.json()) as WorkspacePresenceTokenResponse;\n}\n\nfunction arrayOrEmpty<T>(value: T[] | undefined | null): T[] {\n return Array.isArray(value) ? value : [];\n}\n\n/**\n * Resolve awareness ids to titles. Every section of the response is always\n * present on the returned object (an omitted section reads as empty), so\n * callers can index without guarding. Failures reject with `CollabTokenError`\n * — the same status + detail shape as the mint.\n */\nexport async function resolveWorkspacePresence(args: {\n auth: CollabAuth;\n workspaceId: string;\n request: WorkspacePresenceResolveRequest;\n signal?: AbortSignal;\n}): Promise<WorkspacePresenceResolveResponse> {\n const response = await sendWithCredentialRenewal(args.auth, (headers) =>\n fetch(\n workspaceUrl({ auth: args.auth, workspaceId: args.workspaceId, path: 'presence/resolve' }),\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(args.request),\n signal: args.signal,\n }\n )\n );\n if (!response.ok) {\n throw await collabErrorFromResponse(\n response,\n `Workspace presence resolve failed with status ${response.status}`\n );\n }\n const body = (await response.json()) as Partial<WorkspacePresenceResolveResponse> | null;\n if (!body || typeof body !== 'object') {\n throw new CollabTokenError(response.status, 'Workspace presence resolve returned no body');\n }\n return {\n assets: arrayOrEmpty(body.assets),\n sessions: arrayOrEmpty(body.sessions),\n projects: arrayOrEmpty(body.projects),\n members: arrayOrEmpty(body.members),\n };\n}\n"],"names":[],"mappings":";;AA8EA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,OAAO,oBAAoB,KAAK,UAAU;AAChD,SAAO,GAAG,IAAI,sBAAsB,mBAAmB,WAAW,CAAC,IAAI,IAAI;AAC7E;AASA,eAAsB,2BAA2B,MAIL;AAC1C,QAAM,WAAW,MAAM;AAAA,IAA0B,KAAK;AAAA,IAAM,CAAC,YAC3D,MAAM,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,iBAAA,CAAkB,GAAG;AAAA,MAC9F,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG;AAAA,MAAA;AAAA,MAEL,MAAM,KAAK,UAAU,EAAE;AAAA,MACvB,QAAQ,KAAK;AAAA,IAAA,CACd;AAAA,EAAA;AAEH,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM;AAAA,MACV;AAAA,MACA,oDAAoD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEvE;AACA,SAAQ,MAAM,SAAS,KAAA;AACzB;AAEA,SAAS,aAAgB,OAAoC;AAC3D,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAA;AACxC;AAQA,eAAsB,yBAAyB,MAKD;AAC5C,QAAM,WAAW,MAAM;AAAA,IAA0B,KAAK;AAAA,IAAM,CAAC,YAC3D;AAAA,MACE,aAAa,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,aAAa,MAAM,oBAAoB;AAAA,MACzF;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAG;AAAA,QAAA;AAAA,QAEL,MAAM,KAAK,UAAU,KAAK,OAAO;AAAA,QACjC,QAAQ,KAAK;AAAA,MAAA;AAAA,IACf;AAAA,EACF;AAEF,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM;AAAA,MACV;AAAA,MACA,iDAAiD,SAAS,MAAM;AAAA,IAAA;AAAA,EAEpE;AACA,QAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,iBAAiB,SAAS,QAAQ,6CAA6C;AAAA,EAC3F;AACA,SAAO;AAAA,IACL,QAAQ,aAAa,KAAK,MAAM;AAAA,IAChC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,UAAU,aAAa,KAAK,QAAQ;AAAA,IACpC,SAAS,aAAa,KAAK,OAAO;AAAA,EAAA;AAEtC;"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
+ const AthenaCredentialSourceContext = require("../auth/AthenaCredentialSourceContext.cjs");
4
5
  const AthenaContext = require("../provider/AthenaContext.cjs");
5
6
  const presenceClient = require("./presence-client.cjs");
6
7
  const presenceMint = require("./presence-mint.cjs");
@@ -11,6 +12,7 @@ const NO_HUMANS = [];
11
12
  const NO_AGENTS = [];
12
13
  function useWorkspacePresence(workspaceId, options) {
13
14
  const config = AthenaContext.useAthenaConfig();
15
+ const identityEpoch = AthenaCredentialSourceContext.useAthenaCredentialIdentityEpoch();
14
16
  const enabled = (options == null ? void 0 : options.enabled) !== false && workspaceId !== null;
15
17
  const tickMs = (options == null ? void 0 : options.tickMs) ?? PRESENCE_DEFAULT_TICK_MS;
16
18
  const [entry, setEntry] = react.useState(null);
@@ -25,7 +27,8 @@ function useWorkspacePresence(workspaceId, options) {
25
27
  auth: {
26
28
  backendUrl: config.backendUrl,
27
29
  apiKey: config.apiKey,
28
- token: config.token
30
+ token: config.token,
31
+ getToken: config.getToken
29
32
  }
30
33
  });
31
34
  handleRef.current = next;
@@ -34,7 +37,7 @@ function useWorkspacePresence(workspaceId, options) {
34
37
  handleRef.current = null;
35
38
  next.destroy();
36
39
  };
37
- }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.token]);
40
+ }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
38
41
  const handle = enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;
39
42
  const subscribe = react.useMemo(() => {
40
43
  return (onStoreChange) => {
@@ -90,7 +93,6 @@ function useWorkspacePresence(workspaceId, options) {
90
93
  }
91
94
  function useWorkspacePresenceResolver(workspaceId) {
92
95
  const config = AthenaContext.useAthenaConfig();
93
- const { backendUrl, apiKey, token } = config;
94
96
  return react.useCallback(
95
97
  (request, signal) => {
96
98
  if (workspaceId === null) {
@@ -99,13 +101,18 @@ function useWorkspacePresenceResolver(workspaceId) {
99
101
  );
100
102
  }
101
103
  return presenceMint.resolveWorkspacePresence({
102
- auth: { backendUrl, apiKey, token },
104
+ auth: {
105
+ backendUrl: config.backendUrl,
106
+ apiKey: config.apiKey,
107
+ token: config.token,
108
+ getToken: config.getToken
109
+ },
103
110
  workspaceId,
104
111
  request,
105
112
  signal
106
113
  });
107
114
  },
108
- [workspaceId, backendUrl, apiKey, token]
115
+ [workspaceId, config]
109
116
  );
110
117
  }
111
118
  exports.PRESENCE_DEFAULT_TICK_MS = PRESENCE_DEFAULT_TICK_MS;
@@ -1 +1 @@
1
- {"version":3,"file":"presence-react.cjs","sources":["../../src/collab/presence-react.ts"],"sourcesContent":["/**\n * React bindings for the workspace presence feed.\n *\n * `useWorkspacePresence(workspaceId)` opens (and owns) a\n * `connectWorkspacePresence` handle, resolving auth from the surrounding\n * `AthenaProvider` (per-viewer bearer when present, API key otherwise), and\n * re-renders on status/roster changes via `useSyncExternalStore`. Each person\n * carries a `state` on the intent ladder, recomputed against a clock that\n * ticks every `tickMs` so `active` decays to `idle` and `away` between\n * awareness events.\n *\n * `useWorkspacePresenceResolver(workspaceId)` returns the batch id→title\n * resolver bound to the same auth. It is deliberately thin: callers own\n * caching (TanStack Query is the expected home for it).\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type WorkspacePresenceHandle,\n type WorkspacePresenceStatus,\n connectWorkspacePresence,\n} from './presence-client';\nimport {\n type WorkspacePresenceResolveRequest,\n type WorkspacePresenceResolveResponse,\n resolveWorkspacePresence,\n} from './presence-mint';\nimport {\n type PresenceAgent,\n type PresenceHuman,\n type PresenceState,\n derivePresenceState,\n} from './presence-slots';\n\n/** Default cadence for re-deriving each person's intent state. */\nexport const PRESENCE_DEFAULT_TICK_MS = 10_000;\n\nexport interface PresenceHumanWithState extends PresenceHuman {\n /** Where the person sits on the intent ladder as of the last tick. */\n state: PresenceState;\n}\n\nexport interface UseWorkspacePresenceOptions {\n /** `false` closes the feed without unmounting the caller. Default `true`. */\n enabled?: boolean;\n /** How often `state` is re-derived from `lastIntentAt`. Default 10 s. */\n tickMs?: number;\n}\n\nexport interface UseWorkspacePresenceResult {\n /** `'closed'` while disabled or without a workspace; `'connecting'` until the first mint resolves. */\n status: WorkspacePresenceStatus;\n error: Error | null;\n accessType: 'r' | 'rw' | null;\n humans: PresenceHumanWithState[];\n agents: PresenceAgent[];\n /** Force an early token refresh; resolves immediately when no feed is open. */\n refreshNow: () => Promise<void>;\n}\n\nconst EMPTY_SNAPSHOT = 'init||0';\nconst NO_HUMANS: PresenceHuman[] = [];\nconst NO_AGENTS: PresenceAgent[] = [];\n\nexport function useWorkspacePresence(\n workspaceId: string | null,\n options?: UseWorkspacePresenceOptions\n): UseWorkspacePresenceResult {\n const config = useAthenaConfig();\n const enabled = options?.enabled !== false && workspaceId !== null;\n const tickMs = options?.tickMs ?? PRESENCE_DEFAULT_TICK_MS;\n const [entry, setEntry] = useState<{\n workspaceId: string;\n handle: WorkspacePresenceHandle;\n } | null>(null);\n const handleRef = useRef<WorkspacePresenceHandle | null>(null);\n\n useEffect(() => {\n if (!enabled || workspaceId === null) {\n setEntry(null);\n return;\n }\n const next = connectWorkspacePresence({\n workspaceId,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setEntry({ workspaceId, handle: next });\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.token]);\n\n // React renders with the new arguments before the effect above runs, so a\n // handle opened for a previous workspace, or before the feed was disabled,\n // still sits in state for that one render. Expose it only while it is the\n // handle this render asked for; otherwise the closed / connecting empty\n // result wins, and a disabled feed never leaks a roster.\n const handle =\n enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n // The ladder decays with wall-clock time, not with awareness events; a\n // person who stops typing must drift to idle even when nothing arrives.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!enabled) return;\n setNow(Date.now());\n const timer = setInterval(() => {\n setNow(Date.now());\n }, tickMs);\n return () => {\n clearInterval(timer);\n };\n }, [enabled, tickMs]);\n\n const rosterHumans = handle?.humans ?? NO_HUMANS;\n const humans = useMemo(\n () =>\n rosterHumans.map((human) => ({\n ...human,\n state: derivePresenceState({\n lastIntentAt: human.lastIntentAt,\n hidden: human.hidden,\n manualAway: human.manualAway,\n now,\n }),\n })),\n [rosterHumans, now]\n );\n\n const refreshNow = useCallback(() => handleRef.current?.refreshNow() ?? Promise.resolve(), []);\n\n return {\n status: handle ? handle.status : enabled ? 'connecting' : 'closed',\n error: handle?.error ?? null,\n accessType: handle?.accessType ?? null,\n humans,\n agents: handle?.agents ?? NO_AGENTS,\n refreshNow,\n };\n}\n\nexport type WorkspacePresenceResolver = (\n request: WorkspacePresenceResolveRequest,\n signal?: AbortSignal\n) => Promise<WorkspacePresenceResolveResponse>;\n\n/**\n * A stable resolver bound to the provider's auth and the given workspace.\n * Rejects when called without a workspace; no caching — callers own that.\n */\nexport function useWorkspacePresenceResolver(\n workspaceId: string | null\n): WorkspacePresenceResolver {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, token } = config;\n return useCallback<WorkspacePresenceResolver>(\n (request, signal) => {\n if (workspaceId === null) {\n return Promise.reject(\n new Error('[AthenaSDK] useWorkspacePresenceResolver: no workspace selected')\n );\n }\n return resolveWorkspacePresence({\n auth: { backendUrl, apiKey, token },\n workspaceId,\n request,\n signal,\n });\n },\n [workspaceId, backendUrl, apiKey, token]\n );\n}\n"],"names":["useAthenaConfig","useState","useRef","useEffect","connectWorkspacePresence","useMemo","useSyncExternalStore","derivePresenceState","useCallback","resolveWorkspacePresence"],"mappings":"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"}
1
+ {"version":3,"file":"presence-react.cjs","sources":["../../src/collab/presence-react.ts"],"sourcesContent":["/**\n * React bindings for the workspace presence feed.\n *\n * `useWorkspacePresence(workspaceId)` opens (and owns) a\n * `connectWorkspacePresence` handle, resolving auth from the surrounding\n * `AthenaProvider` (per-viewer bearer when present, API key otherwise), and\n * re-renders on status/roster changes via `useSyncExternalStore`. Each person\n * carries a `state` on the intent ladder, recomputed against a clock that\n * ticks every `tickMs` so `active` decays to `idle` and `away` between\n * awareness events.\n *\n * `useWorkspacePresenceResolver(workspaceId)` returns the batch id→title\n * resolver bound to the same auth. It is deliberately thin: callers own\n * caching (TanStack Query is the expected home for it).\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type WorkspacePresenceHandle,\n type WorkspacePresenceStatus,\n connectWorkspacePresence,\n} from './presence-client';\nimport {\n type WorkspacePresenceResolveRequest,\n type WorkspacePresenceResolveResponse,\n resolveWorkspacePresence,\n} from './presence-mint';\nimport {\n type PresenceAgent,\n type PresenceHuman,\n type PresenceState,\n derivePresenceState,\n} from './presence-slots';\n\n/** Default cadence for re-deriving each person's intent state. */\nexport const PRESENCE_DEFAULT_TICK_MS = 10_000;\n\nexport interface PresenceHumanWithState extends PresenceHuman {\n /** Where the person sits on the intent ladder as of the last tick. */\n state: PresenceState;\n}\n\nexport interface UseWorkspacePresenceOptions {\n /** `false` closes the feed without unmounting the caller. Default `true`. */\n enabled?: boolean;\n /** How often `state` is re-derived from `lastIntentAt`. Default 10 s. */\n tickMs?: number;\n}\n\nexport interface UseWorkspacePresenceResult {\n /** `'closed'` while disabled or without a workspace; `'connecting'` until the first mint resolves. */\n status: WorkspacePresenceStatus;\n error: Error | null;\n accessType: 'r' | 'rw' | null;\n humans: PresenceHumanWithState[];\n agents: PresenceAgent[];\n /** Force an early token refresh; resolves immediately when no feed is open. */\n refreshNow: () => Promise<void>;\n}\n\nconst EMPTY_SNAPSHOT = 'init||0';\nconst NO_HUMANS: PresenceHuman[] = [];\nconst NO_AGENTS: PresenceAgent[] = [];\n\nexport function useWorkspacePresence(\n workspaceId: string | null,\n options?: UseWorkspacePresenceOptions\n): UseWorkspacePresenceResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const enabled = options?.enabled !== false && workspaceId !== null;\n const tickMs = options?.tickMs ?? PRESENCE_DEFAULT_TICK_MS;\n const [entry, setEntry] = useState<{\n workspaceId: string;\n handle: WorkspacePresenceHandle;\n } | null>(null);\n const handleRef = useRef<WorkspacePresenceHandle | null>(null);\n\n useEffect(() => {\n if (!enabled || workspaceId === null) {\n setEntry(null);\n return;\n }\n const next = connectWorkspacePresence({\n workspaceId,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setEntry({ workspaceId, handle: next });\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n // React renders with the new arguments before the effect above runs, so a\n // handle opened for a previous workspace, or before the feed was disabled,\n // still sits in state for that one render. Expose it only while it is the\n // handle this render asked for; otherwise the closed / connecting empty\n // result wins, and a disabled feed never leaks a roster.\n const handle =\n enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n // The ladder decays with wall-clock time, not with awareness events; a\n // person who stops typing must drift to idle even when nothing arrives.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!enabled) return;\n setNow(Date.now());\n const timer = setInterval(() => {\n setNow(Date.now());\n }, tickMs);\n return () => {\n clearInterval(timer);\n };\n }, [enabled, tickMs]);\n\n const rosterHumans = handle?.humans ?? NO_HUMANS;\n const humans = useMemo(\n () =>\n rosterHumans.map((human) => ({\n ...human,\n state: derivePresenceState({\n lastIntentAt: human.lastIntentAt,\n hidden: human.hidden,\n manualAway: human.manualAway,\n now,\n }),\n })),\n [rosterHumans, now]\n );\n\n const refreshNow = useCallback(() => handleRef.current?.refreshNow() ?? Promise.resolve(), []);\n\n return {\n status: handle ? handle.status : enabled ? 'connecting' : 'closed',\n error: handle?.error ?? null,\n accessType: handle?.accessType ?? null,\n humans,\n agents: handle?.agents ?? NO_AGENTS,\n refreshNow,\n };\n}\n\nexport type WorkspacePresenceResolver = (\n request: WorkspacePresenceResolveRequest,\n signal?: AbortSignal\n) => Promise<WorkspacePresenceResolveResponse>;\n\n/**\n * A stable resolver bound to the provider's auth and the given workspace.\n * Credentials are read on every call (the provider serves the current token\n * through getters), and `getToken` renews a refused one once. Rejects when\n * called without a workspace; no caching — callers own that.\n */\nexport function useWorkspacePresenceResolver(\n workspaceId: string | null\n): WorkspacePresenceResolver {\n const config = useAthenaConfig();\n return useCallback<WorkspacePresenceResolver>(\n (request, signal) => {\n if (workspaceId === null) {\n return Promise.reject(\n new Error('[AthenaSDK] useWorkspacePresenceResolver: no workspace selected')\n );\n }\n return resolveWorkspacePresence({\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n workspaceId,\n request,\n signal,\n });\n },\n [workspaceId, config]\n );\n}\n"],"names":["useAthenaConfig","useAthenaCredentialIdentityEpoch","useState","useRef","useEffect","connectWorkspacePresence","useMemo","useSyncExternalStore","derivePresenceState","useCallback","resolveWorkspacePresence"],"mappings":"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"}
@@ -42,6 +42,8 @@ export declare function useWorkspacePresence(workspaceId: string | null, options
42
42
  export type WorkspacePresenceResolver = (request: WorkspacePresenceResolveRequest, signal?: AbortSignal) => Promise<WorkspacePresenceResolveResponse>;
43
43
  /**
44
44
  * A stable resolver bound to the provider's auth and the given workspace.
45
- * Rejects when called without a workspace; no caching — callers own that.
45
+ * Credentials are read on every call (the provider serves the current token
46
+ * through getters), and `getToken` renews a refused one once. Rejects when
47
+ * called without a workspace; no caching — callers own that.
46
48
  */
47
49
  export declare function useWorkspacePresenceResolver(workspaceId: string | null): WorkspacePresenceResolver;
@@ -1,4 +1,5 @@
1
1
  import { useState, useRef, useEffect, useMemo, useSyncExternalStore, useCallback } from "react";
2
+ import { useAthenaCredentialIdentityEpoch } from "../auth/AthenaCredentialSourceContext.js";
2
3
  import { useAthenaConfig } from "../provider/AthenaContext.js";
3
4
  import { connectWorkspacePresence } from "./presence-client.js";
4
5
  import { resolveWorkspacePresence } from "./presence-mint.js";
@@ -9,6 +10,7 @@ const NO_HUMANS = [];
9
10
  const NO_AGENTS = [];
10
11
  function useWorkspacePresence(workspaceId, options) {
11
12
  const config = useAthenaConfig();
13
+ const identityEpoch = useAthenaCredentialIdentityEpoch();
12
14
  const enabled = (options == null ? void 0 : options.enabled) !== false && workspaceId !== null;
13
15
  const tickMs = (options == null ? void 0 : options.tickMs) ?? PRESENCE_DEFAULT_TICK_MS;
14
16
  const [entry, setEntry] = useState(null);
@@ -23,7 +25,8 @@ function useWorkspacePresence(workspaceId, options) {
23
25
  auth: {
24
26
  backendUrl: config.backendUrl,
25
27
  apiKey: config.apiKey,
26
- token: config.token
28
+ token: config.token,
29
+ getToken: config.getToken
27
30
  }
28
31
  });
29
32
  handleRef.current = next;
@@ -32,7 +35,7 @@ function useWorkspacePresence(workspaceId, options) {
32
35
  handleRef.current = null;
33
36
  next.destroy();
34
37
  };
35
- }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.token]);
38
+ }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
36
39
  const handle = enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;
37
40
  const subscribe = useMemo(() => {
38
41
  return (onStoreChange) => {
@@ -88,7 +91,6 @@ function useWorkspacePresence(workspaceId, options) {
88
91
  }
89
92
  function useWorkspacePresenceResolver(workspaceId) {
90
93
  const config = useAthenaConfig();
91
- const { backendUrl, apiKey, token } = config;
92
94
  return useCallback(
93
95
  (request, signal) => {
94
96
  if (workspaceId === null) {
@@ -97,13 +99,18 @@ function useWorkspacePresenceResolver(workspaceId) {
97
99
  );
98
100
  }
99
101
  return resolveWorkspacePresence({
100
- auth: { backendUrl, apiKey, token },
102
+ auth: {
103
+ backendUrl: config.backendUrl,
104
+ apiKey: config.apiKey,
105
+ token: config.token,
106
+ getToken: config.getToken
107
+ },
101
108
  workspaceId,
102
109
  request,
103
110
  signal
104
111
  });
105
112
  },
106
- [workspaceId, backendUrl, apiKey, token]
113
+ [workspaceId, config]
107
114
  );
108
115
  }
109
116
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"presence-react.js","sources":["../../src/collab/presence-react.ts"],"sourcesContent":["/**\n * React bindings for the workspace presence feed.\n *\n * `useWorkspacePresence(workspaceId)` opens (and owns) a\n * `connectWorkspacePresence` handle, resolving auth from the surrounding\n * `AthenaProvider` (per-viewer bearer when present, API key otherwise), and\n * re-renders on status/roster changes via `useSyncExternalStore`. Each person\n * carries a `state` on the intent ladder, recomputed against a clock that\n * ticks every `tickMs` so `active` decays to `idle` and `away` between\n * awareness events.\n *\n * `useWorkspacePresenceResolver(workspaceId)` returns the batch id→title\n * resolver bound to the same auth. It is deliberately thin: callers own\n * caching (TanStack Query is the expected home for it).\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type WorkspacePresenceHandle,\n type WorkspacePresenceStatus,\n connectWorkspacePresence,\n} from './presence-client';\nimport {\n type WorkspacePresenceResolveRequest,\n type WorkspacePresenceResolveResponse,\n resolveWorkspacePresence,\n} from './presence-mint';\nimport {\n type PresenceAgent,\n type PresenceHuman,\n type PresenceState,\n derivePresenceState,\n} from './presence-slots';\n\n/** Default cadence for re-deriving each person's intent state. */\nexport const PRESENCE_DEFAULT_TICK_MS = 10_000;\n\nexport interface PresenceHumanWithState extends PresenceHuman {\n /** Where the person sits on the intent ladder as of the last tick. */\n state: PresenceState;\n}\n\nexport interface UseWorkspacePresenceOptions {\n /** `false` closes the feed without unmounting the caller. Default `true`. */\n enabled?: boolean;\n /** How often `state` is re-derived from `lastIntentAt`. Default 10 s. */\n tickMs?: number;\n}\n\nexport interface UseWorkspacePresenceResult {\n /** `'closed'` while disabled or without a workspace; `'connecting'` until the first mint resolves. */\n status: WorkspacePresenceStatus;\n error: Error | null;\n accessType: 'r' | 'rw' | null;\n humans: PresenceHumanWithState[];\n agents: PresenceAgent[];\n /** Force an early token refresh; resolves immediately when no feed is open. */\n refreshNow: () => Promise<void>;\n}\n\nconst EMPTY_SNAPSHOT = 'init||0';\nconst NO_HUMANS: PresenceHuman[] = [];\nconst NO_AGENTS: PresenceAgent[] = [];\n\nexport function useWorkspacePresence(\n workspaceId: string | null,\n options?: UseWorkspacePresenceOptions\n): UseWorkspacePresenceResult {\n const config = useAthenaConfig();\n const enabled = options?.enabled !== false && workspaceId !== null;\n const tickMs = options?.tickMs ?? PRESENCE_DEFAULT_TICK_MS;\n const [entry, setEntry] = useState<{\n workspaceId: string;\n handle: WorkspacePresenceHandle;\n } | null>(null);\n const handleRef = useRef<WorkspacePresenceHandle | null>(null);\n\n useEffect(() => {\n if (!enabled || workspaceId === null) {\n setEntry(null);\n return;\n }\n const next = connectWorkspacePresence({\n workspaceId,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setEntry({ workspaceId, handle: next });\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.token]);\n\n // React renders with the new arguments before the effect above runs, so a\n // handle opened for a previous workspace, or before the feed was disabled,\n // still sits in state for that one render. Expose it only while it is the\n // handle this render asked for; otherwise the closed / connecting empty\n // result wins, and a disabled feed never leaks a roster.\n const handle =\n enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n // The ladder decays with wall-clock time, not with awareness events; a\n // person who stops typing must drift to idle even when nothing arrives.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!enabled) return;\n setNow(Date.now());\n const timer = setInterval(() => {\n setNow(Date.now());\n }, tickMs);\n return () => {\n clearInterval(timer);\n };\n }, [enabled, tickMs]);\n\n const rosterHumans = handle?.humans ?? NO_HUMANS;\n const humans = useMemo(\n () =>\n rosterHumans.map((human) => ({\n ...human,\n state: derivePresenceState({\n lastIntentAt: human.lastIntentAt,\n hidden: human.hidden,\n manualAway: human.manualAway,\n now,\n }),\n })),\n [rosterHumans, now]\n );\n\n const refreshNow = useCallback(() => handleRef.current?.refreshNow() ?? Promise.resolve(), []);\n\n return {\n status: handle ? handle.status : enabled ? 'connecting' : 'closed',\n error: handle?.error ?? null,\n accessType: handle?.accessType ?? null,\n humans,\n agents: handle?.agents ?? NO_AGENTS,\n refreshNow,\n };\n}\n\nexport type WorkspacePresenceResolver = (\n request: WorkspacePresenceResolveRequest,\n signal?: AbortSignal\n) => Promise<WorkspacePresenceResolveResponse>;\n\n/**\n * A stable resolver bound to the provider's auth and the given workspace.\n * Rejects when called without a workspace; no caching — callers own that.\n */\nexport function useWorkspacePresenceResolver(\n workspaceId: string | null\n): WorkspacePresenceResolver {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, token } = config;\n return useCallback<WorkspacePresenceResolver>(\n (request, signal) => {\n if (workspaceId === null) {\n return Promise.reject(\n new Error('[AthenaSDK] useWorkspacePresenceResolver: no workspace selected')\n );\n }\n return resolveWorkspacePresence({\n auth: { backendUrl, apiKey, token },\n workspaceId,\n request,\n signal,\n });\n },\n [workspaceId, backendUrl, apiKey, token]\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"presence-react.js","sources":["../../src/collab/presence-react.ts"],"sourcesContent":["/**\n * React bindings for the workspace presence feed.\n *\n * `useWorkspacePresence(workspaceId)` opens (and owns) a\n * `connectWorkspacePresence` handle, resolving auth from the surrounding\n * `AthenaProvider` (per-viewer bearer when present, API key otherwise), and\n * re-renders on status/roster changes via `useSyncExternalStore`. Each person\n * carries a `state` on the intent ladder, recomputed against a clock that\n * ticks every `tickMs` so `active` decays to `idle` and `away` between\n * awareness events.\n *\n * `useWorkspacePresenceResolver(workspaceId)` returns the batch id→title\n * resolver bound to the same auth. It is deliberately thin: callers own\n * caching (TanStack Query is the expected home for it).\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type WorkspacePresenceHandle,\n type WorkspacePresenceStatus,\n connectWorkspacePresence,\n} from './presence-client';\nimport {\n type WorkspacePresenceResolveRequest,\n type WorkspacePresenceResolveResponse,\n resolveWorkspacePresence,\n} from './presence-mint';\nimport {\n type PresenceAgent,\n type PresenceHuman,\n type PresenceState,\n derivePresenceState,\n} from './presence-slots';\n\n/** Default cadence for re-deriving each person's intent state. */\nexport const PRESENCE_DEFAULT_TICK_MS = 10_000;\n\nexport interface PresenceHumanWithState extends PresenceHuman {\n /** Where the person sits on the intent ladder as of the last tick. */\n state: PresenceState;\n}\n\nexport interface UseWorkspacePresenceOptions {\n /** `false` closes the feed without unmounting the caller. Default `true`. */\n enabled?: boolean;\n /** How often `state` is re-derived from `lastIntentAt`. Default 10 s. */\n tickMs?: number;\n}\n\nexport interface UseWorkspacePresenceResult {\n /** `'closed'` while disabled or without a workspace; `'connecting'` until the first mint resolves. */\n status: WorkspacePresenceStatus;\n error: Error | null;\n accessType: 'r' | 'rw' | null;\n humans: PresenceHumanWithState[];\n agents: PresenceAgent[];\n /** Force an early token refresh; resolves immediately when no feed is open. */\n refreshNow: () => Promise<void>;\n}\n\nconst EMPTY_SNAPSHOT = 'init||0';\nconst NO_HUMANS: PresenceHuman[] = [];\nconst NO_AGENTS: PresenceAgent[] = [];\n\nexport function useWorkspacePresence(\n workspaceId: string | null,\n options?: UseWorkspacePresenceOptions\n): UseWorkspacePresenceResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const enabled = options?.enabled !== false && workspaceId !== null;\n const tickMs = options?.tickMs ?? PRESENCE_DEFAULT_TICK_MS;\n const [entry, setEntry] = useState<{\n workspaceId: string;\n handle: WorkspacePresenceHandle;\n } | null>(null);\n const handleRef = useRef<WorkspacePresenceHandle | null>(null);\n\n useEffect(() => {\n if (!enabled || workspaceId === null) {\n setEntry(null);\n return;\n }\n const next = connectWorkspacePresence({\n workspaceId,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setEntry({ workspaceId, handle: next });\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, workspaceId, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n // React renders with the new arguments before the effect above runs, so a\n // handle opened for a previous workspace, or before the feed was disabled,\n // still sits in state for that one render. Expose it only while it is the\n // handle this render asked for; otherwise the closed / connecting empty\n // result wins, and a disabled feed never leaks a roster.\n const handle =\n enabled && entry !== null && entry.workspaceId === workspaceId ? entry.handle : null;\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n // The ladder decays with wall-clock time, not with awareness events; a\n // person who stops typing must drift to idle even when nothing arrives.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!enabled) return;\n setNow(Date.now());\n const timer = setInterval(() => {\n setNow(Date.now());\n }, tickMs);\n return () => {\n clearInterval(timer);\n };\n }, [enabled, tickMs]);\n\n const rosterHumans = handle?.humans ?? NO_HUMANS;\n const humans = useMemo(\n () =>\n rosterHumans.map((human) => ({\n ...human,\n state: derivePresenceState({\n lastIntentAt: human.lastIntentAt,\n hidden: human.hidden,\n manualAway: human.manualAway,\n now,\n }),\n })),\n [rosterHumans, now]\n );\n\n const refreshNow = useCallback(() => handleRef.current?.refreshNow() ?? Promise.resolve(), []);\n\n return {\n status: handle ? handle.status : enabled ? 'connecting' : 'closed',\n error: handle?.error ?? null,\n accessType: handle?.accessType ?? null,\n humans,\n agents: handle?.agents ?? NO_AGENTS,\n refreshNow,\n };\n}\n\nexport type WorkspacePresenceResolver = (\n request: WorkspacePresenceResolveRequest,\n signal?: AbortSignal\n) => Promise<WorkspacePresenceResolveResponse>;\n\n/**\n * A stable resolver bound to the provider's auth and the given workspace.\n * Credentials are read on every call (the provider serves the current token\n * through getters), and `getToken` renews a refused one once. Rejects when\n * called without a workspace; no caching — callers own that.\n */\nexport function useWorkspacePresenceResolver(\n workspaceId: string | null\n): WorkspacePresenceResolver {\n const config = useAthenaConfig();\n return useCallback<WorkspacePresenceResolver>(\n (request, signal) => {\n if (workspaceId === null) {\n return Promise.reject(\n new Error('[AthenaSDK] useWorkspacePresenceResolver: no workspace selected')\n );\n }\n return resolveWorkspacePresence({\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n workspaceId,\n request,\n signal,\n });\n },\n [workspaceId, config]\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
+ const AthenaCredentialSourceContext = require("../auth/AthenaCredentialSourceContext.cjs");
4
5
  const AthenaContext = require("../provider/AthenaContext.cjs");
5
6
  const client = require("./client.cjs");
6
7
  function useGenericDoc(assetId, options) {
7
8
  const config = AthenaContext.useAthenaConfig();
9
+ const identityEpoch = AthenaCredentialSourceContext.useAthenaCredentialIdentityEpoch();
8
10
  const access = (options == null ? void 0 : options.access) ?? "view";
9
11
  const [handle, setHandle] = react.useState(null);
10
12
  const handleRef = react.useRef(null);
@@ -15,7 +17,8 @@ function useGenericDoc(assetId, options) {
15
17
  auth: {
16
18
  backendUrl: config.backendUrl,
17
19
  apiKey: config.apiKey,
18
- token: config.token
20
+ token: config.token,
21
+ getToken: config.getToken
19
22
  }
20
23
  });
21
24
  handleRef.current = next;
@@ -24,7 +27,7 @@ function useGenericDoc(assetId, options) {
24
27
  handleRef.current = null;
25
28
  next.destroy();
26
29
  };
27
- }, [assetId, access, config.backendUrl, config.apiKey, config.token]);
30
+ }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
28
31
  const subscribe = react.useMemo(() => {
29
32
  return (onStoreChange) => {
30
33
  if (!handle) return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [assetId, access, config.backendUrl, config.apiKey, config.token]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":["useAthenaConfig","useState","useRef","useEffect","connectGenericDoc","useMemo","useSyncExternalStore"],"mappings":";;;;;AAmCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAASA,cAAAA,gBAAA;AACf,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAkC,IAAI;AAClE,QAAM,YAAYC,MAAAA,OAAgC,IAAI;AAEtDC,QAAAA,UAAU,MAAM;AACd,UAAM,OAAOC,OAAAA,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MAAA;AAAA,IAChB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAGF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,KAAK,CAAC;AAEpE,QAAM,YAAYC,MAAAA,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEMC,QAAAA;AAAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;;"}
1
+ {"version":3,"file":"react.cjs","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":["useAthenaConfig","useAthenaCredentialIdentityEpoch","useState","useRef","useEffect","connectGenericDoc","useMemo","useSyncExternalStore"],"mappings":";;;;;;AAoCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAASA,cAAAA,gBAAA;AACf,QAAM,gBAAgBC,8BAAAA,iCAAA;AACtB,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAkC,IAAI;AAClE,QAAM,YAAYC,MAAAA,OAAgC,IAAI;AAEtDC,QAAAA,UAAU,MAAM;AACd,UAAM,OAAOC,OAAAA,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,MAAA;AAAA,IACnB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAMF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,UAAU,aAAa,CAAC;AAEtF,QAAM,YAAYC,MAAAA,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEMC,QAAAA;AAAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;;"}
@@ -1,8 +1,10 @@
1
1
  import { useState, useRef, useEffect, useMemo, useSyncExternalStore } from "react";
2
+ import { useAthenaCredentialIdentityEpoch } from "../auth/AthenaCredentialSourceContext.js";
2
3
  import { useAthenaConfig } from "../provider/AthenaContext.js";
3
4
  import { connectGenericDoc } from "./client.js";
4
5
  function useGenericDoc(assetId, options) {
5
6
  const config = useAthenaConfig();
7
+ const identityEpoch = useAthenaCredentialIdentityEpoch();
6
8
  const access = (options == null ? void 0 : options.access) ?? "view";
7
9
  const [handle, setHandle] = useState(null);
8
10
  const handleRef = useRef(null);
@@ -13,7 +15,8 @@ function useGenericDoc(assetId, options) {
13
15
  auth: {
14
16
  backendUrl: config.backendUrl,
15
17
  apiKey: config.apiKey,
16
- token: config.token
18
+ token: config.token,
19
+ getToken: config.getToken
17
20
  }
18
21
  });
19
22
  handleRef.current = next;
@@ -22,7 +25,7 @@ function useGenericDoc(assetId, options) {
22
25
  handleRef.current = null;
23
26
  next.destroy();
24
27
  };
25
- }, [assetId, access, config.backendUrl, config.apiKey, config.token]);
28
+ }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
26
29
  const subscribe = useMemo(() => {
27
30
  return (onStoreChange) => {
28
31
  if (!handle) return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [assetId, access, config.backendUrl, config.apiKey, config.token]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":[],"mappings":";;;AAmCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAAS,gBAAA;AACf,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,IAAI;AAClE,QAAM,YAAY,OAAgC,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,OAAO,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MAAA;AAAA,IAChB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAGF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,KAAK,CAAC;AAEpE,QAAM,YAAY,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEM;AAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;"}
1
+ {"version":3,"file":"react.js","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":[],"mappings":";;;;AAoCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAAS,gBAAA;AACf,QAAM,gBAAgB,iCAAA;AACtB,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,IAAI;AAClE,QAAM,YAAY,OAAgC,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,OAAO,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,MAAA;AAAA,IACnB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAMF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,UAAU,aAAa,CAAC;AAEtF,QAAM,YAAY,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEM;AAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;"}
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
4
  const AthenaContext = require("../provider/AthenaContext.cjs");
5
- const statewireAuthHeaders = require("../runtime/statewire-auth-headers.cjs");
5
+ const credentialRenewal = require("../runtime/credential-renewal.cjs");
6
6
  const dictationEngine = require("./dictation-engine.cjs");
7
7
  const recordedDictationEngine = require("./recorded-dictation-engine.cjs");
8
8
  function transcriptionUploadUrl(backendUrl) {
@@ -28,17 +28,18 @@ function transcriptionUploadErrorMessage(status) {
28
28
  function createAthenaUploadTranscriber(getConfig) {
29
29
  return async (clip, { lang, signal }) => {
30
30
  const config = getConfig();
31
- const headers = await statewireAuthHeaders.resolveStatewireAuthHeaders(config, { staleAuth: false });
32
- const form = new FormData();
33
31
  const extension = clip.type.includes("mp4") ? "mp4" : clip.type.includes("ogg") ? "ogg" : "webm";
34
- form.append("file", clip, `dictation.${extension}`);
35
- if (lang) form.append("language", lang);
36
- const response = await fetch(transcriptionUploadUrl(config.backendUrl), {
37
- method: "POST",
38
- headers,
39
- body: form,
40
- credentials: "include",
41
- signal
32
+ const response = await credentialRenewal.sendWithCredentialRenewal(config, (headers) => {
33
+ const form = new FormData();
34
+ form.append("file", clip, `dictation.${extension}`);
35
+ if (lang) form.append("language", lang);
36
+ return fetch(transcriptionUploadUrl(config.backendUrl), {
37
+ method: "POST",
38
+ headers,
39
+ body: form,
40
+ credentials: "include",
41
+ signal
42
+ });
42
43
  });
43
44
  if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));
44
45
  const payload = await response.json();
@@ -1 +1 @@
1
- {"version":3,"file":"use-athena-dictation-engine.cjs","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { resolveStatewireAuthHeaders } from '../runtime/statewire-auth-headers';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up. The session language (host `lang`\n * or the document's) is forwarded so non-English speech is recognised as\n * such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });\n const form = new FormData();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n const response = await fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":["resolveStatewireAuthHeaders","useAthenaConfig","useRef","useMemo","createRecordedDictationEngine","createWebSpeechDictationEngine"],"mappings":";;;;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AASO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,UAAU,MAAMA,qBAAAA,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,UAAM,OAAO,IAAI,SAAA;AACjB,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,SAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,QAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,UAAM,WAAW,MAAM,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAASC,cAAAA,gBAAA;AACf,QAAM,YAAYC,MAAAA,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAOC,MAAAA,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAWC,wBAAAA,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAWC,gBAAAA,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;;;;;"}
1
+ {"version":3,"file":"use-athena-dictation-engine.cjs","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up, and a `401` gets one forced renewal\n * (`getToken({ force: true })`) and a resend before it fails. The session\n * language (host `lang` or the document's) is forwarded so non-English speech\n * is recognised as such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n const response = await sendWithCredentialRenewal(config, (headers) => {\n // A request body is single-use: every attempt builds its own form.\n const form = new FormData();\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n return fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":["sendWithCredentialRenewal","useAthenaConfig","useRef","useMemo","createRecordedDictationEngine","createWebSpeechDictationEngine"],"mappings":";;;;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AAUO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,UAAM,WAAW,MAAMA,kBAAAA,0BAA0B,QAAQ,CAAC,YAAY;AAEpE,YAAM,OAAO,IAAI,SAAA;AACjB,WAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,UAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,aAAO,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,QACtD,QAAQ;AAAA,QACR;AAAA,QACA,MAAM;AAAA,QACN,aAAa;AAAA,QACb;AAAA,MAAA,CACD;AAAA,IACH,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAASC,cAAAA,gBAAA;AACf,QAAM,YAAYC,MAAAA,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAOC,MAAAA,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAWC,wBAAAA,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAWC,gBAAAA,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;;;;;"}