@opengeni/react 0.37.0 → 0.40.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 (71) hide show
  1. package/README.md +88 -2
  2. package/dist/{chunk-7CJOAW3V.js → chunk-EB67J347.js} +1 -135
  3. package/dist/chunk-EB67J347.js.map +1 -0
  4. package/dist/chunk-EKZH6IDG.js +141 -0
  5. package/dist/chunk-EKZH6IDG.js.map +1 -0
  6. package/dist/{chunk-FSPDND3P.js → chunk-JALF5FI3.js} +8 -6
  7. package/dist/{chunk-FSPDND3P.js.map → chunk-JALF5FI3.js.map} +1 -1
  8. package/dist/{chunk-UCZPNXV3.js → chunk-KR2SK5GJ.js} +1292 -711
  9. package/dist/chunk-KR2SK5GJ.js.map +1 -0
  10. package/dist/{chunk-IP22SLO6.js → chunk-OMCFRWHL.js} +28 -441
  11. package/dist/chunk-OMCFRWHL.js.map +1 -0
  12. package/dist/{chunk-HJ4OQVGW.js → chunk-Q2NCKWTK.js} +10 -5
  13. package/dist/chunk-Q2NCKWTK.js.map +1 -0
  14. package/dist/{chunk-ALA3UGWB.js → chunk-SXIQK54Z.js} +8 -6
  15. package/dist/{chunk-ALA3UGWB.js.map → chunk-SXIQK54Z.js.map} +1 -1
  16. package/dist/chunk-WZT5G5OR.js +433 -0
  17. package/dist/chunk-WZT5G5OR.js.map +1 -0
  18. package/dist/client.d.ts +2 -0
  19. package/dist/components/chat-composer.d.ts +5 -1
  20. package/dist/components/human-input-form.d.ts +16 -6
  21. package/dist/components/human-input-surface.d.ts +19 -0
  22. package/dist/components/machine-input-display.d.ts +12 -0
  23. package/dist/components/message-timeline.d.ts +6 -1
  24. package/dist/components/session-chrome.d.ts +1 -0
  25. package/dist/components/tip-follow.d.ts +30 -0
  26. package/dist/composer.js +14 -11
  27. package/dist/hooks/use-composer.d.ts +1 -1
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +2246 -2236
  30. package/dist/index.js.map +1 -1
  31. package/dist/machines.js +4 -3
  32. package/dist/realtime/dropdown-menu.d.ts +16 -0
  33. package/dist/realtime/realtime-control.d.ts +126 -0
  34. package/dist/realtime.d.ts +11 -0
  35. package/dist/realtime.js +1023 -0
  36. package/dist/realtime.js.map +1 -0
  37. package/dist/session-context.d.ts +7 -1
  38. package/dist/session-ui.d.ts +2 -0
  39. package/dist/session-ui.js +5 -3
  40. package/dist/session.js +4 -3
  41. package/dist/timeline/compute-label.d.ts +2 -0
  42. package/dist/timeline/index.d.ts +1 -0
  43. package/package.json +7 -2
  44. package/src/client.ts +13 -0
  45. package/src/components/chat-composer.tsx +11 -2
  46. package/src/components/composer-transcription-control.tsx +2 -2
  47. package/src/components/composer.tsx +14 -5
  48. package/src/components/human-input-form.tsx +506 -186
  49. package/src/components/human-input-surface.tsx +80 -0
  50. package/src/components/machine-input-display.ts +83 -0
  51. package/src/components/message-timeline.tsx +348 -308
  52. package/src/components/model-policy-picker.tsx +8 -3
  53. package/src/components/sandbox-files.tsx +1 -1
  54. package/src/components/session-chrome.tsx +3 -1
  55. package/src/components/tip-follow.ts +127 -6
  56. package/src/hooks/use-composer.ts +16 -13
  57. package/src/index.ts +4 -0
  58. package/src/realtime/dropdown-menu.tsx +123 -0
  59. package/src/realtime/realtime-control.tsx +1178 -0
  60. package/src/realtime.ts +27 -0
  61. package/src/session-context.ts +16 -0
  62. package/src/session-ui.ts +2 -0
  63. package/src/timeline/compute-label.tsx +18 -0
  64. package/src/timeline/index.ts +3 -0
  65. package/src/timeline/tool-renderers.tsx +232 -43
  66. package/styles/index.css +61 -0
  67. package/styles/tokens.css +2 -0
  68. package/dist/chunk-7CJOAW3V.js.map +0 -1
  69. package/dist/chunk-HJ4OQVGW.js.map +0 -1
  70. package/dist/chunk-IP22SLO6.js.map +0 -1
  71. package/dist/chunk-UCZPNXV3.js.map +0 -1
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Batteries-included realtime voice controls and hooks.
3
+ *
4
+ * Import this subpath lazily when the composer action slot is rendered. The
5
+ * base `@opengeni/react` entry remains SSR/browser safe and does not eagerly
6
+ * load microphone, WebRTC, Gateway, or realtime UI code.
7
+ */
8
+ export {
9
+ CodexRealtimeControl,
10
+ NewSessionRealtimeControl,
11
+ RealtimeModelPickerMenu,
12
+ RealtimeVoiceControl,
13
+ SessionCodexRealtimeControl,
14
+ SessionRealtimeControl,
15
+ codexRealtimeAdmissionAllowed,
16
+ codexRealtimeAdmissionBlocker,
17
+ useRealtimeModelSelection,
18
+ useSessionCodexRealtime,
19
+ useSessionRealtime,
20
+ useWorkspaceRealtimeModelSelection,
21
+ } from "./realtime/realtime-control";
22
+ export type {
23
+ RealtimeModelOption,
24
+ SessionRealtimeControllerFactory,
25
+ } from "./realtime/realtime-control";
26
+ export type { EmbeddedRealtimeSessionClientLike } from "./client";
27
+ export type { EmbeddedRealtimeSessionClientOverride } from "./session-context";
@@ -4,6 +4,7 @@ import type {
4
4
  EmbeddedFileAttachmentClientLike,
5
5
  EmbeddedGoalClientLike,
6
6
  EmbeddedHumanInputSessionClientLike,
7
+ EmbeddedRealtimeSessionClientLike,
7
8
  EmbeddedSessionClientLike,
8
9
  EmbeddedSessionLineageClientLike,
9
10
  EmbeddedSessionMcpApprovalPolicyClientLike,
@@ -55,6 +56,11 @@ export type EmbeddedSessionReadClientOverride = {
55
56
  workspaceId?: string | undefined;
56
57
  };
57
58
 
59
+ export type EmbeddedRealtimeSessionClientOverride = {
60
+ client?: EmbeddedRealtimeSessionClientLike | undefined;
61
+ workspaceId?: string | undefined;
62
+ };
63
+
58
64
  export type EmbeddedGoalClientOverride = {
59
65
  client?: EmbeddedGoalClientLike | undefined;
60
66
  workspaceId?: string | undefined;
@@ -240,6 +246,16 @@ export function useEmbeddedSessionMcpApprovalPolicy(
240
246
  );
241
247
  }
242
248
 
249
+ /** Resolve the exact browser/API surface required by `@opengeni/react/realtime`. */
250
+ export function useEmbeddedRealtimeSession(
251
+ override: EmbeddedRealtimeSessionClientOverride = {},
252
+ ): EmbeddedClientContextValue<EmbeddedRealtimeSessionClientLike> {
253
+ // Preserve the existing lazy capability behavior: proxy/test clients may
254
+ // implement only the methods reached by their selected model and lifecycle.
255
+ // The exported structural type remains the complete embedding contract.
256
+ return useEmbeddedClientRefinement(override, [], "realtime hooks");
257
+ }
258
+
243
259
  /**
244
260
  * Resolve the client only — for hooks that are not workspace-scoped
245
261
  * (`useWorkspaces`, `useBillingUsage`).
package/src/session-ui.ts CHANGED
@@ -7,6 +7,8 @@ export type {
7
7
  HumanInputFormMessages,
8
8
  HumanInputFormProps,
9
9
  } from "./components/human-input-form";
10
+ export { HumanInputSurface } from "./components/human-input-surface";
11
+ export type { HumanInputSurfaceProps } from "./components/human-input-surface";
10
12
  export { MessageTimeline, TimelineRow } from "./components/message-timeline";
11
13
  export type { MessageTimelineProps } from "./components/message-timeline";
12
14
  export { BUILT_IN_TURN_SUMMARY_FACET_IDS } from "./timeline/turn-summary";
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /* ----------------------------------------------------------------------------
4
+ Session compute label
5
+
6
+ Optional host-supplied display name for the session's active compute target
7
+ (e.g. "Cloud sandbox" or a Connected Machine name). Used by exec_command
8
+ collapsed previews. The host owns resolution; this package never calls the
9
+ machines API.
10
+ -------------------------------------------------------------------------- */
11
+
12
+ const TimelineComputeLabelContext = createContext<string | null>(null);
13
+
14
+ export const TimelineComputeLabelProvider = TimelineComputeLabelContext.Provider;
15
+
16
+ export function useTimelineComputeLabel(): string | null {
17
+ return useContext(TimelineComputeLabelContext);
18
+ }
@@ -95,6 +95,9 @@ export type { FoldRestingState } from "./fold-memory";
95
95
  // seen activity ids (live tool enter across rail remounts)
96
96
  export { SeenActivityIdsProvider, useSeenActivityIds } from "./seen-activity-ids";
97
97
 
98
+ // session compute label (host-supplied active machine / cloud sandbox name)
99
+ export { TimelineComputeLabelProvider, useTimelineComputeLabel } from "./compute-label";
100
+
98
101
  // turn-collapse summary chip
99
102
  export { BUILT_IN_TURN_SUMMARY_FACET_IDS, TurnSummary, useTurnSettleOpen } from "./turn-summary";
100
103
  export type {
@@ -13,6 +13,7 @@ import {
13
13
  KeyboardIcon,
14
14
  KeyRoundIcon,
15
15
  LockIcon,
16
+ MessageCircleQuestionIcon,
16
17
  MessagesSquareIcon,
17
18
  MessageSquareIcon,
18
19
  MousePointer2Icon,
@@ -28,6 +29,7 @@ import {
28
29
  } from "lucide-react";
29
30
  import type { ReactNode } from "react";
30
31
  import { stringifyPayload, tryParseJson } from "../lib/format";
32
+ import { useTimelineComputeLabel } from "./compute-label";
31
33
  import {
32
34
  applyPatchOpsFromToolItem,
33
35
  controlCaret,
@@ -94,6 +96,14 @@ function RunningPreview({ children }: { children: ReactNode }) {
94
96
  );
95
97
  }
96
98
 
99
+ /** Prefix a collapsed preview with the host-supplied active compute label. */
100
+ function withComputePreview(label: string | null, preview: string): string {
101
+ if (!label) {
102
+ return preview;
103
+ }
104
+ return `on ${label} · ${preview}`;
105
+ }
106
+
97
107
  /* ---- exec_command ---------------------------------------------------------- */
98
108
 
99
109
  function ExecRenderer({ item }: ToolRendererProps) {
@@ -103,6 +113,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
103
113
  const running = item.status === "running";
104
114
  const out = item.output;
105
115
  const title = `$ ${cmd}`;
116
+ const computeLabel = useTimelineComputeLabel();
106
117
 
107
118
  // No output event ever arrived (item.output stays undefined from creation):
108
119
  // the turn failed before the output insert — most likely a NUL byte in the
@@ -116,7 +127,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
116
127
  title={title}
117
128
  titleMono
118
129
  chip={{ tone: "bad", text: "failed" }}
119
- preview="output lost — NUL byte could not be stored"
130
+ preview={withComputePreview(computeLabel, "output lost — NUL byte could not be stored")}
120
131
  >
121
132
  <BodyNote tone="error">
122
133
  output contained a NUL byte and could not be stored; the turn failed on this tool&apos;s
@@ -137,7 +148,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
137
148
  title={title}
138
149
  titleMono
139
150
  chip={{ tone: "bad", text: "failed" }}
140
- preview="tool call failed"
151
+ preview={withComputePreview(computeLabel, "tool call failed")}
141
152
  >
142
153
  <BodyNote tone="error">the tool call failed with no output.</BodyNote>
143
154
  </ActivityDisclosure>
@@ -146,6 +157,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
146
157
 
147
158
  if (running) {
148
159
  const streamed = typeof out === "string" ? stripExecBanner(out) : "";
160
+ const runningPreview = streamed ? `${streamed.split("\n").length} lines` : "running…";
149
161
  return (
150
162
  <ActivityDisclosure
151
163
  icon={<TerminalIcon className={ICON_SIZE} />}
@@ -154,9 +166,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
154
166
  titleMono
155
167
  running
156
168
  preview={
157
- <RunningPreview>
158
- {streamed ? `${streamed.split("\n").length} lines` : "running…"}
159
- </RunningPreview>
169
+ <RunningPreview>{withComputePreview(computeLabel, runningPreview)}</RunningPreview>
160
170
  }
161
171
  >
162
172
  {/* The row title is already `$ ${cmd}`; the TermBlock header drops the
@@ -184,8 +194,9 @@ function ExecRenderer({ item }: ToolRendererProps) {
184
194
  iconTone = "failed";
185
195
  }
186
196
 
187
- const preview = binary ? "binary output" : tailPeek(stripped) || "(no output)";
197
+ const peek = binary ? "binary output" : tailPeek(stripped) || "(no output)";
188
198
  const truncated = execTruncated(text);
199
+ const preview = withComputePreview(computeLabel, truncated ? `⋯ truncated · ${peek}` : peek);
189
200
  // Hand TermBlock the FULL stripped output; it owns the tail/show-more slicing.
190
201
  const body = binary ? "(binary output suppressed)" : stripped;
191
202
 
@@ -198,7 +209,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
198
209
  {...(chip ? { chip } : {})}
199
210
  failed={item.status === "failed"}
200
211
  cancelled={item.status === "cancelled"}
201
- preview={truncated ? `⋯ truncated · ${preview}` : preview}
212
+ preview={preview}
202
213
  >
203
214
  <TermBlock
204
215
  command={null}
@@ -1254,6 +1265,179 @@ function MemoryProposeRenderer({ item }: ToolRendererProps) {
1254
1265
  );
1255
1266
  }
1256
1267
 
1268
+ /* ---- request_human_input --------------------------------------------------- */
1269
+
1270
+ function askToolPreview(args: unknown): string | null {
1271
+ const record = args && typeof args === "object" ? (args as Record<string, unknown>) : null;
1272
+ const questions = Array.isArray(record?.questions) ? record.questions : null;
1273
+ if (!questions || questions.length === 0) {
1274
+ return null;
1275
+ }
1276
+ const first = questions[0];
1277
+ if (!first || typeof first !== "object") {
1278
+ return null;
1279
+ }
1280
+ const q = first as Record<string, unknown>;
1281
+ const text =
1282
+ typeof q.label === "string" && q.label.trim()
1283
+ ? q.label.trim()
1284
+ : typeof q.prompt === "string" && q.prompt.trim()
1285
+ ? q.prompt.trim()
1286
+ : null;
1287
+ if (!text) {
1288
+ return null;
1289
+ }
1290
+ const preview = truncatePreview(text, 90);
1291
+ return questions.length > 1 ? `${preview} · ${questions.length} questions` : preview;
1292
+ }
1293
+
1294
+ function AskRenderer({ item }: ToolRendererProps) {
1295
+ const args = redactSecrets(parseToolArgs(item.arguments));
1296
+ const preview = askToolPreview(args);
1297
+ const icon = <MessageCircleQuestionIcon className={ICON_SIZE} />;
1298
+ const title = "Ask";
1299
+
1300
+ if (item.status === "running") {
1301
+ return (
1302
+ <ActivityDisclosure
1303
+ icon={icon}
1304
+ iconTone="running"
1305
+ title={title}
1306
+ running
1307
+ preview={
1308
+ preview ? (
1309
+ <RunningPreview>{preview}</RunningPreview>
1310
+ ) : (
1311
+ <RunningPreview>Waiting…</RunningPreview>
1312
+ )
1313
+ }
1314
+ >
1315
+ <PayloadBlock label="Arguments" value={args} />
1316
+ </ActivityDisclosure>
1317
+ );
1318
+ }
1319
+
1320
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
1321
+ if ((isError || item.status === "failed") && item.status !== "cancelled") {
1322
+ return (
1323
+ <ActivityDisclosure
1324
+ icon={icon}
1325
+ iconTone="failed"
1326
+ title={title}
1327
+ chip={{ tone: "bad", text: "error" }}
1328
+ preview={truncatePreview(outText, 80) || preview || "Error"}
1329
+ >
1330
+ <PayloadBlock label="Arguments" value={args} />
1331
+ <PayloadBlock label="Error" value={outText} failed />
1332
+ </ActivityDisclosure>
1333
+ );
1334
+ }
1335
+
1336
+ return (
1337
+ <ActivityDisclosure
1338
+ icon={icon}
1339
+ iconTone="muted"
1340
+ title={title}
1341
+ cancelled={item.status === "cancelled"}
1342
+ preview={item.status === "cancelled" ? undefined : (preview ?? undefined)}
1343
+ >
1344
+ <PayloadBlock label="Arguments" value={args} />
1345
+ {outText ? <PayloadBlock label="Result" value={outText} /> : null}
1346
+ </ActivityDisclosure>
1347
+ );
1348
+ }
1349
+
1350
+ /* ---- run_on ---------------------------------------------------------------- */
1351
+
1352
+ function runOnTargetName(output: unknown): string | null {
1353
+ const { text } = unwrapMcpOutput(output);
1354
+ const parsed = tryParseJson(text);
1355
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1356
+ const name = (parsed as { targetName?: unknown }).targetName;
1357
+ if (typeof name === "string" && name.trim()) {
1358
+ return name.trim();
1359
+ }
1360
+ }
1361
+ return null;
1362
+ }
1363
+
1364
+ function runOnOpPreview(args: Record<string, unknown>): string | null {
1365
+ const op = args.op;
1366
+ if (!op || typeof op !== "object" || Array.isArray(op)) {
1367
+ return null;
1368
+ }
1369
+ const record = op as Record<string, unknown>;
1370
+ if (record.kind === "exec" && typeof record.cmd === "string" && record.cmd.trim()) {
1371
+ return `$ ${record.cmd.trim()}`;
1372
+ }
1373
+ if (
1374
+ (record.kind === "read" || record.kind === "write") &&
1375
+ typeof record.path === "string" &&
1376
+ record.path.trim()
1377
+ ) {
1378
+ return truncatePreview(record.path.trim(), 72);
1379
+ }
1380
+ return null;
1381
+ }
1382
+
1383
+ function RunOnRenderer({ item }: ToolRendererProps) {
1384
+ const parsedArgs = parseToolArgs(item.arguments);
1385
+ const args = redactSecrets(parsedArgs);
1386
+ const targetName = runOnTargetName(item.output);
1387
+ const title = targetName ? `Run on ${targetName}` : "Run on";
1388
+ const opPreview = runOnOpPreview(parsedArgs);
1389
+ const icon = <ServerIcon className={ICON_SIZE} />;
1390
+
1391
+ if (item.status === "running") {
1392
+ return (
1393
+ <ActivityDisclosure
1394
+ icon={icon}
1395
+ iconTone="running"
1396
+ title={title}
1397
+ running
1398
+ preview={
1399
+ opPreview ? (
1400
+ <RunningPreview>{opPreview}</RunningPreview>
1401
+ ) : (
1402
+ <RunningPreview>Running…</RunningPreview>
1403
+ )
1404
+ }
1405
+ >
1406
+ <PayloadBlock label="Arguments" value={args} />
1407
+ </ActivityDisclosure>
1408
+ );
1409
+ }
1410
+
1411
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
1412
+ if ((isError || item.status === "failed") && item.status !== "cancelled") {
1413
+ return (
1414
+ <ActivityDisclosure
1415
+ icon={icon}
1416
+ iconTone="failed"
1417
+ title={title}
1418
+ chip={{ tone: "bad", text: "error" }}
1419
+ preview={truncatePreview(outText, 80) || opPreview || "Error"}
1420
+ >
1421
+ <PayloadBlock label="Arguments" value={args} />
1422
+ <PayloadBlock label="Error" value={outText} failed />
1423
+ </ActivityDisclosure>
1424
+ );
1425
+ }
1426
+
1427
+ return (
1428
+ <ActivityDisclosure
1429
+ icon={icon}
1430
+ iconTone="muted"
1431
+ title={title}
1432
+ cancelled={item.status === "cancelled"}
1433
+ preview={item.status === "cancelled" ? undefined : (opPreview ?? undefined)}
1434
+ >
1435
+ <PayloadBlock label="Arguments" value={args} />
1436
+ {outText ? <PayloadBlock label="Result" value={outText} /> : null}
1437
+ </ActivityDisclosure>
1438
+ );
1439
+ }
1440
+
1257
1441
  /* ---- generic fallback (first-party MCP, external MCP, unknown) ------------- */
1258
1442
 
1259
1443
  /**
@@ -1355,42 +1539,45 @@ function truncatePreview(text: string, max: number): string {
1355
1539
  /** Explicit first-party leaf/prefix → canonical product icons (match nav/pages). */
1356
1540
  function GenericToolIcon({ name }: { name: string }) {
1357
1541
  const leaf = mcpToolLeaf(name);
1358
- const Icon = leaf.startsWith("goal_")
1359
- ? TargetIcon
1360
- : leaf.startsWith("memory_") ||
1361
- leaf === "preference_registry_summary" ||
1362
- leaf === "preference_registry_get"
1363
- ? BrainCircuitIcon
1364
- : leaf.startsWith("session_") ||
1365
- leaf === "sessions_list" ||
1366
- leaf === "set_session_title" ||
1367
- leaf === "set_other_session_title"
1368
- ? MessagesSquareIcon
1369
- : leaf.startsWith("sandbox") || leaf === "sandboxes_list" || leaf === "run_on"
1370
- ? ServerIcon
1371
- : leaf.startsWith("rig_")
1372
- ? ServerCogIcon
1373
- : leaf.startsWith("scheduled_")
1374
- ? CalendarClockIcon
1375
- : leaf.startsWith("artifacts_")
1376
- ? PanelsTopLeftIcon
1377
- : leaf.startsWith("social_")
1378
- ? Share2Icon
1379
- : leaf.startsWith("slack_")
1380
- ? MessageSquareIcon
1381
- : leaf.startsWith("github_")
1382
- ? FolderGitIcon
1383
- : leaf.startsWith("variable_")
1384
- ? BoxIcon
1385
- : leaf.startsWith("environment_")
1386
- ? KeyRoundIcon
1387
- : leaf.includes("document") ||
1388
- leaf.includes("knowledge") ||
1389
- leaf === "list_document_bases"
1390
- ? FileSearchIcon
1391
- : leaf === "tool_search" || leaf === "load_skill"
1392
- ? PlugIcon
1393
- : WrenchIcon;
1542
+ const Icon =
1543
+ leaf === "request_human_input"
1544
+ ? MessageCircleQuestionIcon
1545
+ : leaf.startsWith("goal_")
1546
+ ? TargetIcon
1547
+ : leaf.startsWith("memory_") ||
1548
+ leaf === "preference_registry_summary" ||
1549
+ leaf === "preference_registry_get"
1550
+ ? BrainCircuitIcon
1551
+ : leaf.startsWith("session_") ||
1552
+ leaf === "sessions_list" ||
1553
+ leaf === "set_session_title" ||
1554
+ leaf === "set_other_session_title"
1555
+ ? MessagesSquareIcon
1556
+ : leaf.startsWith("sandbox") || leaf === "sandboxes_list" || leaf === "run_on"
1557
+ ? ServerIcon
1558
+ : leaf.startsWith("rig_")
1559
+ ? ServerCogIcon
1560
+ : leaf.startsWith("scheduled_")
1561
+ ? CalendarClockIcon
1562
+ : leaf.startsWith("artifacts_")
1563
+ ? PanelsTopLeftIcon
1564
+ : leaf.startsWith("social_")
1565
+ ? Share2Icon
1566
+ : leaf.startsWith("slack_")
1567
+ ? MessageSquareIcon
1568
+ : leaf.startsWith("github_")
1569
+ ? FolderGitIcon
1570
+ : leaf.startsWith("variable_")
1571
+ ? BoxIcon
1572
+ : leaf.startsWith("environment_")
1573
+ ? KeyRoundIcon
1574
+ : leaf.includes("document") ||
1575
+ leaf.includes("knowledge") ||
1576
+ leaf === "list_document_bases"
1577
+ ? FileSearchIcon
1578
+ : leaf === "tool_search" || leaf === "load_skill"
1579
+ ? PlugIcon
1580
+ : WrenchIcon;
1394
1581
  return <Icon className={ICON_SIZE} />;
1395
1582
  }
1396
1583
 
@@ -1404,6 +1591,8 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1404
1591
  { match: "rawType", type: "hosted_tool_call", render: WebSearchRenderer },
1405
1592
  // First-party sandbox + MCP tools resolve by name (exact or MCP leaf).
1406
1593
  { match: "name", name: "exec_command", render: ExecRenderer },
1594
+ { match: "name", name: "request_human_input", render: AskRenderer },
1595
+ { match: "name", name: "run_on", render: RunOnRenderer },
1407
1596
  { match: "name", name: "write_stdin", render: WriteStdinRenderer },
1408
1597
  { match: "name", name: "apply_patch_call", render: ApplyPatchRenderer },
1409
1598
  { match: "name", name: "apply_patch", render: ApplyPatchRenderer },
package/styles/index.css CHANGED
@@ -19,6 +19,11 @@
19
19
  @theme inline {
20
20
  --font-og-sans: var(--og-font-sans);
21
21
  --font-og-mono: var(--og-font-mono);
22
+ /* Compatibility aliases still used by published components and the exact
23
+ dropdown primitives shared with the web app. Keep the public stylesheet
24
+ self-contained instead of requiring consumers to copy the app theme. */
25
+ --font-sans: var(--og-font-sans);
26
+ --font-mono: var(--og-font-mono);
22
27
 
23
28
  /* Type ramp — a real 4-step scale (no half-pixel hand-tuning). Every
24
29
  timeline string maps to one of these via `text-og-{xs,sm,base,md}`;
@@ -31,6 +36,8 @@
31
36
  --text-og-base--line-height: 1.55;
32
37
  --text-og-md: 15px;
33
38
  --text-og-md--line-height: 1.6;
39
+ --text-2xs: 0.6875rem;
40
+ --text-2xs--line-height: 1rem;
34
41
 
35
42
  --color-og-bg: var(--og-color-bg);
36
43
  --color-og-surface-1: var(--og-color-surface-1);
@@ -44,6 +51,7 @@
44
51
 
45
52
  --color-og-accent: var(--og-color-accent);
46
53
  --color-og-accent-strong: var(--og-color-accent-strong);
54
+ --color-og-accent-deep: var(--og-color-accent-deep);
47
55
  --color-og-accent-fg: var(--og-color-accent-fg);
48
56
  --color-og-accent-soft: var(--og-color-accent-soft);
49
57
 
@@ -55,11 +63,64 @@
55
63
  --color-og-status-cancelled: var(--og-color-status-cancelled);
56
64
  --color-og-danger: var(--og-color-danger);
57
65
 
66
+ --color-bg: var(--og-color-bg);
67
+ --color-surface: var(--og-color-surface-1);
68
+ --color-surface-2: var(--og-color-surface-2);
69
+ --color-surface-3: var(--og-color-surface-3);
70
+ --color-border: var(--og-color-border);
71
+ --color-border-strong: var(--og-color-border-strong);
72
+ --color-fg: var(--og-color-fg);
73
+ --color-fg-muted: var(--og-color-fg-muted);
74
+ --color-fg-subtle: var(--og-color-fg-subtle);
75
+ --color-brand: var(--og-color-accent);
76
+ --color-brand-strong: var(--og-color-accent-deep);
77
+ --color-brand-fg: var(--og-color-accent-fg);
78
+ --color-status-queued: var(--og-color-status-queued);
79
+ --color-status-running: var(--og-color-status-running);
80
+ --color-status-idle: var(--og-color-status-idle);
81
+ --color-status-waiting: var(--og-color-status-waiting);
82
+ --color-status-success: var(--og-color-status-idle);
83
+ --color-status-failed: var(--og-color-status-failed);
84
+ --color-status-cancelled: var(--og-color-status-cancelled);
85
+ --color-danger: var(--og-color-danger);
86
+
87
+ --color-background: var(--color-bg);
88
+ --color-foreground: var(--color-fg);
89
+ --color-card: var(--color-surface);
90
+ --color-card-foreground: var(--color-fg);
91
+ --color-popover: var(--color-surface-3);
92
+ --color-popover-foreground: var(--color-fg);
93
+ --color-primary: var(--color-brand-strong);
94
+ --color-primary-foreground: var(--color-brand-fg);
95
+ --color-secondary: var(--color-surface-2);
96
+ --color-secondary-foreground: var(--color-fg);
97
+ --color-muted: var(--color-surface-2);
98
+ --color-muted-foreground: var(--color-fg-muted);
99
+ --color-accent: var(--color-surface-2);
100
+ --color-accent-foreground: var(--color-fg);
101
+ --color-destructive: var(--og-color-danger);
102
+ --color-destructive-filled: color-mix(in oklch, var(--og-color-danger) 70%, black);
103
+ --color-destructive-foreground: var(--og-color-accent-fg);
104
+ --color-input: var(--color-surface-2);
105
+ --color-ring: var(--color-brand);
106
+ --color-sidebar: var(--color-surface);
107
+ --color-sidebar-foreground: var(--color-fg);
108
+ --color-sidebar-primary: var(--color-brand-strong);
109
+ --color-sidebar-primary-foreground: var(--color-brand-fg);
110
+ --color-sidebar-accent: var(--color-surface-2);
111
+ --color-sidebar-accent-foreground: var(--color-fg);
112
+ --color-sidebar-border: var(--color-border);
113
+ --color-sidebar-ring: var(--color-brand);
114
+
58
115
  --radius-og-xs: var(--og-radius-xs);
59
116
  --radius-og-sm: var(--og-radius-sm);
60
117
  --radius-og-md: var(--og-radius-md);
61
118
  --radius-og-lg: var(--og-radius-lg);
62
119
  --radius-og-xl: var(--og-radius-xl);
120
+ --radius-sm: var(--og-radius-sm);
121
+ --radius-md: var(--og-radius-md);
122
+ --radius-lg: var(--og-radius-lg);
123
+ --radius-xl: var(--og-radius-xl);
63
124
 
64
125
  --shadow-og-sm: var(--og-shadow-sm);
65
126
  --shadow-og-md: var(--og-shadow-md);
package/styles/tokens.css CHANGED
@@ -107,6 +107,8 @@
107
107
  --og-session-chrome-rail-pad: 0.2rem;
108
108
  --og-session-chrome-panel-pad-x: 0.5rem;
109
109
  --og-session-chrome-panel-pad-y: 0.4rem;
110
+ /* Expanded agents/queue/inbox/goal body — scroll inside, never grow unbound. */
111
+ --og-session-chrome-panel-max-height: min(18rem, 40dvh);
110
112
  --og-session-chrome-duration: 220ms;
111
113
  --og-session-chrome-crossfade-duration: 180ms;
112
114
  --og-session-chrome-ease: var(--og-ease-out);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/session-context.ts","../src/hooks/internal.ts"],"sourcesContent":["import type { StreamConnectionState, WorkspaceControlEvent } from \"@opengeni/sdk\";\nimport { createContext, useContext } from \"react\";\nimport type {\n EmbeddedFileAttachmentClientLike,\n EmbeddedGoalClientLike,\n EmbeddedHumanInputSessionClientLike,\n EmbeddedSessionClientLike,\n EmbeddedSessionLineageClientLike,\n EmbeddedSessionMcpApprovalPolicyClientLike,\n EmbeddedSessionReadClientLike,\n SessionClientLike,\n} from \"./client\";\n\nexport type OpenGeniContextValue = {\n client: SessionClientLike;\n workspaceId: string;\n workspaceControlEvent: WorkspaceControlEvent | null;\n workspaceControlConnectionState: StreamConnectionState | \"idle\" | \"error\";\n registerSessionReconciler: (\n sessionId: string,\n key: string,\n reconcile: () => Promise<void>,\n ) => () => void;\n reconcileSession: (sessionId: string) => Promise<void>;\n};\n\nexport const OpenGeniContext = createContext<OpenGeniContextValue | null>(null);\n\nconst NOOP_REGISTER_RECONCILER: OpenGeniContextValue[\"registerSessionReconciler\"] = () => () =>\n undefined;\nconst NOOP_RECONCILE_SESSION: OpenGeniContextValue[\"reconcileSession\"] = async () => undefined;\n\nexport type ClientOverride = {\n client?: SessionClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedSessionClientOverride = {\n client?: EmbeddedSessionClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedHumanInputClientOverride = {\n client?: EmbeddedHumanInputSessionClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedSessionMcpApprovalPolicyClientOverride = {\n client?: EmbeddedSessionMcpApprovalPolicyClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedSessionReadClientOverride = {\n client?: EmbeddedSessionReadClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedGoalClientOverride = {\n client?: EmbeddedGoalClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedSessionLineageClientOverride = {\n client?: EmbeddedSessionLineageClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedFileAttachmentClientOverride = {\n client?: EmbeddedFileAttachmentClientLike | undefined;\n workspaceId?: string | undefined;\n};\n\nexport type EmbeddedSessionContextValue = Omit<OpenGeniContextValue, \"client\"> & {\n client: EmbeddedSessionClientLike;\n};\n\ntype EmbeddedClientOverride<TClient> = {\n client?: TClient | undefined;\n workspaceId?: string | undefined;\n};\n\ntype EmbeddedClientContextValue<TClient> = Omit<OpenGeniContextValue, \"client\"> & {\n client: TClient;\n};\n\nfunction useEmbeddedClientRefinement<TClient extends object>(\n override: EmbeddedClientOverride<TClient>,\n requiredMethods: readonly string[],\n hookName: string,\n): EmbeddedClientContextValue<TClient> {\n const context = useContext(OpenGeniContext);\n const candidate = override.client ?? context?.client;\n const workspaceId = override.workspaceId ?? context?.workspaceId;\n if (!candidate || !workspaceId) {\n throw new Error(\n \"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.\",\n );\n }\n const methods = candidate as Record<string, unknown>;\n const missing = requiredMethods.filter((method) => typeof methods[method] !== \"function\");\n if (missing.length > 0) {\n throw new Error(\n `@opengeni/react: ${hookName} requires client method${missing.length === 1 ? \"\" : \"s\"} ${missing.join(\", \")}.`,\n );\n }\n return {\n client: candidate as TClient,\n workspaceId,\n workspaceControlEvent: context?.workspaceControlEvent ?? null,\n workspaceControlConnectionState: context?.workspaceControlConnectionState ?? \"idle\",\n registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,\n reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,\n };\n}\n\nfunction eventClientMethods(override: object, directMethods: readonly string[]): readonly string[] {\n const hasSharedEventFeed =\n \"events\" in override && (override as { events?: unknown }).events !== undefined;\n return hasSharedEventFeed ? directMethods : [\"getSession\", \"streamEvents\", ...directMethods];\n}\n\n/** Resolve client + workspace from explicit overrides or the provider. */\nexport function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue {\n const context = useContext(OpenGeniContext);\n const client = override.client ?? context?.client;\n const workspaceId = override.workspaceId ?? context?.workspaceId;\n if (!client || !workspaceId) {\n throw new Error(\n \"@opengeni/react: no OpenGeni client/workspace available. Wrap the tree in <OpenGeniProvider> or pass { client, workspaceId } to the hook.\",\n );\n }\n return {\n client,\n workspaceId,\n workspaceControlEvent: context?.workspaceControlEvent ?? null,\n workspaceControlConnectionState: context?.workspaceControlConnectionState ?? \"idle\",\n registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,\n reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,\n };\n}\n\n/**\n * Resolve the narrow client required by the session-only hooks. The full\n * provider client is structurally compatible, while an explicit host proxy\n * only needs to expose session/event/composer/queue/control operations.\n */\nexport function useEmbeddedSession(\n override: EmbeddedSessionClientOverride = {},\n): EmbeddedSessionContextValue {\n return useEmbeddedClientRefinement(\n override,\n [\n \"getSession\",\n \"listEvents\",\n \"streamEvents\",\n \"getComposerDraft\",\n \"saveComposerDraft\",\n \"sendMessage\",\n \"steerMessage\",\n \"getQueue\",\n \"moveQueueItem\",\n \"editQueueItem\",\n \"steerQueueItem\",\n \"deleteQueueItem\",\n \"pauseSession\",\n \"resumeSession\",\n \"sendApprovalDecision\",\n ],\n \"session hooks\",\n );\n}\n\n/** Resolve the exact client surface required by `useSession`. */\nexport function useEmbeddedSessionRead(\n override: EmbeddedSessionReadClientOverride = {},\n): EmbeddedClientContextValue<EmbeddedSessionReadClientLike> {\n return useEmbeddedClientRefinement(\n override,\n eventClientMethods(override, [\"getSession\", \"updateSession\"]),\n \"useSession\",\n );\n}\n\n/** Resolve the exact client surface required by `useGoal`. */\nexport function useEmbeddedGoal(\n override: EmbeddedGoalClientOverride = {},\n): EmbeddedClientContextValue<EmbeddedGoalClientLike> {\n return useEmbeddedClientRefinement(\n override,\n eventClientMethods(override, [\"getGoal\", \"updateGoal\", \"deleteGoal\"]),\n \"useGoal\",\n );\n}\n\n/** Resolve the exact client surface required by `useSessionLineage`. */\nexport function useEmbeddedSessionLineage(\n override: EmbeddedSessionLineageClientOverride = {},\n): EmbeddedClientContextValue<EmbeddedSessionLineageClientLike> {\n return useEmbeddedClientRefinement(\n override,\n eventClientMethods(override, [\"getSessionLineage\"]),\n \"useSessionLineage\",\n );\n}\n\n/** Resolve the exact client surface required by `useFileAttachments`. */\nexport function useEmbeddedFileAttachments(\n override: EmbeddedFileAttachmentClientOverride = {},\n): EmbeddedClientContextValue<EmbeddedFileAttachmentClientLike> {\n return useEmbeddedClientRefinement(override, [\"uploadFile\"], \"useFileAttachments\");\n}\n\n/**\n * Resolve the structured-input refinement without widening the baseline\n * session-only proxy contract.\n */\nexport function useEmbeddedHumanInputSession(override: EmbeddedHumanInputClientOverride = {}): Omit<\n EmbeddedSessionContextValue,\n \"client\"\n> & {\n client: EmbeddedHumanInputSessionClientLike;\n} {\n return useEmbeddedClientRefinement(\n override,\n eventClientMethods(override, [\"listHumanInputRequests\", \"submitHumanInputResponse\"]),\n \"useHumanInputRequests\",\n );\n}\n\n/** Resolve the approval-policy refinement without widening session-only hosts. */\nexport function useEmbeddedSessionMcpApprovalPolicy(\n override: EmbeddedSessionMcpApprovalPolicyClientOverride = {},\n): Omit<EmbeddedSessionContextValue, \"client\"> & {\n client: EmbeddedSessionMcpApprovalPolicyClientLike;\n} {\n return useEmbeddedClientRefinement(\n override,\n eventClientMethods(override, [\"getSession\", \"updateSessionMcpApprovalPolicy\"]),\n \"useSessionMcpApprovalPolicy\",\n );\n}\n\n/**\n * Resolve the client only — for hooks that are not workspace-scoped\n * (`useWorkspaces`, `useBillingUsage`).\n */\nexport function useOpenGeniClient(\n override: Pick<ClientOverride, \"client\"> = {},\n): SessionClientLike {\n const context = useContext(OpenGeniContext);\n const client = override.client ?? context?.client;\n if (!client) {\n throw new Error(\n \"@opengeni/react: no OpenGeni client available. Wrap the tree in <OpenGeniProvider> or pass { client } to the hook.\",\n );\n }\n return client;\n}\n","import type { SessionEvent } from \"@opengeni/sdk\";\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport type { EmbeddedSessionEventClientLike } from \"../client\";\n\nexport type AsyncListState<T> = {\n data: T | null;\n loading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n};\n\n/**\n * Shared fetch + optional polling loop for the list/read hooks. Stale\n * responses (superseded by a newer load or an unmount) are dropped.\n */\nexport function usePolledValue<T>(\n load: (signal?: AbortSignal) => Promise<T>,\n options: { pollIntervalMs?: number | undefined; enabled?: boolean | undefined } = {},\n): AsyncListState<T> {\n const enabled = options.enabled ?? true;\n const pollIntervalMs = options.pollIntervalMs;\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(enabled);\n const [error, setError] = useState<Error | null>(null);\n const generation = useRef(0);\n const activeLoadRef = useRef(load);\n useLayoutEffect(() => {\n activeLoadRef.current = load;\n }, [load]);\n const requestAbortRef = useRef<AbortController | null>(null);\n const [stateIdentity, setStateIdentity] = useState<{ load: typeof load }>(() => ({ load }));\n\n // A new loader identity means a new query (different session/workspace/...):\n // drop the previous result instead of showing it as the new query's data.\n useEffect(() => {\n if (stateIdentity.load !== load) {\n setStateIdentity({ load });\n setData(null);\n setError(null);\n }\n }, [load, stateIdentity.load]);\n\n const run = useCallback(async () => {\n // A callback retained by a completed mutation from the previous query must\n // not supersede or settle the current query's request.\n if (activeLoadRef.current !== load) return;\n const ticket = ++generation.current;\n requestAbortRef.current?.abort();\n const requestAbort = new AbortController();\n requestAbortRef.current = requestAbort;\n try {\n const result = await load(requestAbort.signal);\n if (\n ticket === generation.current &&\n activeLoadRef.current === load &&\n !requestAbort.signal.aborted\n ) {\n setData(result);\n setError(null);\n setLoading(false);\n }\n } catch (cause) {\n if (\n ticket === generation.current &&\n activeLoadRef.current === load &&\n !requestAbort.signal.aborted\n ) {\n setError(cause instanceof Error ? cause : new Error(String(cause)));\n setLoading(false);\n }\n } finally {\n if (requestAbortRef.current === requestAbort) {\n requestAbortRef.current = null;\n }\n }\n }, [load]);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n setLoading(true);\n void run();\n if (pollIntervalMs === undefined || pollIntervalMs <= 0) {\n return () => {\n generation.current += 1;\n requestAbortRef.current?.abort();\n };\n }\n const timer = setInterval(() => void run(), pollIntervalMs);\n return () => {\n clearInterval(timer);\n generation.current += 1;\n requestAbortRef.current?.abort();\n };\n }, [run, enabled, pollIntervalMs]);\n\n const identityMatches = stateIdentity.load === load;\n return {\n data: identityMatches ? data : null,\n loading: identityMatches ? loading : enabled,\n error: identityMatches ? error : null,\n refresh: run,\n };\n}\n\nexport type MutationState = {\n mutating: boolean;\n mutationError: Error | null;\n clearMutationError: () => void;\n};\n\n/**\n * Shared async mutation runner for the write hooks. `run` resolves with the\n * operation's value, or `null` after capturing the error in `mutationError`\n * (callers then roll back optimistic state).\n */\nexport function useMutationRunner(identity: unknown = undefined): MutationState & {\n run: <T>(operation: () => Promise<T>) => Promise<T | null>;\n} {\n const [mutating, setMutating] = useState(false);\n const [mutationError, setMutationError] = useState<Error | null>(null);\n const [stateIdentity, setStateIdentity] = useState<unknown>(() => identity);\n const inFlight = useRef(0);\n const generation = useRef(0);\n const identityRef = useRef(identity);\n useLayoutEffect(() => {\n if (Object.is(identityRef.current, identity)) return;\n identityRef.current = identity;\n generation.current += 1;\n inFlight.current = 0;\n }, [identity]);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n useEffect(() => {\n if (!Object.is(stateIdentity, identity)) {\n setStateIdentity(() => identity);\n setMutating(false);\n setMutationError(null);\n }\n }, [identity, stateIdentity]);\n const run = useCallback(\n async <T>(operation: () => Promise<T>): Promise<T | null> => {\n const ownedIdentity = identity;\n const ownedGeneration = generation.current;\n if (!Object.is(identityRef.current, ownedIdentity)) return null;\n inFlight.current += 1;\n if (mounted.current) {\n setMutating(true);\n setMutationError(null);\n }\n try {\n const result = await operation();\n if (\n !mounted.current ||\n generation.current !== ownedGeneration ||\n !Object.is(identityRef.current, ownedIdentity)\n ) {\n return null;\n }\n return result;\n } catch (cause) {\n if (\n mounted.current &&\n generation.current === ownedGeneration &&\n Object.is(identityRef.current, ownedIdentity)\n ) {\n setMutationError(cause instanceof Error ? cause : new Error(String(cause)));\n }\n return null;\n } finally {\n if (\n generation.current === ownedGeneration &&\n Object.is(identityRef.current, ownedIdentity)\n ) {\n inFlight.current -= 1;\n if (mounted.current && inFlight.current === 0) {\n setMutating(false);\n }\n }\n }\n },\n [identity],\n );\n const identityMatches = Object.is(stateIdentity, identity);\n return {\n mutating: identityMatches && mutating,\n mutationError: identityMatches ? mutationError : null,\n clearMutationError: useCallback(() => {\n if (Object.is(identityRef.current, identity)) setMutationError(null);\n }, [identity]),\n run,\n };\n}\n\nexport type SessionEventFeedOptions = {\n /**\n * Share an existing event log (from `useSessionEvents`) instead of opening\n * a second stream. When omitted the hook tails the session's event stream\n * itself, starting at the current `lastSequence`.\n */\n events?: SessionEvent[] | undefined;\n enabled?: boolean | undefined;\n};\n\n/**\n * Invoke `onEvent` for every session event matching `match` — the live-update\n * primitive behind `useTurnQueue` and `useGoal`. Either watches a shared\n * `events` log or tails the stream directly (reconnect handled by the SDK).\n */\nexport function useSessionEventTrigger(\n client: EmbeddedSessionEventClientLike,\n workspaceId: string,\n sessionId: string | null | undefined,\n match: (event: SessionEvent) => boolean,\n onEvent: (event: SessionEvent) => void,\n options: SessionEventFeedOptions = {},\n reconcileBeforeLive?: (() => void | Promise<void>) | undefined,\n): void {\n const enabled = options.enabled ?? true;\n const events = options.events;\n const sharedFeed = events !== undefined;\n const matchRef = useRef(match);\n const onEventRef = useRef(onEvent);\n const reconcileBeforeLiveRef = useRef(reconcileBeforeLive);\n useLayoutEffect(() => {\n matchRef.current = match;\n onEventRef.current = onEvent;\n reconcileBeforeLiveRef.current = reconcileBeforeLive;\n }, [match, onEvent, reconcileBeforeLive]);\n const consumedRef = useRef(0);\n const feedKeyRef = useRef<string | null>(null);\n const sharedFeedHasEventsRef = useRef(false);\n\n // Shared-log mode: scan only the unseen tail on every append.\n useEffect(() => {\n if (!sharedFeed || !enabled || !sessionId) {\n return;\n }\n const feedKey = `${workspaceId}\\u0000${sessionId}`;\n const firstSequence = events[0]?.sequence ?? 0;\n if (feedKeyRef.current !== feedKey) {\n feedKeyRef.current = feedKey;\n sharedFeedHasEventsRef.current = events.length > 0;\n // The caller has already loaded its authoritative initial projection.\n // Seed from the shared log's current tail so mounting a large historical\n // session cannot replay every old event as a new live trigger.\n consumedRef.current = events.at(-1)?.sequence ?? 0;\n return;\n }\n const firstNonEmptyBatch = !sharedFeedHasEventsRef.current && events.length > 0;\n if (firstNonEmptyBatch || firstSequence > consumedRef.current + 1) {\n // The usual shared feed mounts empty, then receives a historical tail.\n // A later discontinuity can likewise replace the retained browser\n // window. In either case, reconcile once from the latest relevant event\n // instead of replaying the whole retained window as live traffic.\n sharedFeedHasEventsRef.current = events.length > 0;\n consumedRef.current = events.at(-1)?.sequence ?? consumedRef.current;\n let latestMatch: SessionEvent | undefined;\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event && matchRef.current(event)) {\n latestMatch = event;\n break;\n }\n }\n if (latestMatch) {\n onEventRef.current(latestMatch);\n }\n return;\n }\n sharedFeedHasEventsRef.current ||= events.length > 0;\n for (const event of events) {\n if (event.sequence <= consumedRef.current) {\n continue;\n }\n consumedRef.current = event.sequence;\n if (matchRef.current(event)) {\n onEventRef.current(event);\n }\n }\n }, [sharedFeed, enabled, events, workspaceId, sessionId]);\n\n // Self-stream mode: tail from the session's current lastSequence.\n useEffect(() => {\n if (sharedFeed || !enabled || !sessionId) {\n return;\n }\n const controller = new AbortController();\n void (async () => {\n try {\n const session = await client.getSession(workspaceId, sessionId);\n if (controller.signal.aborted) {\n return;\n }\n const stream = client.streamEvents(workspaceId, sessionId, {\n after: session.lastSequence,\n signal: controller.signal,\n beforeLive: async () => await reconcileBeforeLiveRef.current?.(),\n });\n for await (const event of stream) {\n if (matchRef.current(event)) {\n onEventRef.current(event);\n }\n }\n } catch {\n // Live updates are best-effort: the read hooks still expose refresh()\n // and the initial load already populated state.\n }\n })();\n return () => {\n controller.abort();\n };\n }, [sharedFeed, enabled, client, workspaceId, sessionId]);\n}\n\n/** Debounce rapid event bursts into one trailing call (default 150ms). */\nexport function useDebouncedCallback(callback: () => void, delayMs = 150): () => void {\n const callbackRef = useRef(callback);\n useLayoutEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n return () => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n }\n };\n }, []);\n return useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n callbackRef.current();\n }, delayMs);\n }, [delayMs]);\n}\n"],"mappings":"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","names":[]}