@opengeni/react 0.20.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/{chunk-6BXBGJ3B.js → chunk-6XUS5VFM.js} +2 -2
  2. package/dist/{chunk-MRSECXRP.js → chunk-AVPU5PMC.js} +87 -57
  3. package/dist/chunk-AVPU5PMC.js.map +1 -0
  4. package/dist/{chunk-HHFA4AFX.js → chunk-I3BJZIG5.js} +15 -1
  5. package/dist/chunk-I3BJZIG5.js.map +1 -0
  6. package/dist/{chunk-LAZ2A743.js → chunk-RDPDU4TA.js} +8 -7
  7. package/dist/chunk-RDPDU4TA.js.map +1 -0
  8. package/dist/{chunk-6UT5OC3P.js → chunk-SHOFILHJ.js} +130 -16
  9. package/dist/chunk-SHOFILHJ.js.map +1 -0
  10. package/dist/{chunk-EBTT3MYN.js → chunk-YDNWMKXO.js} +11 -1
  11. package/dist/chunk-YDNWMKXO.js.map +1 -0
  12. package/dist/{composer-DvUaa5ki.d.ts → composer-BXb0Q1HF.d.ts} +19 -5
  13. package/dist/composer.d.ts +3 -3
  14. package/dist/composer.js +3 -3
  15. package/dist/fleet-decision-projection-SLNZMS42.js +241 -0
  16. package/dist/fleet-decision-projection-SLNZMS42.js.map +1 -0
  17. package/dist/fleet-decision-row-GGCAT4E4.js +351 -0
  18. package/dist/fleet-decision-row-GGCAT4E4.js.map +1 -0
  19. package/dist/index.d.ts +14 -11
  20. package/dist/index.js +466 -244
  21. package/dist/index.js.map +1 -1
  22. package/dist/machines.d.ts +1 -1
  23. package/dist/machines.js +2 -2
  24. package/dist/{queue-surface-implementation-NQMV2ML3.js → queue-surface-implementation-3UW3MIIR.js} +9 -9
  25. package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
  26. package/dist/{session-Gd4iVPYw.d.ts → session-BEvtFWhe.d.ts} +71 -24
  27. package/dist/{session-context-DIFFlXKc.d.ts → session-context-rgrfElMl.d.ts} +7 -1
  28. package/dist/session.d.ts +3 -3
  29. package/dist/session.js +7 -3
  30. package/dist/{use-composer-BCfm4mzX.d.ts → use-composer-BnEzheSk.d.ts} +13 -20
  31. package/package.json +2 -2
  32. package/src/client.ts +4 -0
  33. package/src/components/composer.tsx +7 -6
  34. package/src/components/message-timeline.tsx +197 -25
  35. package/src/components/queue-draft-policy.ts +18 -0
  36. package/src/components/queue-surface-implementation.tsx +6 -10
  37. package/src/components/queue-surface.tsx +1 -0
  38. package/src/hooks/use-composer.ts +143 -83
  39. package/src/hooks/use-file-attachments.ts +95 -2
  40. package/src/hooks/use-goal.ts +22 -4
  41. package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
  42. package/src/hooks/use-workspace-sessions.ts +30 -5
  43. package/src/index.ts +9 -0
  44. package/src/session-context.ts +25 -0
  45. package/src/session.ts +10 -0
  46. package/src/timeline/activity-rail.tsx +17 -1
  47. package/src/timeline/fleet-decision-projection.ts +366 -0
  48. package/src/timeline/fleet-decision-row.tsx +412 -0
  49. package/src/timeline/index.ts +2 -0
  50. package/src/timeline/projection.ts +19 -8
  51. package/src/timeline/tool-display-name.ts +16 -0
  52. package/src/timeline/tool-renderers.tsx +1 -1
  53. package/src/timeline/types.ts +76 -16
  54. package/dist/chunk-6UT5OC3P.js.map +0 -1
  55. package/dist/chunk-EBTT3MYN.js.map +0 -1
  56. package/dist/chunk-HHFA4AFX.js.map +0 -1
  57. package/dist/chunk-LAZ2A743.js.map +0 -1
  58. package/dist/chunk-MRSECXRP.js.map +0 -1
  59. package/dist/queue-surface-implementation-NQMV2ML3.js.map +0 -1
  60. /package/dist/{chunk-6BXBGJ3B.js.map → chunk-6XUS5VFM.js.map} +0 -0
@@ -0,0 +1,161 @@
1
+ import type {
2
+ SessionEvent,
3
+ SessionMcpApprovalPolicy,
4
+ SessionMcpServerMetadata,
5
+ UpdateSessionMcpApprovalPolicyResponse,
6
+ } from "@opengeni/sdk";
7
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
8
+ import {
9
+ useEmbeddedSessionMcpApprovalPolicy,
10
+ type EmbeddedSessionMcpApprovalPolicyClientOverride,
11
+ } from "../session-context";
12
+ import {
13
+ useDebouncedCallback,
14
+ useMutationRunner,
15
+ usePolledValue,
16
+ useSessionEventTrigger,
17
+ type SessionEventFeedOptions,
18
+ } from "./internal";
19
+
20
+ export function isSessionMcpApprovalPolicyEvent(
21
+ event: Pick<SessionEvent, "type" | "payload">,
22
+ serverId?: string,
23
+ ): boolean {
24
+ if (event.type !== "session.mcp.approval_policy.updated") {
25
+ return false;
26
+ }
27
+ if (serverId === undefined) {
28
+ return true;
29
+ }
30
+ const payload = event.payload as { serverId?: unknown } | null;
31
+ return payload?.serverId === serverId;
32
+ }
33
+
34
+ export type UseSessionMcpApprovalPolicyOptions = EmbeddedSessionMcpApprovalPolicyClientOverride &
35
+ SessionEventFeedOptions & {
36
+ /** Optional safety-net polling (ms). Off by default; policy events drive refreshes. */
37
+ pollIntervalMs?: number | undefined;
38
+ };
39
+
40
+ export type UseSessionMcpApprovalPolicyResult = {
41
+ server: SessionMcpServerMetadata | null;
42
+ policy: SessionMcpApprovalPolicy | null;
43
+ loading: boolean;
44
+ error: Error | null;
45
+ refresh: () => Promise<void>;
46
+ update: (
47
+ policy: SessionMcpApprovalPolicy,
48
+ ) => Promise<UpdateSessionMcpApprovalPolicyResponse | null>;
49
+ updating: boolean;
50
+ clearError: () => void;
51
+ };
52
+
53
+ /**
54
+ * Read and update one existing session MCP server's approval policy. Updates
55
+ * become effective for the next claimed attempt; current work is unchanged.
56
+ */
57
+ export function useSessionMcpApprovalPolicy(
58
+ sessionId: string | null | undefined,
59
+ serverId: string | null | undefined,
60
+ options: UseSessionMcpApprovalPolicyOptions = {},
61
+ ): UseSessionMcpApprovalPolicyResult {
62
+ const { client, workspaceId } = useEmbeddedSessionMcpApprovalPolicy(options);
63
+ const enabled = (options.enabled ?? true) && Boolean(sessionId && serverId);
64
+ const [override, setOverride] = useState<SessionMcpServerMetadata | null>(null);
65
+ const targetKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${serverId ?? ""}`;
66
+ const authoritativeGeneration = useRef(0);
67
+ const mutationIdentity = useMemo(() => ({ client, targetKey }), [client, targetKey]);
68
+ const { run, mutating, mutationError, clearMutationError } = useMutationRunner(mutationIdentity);
69
+
70
+ useLayoutEffect(() => {
71
+ authoritativeGeneration.current += 1;
72
+ setOverride(null);
73
+ }, [client, targetKey]);
74
+
75
+ type PolicyRead = {
76
+ targetKey: string;
77
+ generation: number;
78
+ server: SessionMcpServerMetadata | null;
79
+ };
80
+ const load = useCallback(async (): Promise<PolicyRead> => {
81
+ const generation = authoritativeGeneration.current;
82
+ if (!sessionId || !serverId) {
83
+ return { targetKey, generation, server: null };
84
+ }
85
+ const session = await client.getSession(workspaceId, sessionId);
86
+ return {
87
+ targetKey,
88
+ generation,
89
+ server: session.mcpServers.find((server) => server.id === serverId) ?? null,
90
+ };
91
+ }, [client, serverId, sessionId, targetKey, workspaceId]);
92
+ const {
93
+ data,
94
+ loading,
95
+ error: loadError,
96
+ refresh,
97
+ } = usePolledValue(load, {
98
+ enabled,
99
+ pollIntervalMs: options.pollIntervalMs,
100
+ });
101
+ useEffect(() => {
102
+ if (data?.targetKey === targetKey && data.generation === authoritativeGeneration.current) {
103
+ setOverride(null);
104
+ }
105
+ }, [data, targetKey]);
106
+
107
+ const scheduleRefresh = useDebouncedCallback(() => void refresh());
108
+ const matchesPolicyEvent = useCallback(
109
+ (event: Pick<SessionEvent, "type" | "payload">) =>
110
+ isSessionMcpApprovalPolicyEvent(event, serverId ?? undefined),
111
+ [serverId],
112
+ );
113
+ useSessionEventTrigger(
114
+ client,
115
+ workspaceId,
116
+ sessionId,
117
+ matchesPolicyEvent,
118
+ scheduleRefresh,
119
+ {
120
+ enabled,
121
+ ...(options.events !== undefined ? { events: options.events } : {}),
122
+ },
123
+ async () => await refresh(),
124
+ );
125
+
126
+ const update = useCallback(
127
+ async (
128
+ policy: SessionMcpApprovalPolicy,
129
+ ): Promise<UpdateSessionMcpApprovalPolicyResponse | null> => {
130
+ if (!sessionId || !serverId) {
131
+ return null;
132
+ }
133
+ const response = await run(() =>
134
+ client.updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, {
135
+ requireApproval: policy,
136
+ }),
137
+ );
138
+ if (response) {
139
+ authoritativeGeneration.current += 1;
140
+ setOverride(response.server);
141
+ void refresh();
142
+ }
143
+ return response;
144
+ },
145
+ [client, refresh, run, serverId, sessionId, workspaceId],
146
+ );
147
+
148
+ const server = override ?? data?.server ?? null;
149
+ return {
150
+ server,
151
+ policy: server?.requireApproval ?? null,
152
+ loading,
153
+ error: mutationError ?? loadError,
154
+ refresh: async () => {
155
+ await refresh();
156
+ },
157
+ update,
158
+ updating: mutating,
159
+ clearError: clearMutationError,
160
+ };
161
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Session } from "@opengeni/sdk";
2
- import { useCallback } from "react";
2
+ import { useCallback, useEffect, useRef } from "react";
3
3
  import { useOpenGeni, type ClientOverride } from "../provider";
4
4
  import { usePolledValue } from "./internal";
5
5
 
@@ -8,6 +8,8 @@ export type UseWorkspaceSessionsOptions = ClientOverride & {
8
8
  parentSessionId?: string | null | undefined;
9
9
  cursor?: string | undefined;
10
10
  search?: string | undefined;
11
+ /** Return only the complete personal pinned projection. */
12
+ pinsOnly?: boolean | undefined;
11
13
  /** Refresh interval (ms) for fleet/manager views. Off by default. */
12
14
  pollIntervalMs?: number | undefined;
13
15
  enabled?: boolean | undefined;
@@ -38,7 +40,21 @@ export function useWorkspaceSessions(
38
40
  const parentSessionId = options.parentSessionId;
39
41
  const cursor = options.cursor;
40
42
  const search = options.search;
41
- const queryKey = JSON.stringify({ workspaceId, limit, parentSessionId, cursor, search });
43
+ const pinsOnly = options.pinsOnly;
44
+ const enabled = options.enabled ?? true;
45
+ const queryKey = [
46
+ workspaceId,
47
+ limit ?? "",
48
+ parentSessionId === null ? "null" : (parentSessionId ?? ""),
49
+ cursor ?? "",
50
+ search ?? "",
51
+ pinsOnly ? "1" : "",
52
+ ].join("\u0000");
53
+ const previousQueryKey = useRef(queryKey);
54
+ const queryKeyTransition = previousQueryKey.current !== queryKey;
55
+ useEffect(() => {
56
+ previousQueryKey.current = queryKey;
57
+ }, [queryKey]);
42
58
  const load = useCallback(
43
59
  async () => ({
44
60
  queryKey,
@@ -47,13 +63,14 @@ export function useWorkspaceSessions(
47
63
  ...(parentSessionId !== undefined ? { parentSessionId } : {}),
48
64
  ...(cursor !== undefined ? { cursor } : {}),
49
65
  ...(search !== undefined ? { search } : {}),
66
+ ...(pinsOnly ? { pinsOnly: true } : {}),
50
67
  }),
51
68
  }),
52
- [client, workspaceId, limit, parentSessionId, cursor, search, queryKey],
69
+ [client, workspaceId, limit, parentSessionId, cursor, search, pinsOnly, queryKey],
53
70
  );
54
71
  const state = usePolledValue(load, {
55
72
  pollIntervalMs: options.pollIntervalMs,
56
- enabled: options.enabled,
73
+ enabled,
57
74
  });
58
75
  // usePolledValue drops stale async completions, while the explicit query key
59
76
  // also prevents the previous query's cached value from painting for the one
@@ -69,7 +86,15 @@ export function useWorkspaceSessions(
69
86
  pinned,
70
87
  pinnedTruncated: page?.pinnedTruncated ?? false,
71
88
  nextCursor: page?.nextCursor ?? null,
72
- loading: state.loading,
89
+ // `usePolledValue` clears the old data and starts the new request in an
90
+ // effect. During that query-key transition render, its old loading flag
91
+ // can still be false; expose loading immediately so consumers do not
92
+ // announce a transient false zero-match result.
93
+ loading:
94
+ enabled &&
95
+ (state.loading ||
96
+ queryKeyTransition ||
97
+ (state.data !== null && state.data.queryKey !== queryKey)),
73
98
  error: state.error,
74
99
  refresh: state.refresh,
75
100
  };
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  export type {
11
11
  EmbeddedHumanInputSessionClientLike,
12
+ EmbeddedSessionMcpApprovalPolicyClientLike,
12
13
  EmbeddedSessionClientLike,
13
14
  SessionClientLike,
14
15
  } from "./client";
@@ -78,6 +79,14 @@ export type {
78
79
  UseSessionControlOptions,
79
80
  UseSessionControlResult,
80
81
  } from "./hooks/use-session-control";
82
+ export {
83
+ isSessionMcpApprovalPolicyEvent,
84
+ useSessionMcpApprovalPolicy,
85
+ } from "./hooks/use-session-mcp-approval-policy";
86
+ export type {
87
+ UseSessionMcpApprovalPolicyOptions,
88
+ UseSessionMcpApprovalPolicyResult,
89
+ } from "./hooks/use-session-mcp-approval-policy";
81
90
  export { useScheduledTasks } from "./hooks/use-scheduled-tasks";
82
91
  export type {
83
92
  UseScheduledTasksOptions,
@@ -2,6 +2,7 @@ import type { StreamConnectionState, WorkspaceControlEvent } from "@opengeni/sdk
2
2
  import { createContext, useContext } from "react";
3
3
  import type {
4
4
  EmbeddedHumanInputSessionClientLike,
5
+ EmbeddedSessionMcpApprovalPolicyClientLike,
5
6
  EmbeddedSessionClientLike,
6
7
  SessionClientLike,
7
8
  } from "./client";
@@ -40,6 +41,11 @@ export type EmbeddedHumanInputClientOverride = {
40
41
  workspaceId?: string | undefined;
41
42
  };
42
43
 
44
+ export type EmbeddedSessionMcpApprovalPolicyClientOverride = {
45
+ client?: EmbeddedSessionMcpApprovalPolicyClientLike | undefined;
46
+ workspaceId?: string | undefined;
47
+ };
48
+
43
49
  export type EmbeddedSessionContextValue = Omit<OpenGeniContextValue, "client"> & {
44
50
  client: EmbeddedSessionClientLike;
45
51
  };
@@ -117,6 +123,25 @@ export function useEmbeddedHumanInputSession(override: EmbeddedHumanInputClientO
117
123
  };
118
124
  }
119
125
 
126
+ /** Resolve the approval-policy refinement without widening session-only hosts. */
127
+ export function useEmbeddedSessionMcpApprovalPolicy(
128
+ override: EmbeddedSessionMcpApprovalPolicyClientOverride = {},
129
+ ): Omit<EmbeddedSessionContextValue, "client"> & {
130
+ client: EmbeddedSessionMcpApprovalPolicyClientLike;
131
+ } {
132
+ const embedded = useEmbeddedSession(override);
133
+ const client = embedded.client as Partial<EmbeddedSessionMcpApprovalPolicyClientLike>;
134
+ if (typeof client.updateSessionMcpApprovalPolicy !== "function") {
135
+ throw new Error(
136
+ "@opengeni/react: useSessionMcpApprovalPolicy requires updateSessionMcpApprovalPolicy.",
137
+ );
138
+ }
139
+ return {
140
+ ...embedded,
141
+ client: client as EmbeddedSessionMcpApprovalPolicyClientLike,
142
+ };
143
+ }
144
+
120
145
  /**
121
146
  * Resolve the client only — for hooks that are not workspace-scoped
122
147
  * (`useWorkspaces`, `useBillingUsage`).
package/src/session.ts CHANGED
@@ -6,10 +6,12 @@
6
6
 
7
7
  export type {
8
8
  EmbeddedHumanInputSessionClientLike as HumanInputSessionClientLike,
9
+ EmbeddedSessionMcpApprovalPolicyClientLike as SessionMcpApprovalPolicyClientLike,
9
10
  EmbeddedSessionClientLike as SessionClientLike,
10
11
  } from "./client";
11
12
  export type {
12
13
  EmbeddedHumanInputClientOverride as HumanInputClientOverride,
14
+ EmbeddedSessionMcpApprovalPolicyClientOverride as SessionMcpApprovalPolicyClientOverride,
13
15
  EmbeddedSessionClientOverride as ClientOverride,
14
16
  } from "./session-context";
15
17
 
@@ -38,6 +40,14 @@ export type {
38
40
  UseSessionControlOptions,
39
41
  UseSessionControlResult,
40
42
  } from "./hooks/use-session-control";
43
+ export {
44
+ isSessionMcpApprovalPolicyEvent,
45
+ useSessionMcpApprovalPolicy,
46
+ } from "./hooks/use-session-mcp-approval-policy";
47
+ export type {
48
+ UseSessionMcpApprovalPolicyOptions,
49
+ UseSessionMcpApprovalPolicyResult,
50
+ } from "./hooks/use-session-mcp-approval-policy";
41
51
 
42
52
  export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
43
53
  export type { PendingApproval } from "./approvals";
@@ -5,15 +5,19 @@ import {
5
5
  BrainIcon,
6
6
  SquareTerminalIcon,
7
7
  } from "lucide-react";
8
+ import { lazy, Suspense } from "react";
9
+ import { jsx as rowJsx, jsxs as rowJsxs } from "react/jsx-runtime";
8
10
  import { cn } from "../lib/cn";
9
11
  import { truncate } from "../lib/format";
10
12
  import { defaultToolRegistry } from "./tool-renderers";
11
13
  import { useEntranceAnimation } from "./entrance";
12
14
  import type { ToolRegistry } from "./registry";
13
15
  import { BodyNote, PayloadBlock, ActivityDisclosure } from "./shared";
14
- import { toolDisplayName } from "./projection";
16
+ import { toolDisplayName } from "./tool-display-name";
15
17
  import type { ActivityItem, MemoryItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
16
18
 
19
+ const LazyFleetDecisionRow = lazy(() => import("./fleet-decision-row"));
20
+
17
21
  /* ----------------------------------------------------------------------------
18
22
  Activity rail
19
23
 
@@ -112,6 +116,18 @@ function renderActivity(
112
116
  return <SandboxRow item={item} />;
113
117
  case "memory":
114
118
  return <MemoryRow item={item} onMemoryClick={onMemoryClick} />;
119
+ case "fleet-decision":
120
+ return (
121
+ <Suspense fallback={null}>
122
+ <LazyFleetDecisionRow
123
+ item={item}
124
+ d={ActivityDisclosure}
125
+ b={BodyNote}
126
+ j={rowJsx}
127
+ s={rowJsxs}
128
+ />
129
+ </Suspense>
130
+ );
115
131
  default:
116
132
  return assertNever(item);
117
133
  }