@opengeni/react 0.52.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +51 -1
  2. package/dist/artifacts.d.ts +1 -1
  3. package/dist/artifacts.js +43 -14
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{chunk-ROIRTM37.js → chunk-5UQPNLIO.js} +6 -6
  6. package/dist/{chunk-JR3QFY6B.js → chunk-6KLX2RH5.js} +49 -2
  7. package/dist/chunk-6KLX2RH5.js.map +1 -0
  8. package/dist/{chunk-FWS3KC3Z.js → chunk-B6WPZSTT.js} +104 -104
  9. package/dist/{chunk-FWS3KC3Z.js.map → chunk-B6WPZSTT.js.map} +1 -1
  10. package/dist/{chunk-HDBG7XGO.js → chunk-C5ZPSSS5.js} +50 -1
  11. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  12. package/dist/{chunk-X4S56Y2H.js → chunk-HA46CYQK.js} +135 -11
  13. package/dist/chunk-HA46CYQK.js.map +1 -0
  14. package/dist/{chunk-724BW4TW.js → chunk-IGC6A2ZI.js} +2 -2
  15. package/dist/chunk-LLOLUHVG.js +5937 -0
  16. package/dist/chunk-LLOLUHVG.js.map +1 -0
  17. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  18. package/dist/chunk-MKPIAGVL.js.map +1 -0
  19. package/dist/chunk-R3AZOAB5.js +933 -0
  20. package/dist/chunk-R3AZOAB5.js.map +1 -0
  21. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  22. package/dist/chunk-XBZWOM2K.js.map +1 -0
  23. package/dist/{chunk-JAGDUCZQ.js → chunk-Y7OEDUJ5.js} +17 -13
  24. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  25. package/dist/client.d.ts +6 -4
  26. package/dist/components/artifacts/index.d.ts +1 -0
  27. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  28. package/dist/components/browser-input.d.ts +7 -0
  29. package/dist/components/browser-viewer.d.ts +5 -4
  30. package/dist/components/computer-viewer.d.ts +2 -1
  31. package/dist/components/desktop-viewer.d.ts +26 -2
  32. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  33. package/dist/components/sandbox-terminal.d.ts +4 -1
  34. package/dist/components/sandbox-workspace.d.ts +3 -0
  35. package/dist/composer.js +6 -6
  36. package/dist/hooks/use-auth-runs.d.ts +32 -0
  37. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  38. package/dist/hooks/use-browser-identities.d.ts +5 -1
  39. package/dist/hooks/use-browser-session.d.ts +2 -1
  40. package/dist/hooks/use-browser-sessions.d.ts +1 -1
  41. package/dist/hooks/use-channels.d.ts +26 -0
  42. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  43. package/dist/hooks/use-computer-session.d.ts +2 -1
  44. package/dist/hooks/use-computer-sessions.d.ts +1 -1
  45. package/dist/hooks/use-desktop-stream.d.ts +4 -0
  46. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  47. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  48. package/dist/hooks/use-network-routes.d.ts +25 -0
  49. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  50. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  51. package/dist/hooks/use-terminal-stream.d.ts +3 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +1220 -1545
  54. package/dist/index.js.map +1 -1
  55. package/dist/interaction.d.ts +13 -3
  56. package/dist/interaction.js +520 -6
  57. package/dist/interaction.js.map +1 -1
  58. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  59. package/dist/lib/host-platform.d.ts +5 -0
  60. package/dist/lib/relay-wire.d.ts +20 -1
  61. package/dist/machines.js +3 -3
  62. package/dist/model-policy.d.ts +1 -1
  63. package/dist/model-policy.js +1 -1
  64. package/dist/provider.d.ts +3 -2
  65. package/dist/realtime/realtime-control.d.ts +6 -2
  66. package/dist/realtime.js +138 -24
  67. package/dist/realtime.js.map +1 -1
  68. package/dist/session-context.d.ts +10 -2
  69. package/dist/session-ui.js +3 -3
  70. package/dist/session.js +11 -11
  71. package/dist/timeline/types.d.ts +5 -0
  72. package/package.json +2 -2
  73. package/src/artifacts.ts +3 -0
  74. package/src/client.ts +73 -3
  75. package/src/components/artifacts/index.ts +5 -0
  76. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  77. package/src/components/browser-input.ts +65 -0
  78. package/src/components/browser-viewer.tsx +1287 -177
  79. package/src/components/computer-viewer.tsx +611 -114
  80. package/src/components/desktop-viewer.tsx +114 -1
  81. package/src/components/interaction-intervention-banner.tsx +99 -0
  82. package/src/components/machines/machine-detail.tsx +1 -1
  83. package/src/components/model-policy-picker.tsx +15 -0
  84. package/src/components/sandbox-terminal.tsx +24 -7
  85. package/src/components/sandbox-workspace.tsx +42 -4
  86. package/src/components/workspace-dock.tsx +59 -5
  87. package/src/hooks/use-attached-browsers.ts +13 -11
  88. package/src/hooks/use-auth-runs.ts +278 -0
  89. package/src/hooks/use-browser-downloads.ts +218 -0
  90. package/src/hooks/use-browser-frame-stream.ts +168 -59
  91. package/src/hooks/use-browser-identities.ts +47 -6
  92. package/src/hooks/use-browser-session.ts +94 -27
  93. package/src/hooks/use-browser-sessions.ts +19 -14
  94. package/src/hooks/use-channels.ts +99 -0
  95. package/src/hooks/use-computer-frame-stream.ts +184 -61
  96. package/src/hooks/use-computer-session.ts +22 -2
  97. package/src/hooks/use-computer-sessions.ts +19 -13
  98. package/src/hooks/use-desktop-stream.ts +39 -7
  99. package/src/hooks/use-interaction-interventions.ts +242 -0
  100. package/src/hooks/use-interaction-invalidation.ts +66 -0
  101. package/src/hooks/use-machine-chip.ts +13 -3
  102. package/src/hooks/use-network-routes.ts +214 -0
  103. package/src/hooks/use-site-auth-connections.ts +226 -0
  104. package/src/hooks/use-supergrok-accounts.ts +81 -0
  105. package/src/hooks/use-terminal-stream.ts +288 -55
  106. package/src/index.ts +8 -0
  107. package/src/interaction.ts +30 -0
  108. package/src/lib/host-platform.ts +10 -0
  109. package/src/lib/relay-wire.ts +43 -4
  110. package/src/lib/terminal-capability.ts +1 -1
  111. package/src/model-policy.ts +19 -4
  112. package/src/provider.tsx +133 -1
  113. package/src/realtime/realtime-control.tsx +181 -26
  114. package/src/session-context.ts +66 -1
  115. package/src/timeline/activity-rail.tsx +6 -0
  116. package/src/timeline/projection.ts +58 -0
  117. package/src/timeline/types.ts +2 -0
  118. package/styles/compiled.css +64 -6
  119. package/dist/chunk-23EJ676W.js.map +0 -1
  120. package/dist/chunk-6BBWWOOU.js +0 -82
  121. package/dist/chunk-6BBWWOOU.js.map +0 -1
  122. package/dist/chunk-7I2KB3OK.js +0 -3895
  123. package/dist/chunk-7I2KB3OK.js.map +0 -1
  124. package/dist/chunk-HDBG7XGO.js.map +0 -1
  125. package/dist/chunk-JAGDUCZQ.js.map +0 -1
  126. package/dist/chunk-JR3QFY6B.js.map +0 -1
  127. package/dist/chunk-WYA65Y3F.js.map +0 -1
  128. package/dist/chunk-X4S56Y2H.js.map +0 -1
  129. package/dist/interactive-workbench-viewers-KHNOHWQE.js +0 -13
  130. /package/dist/{chunk-ROIRTM37.js.map → chunk-5UQPNLIO.js.map} +0 -0
  131. /package/dist/{chunk-724BW4TW.js.map → chunk-IGC6A2ZI.js.map} +0 -0
  132. /package/dist/{interactive-workbench-viewers-KHNOHWQE.js.map → interactive-workbench-viewers-TOHBXJEE.js.map} +0 -0
@@ -2,6 +2,7 @@ import type {
2
2
  BrowserAction,
3
3
  BrowserActionBatch,
4
4
  BrowserActionReceipt,
5
+ BrowserClipboard,
5
6
  BrowserDiagnosticBatch,
6
7
  BrowserDiagnosticsOptions,
7
8
  BrowserFrame,
@@ -9,6 +10,7 @@ import type {
9
10
  BrowserSession,
10
11
  BrowserTarget,
11
12
  } from "@opengeni/sdk/interaction";
13
+ import { OpenGeniApiError } from "@opengeni/sdk";
12
14
  import { useCallback, useEffect, useRef, useState } from "react";
13
15
  import {
14
16
  type EmbeddedBrowserInteractionClientOverride,
@@ -45,6 +47,7 @@ export type UseBrowserSessionResult = {
45
47
  frame: BrowserFrame,
46
48
  operationId?: string,
47
49
  ) => Promise<BrowserActionReceipt>;
50
+ readClipboard: () => Promise<BrowserClipboard>;
48
51
  diagnostics: (options?: BrowserDiagnosticsOptions) => Promise<BrowserDiagnosticBatch>;
49
52
  };
50
53
 
@@ -118,14 +121,8 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
118
121
  ]);
119
122
  if (!mountedRef.current || requestRef.current.id !== id) return;
120
123
  const selected = chooseTarget(targetResponse.targets, selectedTargetIdRef.current);
121
- const observation = selected
122
- ? await client.observeBrowserTarget(workspaceId, browserSessionId, selected.id, {
123
- signal: controller.signal,
124
- })
125
- : null;
126
- if (!mountedRef.current || requestRef.current.id !== id) return;
127
124
  selectedTargetIdRef.current = selected?.id ?? null;
128
- observationRef.current = { browserSessionId, observation };
125
+ observationRef.current = { browserSessionId, observation: null };
129
126
  setState((current) =>
130
127
  current.browserSessionId === browserSessionId
131
128
  ? {
@@ -133,12 +130,36 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
133
130
  session,
134
131
  targets: targetResponse.targets,
135
132
  selectedTargetId: selected?.id ?? null,
136
- observation,
133
+ observation: null,
137
134
  loading: false,
138
135
  error: null,
139
136
  }
140
137
  : current,
141
138
  );
139
+ if (!selected) return;
140
+ try {
141
+ const observation = await client.observeBrowserTarget(
142
+ workspaceId,
143
+ browserSessionId,
144
+ selected.id,
145
+ { signal: controller.signal },
146
+ );
147
+ if (!mountedRef.current || requestRef.current.id !== id) return;
148
+ observationRef.current = { browserSessionId, observation };
149
+ setState((current) =>
150
+ current.browserSessionId === browserSessionId &&
151
+ current.selectedTargetId === observation.target.id
152
+ ? { ...current, observation }
153
+ : current,
154
+ );
155
+ } catch (cause) {
156
+ if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
157
+ return;
158
+ // Target inventory and the media plane remain authoritative for the
159
+ // human-facing browser. A failed semantic snapshot must not blank or
160
+ // disable an otherwise live visual browser.
161
+ void cause;
162
+ }
142
163
  } catch (cause) {
143
164
  if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id) return;
144
165
  setState((current) =>
@@ -239,19 +260,50 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
239
260
  async (targetId: string): Promise<BrowserTarget> => {
240
261
  if (!browserSessionId) throw new Error("No BrowserSession is selected.");
241
262
  return await runMutation(browserSessionId, async () => {
242
- const target = await client.selectBrowserTarget(workspaceId, browserSessionId, targetId);
243
- const observation = await client.observeBrowserTarget(
244
- workspaceId,
245
- browserSessionId,
246
- target.id,
247
- );
263
+ let targets = visible.targets;
264
+ let target: BrowserTarget;
265
+ let observation: BrowserObservation;
266
+ try {
267
+ observation = await client.selectBrowserTarget(workspaceId, browserSessionId, targetId);
268
+ target = observation.target;
269
+ } catch (cause) {
270
+ if (!isMissingBrowserTarget(cause)) throw cause;
271
+ // A physical/attached tab may disappear between inventory and click.
272
+ // Reconcile once from the authoritative controller inventory and move
273
+ // to its live selected/first page instead of leaving a dead tab ID in
274
+ // React state. Session-level 404s are deliberately not swallowed.
275
+ const response = await client.listBrowserTargets(workspaceId, browserSessionId);
276
+ targets = response.targets;
277
+ const fallback = chooseTarget(targets, null);
278
+ if (!fallback) {
279
+ selectedTargetIdRef.current = null;
280
+ observationRef.current = { browserSessionId, observation: null };
281
+ setState((current) =>
282
+ current.browserSessionId === browserSessionId
283
+ ? {
284
+ ...current,
285
+ targets,
286
+ selectedTargetId: null,
287
+ observation: null,
288
+ }
289
+ : current,
290
+ );
291
+ throw cause;
292
+ }
293
+ observation = await client.selectBrowserTarget(
294
+ workspaceId,
295
+ browserSessionId,
296
+ fallback.id,
297
+ );
298
+ target = observation.target;
299
+ }
248
300
  selectedTargetIdRef.current = target.id;
249
301
  observationRef.current = { browserSessionId, observation };
250
302
  setState((current) =>
251
303
  current.browserSessionId === browserSessionId
252
304
  ? {
253
305
  ...current,
254
- targets: current.targets.map((candidate) => ({
306
+ targets: targets.map((candidate) => ({
255
307
  ...candidate,
256
308
  selected: candidate.id === target.id,
257
309
  })),
@@ -263,23 +315,19 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
263
315
  return target;
264
316
  });
265
317
  },
266
- [browserSessionId, client, runMutation, workspaceId],
318
+ [browserSessionId, client, runMutation, visible.targets, workspaceId],
267
319
  );
268
320
 
269
321
  const openTarget = useCallback(
270
322
  async (url?: string): Promise<BrowserTarget> => {
271
323
  if (!browserSessionId) throw new Error("No BrowserSession is selected.");
272
324
  return await runMutation(browserSessionId, async () => {
273
- const target = await client.openBrowserTarget(
325
+ const observation = await client.openBrowserTarget(
274
326
  workspaceId,
275
327
  browserSessionId,
276
328
  url === undefined ? {} : { url },
277
329
  );
278
- const observation = await client.observeBrowserTarget(
279
- workspaceId,
280
- browserSessionId,
281
- target.id,
282
- );
330
+ const target = observation.target;
283
331
  selectedTargetIdRef.current = target.id;
284
332
  observationRef.current = { browserSessionId, observation };
285
333
  setState((current) =>
@@ -362,10 +410,11 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
362
410
  if (!fence) {
363
411
  throw new Error("The browser page is not ready for input.");
364
412
  }
365
- return await runMutation(browserSessionId, async () => {
413
+ try {
366
414
  const receipt = await client.actInBrowser(workspaceId, browserSessionId, {
367
415
  operationId,
368
416
  ...fence,
417
+ observationMode: "none",
369
418
  action,
370
419
  });
371
420
  if (receipt.observation) {
@@ -382,13 +431,17 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
382
431
  }
383
432
  : current,
384
433
  );
385
- } else {
386
- void refresh();
387
434
  }
388
435
  return receipt;
389
- });
436
+ } catch (cause) {
437
+ const error = cause instanceof Error ? cause : new Error(String(cause));
438
+ setState((current) =>
439
+ current.browserSessionId === browserSessionId ? { ...current, error } : current,
440
+ );
441
+ throw error;
442
+ }
390
443
  },
391
- [browserSessionId, client, refresh, runMutation, workspaceId],
444
+ [browserSessionId, client, workspaceId],
392
445
  );
393
446
 
394
447
  const act = useCallback(
@@ -424,6 +477,15 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
424
477
  [browserSessionId, client, workspaceId],
425
478
  );
426
479
 
480
+ const readClipboard = useCallback(async (): Promise<BrowserClipboard> => {
481
+ if (!browserSessionId) throw new Error("No BrowserSession is selected.");
482
+ const clipboard = await client.readBrowserClipboard(workspaceId, browserSessionId);
483
+ if (clipboard.browserSessionId !== browserSessionId) {
484
+ throw new Error("Browser clipboard belongs to another BrowserSession.");
485
+ }
486
+ return clipboard;
487
+ }, [browserSessionId, client, workspaceId]);
488
+
427
489
  return {
428
490
  session: visible.session,
429
491
  targets: visible.targets,
@@ -439,6 +501,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
439
501
  closeTarget,
440
502
  act,
441
503
  actFromFrame,
504
+ readClipboard,
442
505
  diagnostics,
443
506
  };
444
507
  }
@@ -474,3 +537,7 @@ function replaceTarget(targets: readonly BrowserTarget[], target: BrowserTarget)
474
537
  next.push(target);
475
538
  return next.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
476
539
  }
540
+
541
+ function isMissingBrowserTarget(error: unknown): boolean {
542
+ return error instanceof OpenGeniApiError && error.code === "target_not_found";
543
+ }
@@ -9,13 +9,13 @@ import {
9
9
  type EmbeddedBrowserInteractionClientOverride,
10
10
  useEmbeddedBrowserInteraction,
11
11
  } from "../session-context";
12
- import { usePageLiveActivity } from "./internal";
12
+ import { useInteractionInvalidation } from "./use-interaction-invalidation";
13
13
 
14
14
  export type UseBrowserSessionsOptions = EmbeddedBrowserInteractionClientOverride & {
15
15
  /** Current agent/session relevance. Workspace peers remain in `sessions`. */
16
16
  sessionId?: string | undefined;
17
17
  enabled?: boolean | undefined;
18
- /** Temporary invalidation path until the interaction event stream lands. */
18
+ /** Polling fallback used only while the workspace invalidation stream is unavailable. */
19
19
  pollIntervalMs?: number | undefined;
20
20
  };
21
21
 
@@ -50,10 +50,14 @@ export type UseBrowserSessionsResult = {
50
50
  export function useBrowserSessions(
51
51
  options: UseBrowserSessionsOptions = {},
52
52
  ): UseBrowserSessionsResult {
53
- const { client, workspaceId, registerSessionReconciler } = useEmbeddedBrowserInteraction(options);
53
+ const {
54
+ client,
55
+ workspaceId,
56
+ registerSessionReconciler,
57
+ workspaceInteractionEvent,
58
+ workspaceInteractionConnectionState,
59
+ } = useEmbeddedBrowserInteraction(options);
54
60
  const enabled = options.enabled ?? true;
55
- const pageLive = usePageLiveActivity();
56
- const pollIntervalMs = Math.max(1_000, options.pollIntervalMs ?? 3_000);
57
61
  const [state, setState] = useState<{
58
62
  workspaceId: string;
59
63
  revision: number;
@@ -137,15 +141,16 @@ export function useBrowserSessions(
137
141
  };
138
142
  }, [cancelLoad, enabled, load, workspaceId]);
139
143
 
140
- useEffect(() => {
141
- if (!enabled || !pageLive) return;
142
- const timer = setInterval(() => {
143
- // Let a slow list request finish. A poll must not repeatedly abort and
144
- // restart the same read; an explicit Refresh may still replace it.
145
- if (!requestRef.current.controller) void load(false);
146
- }, pollIntervalMs);
147
- return () => clearInterval(timer);
148
- }, [enabled, load, pageLive, pollIntervalMs]);
144
+ useInteractionInvalidation({
145
+ workspaceId,
146
+ key: workspaceId,
147
+ enabled,
148
+ event: workspaceInteractionEvent,
149
+ connectionState: workspaceInteractionConnectionState,
150
+ knownRevision: visibleState.revision,
151
+ refresh,
152
+ fallbackPollIntervalMs: options.pollIntervalMs,
153
+ });
149
154
 
150
155
  useEffect(() => {
151
156
  if (!enabled || !options.sessionId) return;
@@ -0,0 +1,99 @@
1
+ import type { Channel, CreateChannelRequest, Session, UpdateChannelRequest } from "@opengeni/sdk";
2
+ import { useCallback } from "react";
3
+ import { useOpenGeni, type ClientOverride } from "../provider";
4
+ import { useMutationRunner, usePolledValue } from "./internal";
5
+
6
+ export type UseChannelsOptions = ClientOverride & {
7
+ pollIntervalMs?: number | undefined;
8
+ enabled?: boolean | undefined;
9
+ };
10
+
11
+ export type UseChannelsResult = {
12
+ channels: Channel[];
13
+ loading: boolean;
14
+ error: Error | null;
15
+ refresh: () => Promise<void>;
16
+ create: (request: CreateChannelRequest) => Promise<Channel | null>;
17
+ update: (channelId: string, request: UpdateChannelRequest) => Promise<Channel | null>;
18
+ remove: (channelId: string) => Promise<boolean>;
19
+ /** Re-file a session into a channel; null moves it back to the inbox. */
20
+ moveSession: (sessionId: string, channelId: string | null) => Promise<Session | null>;
21
+ mutating: boolean;
22
+ mutationError: Error | null;
23
+ clearMutationError: () => void;
24
+ };
25
+
26
+ /**
27
+ * Workspace-shared channels: rail organization for root sessions
28
+ * ("workstreams") by work type. Pure metadata — filing a session into a
29
+ * channel never affects execution.
30
+ */
31
+ export function useChannels(options: UseChannelsOptions = {}): UseChannelsResult {
32
+ const { client, workspaceId } = useOpenGeni(options);
33
+ const load = useCallback(
34
+ async () => await client.listChannels(workspaceId),
35
+ [client, workspaceId],
36
+ );
37
+ const { data, loading, error, refresh } = usePolledValue(load, {
38
+ pollIntervalMs: options.pollIntervalMs,
39
+ enabled: options.enabled,
40
+ });
41
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
42
+
43
+ const create = useCallback(
44
+ async (request: CreateChannelRequest): Promise<Channel | null> => {
45
+ const result = await run(() => client.createChannel(workspaceId, request));
46
+ if (result) {
47
+ await refresh();
48
+ }
49
+ return result;
50
+ },
51
+ [client, workspaceId, run, refresh],
52
+ );
53
+
54
+ const update = useCallback(
55
+ async (channelId: string, request: UpdateChannelRequest): Promise<Channel | null> => {
56
+ const result = await run(() => client.updateChannel(workspaceId, channelId, request));
57
+ if (result) {
58
+ await refresh();
59
+ }
60
+ return result;
61
+ },
62
+ [client, workspaceId, run, refresh],
63
+ );
64
+
65
+ const remove = useCallback(
66
+ async (channelId: string): Promise<boolean> => {
67
+ const result = await run(async () => {
68
+ await client.deleteChannel(workspaceId, channelId);
69
+ return true;
70
+ });
71
+ if (result) {
72
+ await refresh();
73
+ }
74
+ return result === true;
75
+ },
76
+ [client, workspaceId, run, refresh],
77
+ );
78
+
79
+ const moveSession = useCallback(
80
+ async (sessionId: string, channelId: string | null): Promise<Session | null> => {
81
+ return await run(() => client.updateSessionChannel(workspaceId, sessionId, { channelId }));
82
+ },
83
+ [client, workspaceId, run],
84
+ );
85
+
86
+ return {
87
+ channels: data ?? [],
88
+ loading,
89
+ error,
90
+ refresh,
91
+ create,
92
+ update,
93
+ remove,
94
+ moveSession,
95
+ mutating,
96
+ mutationError,
97
+ clearMutationError,
98
+ };
99
+ }