@opengeni/react 0.15.0 → 0.23.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 (103) hide show
  1. package/README.md +110 -10
  2. package/dist/{chunk-TOJR776I.js → chunk-6XUS5VFM.js} +45 -369
  3. package/dist/chunk-6XUS5VFM.js.map +1 -0
  4. package/dist/chunk-CATATY77.js +2393 -0
  5. package/dist/chunk-CATATY77.js.map +1 -0
  6. package/dist/chunk-I3BJZIG5.js +439 -0
  7. package/dist/chunk-I3BJZIG5.js.map +1 -0
  8. package/dist/chunk-TK7G6XLT.js +18 -0
  9. package/dist/chunk-TK7G6XLT.js.map +1 -0
  10. package/dist/chunk-UAHPKLB6.js +2237 -0
  11. package/dist/chunk-UAHPKLB6.js.map +1 -0
  12. package/dist/chunk-UQS7OQDE.js +591 -0
  13. package/dist/chunk-UQS7OQDE.js.map +1 -0
  14. package/dist/chunk-YDNWMKXO.js +102 -0
  15. package/dist/chunk-YDNWMKXO.js.map +1 -0
  16. package/dist/composer-CVm5uoUI.d.ts +585 -0
  17. package/dist/composer.d.ts +6 -0
  18. package/dist/composer.js +56 -0
  19. package/dist/composer.js.map +1 -0
  20. package/dist/index.d.ts +1288 -1785
  21. package/dist/index.js +7140 -8817
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +3 -1
  25. package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
  26. package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
  27. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
  28. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
  29. package/dist/queue-surface-implementation-3UW3MIIR.js +890 -0
  30. package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
  31. package/dist/session-Db00TRwl.d.ts +446 -0
  32. package/dist/session-context-rgrfElMl.d.ts +58 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +54 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-By1wn3MM.d.ts +99 -0
  37. package/package.json +20 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +44 -0
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +94 -993
  42. package/src/components/code-editor.tsx +104 -26
  43. package/src/components/command-palette.tsx +8 -2
  44. package/src/components/composer-transcription-control.tsx +211 -0
  45. package/src/components/composer.tsx +1536 -0
  46. package/src/components/desktop-viewer.tsx +3 -3
  47. package/src/components/enrollment-consent.tsx +2 -2
  48. package/src/components/file-browser.tsx +367 -29
  49. package/src/components/human-input-form.tsx +417 -0
  50. package/src/components/machine-card.tsx +1 -1
  51. package/src/components/machines-dashboard.tsx +3 -3
  52. package/src/components/message-timeline.tsx +205 -25
  53. package/src/components/model-picker.tsx +12 -3
  54. package/src/components/pierre-diff.tsx +32 -6
  55. package/src/components/queue-draft-policy.ts +18 -0
  56. package/src/components/queue-surface-implementation.tsx +1037 -0
  57. package/src/components/queue-surface-state.tsx +94 -0
  58. package/src/components/queue-surface.tsx +42 -559
  59. package/src/components/sandbox-files.tsx +138 -14
  60. package/src/components/sandbox-terminal.tsx +8 -2
  61. package/src/components/sandbox-workspace.tsx +339 -119
  62. package/src/components/workbench-changes.tsx +84 -33
  63. package/src/components/workspace-dock.tsx +188 -18
  64. package/src/composer.ts +60 -0
  65. package/src/hooks/internal.ts +115 -34
  66. package/src/hooks/use-composer.ts +385 -104
  67. package/src/hooks/use-human-input.ts +131 -0
  68. package/src/hooks/use-machine-chip.ts +2 -2
  69. package/src/hooks/use-machines.ts +27 -11
  70. package/src/hooks/use-sandbox-files.ts +310 -62
  71. package/src/hooks/use-sandbox-git.ts +154 -40
  72. package/src/hooks/use-sandbox-terminal.ts +28 -6
  73. package/src/hooks/use-session-capabilities.ts +38 -9
  74. package/src/hooks/use-session-control.ts +37 -8
  75. package/src/hooks/use-session-events.ts +522 -48
  76. package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
  77. package/src/hooks/use-slash-commands.ts +38 -38
  78. package/src/hooks/use-transcription.ts +757 -0
  79. package/src/hooks/use-turn-queue.ts +134 -66
  80. package/src/hooks/use-windowed-sections.ts +2 -2
  81. package/src/hooks/use-workspace-capture.ts +146 -40
  82. package/src/hooks/use-workspace-edit.ts +50 -14
  83. package/src/hooks/use-workspace-sessions.ts +3 -0
  84. package/src/human-input.ts +72 -0
  85. package/src/index.ts +85 -6
  86. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  87. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  88. package/src/lib/use-portal-token-style.ts +51 -0
  89. package/src/lib/use-unicode-fonts.ts +57 -0
  90. package/src/provider.tsx +4 -69
  91. package/src/session-context.ts +160 -0
  92. package/src/session.ts +89 -0
  93. package/src/timeline/parsers.ts +35 -0
  94. package/src/timeline/projection.ts +12 -4
  95. package/src/timeline/tool-renderers.tsx +41 -0
  96. package/src/timeline/turn-summary.tsx +2 -2
  97. package/src/timeline/types.ts +10 -9
  98. package/styles/index.css +56 -0
  99. package/styles/index.d.ts +2 -0
  100. package/styles/tokens.css +11 -6
  101. package/styles/tokens.d.ts +2 -0
  102. package/dist/chunk-TOJR776I.js.map +0 -1
  103. package/dist/machines-BpdwuQcD.d.ts +0 -449
@@ -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
+ }
@@ -194,6 +194,10 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
194
194
  }
195
195
  }, [items, clampedHighlight, autocomplete]);
196
196
 
197
+ // Pointer and keyboard activation share this synchronous fence. React state
198
+ // cannot disable a command row before a second same-tick activation arrives.
199
+ const runningRef = useRef(false);
200
+
197
201
  // Resolve a SPECIFIC, already-chosen command against the current draft, then
198
202
  // either autocomplete (name-only / required arg missing) or execute. This is
199
203
  // the shared core for both Enter (which first resolves WHICH command via the
@@ -201,39 +205,44 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
201
205
  // command — the clicked row — and must not re-resolve to a near-match).
202
206
  const runResolved = useCallback(
203
207
  async (command: SlashCommand, executionOptions?: { explicit?: boolean }): Promise<void> => {
204
- if (!parsed) {
205
- return;
206
- }
207
- const explicit = executionOptions?.explicit ?? false;
208
- // Token-vs-command equality is case-insensitive (matching the registry's
209
- // matchCommand/filterCommands), so a fully-typed "/Clear" counts as having
210
- // named `clear` and runs rather than autocompleting.
211
- const nameMatchesToken =
212
- command.name === parsed.name.toLowerCase() ||
213
- (command.aliases?.includes(parsed.name.toLowerCase()) ?? false);
214
- // A name-only token whose name doesn't yet equal the resolved command (e.g.
215
- // "/cl" -> clear) first autocompletes so the operator sees the full name.
216
- // An EXPLICIT pointer click skips this: the operator already chose the row,
217
- // so clicking "/clear-view" (while the token is "clear") runs it outright
218
- // rather than merely filling the name and waiting for a second Enter.
219
- if (!explicit && !activeCommand && !nameMatchesToken && !parsed.hasTrailingSpace) {
220
- autocomplete(command);
208
+ if (!parsed || runningRef.current) {
221
209
  return;
222
210
  }
223
- // When the click resolves a different command than the typed token, the
224
- // typed token's tail isn't this command's args — run with no positional
225
- // args and let the required-arg guard below prompt for them via autocomplete.
226
- const args = nameMatchesToken || parsed.hasTrailingSpace ? parsed.args : [];
227
- const missing = firstMissingRequiredArg(command, args);
228
- if (missing) {
229
- // A required arg is absent: keep the palette open at the arg hint rather
230
- // than firing a half-formed command.
231
- if (!parsed.hasTrailingSpace) {
211
+ runningRef.current = true;
212
+ try {
213
+ const explicit = executionOptions?.explicit ?? false;
214
+ // Token-vs-command equality is case-insensitive (matching the registry's
215
+ // matchCommand/filterCommands), so a fully-typed "/Clear" counts as having
216
+ // named `clear` and runs rather than autocompleting.
217
+ const nameMatchesToken =
218
+ command.name === parsed.name.toLowerCase() ||
219
+ (command.aliases?.includes(parsed.name.toLowerCase()) ?? false);
220
+ // A name-only token whose name doesn't yet equal the resolved command (e.g.
221
+ // "/cl" -> clear) first autocompletes so the operator sees the full name.
222
+ // An EXPLICIT pointer click skips this: the operator already chose the row,
223
+ // so clicking "/clear-view" (while the token is "clear") runs it outright
224
+ // rather than merely filling the name and waiting for a second Enter.
225
+ if (!explicit && !activeCommand && !nameMatchesToken && !parsed.hasTrailingSpace) {
232
226
  autocomplete(command);
227
+ return;
233
228
  }
234
- return;
229
+ // When the click resolves a different command than the typed token, the
230
+ // typed token's tail isn't this command's args — run with no positional
231
+ // args and let the required-arg guard below prompt for them via autocomplete.
232
+ const args = nameMatchesToken || parsed.hasTrailingSpace ? parsed.args : [];
233
+ const missing = firstMissingRequiredArg(command, args);
234
+ if (missing) {
235
+ // A required arg is absent: keep the palette open at the arg hint rather
236
+ // than firing a half-formed command.
237
+ if (!parsed.hasTrailingSpace) {
238
+ autocomplete(command);
239
+ }
240
+ return;
241
+ }
242
+ await execute(command, args);
243
+ } finally {
244
+ runningRef.current = false;
235
245
  }
236
- await execute(command, args);
237
246
  },
238
247
  [parsed, activeCommand, autocomplete, execute],
239
248
  );
@@ -286,9 +295,6 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
286
295
  [items, runResolved],
287
296
  );
288
297
 
289
- // Track an in-flight run so Enter can't double-fire.
290
- const runningRef = useRef(false);
291
-
292
298
  const onKeyDown = useCallback(
293
299
  (event: KeyboardEvent<HTMLTextAreaElement>): boolean => {
294
300
  if (!open) {
@@ -322,13 +328,7 @@ export function useSlashCommands(options: UseSlashCommandsOptions): UseSlashComm
322
328
  return false;
323
329
  }
324
330
  event.preventDefault();
325
- if (runningRef.current) {
326
- return true;
327
- }
328
- runningRef.current = true;
329
- void runHighlighted().finally(() => {
330
- runningRef.current = false;
331
- });
331
+ void runHighlighted();
332
332
  return true;
333
333
  }
334
334
  case "Escape": {