@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
@@ -1,7 +1,9 @@
1
+ import { connectionServicePresentation } from "./connection-service-presentation";
1
2
  import { ConnectionCatalog } from "./connection-catalog";
3
+ import { ConnectionLogo } from "./connection-logo";
2
4
  import { useEffect, useState, type FormEvent } from "react";
3
- import type { ConnectController, ConnectProvider } from "@opengeni/connect";
4
- import { useConnect } from "./connect";
5
+ import type { ConnectAccount, ConnectController, ConnectProvider } from "@opengeni/connect";
6
+ import { useConnect } from "./hooks/use-connect";
5
7
 
6
8
  export type ConnectChooserProps = {
7
9
  controller: ConnectController;
@@ -9,6 +11,12 @@ export type ConnectChooserProps = {
9
11
  returnUrl: string;
10
12
  className?: string;
11
13
  presentation?: "select" | "catalog";
14
+ /** Compact mechanism picker when service discovery is already shown by the host. */
15
+ customOnly?: boolean;
16
+ providerOnly?: boolean;
17
+ compact?: boolean;
18
+ /** Select a service from the unified discovery list. */
19
+ initialProviderId?: string;
12
20
  };
13
21
 
14
22
  /** Catalog readiness is supplied by the authenticated backend, never inferred
@@ -31,34 +39,76 @@ function ScopedChooser({
31
39
  returnUrl,
32
40
  className,
33
41
  presentation = "select",
42
+ customOnly = false,
43
+ providerOnly = false,
44
+ compact = false,
45
+ initialProviderId = "",
34
46
  }: ConnectChooserProps) {
35
47
  const view = useConnect(controller);
36
48
  const [catalog, setCatalog] = useState<ConnectProvider[] | null>(null);
49
+ const [accounts, setAccounts] = useState<ConnectAccount[]>([]);
37
50
  const [failed, setFailed] = useState(false);
38
51
  const [load, setLoad] = useState(0);
39
52
  const [query, setQuery] = useState("");
40
- const [providerId, setProviderId] = useState("");
53
+ const [providerId, setProviderId] = useState(initialProviderId);
41
54
  const [ownership, setOwnership] = useState("");
42
55
  useEffect(() => {
43
56
  const abort = new AbortController();
44
57
  setCatalog(null);
58
+ setAccounts([]);
45
59
  setFailed(false);
46
- setProviderId("");
60
+ setProviderId(initialProviderId);
47
61
  setOwnership("");
48
62
  // Promise boundary also handles a synchronously throwing injected transport.
49
63
  void Promise.resolve()
50
64
  .then(() => {
51
65
  abort.signal.throwIfAborted();
52
- return controller.transport.catalog(controller.workspaceId, { signal: abort.signal });
66
+ return Promise.all([
67
+ controller.transport.catalog(controller.workspaceId, { signal: abort.signal }),
68
+ presentation === "catalog"
69
+ ? controller.transport.accounts(controller.workspaceId, { signal: abort.signal })
70
+ : Promise.resolve([] as ConnectAccount[]),
71
+ ]);
53
72
  })
54
- .then((providers) => {
55
- if (!abort.signal.aborted) setCatalog(structuredClone(providers));
73
+ .then(([providers, inventory]) => {
74
+ if (!abort.signal.aborted) {
75
+ setCatalog(
76
+ structuredClone(
77
+ providers.filter(
78
+ (entry) =>
79
+ entry.readiness === "available" &&
80
+ entry.ownership.length > 0 &&
81
+ (!providerOnly ||
82
+ (entry.family !== "mcp" &&
83
+ !entry.setup.some((kind) =>
84
+ ["installation", "openapi", "graphql"].includes(kind),
85
+ ))) &&
86
+ (!customOnly ||
87
+ entry.family === "mcp" ||
88
+ entry.setup.some((kind) =>
89
+ ["installation", "openapi", "graphql"].includes(kind),
90
+ )),
91
+ ),
92
+ ),
93
+ );
94
+ setAccounts(structuredClone(inventory));
95
+ const initial = providers.find((entry) => entry.id === initialProviderId);
96
+ if (initial)
97
+ setOwnership(
98
+ initial.ownership.includes("personal") &&
99
+ ["google", "microsoft"].includes(initial.family)
100
+ ? "personal"
101
+ : initial.ownership.includes("workspace")
102
+ ? "workspace"
103
+ : (initial.ownership[0] ?? ""),
104
+ );
105
+ }
56
106
  })
57
107
  .catch(() => {
58
108
  if (!abort.signal.aborted) setFailed(true);
59
109
  });
60
110
  return () => abort.abort();
61
- }, [controller, load]);
111
+ }, [controller, load, presentation, customOnly, providerOnly, initialProviderId]);
62
112
  const provider = catalog?.find((entry) => entry.id === providerId);
63
113
  const canBegin =
64
114
  provider?.readiness === "available" &&
@@ -76,6 +126,9 @@ function ScopedChooser({
76
126
  setFailed(true);
77
127
  }
78
128
  };
129
+ // Optional adapters must not add a temporary section that disappears once
130
+ // readiness arrives. The primary discovery surface already owns loading.
131
+ if (compact && (!catalog || catalog.length === 0) && !failed) return null;
79
132
  return (
80
133
  <section
81
134
  className={className}
@@ -92,42 +145,117 @@ function ScopedChooser({
92
145
  {Boolean(catalog?.length) && (
93
146
  <form onSubmit={submit}>
94
147
  <fieldset disabled={view.busy}>
95
- <legend>Provider and ownership</legend>
148
+ <legend hidden={customOnly || compact}>
149
+ {presentation === "catalog" ? "Add a connection" : "Provider and ownership"}
150
+ </legend>
96
151
  {presentation === "catalog" && !provider ? (
97
152
  <>
98
- <label>
153
+ <label hidden={customOnly || compact}>
99
154
  Search connections
100
155
  <input
101
156
  type="search"
157
+ placeholder="Find a service…"
102
158
  value={query}
103
159
  onChange={(event) => setQuery(event.target.value)}
104
160
  />
105
161
  </label>
106
- <ConnectionCatalog
107
- query={query}
108
- services={catalog!.map((entry) => ({
109
- id: entry.id,
110
- name: entry.label,
111
- options: [
112
- {
113
- id: entry.id,
114
- name: entry.label,
115
- description: entry.reason,
116
- status: entry.readiness.replaceAll("_", " "),
117
- connected: false,
118
- onOpen: () => {
119
- if (!view.busy) {
120
- setProviderId(entry.id);
121
- setOwnership(entry.ownership.length === 1 ? entry.ownership[0]! : "");
122
- }
123
- },
124
- },
125
- ],
126
- }))}
127
- />
162
+ {[
163
+ {
164
+ name: "Services",
165
+ entries: catalog!.filter(
166
+ (entry) =>
167
+ entry.readiness === "available" &&
168
+ entry.family !== "mcp" &&
169
+ !entry.setup.some((kind) =>
170
+ ["installation", "openapi", "graphql"].includes(kind),
171
+ ),
172
+ ),
173
+ secondary: false,
174
+ },
175
+ {
176
+ name: "Custom connections",
177
+ entries: catalog!.filter(
178
+ (entry) =>
179
+ entry.readiness === "available" &&
180
+ (entry.family === "mcp" ||
181
+ entry.setup.some((kind) =>
182
+ ["installation", "openapi", "graphql"].includes(kind),
183
+ )),
184
+ ),
185
+ secondary: true,
186
+ },
187
+ ].map((group) => {
188
+ const entries = group.entries.filter((entry) =>
189
+ entry.label.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim()),
190
+ );
191
+ if (!entries.length) return null;
192
+ const rows = (
193
+ <ConnectionCatalog
194
+ services={entries.map((entry) => {
195
+ const connected = accounts.some(
196
+ (account) =>
197
+ account.providerId === entry.id && account.status === "connected",
198
+ );
199
+ return {
200
+ id: entry.id,
201
+ name: entry.label,
202
+ logo: (
203
+ <ConnectionLogo
204
+ src={connectionServicePresentation(entry).logo}
205
+ name={entry.label}
206
+ />
207
+ ),
208
+ options: [
209
+ {
210
+ id: entry.id,
211
+ name: entry.label,
212
+ status: connected
213
+ ? "Connected"
214
+ : entry.readiness === "available"
215
+ ? "Connect"
216
+ : "Unavailable",
217
+ state: connected
218
+ ? "added"
219
+ : entry.readiness === "available"
220
+ ? "available"
221
+ : "unavailable",
222
+ connected,
223
+ onOpen: () => {
224
+ if (!view.busy) {
225
+ setProviderId(entry.id);
226
+ setOwnership(
227
+ entry.ownership.length === 1 ? entry.ownership[0]! : "",
228
+ );
229
+ }
230
+ },
231
+ },
232
+ ],
233
+ };
234
+ })}
235
+ />
236
+ );
237
+ return group.secondary && !customOnly ? (
238
+ <details
239
+ className="og-connect-secondary"
240
+ key={group.name}
241
+ open={query.trim() ? true : undefined}
242
+ >
243
+ <summary>
244
+ {group.name}
245
+ <span>{entries.length}</span>
246
+ </summary>
247
+ {rows}
248
+ </details>
249
+ ) : (
250
+ <div key={group.name}>{rows}</div>
251
+ );
252
+ })}
253
+ {!catalog!.some((entry) =>
254
+ entry.label.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim()),
255
+ ) && <p role="status">No services match your search.</p>}
128
256
  </>
129
257
  ) : null}
130
- {presentation === "catalog" && provider ? (
258
+ {presentation === "catalog" && provider && !initialProviderId ? (
131
259
  <button
132
260
  type="button"
133
261
  onClick={() => {
@@ -165,8 +293,14 @@ function ScopedChooser({
165
293
  {provider && (
166
294
  <>
167
295
  <strong>{presentation === "catalog" ? provider.label : null}</strong>
296
+ {provider.readiness !== "available" && (
297
+ <p role="status">
298
+ {provider.reason ??
299
+ "This service is not available in this environment. Contact your administrator to enable it."}
300
+ </p>
301
+ )}
168
302
  <label>
169
- Ownership
303
+ Who can use this connection?
170
304
  <select
171
305
  required
172
306
  value={ownership}
@@ -177,9 +311,7 @@ function ScopedChooser({
177
311
  </option>
178
312
  {provider.ownership.map((choice) => (
179
313
  <option key={choice} value={choice}>
180
- {choice === "personal"
181
- ? "Personal — owned by you"
182
- : "Workspace — shared connection"}
314
+ {choice === "personal" ? "Only me" : "Everyone in this workspace"}
183
315
  </option>
184
316
  ))}
185
317
  </select>
@@ -191,12 +323,12 @@ function ScopedChooser({
191
323
  type="submit"
192
324
  disabled={!canBegin}
193
325
  >
194
- Start setup
326
+ Continue
195
327
  </button>
196
328
  </fieldset>
197
329
  </form>
198
330
  )}
199
- <button type="button" disabled={view.busy} onClick={() => setLoad(load + 1)}>
331
+ <button hidden={!failed} type="button" disabled={view.busy} onClick={() => setLoad(load + 1)}>
200
332
  Reload providers
201
333
  </button>
202
334
  </section>
@@ -1,12 +1,23 @@
1
1
  import { ConnectAccounts } from "./connect-accounts";
2
2
  import { ConnectChooser } from "./connect-chooser";
3
3
  import { ConnectSetup, type ConnectSetupProps } from "./connect-setup";
4
+ import { useConnect } from "./hooks/use-connect";
5
+ import type { OpenGeniClient } from "@opengeni/sdk";
6
+ import { useState } from "react";
7
+ import { connectionServicePresentation } from "./connection-service-presentation";
8
+ import { ConnectionDiscovery } from "./connection-discovery";
4
9
 
5
10
  export type ConnectPanelProps = ConnectSetupProps & {
6
11
  returnUrl: string;
7
- title?: string;
12
+ /** Null omits the heading when the host already supplies one. */
13
+ title?: string | null;
8
14
  showAccounts?: boolean;
15
+ /** Presentation only; the host backend owns acquisition admission. */
16
+ showCustomConnections?: boolean;
17
+ /** Include ready, non-custom adapters from the host's Connect catalogue. */
18
+ showProviderConnections?: boolean;
9
19
  presentation?: "select" | "catalog";
20
+ client?: OpenGeniClient | undefined;
10
21
  };
11
22
 
12
23
  /** Optional composition over the same unstyled surfaces. Import
@@ -16,20 +27,73 @@ export function ConnectPanel({
16
27
  returnUrl,
17
28
  title = "Connections",
18
29
  showAccounts = true,
30
+ showCustomConnections = true,
31
+ showProviderConnections = false,
19
32
  presentation = "select",
20
33
  className,
34
+ client,
21
35
  ...setup
22
36
  }: ConnectPanelProps) {
37
+ const { attempt } = useConnect(setup.controller);
38
+ const [inventoryRevision, setInventoryRevision] = useState(0);
39
+ // Unknown/failed outcomes still need reconciliation or cancellation, not a
40
+ // competing new acquisition. Unmounting observers never cancels controller work.
41
+ const activeSetup =
42
+ attempt !== null && !["complete", "cancelled", "expired"].includes(attempt.state);
43
+ const serviceName = attempt
44
+ ? connectionServicePresentation({ id: attempt.providerId, label: "" }).name
45
+ : "";
23
46
  return (
24
47
  <div className={["og-connect", className].filter(Boolean).join(" ")}>
25
- <h2>{title}</h2>
26
- <ConnectChooser
27
- presentation={presentation}
28
- controller={setup.controller}
29
- returnUrl={returnUrl}
30
- />
31
- <ConnectSetup {...setup} />
32
- {showAccounts && <ConnectAccounts controller={setup.controller} returnUrl={returnUrl} />}
48
+ {title !== null && <h2>{title}</h2>}
49
+ {showAccounts && !activeSetup && (
50
+ <ConnectAccounts
51
+ key={inventoryRevision}
52
+ controller={setup.controller}
53
+ returnUrl={returnUrl}
54
+ client={client}
55
+ onDisconnected={() => setInventoryRevision((value) => value + 1)}
56
+ />
57
+ )}
58
+ {!activeSetup &&
59
+ (client && presentation === "catalog" ? (
60
+ <>
61
+ <ConnectionDiscovery
62
+ key={inventoryRevision}
63
+ controller={showProviderConnections ? setup.controller : undefined}
64
+ client={client}
65
+ workspaceId={setup.controller.workspaceId}
66
+ returnUrl={returnUrl}
67
+ onConfigured={() => setInventoryRevision((value) => value + 1)}
68
+ />
69
+ {showCustomConnections && (
70
+ <details className="og-connect-secondary">
71
+ <summary>Custom connection</summary>
72
+ <ConnectChooser
73
+ customOnly
74
+ presentation={presentation}
75
+ controller={setup.controller}
76
+ returnUrl={returnUrl}
77
+ />
78
+ </details>
79
+ )}
80
+ </>
81
+ ) : (
82
+ <ConnectChooser
83
+ presentation={presentation}
84
+ controller={setup.controller}
85
+ returnUrl={returnUrl}
86
+ />
87
+ ))}
88
+ {activeSetup && (
89
+ <ConnectSetup
90
+ {...setup}
91
+ authorizeLabel={
92
+ setup.authorizeLabel ??
93
+ (serviceName ? `Continue to ${serviceName}` : "Authorize connection")
94
+ }
95
+ />
96
+ )}
33
97
  </div>
34
98
  );
35
99
  }
@@ -1,6 +1,6 @@
1
1
  import { useState, type FormEvent } from "react";
2
2
  import type { ConnectAttempt, ConnectController } from "@opengeni/connect";
3
- import { useConnect } from "./connect";
3
+ import { useConnect } from "./hooks/use-connect";
4
4
 
5
5
  const setupStatus: Record<ConnectAttempt["state"], string> = {
6
6
  ready: "Ready to connect",
@@ -9,9 +9,9 @@ const setupStatus: Record<ConnectAttempt["state"], string> = {
9
9
  provider_wait: "Waiting for authorization",
10
10
  account_selection: "Choose an account",
11
11
  resource_selection: "Choose resources",
12
- preview: "Review available operations",
13
- installing: "Installing selected operations…",
14
- connected_but_incomplete: "Account connected — finish setup",
12
+ preview: "Choose tools",
13
+ installing: "Adding selected tools…",
14
+ connected_but_incomplete: "Choose which tools to add",
15
15
  complete: "Connection ready",
16
16
  cancelled: "Setup cancelled",
17
17
  expired: "Setup expired",
@@ -35,12 +35,16 @@ export type ConnectSetupProps = {
35
35
  controller: ConnectController;
36
36
  /** Called synchronously in the click handler so a host can open a popup. */
37
37
  onAuthorize: (attempt: ConnectAttempt) => void | Promise<unknown>;
38
+ /** Called synchronously on Continue; hosts can reserve a popup before advancing. */
39
+ onSelectAccount?: (accountId: string) => void | Promise<unknown>;
38
40
  /** Host pagination must retain selections across pages and submit the final set. */
39
41
  onBrowseResources?: (attempt: ConnectAttempt) => void | Promise<unknown>;
40
42
  className?: string;
43
+ /** Provider-specific label supplied by the embedding host. */
44
+ authorizeLabel?: string | undefined;
41
45
  };
42
46
 
43
- /** Unstyled setup surface. No provider secrets enter React state. Host owns
47
+ /** Controller-backed setup surface, styled by opt-in connect.css. No provider secrets enter React state. Host owns
44
48
  * chooser, controller lifetime, navigation and recovery after full redirect. */
45
49
  export function ConnectSetup(props: ConnectSetupProps) {
46
50
  // Attempt IDs/revisions are not a substitute for actor scope. A host can
@@ -55,9 +59,17 @@ export function ConnectSetup(props: ConnectSetupProps) {
55
59
  return <ScopedSetup key={generation} {...props} />;
56
60
  }
57
61
 
58
- function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }: ConnectSetupProps) {
62
+ function ScopedSetup({
63
+ controller,
64
+ onAuthorize,
65
+ onBrowseResources,
66
+ onSelectAccount,
67
+ className,
68
+ authorizeLabel = "Authorize connection",
69
+ }: ConnectSetupProps) {
59
70
  const view = useConnect(controller);
60
71
  const [localError, setLocalError] = useState(false);
72
+ const [navigating, setNavigating] = useState(false);
61
73
  const invoke = (operation: () => unknown | Promise<unknown>) => {
62
74
  setLocalError(false);
63
75
  try {
@@ -69,7 +81,10 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
69
81
  const attempt = view.attempt;
70
82
  if (!attempt)
71
83
  return (
72
- <section className={className} aria-label="Connection setup">
84
+ <section
85
+ className={["og-connect-setup", className].filter(Boolean).join(" ")}
86
+ aria-label="Connection setup"
87
+ >
73
88
  <p role="status">Choose a connection to begin.</p>
74
89
  </section>
75
90
  );
@@ -80,7 +95,7 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
80
95
  event.preventDefault();
81
96
  const form = event.currentTarget;
82
97
  const data = new FormData(form);
83
- if (view.busy) return;
98
+ if (view.busy || navigating) return;
84
99
  const key = crypto.randomUUID();
85
100
  if (action.type === "credentials") {
86
101
  const values = Object.fromEntries(
@@ -94,7 +109,15 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
94
109
  if (
95
110
  action.accounts.some((account) => account.id === accountId && account.status !== "disabled")
96
111
  )
97
- invoke(() => view.advance({ type: "account", accountId }, key));
112
+ invoke(async () => {
113
+ if (!onSelectAccount) return view.advance({ type: "account", accountId }, key);
114
+ setNavigating(true);
115
+ try {
116
+ await onSelectAccount(accountId);
117
+ } finally {
118
+ setNavigating(false);
119
+ }
120
+ });
98
121
  } else if (action.type === "select_resources") {
99
122
  if (action.cursor) return;
100
123
  const resourceIds = data.getAll("resource").map(String);
@@ -115,27 +138,47 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
115
138
  }
116
139
  };
117
140
  return (
118
- <section className={className} aria-label="Connection setup" aria-busy={view.busy}>
119
- <p>Ownership: {attempt.ownership === "personal" ? "Personal" : "Workspace"}</p>
120
- <p role="status">{setupStatus[attempt.state]}</p>
141
+ <section
142
+ className={["og-connect-setup", className].filter(Boolean).join(" ")}
143
+ aria-label="Connection setup"
144
+ aria-busy={view.busy || navigating}
145
+ >
146
+ <div className="og-connect-setup-summary">
147
+ <p className="og-connect-setup-scope">
148
+ {attempt.ownership === "personal" ? "Personal connection" : "Workspace connection"}
149
+ </p>
150
+ <p role="status" className="og-connect-setup-status">
151
+ {setupStatus[attempt.state]}
152
+ </p>
153
+ <p className="og-connect-setup-description">
154
+ {attempt.ownership === "personal"
155
+ ? "Only your work can use this connection."
156
+ : "Work in this workspace can use this connection."}
157
+ </p>
158
+ </div>
121
159
  {attempt.account && <p>Account: {attempt.account.label}</p>}
122
160
  {(localError || view.error || attempt.error) && (
123
161
  <p role="alert">
124
162
  {attempt.error?.code === "source_changed"
125
- ? "The integration source changed. Review the new operations before installing."
163
+ ? "The integration source changed. Review the updated tools before adding them."
126
164
  : "Connection setup could not continue. Refresh its status before trying again."}
127
165
  </p>
128
166
  )}
129
167
  {!terminal && (
130
168
  <form key={`${attempt.id}:${attempt.revision}`} onSubmit={submit} autoComplete="off">
131
- <fieldset disabled={view.busy}>
132
- <legend>Connection details</legend>
169
+ <fieldset disabled={view.busy || navigating}>
170
+ {["credentials", "select_account", "select_resources", "preview"].includes(
171
+ action.type,
172
+ ) ? (
173
+ <legend>Connection details</legend>
174
+ ) : null}
133
175
  {attempt.state === "connected_but_incomplete" && action.type === "none" && (
134
176
  <button
135
177
  type="button"
178
+ className="og-connect-setup-primary"
136
179
  onClick={() => invoke(() => view.advance({ type: "retry" }, crypto.randomUUID()))}
137
180
  >
138
- Review integration operations
181
+ Choose tools
139
182
  </button>
140
183
  )}
141
184
  {action.type === "credentials" &&
@@ -204,7 +247,7 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
204
247
  )}
205
248
  {action.type === "preview" && (
206
249
  <>
207
- <p>Select the operations to install.</p>
250
+ <p>Choose the tools agents can use with this account.</p>
208
251
  {action.operations.map((operation) => (
209
252
  <label key={operation.id}>
210
253
  <input type="checkbox" name="operation" value={operation.id} />
@@ -218,17 +261,19 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
218
261
  ) && (
219
262
  <button
220
263
  type="submit"
264
+ className="og-connect-setup-primary"
221
265
  disabled={action.type === "select_resources" && Boolean(action.cursor)}
222
266
  >
223
- {action.type === "preview" ? "Install selected operations" : "Continue"}
267
+ {action.type === "preview" ? "Add selected tools" : "Continue"}
224
268
  </button>
225
269
  )}
226
270
  {action.type === "authorize" && (
227
271
  <button
228
272
  type="button"
273
+ className="og-connect-setup-primary"
229
274
  onClick={() => invoke(() => onAuthorize(structuredClone(attempt)))}
230
275
  >
231
- Authorize connection
276
+ {navigating ? "Waiting for authorization…" : authorizeLabel}
232
277
  </button>
233
278
  )}
234
279
  {action.type === "wait" && (
@@ -246,12 +291,14 @@ function ScopedSetup({ controller, onAuthorize, onBrowseResources, className }:
246
291
  )}
247
292
  </>
248
293
  )}
249
- <button type="button" onClick={() => invoke(() => view.refresh())}>
250
- Check status
251
- </button>
252
- <button type="button" onClick={() => invoke(() => view.cancel(crypto.randomUUID()))}>
253
- Cancel setup
254
- </button>
294
+ <div className="og-connect-setup-secondary">
295
+ <button type="button" onClick={() => invoke(() => view.refresh())}>
296
+ Check status
297
+ </button>
298
+ <button type="button" onClick={() => invoke(() => view.cancel(crypto.randomUUID()))}>
299
+ Cancel setup
300
+ </button>
301
+ </div>
255
302
  </fieldset>
256
303
  </form>
257
304
  )}
package/src/connect.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  // Optional, unstyled React adapter over the shared headless controller.
2
2
  // The host owns controller lifetime; unmounting one observer does not cancel an
3
3
  // attempt another observer is displaying or a durable backend operation.
4
- import { useMemo, useSyncExternalStore } from "react";
4
+ export { useConnect } from "./hooks/use-connect";
5
+ export {
6
+ CapabilityCatalogRow,
7
+ CapabilityCatalogIndicator,
8
+ type CapabilityCatalogRowProps,
9
+ type CapabilityCatalogStatus,
10
+ } from "./capability-catalog-row";
5
11
  export {
6
12
  SkillDiscovery,
7
13
  type SkillDiscoveryProps,
@@ -9,11 +15,15 @@ export {
9
15
  type SkillDiscoveryItem,
10
16
  type SkillDiscoveryPage,
11
17
  } from "./skill-discovery";
12
- import type { ConnectController } from "@opengeni/connect";
13
18
  export { ConnectSetup, type ConnectSetupProps } from "./connect-setup";
14
19
  export { ConnectChooser, type ConnectChooserProps } from "./connect-chooser";
15
20
  export { ConnectAccounts, type ConnectAccountsProps } from "./connect-accounts";
16
21
  export { ConnectPanel, type ConnectPanelProps } from "./connect-panel";
22
+ export { ConnectionDiscovery, type ConnectionDiscoveryProps } from "./connection-discovery";
23
+ export {
24
+ McpConnectionCard,
25
+ type McpConnectionCardProps,
26
+ } from "./components/session-mcp-capability-card";
17
27
  export { DeviceAuthorization, type DeviceAuthorizationProps } from "./device-authorization";
18
28
  export {
19
29
  IdentityLinkConsent,
@@ -34,26 +44,6 @@ export type {
34
44
  ConnectTransport,
35
45
  } from "@opengeni/connect";
36
46
 
37
- export function useConnect(controller: ConnectController) {
38
- const snapshot = useSyncExternalStore(
39
- controller.subscribe,
40
- controller.getSnapshot,
41
- controller.getSnapshot,
42
- );
43
- const actions = useMemo(
44
- () => ({
45
- begin: controller.begin.bind(controller),
46
- recover: controller.recover.bind(controller),
47
- refresh: controller.refresh.bind(controller),
48
- waitForAction: controller.waitForAction.bind(controller),
49
- advance: controller.advance.bind(controller),
50
- cancel: controller.cancel.bind(controller),
51
- }),
52
- [controller],
53
- );
54
- return useMemo(() => ({ ...snapshot, ...actions }), [snapshot, actions]);
55
- }
56
-
57
47
  export {
58
48
  ConnectionCatalog,
59
49
  ConnectionServiceRow,
@@ -65,5 +55,6 @@ export {
65
55
  export { ConnectionInstalled, type ConnectionInstalledItem } from "./connection-installed";
66
56
  export { ConnectionTypePicker, type ConnectionType } from "./connection-type-picker";
67
57
  export { ConnectionLogo } from "./connection-logo";
58
+ export { capabilityLogoFallback } from "./capability-logo-fallback";
68
59
  export { PluginDiscovery, type PluginDiscoveryProps } from "./plugin-discovery";
69
60
  export { PluginDetails } from "./plugin-details";