@opengeni/react 6.1.0 → 7.0.0-canary.35585322809001

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 (206) hide show
  1. package/README.md +83 -2
  2. package/dist/artifacts-document.js +1 -1
  3. package/dist/artifacts-presentation.js +1 -1
  4. package/dist/artifacts-spreadsheet.js +1 -1
  5. package/dist/artifacts.js +3 -3
  6. package/dist/{browser-viewer-NTD6UIPR.js → browser-viewer-LGH4IP47.js} +3 -3
  7. package/dist/capability-catalog-row.d.ts +20 -0
  8. package/dist/capability-logo-fallback.d.ts +3 -0
  9. package/dist/{chunk-NOSYBS2D.js → chunk-2VHARTBL.js} +2 -1
  10. package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
  11. package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
  12. package/dist/chunk-5UONOQ3Y.js +635 -0
  13. package/dist/chunk-5UONOQ3Y.js.map +1 -0
  14. package/dist/{chunk-NYESIADM.js → chunk-AKOMC3VO.js} +3 -3
  15. package/dist/chunk-AKOMC3VO.js.map +1 -0
  16. package/dist/chunk-BRM6FVZA.js +302 -0
  17. package/dist/chunk-BRM6FVZA.js.map +1 -0
  18. package/dist/{chunk-TDSEILUH.js → chunk-D5NOQPLR.js} +1289 -1530
  19. package/dist/chunk-D5NOQPLR.js.map +1 -0
  20. package/dist/chunk-D7GAPJ3V.js +1558 -0
  21. package/dist/chunk-D7GAPJ3V.js.map +1 -0
  22. package/dist/{chunk-5M6VTFJ5.js → chunk-GBPCZH3Y.js} +2 -2
  23. package/dist/{chunk-YBM6CQRU.js → chunk-GEN54DU5.js} +4 -4
  24. package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
  25. package/dist/chunk-HJ5VJ4JB.js.map +1 -0
  26. package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
  27. package/dist/{chunk-KANFU34B.js → chunk-IYUPN32E.js} +6 -2
  28. package/dist/chunk-IYUPN32E.js.map +1 -0
  29. package/dist/{chunk-L2NBTKRX.js → chunk-JKLGJQO5.js} +193 -12
  30. package/dist/chunk-JKLGJQO5.js.map +1 -0
  31. package/dist/{chunk-NLTS7JHI.js → chunk-JPHDWKZE.js} +5 -5
  32. package/dist/{chunk-ICA6R6TW.js → chunk-OWHPKAOV.js} +28 -20
  33. package/dist/chunk-OWHPKAOV.js.map +1 -0
  34. package/dist/{chunk-24M4YBCL.js → chunk-PNAVEK2U.js} +4 -4
  35. package/dist/{chunk-45XEUVTK.js → chunk-UXE5SSNN.js} +293 -463
  36. package/dist/chunk-UXE5SSNN.js.map +1 -0
  37. package/dist/{chunk-PEI6RANI.js → chunk-VR4Z73EU.js} +203 -9
  38. package/dist/chunk-VR4Z73EU.js.map +1 -0
  39. package/dist/{chunk-HUIKIDM5.js → chunk-XMXRI5JK.js} +65 -18
  40. package/dist/chunk-XMXRI5JK.js.map +1 -0
  41. package/dist/client.d.ts +1 -1
  42. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  43. package/dist/components/chat-composer.d.ts +13 -3
  44. package/dist/components/markdown.d.ts +4 -3
  45. package/dist/components/message-timeline.d.ts +7 -2
  46. package/dist/components/preview-loading.d.ts +2 -0
  47. package/dist/components/session-capability-frame.d.ts +24 -0
  48. package/dist/components/session-chrome.d.ts +1 -2
  49. package/dist/components/session-commands.d.ts +6 -0
  50. package/dist/components/session-connection-request.d.ts +8 -0
  51. package/dist/components/session-mcp-capability-card.d.ts +22 -0
  52. package/dist/components/timeline-search.d.ts +34 -0
  53. package/dist/composer.js +7 -7
  54. package/dist/connect-account-identity.d.ts +8 -0
  55. package/dist/connect-accounts.d.ts +5 -0
  56. package/dist/connect-chooser.d.ts +6 -0
  57. package/dist/connect-panel.d.ts +9 -2
  58. package/dist/connect-setup.d.ts +5 -1
  59. package/dist/connect.d.ts +5 -21
  60. package/dist/connect.js +1268 -584
  61. package/dist/connect.js.map +1 -1
  62. package/dist/connection-catalog.d.ts +6 -1
  63. package/dist/connection-discovery.d.ts +12 -0
  64. package/dist/connection-installed.d.ts +1 -0
  65. package/dist/connection-logo.d.ts +5 -2
  66. package/dist/connection-service-logo.d.ts +8 -0
  67. package/dist/connection-service-presentation.d.ts +13 -0
  68. package/dist/connection-skeleton.d.ts +5 -0
  69. package/dist/discovery-cache.d.ts +5 -0
  70. package/dist/hooks/use-codex-accounts.d.ts +11 -5
  71. package/dist/hooks/use-connect.d.ts +22 -0
  72. package/dist/hooks/use-session-events.d.ts +5 -0
  73. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  74. package/dist/hooks/use-workspace-sessions.d.ts +2 -0
  75. package/dist/index.d.ts +8 -2
  76. package/dist/index.js +771 -793
  77. package/dist/index.js.map +1 -1
  78. package/dist/interaction.js +2 -2
  79. package/dist/machines.js +2 -2
  80. package/dist/mcp-connection-status.d.ts +14 -0
  81. package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
  82. package/dist/model-policy.d.ts +6 -2
  83. package/dist/model-policy.js +1 -1
  84. package/dist/{platform-activity-row-DUE2W74J.js → platform-activity-row-HLOH7FW4.js} +9 -4
  85. package/dist/platform-activity-row-HLOH7FW4.js.map +1 -0
  86. package/dist/plugin-discovery.d.ts +6 -0
  87. package/dist/{queue-surface-implementation-LK3S4BIW.js → queue-surface-implementation-3RIFK2BS.js} +4 -4
  88. package/dist/realtime.js +22 -22
  89. package/dist/realtime.js.map +1 -1
  90. package/dist/session-auth-recommendation.d.ts +5 -0
  91. package/dist/session-capability-policy.d.ts +12 -0
  92. package/dist/session-ui.d.ts +2 -0
  93. package/dist/session-ui.js +14 -12
  94. package/dist/session.d.ts +1 -0
  95. package/dist/session.js +6 -4
  96. package/dist/skill-discovery.d.ts +11 -0
  97. package/dist/timeline/compaction-copy.d.ts +10 -0
  98. package/dist/timeline/projection.d.ts +3 -1
  99. package/dist/timeline/turn-summary.d.ts +2 -2
  100. package/dist/timeline/types.d.ts +22 -1
  101. package/dist/{timeline-annotation-markers-DRSTLEM6.js → timeline-annotation-markers-OY6OWR3V.js} +2 -2
  102. package/dist/{timeline-annotation-selection-IHLUGVND.js → timeline-annotation-selection-BOXA2TIN.js} +2 -2
  103. package/dist/{timeline-annotations-dialog-6Q53N5FG.js → timeline-annotations-dialog-RGY3MHL3.js} +3 -3
  104. package/package.json +3 -3
  105. package/src/assets/README.md +8 -0
  106. package/src/assets/gmail.ico +0 -0
  107. package/src/assets/outlook.ico +0 -0
  108. package/src/capability-catalog-row.tsx +94 -0
  109. package/src/capability-logo-fallback.ts +22 -0
  110. package/src/client.ts +0 -9
  111. package/src/components/artifacts/document-editor.tsx +63 -6
  112. package/src/components/artifacts/presentation-editor.tsx +214 -9
  113. package/src/components/artifacts/spreadsheet-canvas.ts +3 -8
  114. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  115. package/src/components/artifacts/spreadsheet-grid.tsx +10 -11
  116. package/src/components/chat-composer.tsx +26 -3
  117. package/src/components/composer.tsx +10 -4
  118. package/src/components/human-input-form.tsx +12 -2
  119. package/src/components/markdown.tsx +86 -4
  120. package/src/components/message-timeline.tsx +104 -36
  121. package/src/components/model-picker.tsx +11 -3
  122. package/src/components/model-policy-picker-menu.tsx +10 -3
  123. package/src/components/preview-loading.tsx +154 -0
  124. package/src/components/sandbox-workspace.tsx +20 -36
  125. package/src/components/session-capability-frame.tsx +163 -0
  126. package/src/components/session-chrome.tsx +46 -77
  127. package/src/components/session-commands.tsx +18 -0
  128. package/src/components/session-connection-request.tsx +97 -0
  129. package/src/components/session-conversation.tsx +5 -4
  130. package/src/components/session-mcp-capability-card.tsx +532 -0
  131. package/src/components/session-status.tsx +2 -2
  132. package/src/components/timeline-anchor.tsx +9 -8
  133. package/src/components/timeline-search.tsx +168 -0
  134. package/src/components/user-message-body.tsx +7 -0
  135. package/src/connect-account-identity.tsx +34 -0
  136. package/src/connect-accounts.tsx +114 -52
  137. package/src/connect-chooser.tsx +171 -39
  138. package/src/connect-panel.tsx +73 -9
  139. package/src/connect-setup.tsx +72 -25
  140. package/src/connect.ts +13 -22
  141. package/src/connection-assets.d.ts +4 -0
  142. package/src/connection-catalog.tsx +61 -39
  143. package/src/connection-discovery.tsx +235 -0
  144. package/src/connection-installed.tsx +2 -0
  145. package/src/connection-logo.tsx +18 -10
  146. package/src/connection-service-logo.tsx +55 -0
  147. package/src/connection-service-presentation.ts +46 -0
  148. package/src/connection-skeleton.tsx +16 -0
  149. package/src/conversation-timeline.ts +1 -0
  150. package/src/discovery-cache.ts +42 -0
  151. package/src/hooks/use-codex-accounts.ts +33 -14
  152. package/src/hooks/use-composer.ts +4 -5
  153. package/src/hooks/use-connect.ts +23 -0
  154. package/src/hooks/use-session-events.ts +114 -1
  155. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  156. package/src/hooks/use-workspace-sessions.ts +10 -0
  157. package/src/index.ts +22 -2
  158. package/src/mcp-connection-status.ts +35 -0
  159. package/src/model-policy.ts +21 -0
  160. package/src/plugin-details.tsx +8 -5
  161. package/src/plugin-discovery.tsx +92 -72
  162. package/src/realtime/realtime-control.tsx +6 -8
  163. package/src/session-auth-recommendation.ts +48 -0
  164. package/src/session-capability-policy.ts +83 -0
  165. package/src/session-ui.ts +2 -0
  166. package/src/session.ts +1 -0
  167. package/src/skill-discovery.tsx +101 -56
  168. package/src/timeline/compaction-copy.ts +40 -0
  169. package/src/timeline/platform-activity-row.tsx +8 -3
  170. package/src/timeline/projection.ts +74 -2
  171. package/src/timeline/shared.tsx +6 -1
  172. package/src/timeline/startup-timings.tsx +1 -1
  173. package/src/timeline/turn-summary.tsx +17 -3
  174. package/src/timeline/types.ts +22 -2
  175. package/styles/compiled.css +325 -27
  176. package/styles/connect.css +90 -15
  177. package/styles/index.css +46 -0
  178. package/styles/session-capability.css +42 -0
  179. package/dist/chunk-45XEUVTK.js.map +0 -1
  180. package/dist/chunk-HQV2I5HV.js +0 -124
  181. package/dist/chunk-HQV2I5HV.js.map +0 -1
  182. package/dist/chunk-HUIKIDM5.js.map +0 -1
  183. package/dist/chunk-ICA6R6TW.js.map +0 -1
  184. package/dist/chunk-IHQII5WO.js +0 -886
  185. package/dist/chunk-IHQII5WO.js.map +0 -1
  186. package/dist/chunk-KANFU34B.js.map +0 -1
  187. package/dist/chunk-L2NBTKRX.js.map +0 -1
  188. package/dist/chunk-NYESIADM.js.map +0 -1
  189. package/dist/chunk-PEI6RANI.js.map +0 -1
  190. package/dist/chunk-TDSEILUH.js.map +0 -1
  191. package/dist/chunk-UMECRJRV.js.map +0 -1
  192. package/dist/hooks/use-packs.d.ts +0 -32
  193. package/dist/platform-activity-row-DUE2W74J.js.map +0 -1
  194. package/src/hooks/use-packs.ts +0 -157
  195. /package/dist/{browser-viewer-NTD6UIPR.js.map → browser-viewer-LGH4IP47.js.map} +0 -0
  196. /package/dist/{chunk-NOSYBS2D.js.map → chunk-2VHARTBL.js.map} +0 -0
  197. /package/dist/{chunk-5M6VTFJ5.js.map → chunk-GBPCZH3Y.js.map} +0 -0
  198. /package/dist/{chunk-YBM6CQRU.js.map → chunk-GEN54DU5.js.map} +0 -0
  199. /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
  200. /package/dist/{chunk-NLTS7JHI.js.map → chunk-JPHDWKZE.js.map} +0 -0
  201. /package/dist/{chunk-24M4YBCL.js.map → chunk-PNAVEK2U.js.map} +0 -0
  202. /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
  203. /package/dist/{queue-surface-implementation-LK3S4BIW.js.map → queue-surface-implementation-3RIFK2BS.js.map} +0 -0
  204. /package/dist/{timeline-annotation-markers-DRSTLEM6.js.map → timeline-annotation-markers-OY6OWR3V.js.map} +0 -0
  205. /package/dist/{timeline-annotation-selection-IHLUGVND.js.map → timeline-annotation-selection-BOXA2TIN.js.map} +0 -0
  206. /package/dist/{timeline-annotations-dialog-6Q53N5FG.js.map → timeline-annotations-dialog-RGY3MHL3.js.map} +0 -0
@@ -3,6 +3,7 @@ import type {
3
3
  CodexAccount,
4
4
  CodexAccountsResponse,
5
5
  CodexRotationSettings,
6
+ SessionCodexAccountsResponse,
6
7
  SessionEvent,
7
8
  } from "@opengeni/sdk";
8
9
  import { useOpenGeni, type ClientOverride } from "../provider";
@@ -25,6 +26,8 @@ export function isCodexAccountEvent(event: Pick<SessionEvent, "type">): boolean
25
26
  "turn.completed",
26
27
  "turn.failed",
27
28
  "turn.cancelled",
29
+ "turn.requires_action",
30
+ "session.status.changed",
28
31
  ].includes(event.type);
29
32
  }
30
33
 
@@ -32,11 +35,17 @@ export function isCodexAccountEvent(event: Pick<SessionEvent, "type">): boolean
32
35
  * The structural slice of the SDK client the Codex-accounts surface needs. Method
33
36
  * NAMES + SIGNATURES match `OpenGeniClient` so the real client satisfies it
34
37
  * directly; declared structurally (not a hard Pick) so a test/Geni client can
35
- * stand in. `getSession` reads the session's pin/last; `pinSessionCodexAccount`
38
+ * stand in. `listSessionCodexAccounts` reads the accepted selection and choices
39
+ * atomically; `pinSessionCodexAccount`
36
40
  * is the optional mutation (absent ⇒ the indicator hides the switch affordance).
37
41
  */
38
42
  export type CodexAccountsClientLike = {
39
43
  listCodexAccounts: (workspaceId: string) => Promise<CodexAccountsResponse>;
44
+ /** Required for session-scoped use; never fall back to another pool. */
45
+ listSessionCodexAccounts?: (
46
+ workspaceId: string,
47
+ sessionId: string,
48
+ ) => Promise<SessionCodexAccountsResponse>;
40
49
  getSession?: (
41
50
  workspaceId: string,
42
51
  sessionId: string,
@@ -65,18 +74,21 @@ export type UseCodexAccountsOptions = ClientOverride &
65
74
 
66
75
  export type UseCodexAccountsResult = {
67
76
  accounts: CodexAccount[];
68
- /** The workspace ACTIVE account (used when a session is unpinned). */
77
+ /** The ACTIVE account for the retry pool, or current workspace pool for new work. */
69
78
  activeAccountId: string | null;
70
79
  /** The session's PINNED account (null ⇒ following workspace active). */
71
80
  pinnedAccountId: string | null;
72
81
  /** Current preference only; automatic allocation can choose another account. */
73
82
  effectiveAccountId: string | null;
74
83
  currentSelection: { credentialId: string | null; waiting: boolean } | null;
84
+ /** The accepted account can differ from the choices for the next turn. */
85
+ currentAccount: CodexAccount | null;
75
86
  switchAppliedTo: "waiting_turn" | "next_turn" | null;
76
87
  /** The account the session's last turn ACTUALLY ran on (the "Running on:" source). */
77
88
  lastAccountId: string | null;
78
89
  settings: CodexRotationSettings;
79
90
  loading: boolean;
91
+ error: Error | null;
80
92
  refresh: () => Promise<void>;
81
93
  /**
82
94
  * Trigger a LIVE batched /wham/usage refresh across all accounts, then re-read
@@ -101,6 +113,7 @@ const EMPTY_SETTINGS: CodexRotationSettings = {
101
113
  };
102
114
 
103
115
  type CodexAccountsState = {
116
+ currentAccount: CodexAccount | null;
104
117
  currentSelection: { credentialId: string | null; waiting: boolean } | null;
105
118
  accounts: CodexAccount[];
106
119
  activeAccountId: string | null;
@@ -110,6 +123,7 @@ type CodexAccountsState = {
110
123
  };
111
124
 
112
125
  const EMPTY_STATE: CodexAccountsState = {
126
+ currentAccount: null,
113
127
  currentSelection: null,
114
128
  accounts: [],
115
129
  activeAccountId: null,
@@ -119,8 +133,8 @@ const EMPTY_STATE: CodexAccountsState = {
119
133
  };
120
134
 
121
135
  /**
122
- * The workspace's Codex accounts + the per-workspace active pointer + (when
123
- * session-scoped) the session pin and last-ran-on account. Composed like
136
+ * The workspace's Codex accounts, or session-authorized retry/next-turn choices
137
+ * and accepted current account. Composed like
124
138
  * `useMachines`: slow polling (the realtime work is done by the
125
139
  * `codex.account.switched` event trigger) + a `pin` mutation.
126
140
  * Dual-consumer safe via the structural `CodexAccountsClientLike` surface.
@@ -132,25 +146,27 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
132
146
  const sharedEvents = options.events;
133
147
 
134
148
  const load = useCallback(async (): Promise<CodexAccountsState> => {
135
- const accountsP = codexClient.listCodexAccounts(workspaceId);
136
- const sessionP =
137
- sessionId && codexClient.getSession
138
- ? codexClient.getSession(workspaceId, sessionId).catch(() => null)
139
- : Promise.resolve(null);
140
- const [acc, session] = await Promise.all([accountsP, sessionP]);
149
+ if (sessionId) {
150
+ if (!codexClient.listSessionCodexAccounts)
151
+ throw new Error("Session Codex account projection is unavailable");
152
+ return await codexClient.listSessionCodexAccounts(workspaceId, sessionId);
153
+ }
154
+ const acc = await codexClient.listCodexAccounts(workspaceId);
141
155
  return {
142
- currentSelection: session?.codexCurrentSelection ?? null,
156
+ currentSelection: null,
157
+ currentAccount: null,
143
158
  accounts: acc.accounts,
144
159
  activeAccountId: acc.activeAccountId,
145
160
  settings: acc.settings,
146
- pinnedAccountId: session?.codexPinnedCredentialId ?? null,
147
- lastAccountId: session?.codexLastCredentialId ?? null,
161
+ pinnedAccountId: null,
162
+ lastAccountId: null,
148
163
  };
149
164
  }, [codexClient, workspaceId, sessionId]);
150
165
 
151
166
  const {
152
167
  data: loadedData,
153
168
  loading,
169
+ error,
154
170
  refresh,
155
171
  } = usePolledValue(load, {
156
172
  pollIntervalMs: options.pollIntervalMs,
@@ -212,10 +228,12 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
212
228
  return result === true;
213
229
  }, [codexClient, workspaceId, runUsageMutation, refresh]);
214
230
 
215
- const data = loadedData ?? EMPTY_STATE;
231
+ // Reauthorization failure must remove a previously visible session pool.
232
+ const data = (sessionId && error ? null : loadedData) ?? EMPTY_STATE;
216
233
  const effectiveAccountId = data.pinnedAccountId ?? data.activeAccountId;
217
234
 
218
235
  return {
236
+ currentAccount: data.currentAccount,
219
237
  currentSelection: data.currentSelection,
220
238
  switchAppliedTo:
221
239
  switchReceipt?.sessionId === sessionId ? (switchReceipt?.appliedTo ?? null) : null,
@@ -226,6 +244,7 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
226
244
  lastAccountId: data.lastAccountId,
227
245
  settings: data.settings,
228
246
  loading,
247
+ error,
229
248
  refresh,
230
249
  refreshUsage,
231
250
  refreshingUsage,
@@ -1475,9 +1475,10 @@ export function useComposer(
1475
1475
  ...(wireInput.mcpCredentialUpdates
1476
1476
  ? { mcpCredentialUpdates: wireInput.mcpCredentialUpdates }
1477
1477
  : {}),
1478
- ...(wireInput.connectionAuthorities
1479
- ? { connectionAuthorities: wireInput.connectionAuthorities }
1478
+ ...(wireInput.connectionAccounts
1479
+ ? { connectionAccounts: wireInput.connectionAccounts }
1480
1480
  : {}),
1481
+
1481
1482
  ...(wireInput.personalResourceAttachment
1482
1483
  ? { personalResourceAttachment: wireInput.personalResourceAttachment }
1483
1484
  : {}),
@@ -1751,9 +1752,7 @@ export function useComposer(
1751
1752
  ...(input.mcpCredentialUpdates
1752
1753
  ? { mcpCredentialUpdates: input.mcpCredentialUpdates }
1753
1754
  : {}),
1754
- ...(input.connectionAuthorities
1755
- ? { connectionAuthorities: input.connectionAuthorities }
1756
- : {}),
1755
+ ...(input.connectionAccounts ? { connectionAccounts: input.connectionAccounts } : {}),
1757
1756
  ...(input.personalResourceAttachment
1758
1757
  ? { personalResourceAttachment: input.personalResourceAttachment }
1759
1758
  : {}),
@@ -0,0 +1,23 @@
1
+ import type { ConnectController } from "@opengeni/connect";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+
4
+ /** Internal consumers import the hook directly so setup surfaces stay lazy. */
5
+ export function useConnect(controller: ConnectController) {
6
+ const snapshot = useSyncExternalStore(
7
+ controller.subscribe,
8
+ controller.getSnapshot,
9
+ controller.getSnapshot,
10
+ );
11
+ const actions = useMemo(
12
+ () => ({
13
+ begin: controller.begin.bind(controller),
14
+ recover: controller.recover.bind(controller),
15
+ refresh: controller.refresh.bind(controller),
16
+ waitForAction: controller.waitForAction.bind(controller),
17
+ advance: controller.advance.bind(controller),
18
+ cancel: controller.cancel.bind(controller),
19
+ }),
20
+ [controller],
21
+ );
22
+ return useMemo(() => ({ ...snapshot, ...actions }), [snapshot, actions]);
23
+ }
@@ -73,6 +73,9 @@ export type UseSessionEventsResult = {
73
73
  * "Jump to latest" when the tip is not in memory (history view).
74
74
  */
75
75
  jumpToLatest: () => Promise<void>;
76
+ /** Replace history with a bounded window containing this exact durable event. */
77
+ jumpToSequence: (sequence: number, options?: { signal?: AbortSignal }) => Promise<boolean>;
78
+ loadingTarget: boolean;
76
79
  error: Error | null;
77
80
  };
78
81
 
@@ -136,6 +139,8 @@ export function useSessionEvents(
136
139
  const replay = options.replay ?? "windowed";
137
140
  const fullReplay = replay === "full" || after !== 0;
138
141
  const streamKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${after}\u0000${fullReplay ? "full" : "windowed"}`;
142
+ const navigationIdentityRef = useRef({ client, streamKey, enabled });
143
+ navigationIdentityRef.current = { client, streamKey, enabled };
139
144
 
140
145
  const [eventWindow, setEventWindow] = useState<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
141
146
  const [connectionState, setConnectionState] = useState<SessionEventsConnectionState>("idle");
@@ -151,6 +156,8 @@ export function useSessionEvents(
151
156
  const [loadingNewer, setLoadingNewer] = useState(false);
152
157
  const [loadingOldest, setLoadingOldest] = useState(false);
153
158
  const [loadingLatest, setLoadingLatest] = useState(false);
159
+ const [loadingTarget, setLoadingTarget] = useState(false);
160
+ const loadingTargetRef = useRef(false);
154
161
  const [streamEpoch, setStreamEpoch] = useState(0);
155
162
  // "history" = reading a non-tip window; live SSE must not fill the gap.
156
163
  const [viewMode, setViewMode] = useState<"live" | "history">("live");
@@ -191,6 +198,8 @@ export function useSessionEvents(
191
198
  loadingOlderRef.current = false;
192
199
  loadingNewerRef.current = false;
193
200
  loadingOldestRef.current = false;
201
+ loadingTargetRef.current = false;
202
+ setLoadingTarget(false);
194
203
  setLoadingOlder(false);
195
204
  setLoadingNewer(false);
196
205
  setLoadingOldest(false);
@@ -525,6 +534,7 @@ export function useSessionEvents(
525
534
  ]);
526
535
 
527
536
  const navigationBusy = (): boolean =>
537
+ loadingTargetRef.current ||
528
538
  loadingOlderRef.current ||
529
539
  loadingNewerRef.current ||
530
540
  loadingOldestRef.current ||
@@ -826,7 +836,102 @@ export function useSessionEvents(
826
836
  }
827
837
  }, [client, workspaceId, sessionId]);
828
838
 
839
+ const jumpToSequence = useCallback(
840
+ async (sequence: number, navigationOptions?: { signal?: AbortSignal }): Promise<boolean> => {
841
+ const signal = navigationOptions?.signal;
842
+ if (!sessionId || !Number.isSafeInteger(sequence) || sequence < 1) return false;
843
+ // A navigation that was already cancelled (for example Find closed before
844
+ // the caller could react) must not disturb any current view state.
845
+ if (signal?.aborted) return false;
846
+ // Explicit targets supersede both other targets and adjacent-page requests.
847
+ const generation = ++navigationGenerationRef.current;
848
+ const current = () =>
849
+ generation === navigationGenerationRef.current &&
850
+ navigationIdentityRef.current.client === client &&
851
+ navigationIdentityRef.current.streamKey === streamKey &&
852
+ navigationIdentityRef.current.enabled === enabled;
853
+ const previousMode = viewModeRef.current;
854
+ streamAbortRef.current?.abort();
855
+ generationRef.current += 1;
856
+ viewModeRef.current = "history";
857
+ setViewMode("history");
858
+ loadingOlderRef.current = loadingNewerRef.current = loadingOldestRef.current = false;
859
+ setLoadingOlder(false);
860
+ setLoadingNewer(false);
861
+ setLoadingOldest(false);
862
+ loadingTargetRef.current = true;
863
+ setLoadingTarget(true);
864
+ let published = false;
865
+ try {
866
+ const [before, following] = await Promise.all([
867
+ loadPreviousPage(client, workspaceId, sessionId, sequence + 1, { pageSize: 128 }),
868
+ loadNextPage(client, workspaceId, sessionId, sequence, { pageSize: 128 }),
869
+ ]);
870
+ // Close-during-fetch fence: a pending jump must not replace the window
871
+ // after its owner (for example ConversationFind) has gone away. The
872
+ // finally block still settles loadingTarget for this generation.
873
+ if (signal?.aborted) return false;
874
+ if (!current()) return false;
875
+ if (!before.some((event) => event.sequence === sequence)) return false;
876
+ // Bound each side, keeping the target even if its message alone exceeds
877
+ // the ordinary byte budget. Never evict the very event being navigated to.
878
+ const prefix = boundBrowserSessionEventWindow(before, {
879
+ direction: "newest",
880
+ maxBytes: SESSION_EVENT_BROWSER_MAX_BYTES / 2,
881
+ });
882
+ const retained = boundBrowserSessionEventWindow([...prefix.events, ...following], {
883
+ direction: "oldest",
884
+ });
885
+ if (!retained.events.some((event) => event.sequence === sequence)) return false;
886
+ streamAbortRef.current?.abort();
887
+ generationRef.current += 1;
888
+ const status = observeSessionStatus(retained.events, sessionStatusRef);
889
+ eventWindowRef.current = retained;
890
+ oldestSequenceRef.current = retained.events[0]!.sequence;
891
+ newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
892
+ lastSequenceRef.current = Math.max(lastSequenceRef.current, newestSequenceRef.current ?? 0);
893
+ hasOlderRef.current = !isLogStart(retained.events[0]!);
894
+ // Compact/byte-bounded pages may be short despite more durable history.
895
+ // Only an empty forward page proves the end (same rule as loadNewer).
896
+ hasNewerRef.current =
897
+ following.length > 0 ||
898
+ retained.truncated ||
899
+ (newestSequenceRef.current ?? 0) < lastSequenceRef.current;
900
+ initialWindowLoadedRef.current = true;
901
+ viewModeRef.current = "history";
902
+ setEventWindow(retained);
903
+ setHasOlder(hasOlderRef.current);
904
+ setHasNewer(hasNewerRef.current);
905
+ setInitialLoading(false);
906
+ setViewMode("history");
907
+ setNewerError(null);
908
+ if (status !== undefined) setSessionStatusProjection(status);
909
+ published = true;
910
+ return true;
911
+ } catch (reason) {
912
+ if (!current() || signal?.aborted) return false;
913
+ throw reason;
914
+ } finally {
915
+ if (current()) {
916
+ loadingTargetRef.current = false;
917
+ setLoadingTarget(false);
918
+ if (!published && previousMode === "live") {
919
+ viewModeRef.current = "live";
920
+ setViewMode("live");
921
+ setStreamEpoch((epoch) => epoch + 1);
922
+ }
923
+ }
924
+ }
925
+ },
926
+ [client, workspaceId, sessionId, streamKey, enabled],
927
+ );
928
+
829
929
  const jumpToLatest = useCallback(async (): Promise<void> => {
930
+ if (loadingTargetRef.current) {
931
+ navigationGenerationRef.current += 1;
932
+ loadingTargetRef.current = false;
933
+ setLoadingTarget(false);
934
+ }
830
935
  if (!sessionId || navigationBusy()) {
831
936
  return;
832
937
  }
@@ -867,7 +972,13 @@ export function useSessionEvents(
867
972
 
868
973
  const identityMatches = stateStreamKey === streamKey;
869
974
  const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
870
- const timeline = useMemo(() => buildTimeline(visibleEvents), [visibleEvents]);
975
+ const timeline = useMemo(
976
+ () =>
977
+ buildTimeline(visibleEvents, {
978
+ partialStart: hasOlder || eventWindow.truncated || after > 0,
979
+ }),
980
+ [visibleEvents, hasOlder, eventWindow.truncated, after],
981
+ );
871
982
 
872
983
  return {
873
984
  events: visibleEvents,
@@ -889,6 +1000,8 @@ export function useSessionEvents(
889
1000
  loadOldest,
890
1001
  loadingLatest: !identityMatches ? false : loadingLatest,
891
1002
  jumpToLatest,
1003
+ jumpToSequence,
1004
+ loadingTarget: identityMatches && loadingTarget,
892
1005
  error: identityMatches ? (newerError ?? error) : null,
893
1006
  };
894
1007
  }
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  SessionEvent,
3
3
  SessionMcpApprovalPolicy,
4
- SessionMcpServerMetadata,
4
+ SessionMcpApprovalPolicyTarget,
5
5
  UpdateSessionMcpApprovalPolicyResponse,
6
6
  } from "@opengeni/sdk";
7
7
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -38,7 +38,7 @@ export type UseSessionMcpApprovalPolicyOptions = EmbeddedSessionMcpApprovalPolic
38
38
  };
39
39
 
40
40
  export type UseSessionMcpApprovalPolicyResult = {
41
- server: SessionMcpServerMetadata | null;
41
+ server: SessionMcpApprovalPolicyTarget | null;
42
42
  policy: SessionMcpApprovalPolicy | null;
43
43
  loading: boolean;
44
44
  error: Error | null;
@@ -61,7 +61,7 @@ export function useSessionMcpApprovalPolicy(
61
61
  ): UseSessionMcpApprovalPolicyResult {
62
62
  const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
63
63
  const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
64
- const [override, setOverride] = useState<SessionMcpServerMetadata | null>(null);
64
+ const [override, setOverride] = useState<SessionMcpApprovalPolicyTarget | null>(null);
65
65
  const targetKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${serverId ?? ""}`;
66
66
  const authoritativeGeneration = useRef(0);
67
67
  const mutationIdentity = useMemo(() => ({ client, targetKey }), [client, targetKey]);
@@ -75,7 +75,7 @@ export function useSessionMcpApprovalPolicy(
75
75
  type PolicyRead = {
76
76
  targetKey: string;
77
77
  generation: number;
78
- server: SessionMcpServerMetadata | null;
78
+ server: SessionMcpApprovalPolicyTarget | null;
79
79
  };
80
80
  const load = useCallback(async (): Promise<PolicyRead> => {
81
81
  const generation = authoritativeGeneration.current;
@@ -86,7 +86,15 @@ export function useSessionMcpApprovalPolicy(
86
86
  return {
87
87
  targetKey,
88
88
  generation,
89
- server: session.mcpServers.find((server) => server.id === serverId) ?? null,
89
+ server:
90
+ session.mcpServers.find((server) => server.id === serverId) ??
91
+ (session.mcpApprovalPolicies && Object.hasOwn(session.mcpApprovalPolicies, serverId)
92
+ ? {
93
+ id: serverId,
94
+ source: "workspace",
95
+ requireApproval: session.mcpApprovalPolicies[serverId]!,
96
+ }
97
+ : null),
90
98
  };
91
99
  }, [client, serverId, sessionId, targetKey, workspaceId]);
92
100
  const {
@@ -11,6 +11,8 @@ export type UseWorkspaceSessionsOptions = ClientOverride & {
11
11
  /** Return only the complete personal pinned projection. */
12
12
  pinsOnly?: boolean | undefined;
13
13
  archivedOnly?: boolean | undefined;
14
+ sortBy?: "updatedAt" | "createdAt" | "name" | undefined;
15
+ archiveStatus?: "active" | "archived" | "all" | undefined;
14
16
  /** Refresh interval (ms) for fleet/manager views. Off by default. */
15
17
  pollIntervalMs?: number | undefined;
16
18
  enabled?: boolean | undefined;
@@ -49,6 +51,8 @@ export function useWorkspaceSessions(
49
51
  const search = options.search;
50
52
  const pinsOnly = options.pinsOnly;
51
53
  const archivedOnly = options.archivedOnly;
54
+ const sortBy = options.sortBy;
55
+ const archiveStatus = options.archiveStatus;
52
56
  const enabled = options.enabled ?? true;
53
57
  const nextReadRevision = useRef(0);
54
58
  const nextReadGeneration = useRef(0);
@@ -61,6 +65,8 @@ export function useWorkspaceSessions(
61
65
  search ?? "",
62
66
  pinsOnly ? "1" : "",
63
67
  archivedOnly ? "archived" : "active",
68
+ sortBy ?? "",
69
+ archiveStatus ?? "",
64
70
  ].join("\u0000");
65
71
  const previousQueryKey = useRef(queryKey);
66
72
  const queryKeyTransition = previousQueryKey.current !== queryKey;
@@ -77,6 +83,8 @@ export function useWorkspaceSessions(
77
83
  ...(search !== undefined ? { search } : {}),
78
84
  ...(pinsOnly ? { pinsOnly: true } : {}),
79
85
  ...(archivedOnly ? { archivedOnly: true } : {}),
86
+ ...(sortBy ? { sortBy } : {}),
87
+ ...(archiveStatus ? { archiveStatus } : {}),
80
88
  signal,
81
89
  });
82
90
  return {
@@ -96,6 +104,8 @@ export function useWorkspaceSessions(
96
104
  search,
97
105
  pinsOnly,
98
106
  archivedOnly,
107
+ sortBy,
108
+ archiveStatus,
99
109
  queryKey,
100
110
  ],
101
111
  );
package/src/index.ts CHANGED
@@ -1,3 +1,22 @@
1
+ export { sessionAuthRecommendation } from "./session-auth-recommendation";
2
+ export {
3
+ SessionConnectionRequest,
4
+ type SessionConnectionRequestProps,
5
+ } from "./components/session-connection-request";
6
+ export {
7
+ SessionMcpCapabilityCard,
8
+ McpConnectionCard,
9
+ type McpConnectionCardProps,
10
+ type SessionMcpCapabilityCardProps,
11
+ } from "./components/session-mcp-capability-card";
12
+ export {
13
+ SessionCapabilityFrame,
14
+ type SessionCapabilityFrameProps,
15
+ } from "./components/session-capability-frame";
16
+ export {
17
+ attachSessionCapability,
18
+ completeSessionCapabilityOAuth,
19
+ } from "./session-capability-policy";
1
20
  export { retainedImageId } from "./components/markdown";
2
21
  // oxlint-disable-next-line typescript/triple-slash-reference -- package consumers must load the ambient type for this optional untyped peer without emitting a runtime import.
3
22
  /// <reference path="./types/external.d.ts" />
@@ -217,8 +236,6 @@ export type {
217
236
  UseRigChangesOptions,
218
237
  UseRigChangesResult,
219
238
  } from "./hooks/use-rigs";
220
- export { usePacks } from "./hooks/use-packs";
221
- export type { UsePacksOptions, UsePacksResult } from "./hooks/use-packs";
222
239
  export { useWorkspaces } from "./hooks/use-workspaces";
223
240
  export type { UseWorkspacesOptions, UseWorkspacesResult } from "./hooks/use-workspaces";
224
241
  export { useBillingUsage } from "./hooks/use-billing-usage";
@@ -462,6 +479,7 @@ export type { CommandPaletteProps } from "./components/command-palette";
462
479
 
463
480
  // Components
464
481
  export { ChatComposer } from "./components/chat-composer";
482
+ export { conversationTimeline } from "./conversation-timeline";
465
483
  export { SessionConversation } from "./components/session-conversation";
466
484
  export type { SessionConversationProps } from "./components/session-conversation";
467
485
  export type { ChatComposerProps } from "./components/chat-composer";
@@ -511,6 +529,7 @@ export { MessageTimeline, TimelineRow } from "./components/message-timeline";
511
529
  export { GeneratedVideoPlayer } from "./components/generated-video-player";
512
530
  export type { GeneratedVideoPlayerProps } from "./components/generated-video-player";
513
531
  export type { MessageTimelineProps } from "./components/message-timeline";
532
+ export type { TimelineSearchTarget } from "./components/timeline-search";
514
533
  export { UserMessageBody, userMessageLikelyNeedsDisclosure } from "./components/user-message-body";
515
534
  export type {
516
535
  UserMessageBodyProps,
@@ -617,6 +636,7 @@ export {
617
636
  tryParseJson,
618
637
  } from "./lib/format";
619
638
  export { SessionCommandsPanel } from "./components/session-commands-panel";
639
+ export { SessionCommands } from "./components/session-commands";
620
640
 
621
641
  export { KnowledgeActivityProvider } from "./timeline/knowledge-receipt";
622
642
  export type { KnowledgeActivityActions } from "./timeline/knowledge-receipt";
@@ -0,0 +1,35 @@
1
+ import type { CapabilityCatalogItem, ConnectionMetadata } from "@opengeni/sdk";
2
+
3
+ /** Status only; runtime use is authorized for the authenticated initiating user. */
4
+ export function matchingActiveMcpConnections(
5
+ item: CapabilityCatalogItem,
6
+ connections: ConnectionMetadata[],
7
+ ) {
8
+ return matchingMcpConnections(item, connections).filter((entry) => entry.status === "active");
9
+ }
10
+
11
+ function matchingMcpConnections(item: CapabilityCatalogItem, connections: ConnectionMetadata[]) {
12
+ const ref = item.connectionRef;
13
+ if (!item.enabled || !ref) return [];
14
+ return connections.filter(
15
+ (entry) =>
16
+ entry.status !== "revoked" &&
17
+ entry.kind === "oauth2" &&
18
+ entry.metadata.mcpUrl === (item.mcpUrl ?? item.endpointUrl) &&
19
+ (ref.subjectScope !== "subject"
20
+ ? entry.id === ref.connectionId && entry.subjectId === null
21
+ : entry.subjectId !== null),
22
+ );
23
+ }
24
+
25
+ /** Installation availability is not account readiness. Revoked history is not actionable. */
26
+ export function mcpConnectionDiscoveryState(
27
+ item: CapabilityCatalogItem,
28
+ connections: ConnectionMetadata[],
29
+ ) {
30
+ const matching = matchingMcpConnections(item, connections);
31
+ const active = matching.filter((entry) => entry.status === "active");
32
+ if (active.length === 1) return { status: "added", label: "Connected" } as const;
33
+ if (matching.length > 0) return { status: "attention", label: "Review connection" } as const;
34
+ return { status: "available", label: "Connect" } as const;
35
+ }
@@ -274,9 +274,11 @@ export function findPickerRow<TCatalog extends ClientModel>(
274
274
 
275
275
  export function groupPickerRowsByBillingClass(
276
276
  rows: PickerModelRow[],
277
+ options?: { codexOnly?: boolean },
277
278
  ): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }>;
278
279
  export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
279
280
  rows: PickerModelRow<TCatalog>[],
281
+ options?: { codexOnly?: boolean },
280
282
  ): Array<{
281
283
  billingClass: PickerBillingClass;
282
284
  label: string;
@@ -284,6 +286,7 @@ export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
284
286
  }>;
285
287
  export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
286
288
  rows: PickerModelRow<TCatalog>[],
289
+ options?: { codexOnly?: boolean },
287
290
  ): Array<{
288
291
  billingClass: PickerBillingClass;
289
292
  label: string;
@@ -307,5 +310,23 @@ export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
307
310
  rows: [row],
308
311
  });
309
312
  }
313
+ const codexIndex = groups.findIndex((group) => group.billingClass === "codex_subscription");
314
+ const codex = groups[codexIndex];
315
+ const selectableOpenGeni = groups
316
+ .find((group) => group.billingClass === "opengeni_credits")
317
+ ?.rows.filter((row) => row.selectable);
318
+ // Presentation only: never change the catalog/default model ordering or selection.
319
+ const onlyFreeOpenGeni =
320
+ selectableOpenGeni !== undefined &&
321
+ selectableOpenGeni.length > 0 &&
322
+ selectableOpenGeni.every((row) => row.catalog.cost === "free");
323
+ if (
324
+ codex &&
325
+ codex.rows.some((row) => row.selectable) &&
326
+ (options?.codexOnly || onlyFreeOpenGeni)
327
+ ) {
328
+ groups.splice(codexIndex, 1);
329
+ groups.unshift(codex);
330
+ }
310
331
  return groups;
311
332
  }
@@ -1,6 +1,8 @@
1
1
  import { useState } from "react";
2
2
  import { pluginMcpUnavailableReason, type PluginDiscoveryItem } from "@opengeni/sdk";
3
+ import { BoxesIcon } from "lucide-react";
3
4
  import { Markdown } from "./components/markdown";
5
+ import { ConnectionLogo } from "./connection-logo";
4
6
 
5
7
  const componentLabels: Record<string, string> = {
6
8
  skills: "Skills",
@@ -38,9 +40,8 @@ export function PluginDetails({
38
40
  const installableCount =
39
41
  (item.skills?.length ?? 0) + (item.mcpServers?.filter(supported).length ?? 0);
40
42
  const exceedsInstallLimit = installableCount > 64;
41
- const installable = installableCount > 0 && !exceedsInstallLimit;
43
+ const installable = !contentsUnknown && installableCount > 0 && !exceedsInstallLimit;
42
44
  const [expanded, setExpanded] = useState(false);
43
- const [logoFailed, setLogoFailed] = useState(false);
44
45
  const description = item.longDescription || item.description;
45
46
  const lengthy = description.length > 700;
46
47
  const registryLabel =
@@ -52,9 +53,11 @@ export function PluginDetails({
52
53
  return (
53
54
  <article className="og-plugin-details">
54
55
  <header className="og-plugin-details-header">
55
- {item.logoUrl && !logoFailed ? (
56
- <img src={item.logoUrl} alt="" onError={() => setLogoFailed(true)} />
57
- ) : null}
56
+ <ConnectionLogo
57
+ src={item.logoUrl}
58
+ name={item.displayName}
59
+ fallback={<BoxesIcon aria-hidden="true" />}
60
+ />
58
61
  <div>
59
62
  <h2>{item.displayName.replace(/-/g, " ")}</h2>
60
63
  {item.author?.name ? <p>By {item.author.name}</p> : null}