@opengeni/react 3.8.0-canary.1 → 4.0.1-canary.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 (63) hide show
  1. package/README.md +10 -24
  2. package/dist/artifacts.js +7 -7
  3. package/dist/{browser-viewer-SS46LUAC.js → browser-viewer-NTD6UIPR.js} +3 -3
  4. package/dist/{chunk-VEPNNDNG.js → chunk-24M4YBCL.js} +4 -4
  5. package/dist/{chunk-ELIYWBZR.js → chunk-BV5A4OTE.js} +4 -4
  6. package/dist/{chunk-7W6PVVTK.js → chunk-CQG7YUJB.js} +2582 -1037
  7. package/dist/chunk-CQG7YUJB.js.map +1 -0
  8. package/dist/{chunk-BXJOQFCD.js → chunk-HUMIRRYI.js} +84 -221
  9. package/dist/chunk-HUMIRRYI.js.map +1 -0
  10. package/dist/{chunk-7BWFU6H4.js → chunk-Z7WBDB4E.js} +395 -287
  11. package/dist/chunk-Z7WBDB4E.js.map +1 -0
  12. package/dist/components/human-input-form.d.ts +3 -1
  13. package/dist/components/human-input-surface.d.ts +3 -2
  14. package/dist/components/markdown-table-layout.d.ts +9 -0
  15. package/dist/components/session-conversation.d.ts +2 -0
  16. package/dist/composer.js +2 -3
  17. package/dist/{computer-viewer-FWEZISW4.js → computer-viewer-7TEZC6Y2.js} +3 -3
  18. package/dist/hooks/use-codex-accounts.d.ts +11 -1
  19. package/dist/hooks/use-session-events.d.ts +2 -2
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +44 -34
  22. package/dist/index.js.map +1 -1
  23. package/dist/interaction.js +3 -3
  24. package/dist/markdown-table-layout-3Z3AWBY7.js +71 -0
  25. package/dist/markdown-table-layout-3Z3AWBY7.js.map +1 -0
  26. package/dist/older-history.d.ts +3 -2
  27. package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-27LKAWZD.js} +2 -2
  28. package/dist/platform-activity-row-27LKAWZD.js.map +1 -0
  29. package/dist/realtime.js +3 -3
  30. package/dist/session-ui.js +6 -9
  31. package/dist/session.js +1 -1
  32. package/dist/timeline/types.d.ts +3 -0
  33. package/package.json +2 -6
  34. package/src/components/human-input-form.tsx +185 -53
  35. package/src/components/human-input-surface.tsx +3 -0
  36. package/src/components/markdown-table-layout.ts +85 -0
  37. package/src/components/markdown.tsx +20 -2
  38. package/src/components/message-timeline.tsx +47 -7
  39. package/src/components/session-commands-panel.tsx +7 -1
  40. package/src/components/session-conversation.tsx +4 -1
  41. package/src/hooks/use-codex-accounts.ts +32 -5
  42. package/src/hooks/use-session-events.ts +16 -277
  43. package/src/index.ts +0 -1
  44. package/src/older-history.ts +22 -4
  45. package/src/timeline/platform-activity-row.tsx +4 -1
  46. package/src/timeline/projection.ts +99 -9
  47. package/src/timeline/types.ts +3 -0
  48. package/dist/chat.d.ts +0 -37
  49. package/dist/chat.js +0 -315
  50. package/dist/chat.js.map +0 -1
  51. package/dist/chunk-7BWFU6H4.js.map +0 -1
  52. package/dist/chunk-7W6PVVTK.js.map +0 -1
  53. package/dist/chunk-BXJOQFCD.js.map +0 -1
  54. package/dist/chunk-HQV2I5HV.js +0 -124
  55. package/dist/chunk-HQV2I5HV.js.map +0 -1
  56. package/dist/chunk-YAIPFO5D.js +0 -1416
  57. package/dist/chunk-YAIPFO5D.js.map +0 -1
  58. package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
  59. package/src/chat.tsx +0 -393
  60. /package/dist/{browser-viewer-SS46LUAC.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
  61. /package/dist/{chunk-VEPNNDNG.js.map → chunk-24M4YBCL.js.map} +0 -0
  62. /package/dist/{chunk-ELIYWBZR.js.map → chunk-BV5A4OTE.js.map} +0 -0
  63. /package/dist/{computer-viewer-FWEZISW4.js.map → computer-viewer-7TEZC6Y2.js.map} +0 -0
@@ -0,0 +1,85 @@
1
+ export function markdownTableWidth({
2
+ columnLeft,
3
+ columnWidth,
4
+ contentLeft,
5
+ contentRight,
6
+ preferredWidth,
7
+ }: {
8
+ columnLeft: number;
9
+ columnWidth: number;
10
+ contentLeft: number;
11
+ contentRight: number;
12
+ preferredWidth: number;
13
+ }): number {
14
+ const center = columnLeft + columnWidth / 2;
15
+ const available = 2 * Math.min(center - contentLeft, contentRight - center);
16
+ return Math.max(columnWidth, Math.min(preferredWidth, available));
17
+ }
18
+
19
+ /** Loaded only for assistant tables. Keep prose and nested scroll owners intact. */
20
+ export function observeMarkdownTableLayout(wrapper: HTMLDivElement, table: HTMLTableElement) {
21
+ const body = wrapper.parentElement;
22
+ const scroller = body
23
+ ?.closest("[data-og-wide-table-message]")
24
+ ?.closest<HTMLElement>("[data-og-timeline-scroller]");
25
+ if (!body?.classList.contains("og-markdown-body") || !scroller) return;
26
+
27
+ let width: number | null = null;
28
+ const reset = () => {
29
+ width = null;
30
+ wrapper.style.width = "";
31
+ wrapper.style.maxWidth = "";
32
+ wrapper.style.marginInline = "";
33
+ };
34
+ const measure = () => {
35
+ // Intermediate clipped/scrollable surfaces own their own content bounds.
36
+ for (
37
+ let ancestor: HTMLElement | null = body;
38
+ ancestor && ancestor !== scroller;
39
+ ancestor = ancestor.parentElement
40
+ ) {
41
+ if (getComputedStyle(ancestor).overflowX !== "visible") {
42
+ reset();
43
+ return;
44
+ }
45
+ }
46
+ const column = body.getBoundingClientRect();
47
+ const panel = scroller.getBoundingClientRect();
48
+ const panelStyle = getComputedStyle(scroller);
49
+ const left = panel.left + scroller.clientLeft + parseFloat(panelStyle.paddingLeft);
50
+ const right =
51
+ panel.left + scroller.clientLeft + scroller.clientWidth - parseFloat(panelStyle.paddingRight);
52
+
53
+ // Measure the original table, preserving its wrapping and TSV copy DOM.
54
+ const previousWidth = table.style.width;
55
+ let preferred: number;
56
+ try {
57
+ table.style.width = "max-content";
58
+ preferred = table.getBoundingClientRect().width;
59
+ } finally {
60
+ table.style.width = previousWidth;
61
+ }
62
+ const next = markdownTableWidth({
63
+ columnLeft: column.left,
64
+ columnWidth: column.width,
65
+ contentLeft: left,
66
+ contentRight: right,
67
+ preferredWidth: preferred,
68
+ });
69
+ if (width !== null && Math.abs(width - next) < 0.5) return;
70
+ width = next;
71
+ wrapper.style.width = `${width}px`;
72
+ wrapper.style.maxWidth = "none";
73
+ wrapper.style.marginInline = `calc((100% - ${width}px) / 2)`;
74
+ };
75
+
76
+ measure();
77
+ const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(measure);
78
+ observer?.observe(scroller);
79
+ observer?.observe(body);
80
+ observer?.observe(table);
81
+ return () => {
82
+ observer?.disconnect();
83
+ reset();
84
+ };
85
+ }
@@ -411,9 +411,27 @@ function MarkdownCodeBlock({ children }: { children?: ReactNode }) {
411
411
  }
412
412
 
413
413
  function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutRef<"table">) {
414
- const tableRef = useRef<HTMLTableElement | null>(null);
414
+ const wrapperRef = useRef<HTMLDivElement>(null);
415
+ const tableRef = useRef<HTMLTableElement>(null);
416
+ useEffect(() => {
417
+ const wrapper = wrapperRef.current;
418
+ const table = tableRef.current;
419
+ if (!wrapper?.closest("[data-og-wide-table-message]") || !table) return;
420
+ let disposed = false;
421
+ let cleanup: (() => void) | undefined;
422
+ // Ordinary tables remain usable even if this optional layout chunk fails.
423
+ void import("./markdown-table-layout")
424
+ .then(({ observeMarkdownTableLayout }) => {
425
+ if (!disposed) cleanup = observeMarkdownTableLayout(wrapper, table);
426
+ })
427
+ .catch(() => {});
428
+ return () => {
429
+ disposed = true;
430
+ cleanup?.();
431
+ };
432
+ }, [children]);
415
433
  return (
416
- <div className="group/copy relative mt-3 max-w-full first:mt-0">
434
+ <div ref={wrapperRef} className="group/copy relative mt-3 max-w-full first:mt-0">
417
435
  <div className="pointer-events-none absolute top-0 right-0 z-10">
418
436
  <div className="pointer-events-auto">
419
437
  <CopyButton
@@ -293,13 +293,18 @@ function invokeOlderLoad(
293
293
  load: OlderHistoryLoader,
294
294
  noProgress: () => void,
295
295
  attempt: OlderLoadAttempt,
296
+ preserveTail = false,
296
297
  ): 1 | undefined {
297
298
  try {
298
299
  // Receipt creation is captured synchronously through legacy wrappers such
299
300
  // as `() => void loadOlder()`, even when the wrapper discards the return.
300
- const result = invokeOlderHistoryLoaderWithReceiptCapture(load, (receipt) => {
301
- attempt[2] = receipt;
302
- }) as OlderHistoryLoadReceipt | PromiseLike<unknown> | unknown;
301
+ const result = invokeOlderHistoryLoaderWithReceiptCapture(
302
+ load,
303
+ (receipt) => {
304
+ attempt[2] = receipt;
305
+ },
306
+ preserveTail,
307
+ ) as OlderHistoryLoadReceipt | PromiseLike<unknown> | unknown;
303
308
  const receipt =
304
309
  attempt[2] ??
305
310
  (typeof (result as { committed?: unknown } | undefined)?.committed === "boolean"
@@ -978,7 +983,7 @@ export function MessageTimeline({
978
983
  // exact `false` is the first-party request-not-accepted receipt.
979
984
  // All other fulfillment retains this exact owner until its prepend
980
985
  // boundary commits; promise settlement alone cannot prove progress.
981
- invokeOlderLoad(onLoadOlder, noProgress, attempt);
986
+ invokeOlderLoad(onLoadOlder, noProgress, attempt, !retry);
982
987
  },
983
988
  [hasOlder, loadingOlder, olderBoundaryKey, onLoadOlder],
984
989
  );
@@ -1536,7 +1541,9 @@ export function MessageTimeline({
1536
1541
  }
1537
1542
  const observer = new IntersectionObserver(
1538
1543
  (entries) => {
1539
- if (entries.some((entry) => entry.isIntersecting)) {
1544
+ // An underfilled history window has both sentinels visible. Advancing
1545
+ // it automatically would undo an explicit older-page navigation.
1546
+ if (maxScrollOf(root) > 1 && entries.some((entry) => entry.isIntersecting)) {
1540
1547
  onLoadNewer();
1541
1548
  }
1542
1549
  },
@@ -1959,7 +1966,9 @@ export function MessageTimeline({
1959
1966
  className="pointer-events-auto rounded-full border border-og-border px-3 py-1.5 text-og-control"
1960
1967
  >
1961
1968
  {underfillRetryReady
1962
- ? "Retry earlier activity"
1969
+ ? underfillSettledAttempt?.[2]?.tailPreserved
1970
+ ? "Load earlier activity"
1971
+ : "Retry earlier activity"
1963
1972
  : "Jump to start"}
1964
1973
  </button>
1965
1974
  ) : null}
@@ -3264,7 +3273,12 @@ function AgentMessageRow({
3264
3273
  )
3265
3274
  }
3266
3275
  >
3267
- <div data-og-annotation-source-key={item.annotationSource?.eventId}>{body}</div>
3276
+ <div
3277
+ data-og-wide-table-message=""
3278
+ data-og-annotation-source-key={item.annotationSource?.eventId}
3279
+ >
3280
+ {body}
3281
+ </div>
3268
3282
  </CopyHoverFrame>
3269
3283
  );
3270
3284
  }
@@ -3702,6 +3716,32 @@ function formatVideoDuration(seconds: number): string {
3702
3716
 
3703
3717
  function NoticeRow({ item }: { item: NoticeItem }) {
3704
3718
  const enter = useEntranceAnimation();
3719
+ if (item.recordedOutcome) {
3720
+ return (
3721
+ <details
3722
+ className="group text-og-sm text-og-fg-muted"
3723
+ role="note"
3724
+ data-og-recorded-outcome="wait"
3725
+ >
3726
+ <summary className="flex cursor-pointer list-none items-center gap-2 py-1 [&::-webkit-details-marker]:hidden">
3727
+ <ChevronRightIcon
3728
+ aria-hidden
3729
+ className="size-3.5 transition-transform group-open:rotate-90"
3730
+ />
3731
+ <span>
3732
+ Wait recorded ·{" "}
3733
+ <time dateTime={item.occurredAt}>
3734
+ {new Date(item.occurredAt).toLocaleString(undefined, {
3735
+ dateStyle: "medium",
3736
+ timeStyle: "short",
3737
+ })}
3738
+ </time>
3739
+ </span>
3740
+ </summary>
3741
+ <p className="mt-1 whitespace-pre-wrap break-words pl-5 text-og-fg-muted">{item.text}</p>
3742
+ </details>
3743
+ );
3744
+ }
3705
3745
  const tone =
3706
3746
  item.tone === "failed"
3707
3747
  ? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
@@ -80,7 +80,13 @@ export function SessionCommandsPanel({
80
80
  </div>
81
81
  </details>
82
82
  <p className="mt-1 text-og-fg-subtle">
83
- {command.state === "stopping" ? "Stopping…" : "Running"}
83
+ {command.observationStatus === "unavailable"
84
+ ? command.state === "stopping"
85
+ ? "Stop requested · status unavailable"
86
+ : "Command status unavailable"
87
+ : command.state === "stopping"
88
+ ? "Stopping…"
89
+ : "Running"}
84
90
  </p>
85
91
  </div>
86
92
  {!readOnly ? (
@@ -9,13 +9,14 @@ import { useComposer } from "../hooks/use-composer";
9
9
  import { useHumanInputRequests } from "../hooks/use-human-input";
10
10
  import { ChatComposer, type ChatComposerProps } from "./chat-composer";
11
11
  import { QueueSurface } from "./queue-surface";
12
- import { HumanInputSurface } from "./human-input-surface";
12
+ import { HumanInputSurface, type HumanInputSurfaceProps } from "./human-input-surface";
13
13
  import { MessageTimeline } from "./message-timeline";
14
14
  import { conversationTimeline } from "../conversation-timeline";
15
15
  import { cn } from "../lib/cn";
16
16
 
17
17
  export type SessionConversationProps = ClientOverride & {
18
18
  sessionId: string;
19
+ loadSkillReview?: HumanInputSurfaceProps["loadSkillReview"];
19
20
  className?: string;
20
21
  /** Defaults to filling the host. The host owns available height. */
21
22
  height?: CSSProperties["height"];
@@ -31,6 +32,7 @@ export function SessionConversation(props: SessionConversationProps) {
31
32
 
32
33
  function Conversation({
33
34
  sessionId,
35
+ loadSkillReview,
34
36
  client,
35
37
  workspaceId,
36
38
  className,
@@ -89,6 +91,7 @@ function Conversation({
89
91
  />
90
92
  <div className="min-h-0 max-h-[40%] shrink-0 overflow-y-auto" data-og-conversation-inputs="">
91
93
  <HumanInputSurface
94
+ loadSkillReview={loadSkillReview}
92
95
  requests={human.requests}
93
96
  onSubmit={async (id, response) => {
94
97
  await human.respond(id, response);
@@ -15,7 +15,16 @@ import {
15
15
 
16
16
  /** Events that change which Codex account a session runs on (or just ran). */
17
17
  export function isCodexAccountEvent(event: Pick<SessionEvent, "type">): boolean {
18
- return event.type === "codex.account.switched";
18
+ return [
19
+ "codex.account.switched",
20
+ "codex.account.selection.changed",
21
+ "codex.capacity.waiting",
22
+ "codex.capacity.resumed",
23
+ "codex.capacity.superseded",
24
+ "turn.completed",
25
+ "turn.failed",
26
+ "turn.cancelled",
27
+ ].includes(event.type);
19
28
  }
20
29
 
21
30
  /**
@@ -30,12 +39,16 @@ export type CodexAccountsClientLike = {
30
39
  getSession?: (
31
40
  workspaceId: string,
32
41
  sessionId: string,
33
- ) => Promise<{ codexPinnedCredentialId?: string | null; codexLastCredentialId?: string | null }>;
42
+ ) => Promise<{
43
+ codexPinnedCredentialId?: string | null;
44
+ codexLastCredentialId?: string | null;
45
+ codexCurrentSelection?: { credentialId: string | null; waiting: boolean } | null;
46
+ }>;
34
47
  pinSessionCodexAccount?: (
35
48
  workspaceId: string,
36
49
  sessionId: string,
37
50
  target: string,
38
- ) => Promise<{ pinned: string }>;
51
+ ) => Promise<{ pinned: string; appliedTo?: "waiting_turn" | "next_turn" }>;
39
52
  /** Optional (absent ⇒ the card hides live refresh): batched live /wham/usage refresh. */
40
53
  refreshCodexUsage?: (workspaceId: string) => Promise<{ usage: Record<string, unknown> }>;
41
54
  };
@@ -55,8 +68,10 @@ export type UseCodexAccountsResult = {
55
68
  activeAccountId: string | null;
56
69
  /** The session's PINNED account (null ⇒ following workspace active). */
57
70
  pinnedAccountId: string | null;
58
- /** The account the next turn will run on: pin > workspace active. */
71
+ /** Current preference only; automatic allocation can choose another account. */
59
72
  effectiveAccountId: string | null;
73
+ currentSelection: { credentialId: string | null; waiting: boolean } | null;
74
+ switchAppliedTo: "waiting_turn" | "next_turn" | null;
60
75
  /** The account the session's last turn ACTUALLY ran on (the "Running on:" source). */
61
76
  lastAccountId: string | null;
62
77
  settings: CodexRotationSettings;
@@ -85,6 +100,7 @@ const EMPTY_SETTINGS: CodexRotationSettings = {
85
100
  };
86
101
 
87
102
  type CodexAccountsState = {
103
+ currentSelection: { credentialId: string | null; waiting: boolean } | null;
88
104
  accounts: CodexAccount[];
89
105
  activeAccountId: string | null;
90
106
  settings: CodexRotationSettings;
@@ -93,6 +109,7 @@ type CodexAccountsState = {
93
109
  };
94
110
 
95
111
  const EMPTY_STATE: CodexAccountsState = {
112
+ currentSelection: null,
96
113
  accounts: [],
97
114
  activeAccountId: null,
98
115
  settings: EMPTY_SETTINGS,
@@ -121,6 +138,7 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
121
138
  : Promise.resolve(null);
122
139
  const [acc, session] = await Promise.all([accountsP, sessionP]);
123
140
  return {
141
+ currentSelection: session?.codexCurrentSelection ?? null,
124
142
  accounts: acc.accounts,
125
143
  activeAccountId: acc.activeAccountId,
126
144
  settings: acc.settings,
@@ -140,6 +158,10 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
140
158
  const { run: runMutation, mutating: pinning, mutationError } = useMutationRunner();
141
159
  const { run: runUsageMutation, mutating: refreshingUsage } = useMutationRunner();
142
160
  const [pinningTarget, setPinningTarget] = useState<string | null>(null);
161
+ const [switchReceipt, setSwitchReceipt] = useState<{
162
+ sessionId: string;
163
+ appliedTo: "waiting_turn" | "next_turn";
164
+ } | null>(null);
143
165
 
144
166
  // Refresh only after the durable post-selection event. `turn.started` is
145
167
  // emitted before account selection settles and races this authoritative read.
@@ -161,8 +183,10 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
161
183
  return false;
162
184
  }
163
185
  setPinningTarget(target);
186
+ setSwitchReceipt(null);
164
187
  const result = await runMutation(async () => {
165
- await codexClient.pinSessionCodexAccount!(workspaceId, sessionId, target);
188
+ const receipt = await codexClient.pinSessionCodexAccount!(workspaceId, sessionId, target);
189
+ setSwitchReceipt({ sessionId, appliedTo: receipt.appliedTo ?? "next_turn" });
166
190
  return true;
167
191
  });
168
192
  setPinningTarget(null);
@@ -191,6 +215,9 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
191
215
  const effectiveAccountId = data.pinnedAccountId ?? data.activeAccountId;
192
216
 
193
217
  return {
218
+ currentSelection: data.currentSelection,
219
+ switchAppliedTo:
220
+ switchReceipt?.sessionId === sessionId ? (switchReceipt?.appliedTo ?? null) : null,
194
221
  accounts: data.accounts,
195
222
  activeAccountId: data.activeAccountId,
196
223
  pinnedAccountId: data.pinnedAccountId,