@opengeni/react 3.8.0-canary.1 → 4.0.1-canary.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 (58) hide show
  1. package/README.md +5 -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-HO6X4FHG.js} +2559 -1037
  7. package/dist/chunk-HO6X4FHG.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/session-conversation.d.ts +2 -0
  15. package/dist/composer.js +2 -3
  16. package/dist/{computer-viewer-FWEZISW4.js → computer-viewer-7TEZC6Y2.js} +3 -3
  17. package/dist/hooks/use-codex-accounts.d.ts +11 -1
  18. package/dist/hooks/use-session-events.d.ts +2 -2
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +44 -34
  21. package/dist/index.js.map +1 -1
  22. package/dist/interaction.js +8 -8
  23. package/dist/older-history.d.ts +3 -2
  24. package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-27LKAWZD.js} +2 -2
  25. package/dist/platform-activity-row-27LKAWZD.js.map +1 -0
  26. package/dist/realtime.js +3 -3
  27. package/dist/session-ui.js +6 -9
  28. package/dist/session.js +1 -1
  29. package/dist/timeline/types.d.ts +3 -0
  30. package/package.json +2 -6
  31. package/src/components/human-input-form.tsx +185 -53
  32. package/src/components/human-input-surface.tsx +3 -0
  33. package/src/components/message-timeline.tsx +41 -6
  34. package/src/components/session-commands-panel.tsx +7 -1
  35. package/src/components/session-conversation.tsx +4 -1
  36. package/src/hooks/use-codex-accounts.ts +32 -5
  37. package/src/hooks/use-session-events.ts +16 -277
  38. package/src/index.ts +0 -1
  39. package/src/older-history.ts +22 -4
  40. package/src/timeline/platform-activity-row.tsx +4 -1
  41. package/src/timeline/projection.ts +99 -9
  42. package/src/timeline/types.ts +3 -0
  43. package/dist/chat.d.ts +0 -37
  44. package/dist/chat.js +0 -315
  45. package/dist/chat.js.map +0 -1
  46. package/dist/chunk-7BWFU6H4.js.map +0 -1
  47. package/dist/chunk-7W6PVVTK.js.map +0 -1
  48. package/dist/chunk-BXJOQFCD.js.map +0 -1
  49. package/dist/chunk-HQV2I5HV.js +0 -124
  50. package/dist/chunk-HQV2I5HV.js.map +0 -1
  51. package/dist/chunk-YAIPFO5D.js +0 -1416
  52. package/dist/chunk-YAIPFO5D.js.map +0 -1
  53. package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
  54. package/src/chat.tsx +0 -393
  55. /package/dist/{browser-viewer-SS46LUAC.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
  56. /package/dist/{chunk-VEPNNDNG.js.map → chunk-24M4YBCL.js.map} +0 -0
  57. /package/dist/{chunk-ELIYWBZR.js.map → chunk-BV5A4OTE.js.map} +0 -0
  58. /package/dist/{computer-viewer-FWEZISW4.js.map → computer-viewer-7TEZC6Y2.js.map} +0 -0
@@ -1,3 +1,9 @@
1
+ import {
2
+ ComputerViewer,
3
+ useComputerFrameStream,
4
+ useComputerSession,
5
+ useComputerSessions
6
+ } from "./chunk-BV5A4OTE.js";
1
7
  import {
2
8
  BrowserViewer,
3
9
  useAttachedBrowsers,
@@ -7,19 +13,14 @@ import {
7
13
  useBrowserSession,
8
14
  useBrowserSessions,
9
15
  useSiteAuthConnections
10
- } from "./chunk-VEPNNDNG.js";
11
- import {
12
- ComputerViewer,
13
- useComputerFrameStream,
14
- useComputerSession,
15
- useComputerSessions
16
- } from "./chunk-ELIYWBZR.js";
16
+ } from "./chunk-24M4YBCL.js";
17
17
  import {
18
18
  useInteractionInterventions,
19
19
  useInteractionInvalidation
20
20
  } from "./chunk-H5YGYNXS.js";
21
21
  import "./chunk-4MDLEHXL.js";
22
22
  import "./chunk-5YK2ZDIG.js";
23
+ import "./chunk-JVYQRRC4.js";
23
24
  import "./chunk-PLYRL5ER.js";
24
25
  import "./chunk-KZ73RL76.js";
25
26
  import {
@@ -28,7 +29,6 @@ import {
28
29
  useEmbeddedInteraction,
29
30
  useEmbeddedInterventions
30
31
  } from "./chunk-NEXLLTP4.js";
31
- import "./chunk-JVYQRRC4.js";
32
32
  import "./chunk-22KP6LR5.js";
33
33
 
34
34
  // src/hooks/use-network-routes.ts
@@ -8,6 +8,7 @@
8
8
  */
9
9
  export type OlderHistoryLoadReceipt = Promise<boolean> & {
10
10
  readonly committed: boolean;
11
+ readonly tailPreserved?: boolean;
11
12
  };
12
13
  /**
13
14
  * Source-compatible public callback accepted by MessageTimeline.
@@ -20,10 +21,10 @@ export type OlderHistoryLoadReceipt = Promise<boolean> & {
20
21
  export type OlderHistoryLoader = () => unknown;
21
22
  type OlderHistoryReceiptCapture = (receipt: OlderHistoryLoadReceipt) => void;
22
23
  /** Bind a receipt before a synchronous forwarding stack can publish state. */
23
- export declare function invokeOlderHistoryLoaderWithReceiptCapture(load: OlderHistoryLoader, capture: OlderHistoryReceiptCapture): unknown;
24
+ export declare function invokeOlderHistoryLoaderWithReceiptCapture(load: OlderHistoryLoader, capture: OlderHistoryReceiptCapture, preserveTail?: boolean): unknown;
24
25
  /**
25
26
  * Build an await-compatible older-history receipt for custom timeline hosts.
26
27
  * Call `markCommitted` immediately before publishing an accepted older window.
27
28
  */
28
- export declare function createOlderHistoryLoadReceipt(load: (markCommitted: () => void) => boolean | Promise<boolean>): OlderHistoryLoadReceipt;
29
+ export declare function createOlderHistoryLoadReceipt(load: (markCommitted: () => void, preserveTail: boolean, markTailPreserved: () => void) => boolean | Promise<boolean>): OlderHistoryLoadReceipt;
29
30
  export {};
@@ -47,7 +47,7 @@ function PlatformActivityRow({
47
47
  return j(ActivityDisclosure, {
48
48
  icon: j(BotIcon, { className: "size-3.5" }),
49
49
  iconTone: failed ? "failed" : running ? "running" : "muted",
50
- title: startupPhaseTitle(item.phase, item.status, item.outcome),
50
+ title: item.blockedReason === "rotation_in_progress" ? "Waiting for sandbox rotation" : startupPhaseTitle(item.phase, item.status, item.outcome),
51
51
  preview: item.phase === "model_preparation" ? "Includes overlapping sandbox, rig, repository, and runtime setup shown below." : void 0,
52
52
  running,
53
53
  failed,
@@ -166,4 +166,4 @@ function startupDuration(durationMs) {
166
166
  export {
167
167
  PlatformActivityRow as default
168
168
  };
169
- //# sourceMappingURL=platform-activity-row-BJHUHXAT.js.map
169
+ //# sourceMappingURL=platform-activity-row-27LKAWZD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/timeline/platform-activity-row.tsx"],"sourcesContent":["import type { ReasoningItem, SandboxItem, StartupPhaseItem } from \"./types\";\n\ntype RowJsx = typeof import(\"react/jsx-runtime\").jsx;\ntype RowJsxs = typeof import(\"react/jsx-runtime\").jsxs;\n\nexport default function PlatformActivityRow({\n item,\n d: ActivityDisclosure,\n p: PayloadBlock,\n t: displayName,\n b: BotIcon,\n m: Markdown,\n r: truncate,\n j,\n s,\n}: {\n item: ReasoningItem | SandboxItem | StartupPhaseItem;\n d: typeof import(\"./shared\").ActivityDisclosure;\n p: typeof import(\"./shared\").PayloadBlock;\n t: (name: string) => string;\n b: typeof import(\"lucide-react\").BotIcon;\n m: typeof import(\"../components/markdown\").Markdown;\n r: typeof import(\"../lib/format\").truncate;\n j: RowJsx;\n s: RowJsxs;\n}) {\n if (item.kind === \"reasoning\") {\n return j(ActivityDisclosure, {\n icon: brainIcon(j, s),\n iconTone: \"muted\",\n title: item.streaming\n ? \"Thinking\"\n : j(\"span\", {\n className: \"font-normal italic text-og-fg-subtle\",\n children: \"Thought\",\n }),\n running: item.streaming,\n preview: truncate(item.text, 110),\n children: j(\"div\", {\n className: \"text-og-base leading-6 text-og-fg-muted [&_strong]:text-og-fg-muted\",\n children: j(Markdown, { streaming: item.streaming, children: item.text }),\n }),\n });\n }\n if (item.kind === \"sandbox\") {\n return s(ActivityDisclosure, {\n icon: terminalIcon(j, s),\n iconTone:\n item.status === \"failed\" ? \"failed\" : item.status === \"running\" ? \"running\" : \"muted\",\n title: sandboxRowTitle(item, displayName),\n running: item.status === \"running\",\n failed: item.status === \"failed\",\n cancelled: item.status === \"cancelled\",\n preview: item.command ?? undefined,\n children: [\n item.command ? j(PayloadBlock, { label: \"Command\", value: item.command }, \"command\") : null,\n item.output ? j(PayloadBlock, { label: \"Output\", value: item.output }, \"output\") : null,\n ],\n });\n }\n\n const failed = item.status === \"failed\";\n const running = item.status === \"running\";\n const duration = startupDuration(item.durationMs);\n return j(ActivityDisclosure, {\n icon: j(BotIcon, { className: \"size-3.5\" }),\n iconTone: failed ? \"failed\" : running ? \"running\" : \"muted\",\n title:\n item.blockedReason === \"rotation_in_progress\"\n ? \"Waiting for sandbox rotation\"\n : startupPhaseTitle(item.phase, item.status, item.outcome),\n preview:\n item.phase === \"model_preparation\"\n ? \"Includes overlapping sandbox, rig, repository, and runtime setup shown below.\"\n : undefined,\n running,\n failed,\n cancelled: item.status === \"cancelled\",\n chip: duration ? { tone: failed ? \"bad\" : \"muted\", text: duration } : undefined,\n expandable: false,\n });\n}\n\nfunction brainIcon(j: RowJsx, s: RowJsxs) {\n return lucideIcon(j, s, \"brain\", [\n j(\"path\", { d: \"M 12 18V5\" }, \"spine\"),\n j(\"path\", { d: \"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4\" }, \"center\"),\n j(\"path\", { d: \"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5\" }, \"top\"),\n j(\"path\", { d: \"M17.997 5.125a4 4 0 0 1 2.526 5.77\" }, \"upper-right\"),\n j(\"path\", { d: \"M18 18a4 4 0 0 0 2-7.464\" }, \"right\"),\n j(\"path\", { d: \"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517\" }, \"base\"),\n j(\"path\", { d: \"M6 18a4 4 0 0 1-2-7.464\" }, \"left\"),\n j(\"path\", { d: \"M6.003 5.125a4 4 0 0 0-2.526 5.77\" }, \"upper-left\"),\n ]);\n}\n\nfunction terminalIcon(j: RowJsx, s: RowJsxs) {\n return lucideIcon(j, s, \"square-terminal\", [\n j(\"path\", { d: \"m7 11 2-2-2-2\" }, \"prompt\"),\n j(\"path\", { d: \"M11 13h4\" }, \"cursor\"),\n j(\"rect\", { width: 18, height: 18, x: 3, y: 3, rx: 2, ry: 2 }, \"frame\"),\n ]);\n}\n\nfunction lucideIcon(j: RowJsx, s: RowJsxs, name: string, children: ReturnType<RowJsx>[]) {\n return s(\"svg\", {\n \"aria-hidden\": \"true\",\n className: `lucide lucide-${name} size-3.5`,\n fill: \"none\",\n height: 24,\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: 2,\n viewBox: \"0 0 24 24\",\n width: 24,\n children,\n });\n}\n\nfunction sandboxRowTitle(item: SandboxItem, displayName: (name: string) => string): string {\n if (item.name === \"sandbox.provision\") {\n return startupPhaseTitle(\n \"sandbox\",\n item.status === \"cancelled\" ? \"complete\" : item.status,\n item.origin ?? null,\n );\n }\n return displayName(item.name);\n}\n\nfunction startupPhaseTitle(\n phase: StartupPhaseItem[\"phase\"],\n status: StartupPhaseItem[\"status\"],\n outcome: StartupPhaseItem[\"outcome\"],\n): string {\n if (status === \"complete\" && phase === \"sandbox\" && outcome && outcome !== \"skipped\") {\n return `Sandbox ${outcome === \"resumed\" ? \"reattached\" : outcome}`;\n }\n if (status === \"complete\" && phase === \"rig\" && outcome === \"skipped\") {\n return \"Rig already ready\";\n }\n const statusIndex =\n status === \"running\" ? 0 : status === \"failed\" ? 1 : status === \"cancelled\" ? 2 : 3;\n return STARTUP_PHASE_TITLES[phase][statusIndex];\n}\n\nconst STARTUP_PHASE_TITLES: Record<\n StartupPhaseItem[\"phase\"],\n readonly [string, string, string, string]\n> = {\n queue: [\n \"Waiting for a worker\",\n \"Worker startup failed\",\n \"Worker wait interrupted\",\n \"Worker started\",\n ],\n sandbox: [\n \"Starting sandbox\",\n \"Sandbox didn’t start\",\n \"Sandbox startup interrupted\",\n \"Sandbox ready\",\n ],\n rig: [\"Setting up rig\", \"Rig setup failed\", \"Rig setup interrupted\", \"Rig ready\"],\n repository: [\n \"Preparing repository\",\n \"Repository preparation failed\",\n \"Repository preparation interrupted\",\n \"Repository ready\",\n ],\n files: [\n \"Preparing files\",\n \"File preparation failed\",\n \"File preparation interrupted\",\n \"Files ready\",\n ],\n tools: [\n \"Connecting tools\",\n \"Tool connection failed\",\n \"Tool connection interrupted\",\n \"Tools ready\",\n ],\n model_preparation: [\n \"Preparing runtime and model request\",\n \"Runtime/model preparation failed\",\n \"Runtime/model preparation interrupted\",\n \"Model request dispatched\",\n ],\n provider_first_byte: [\n \"Waiting for model\",\n \"Model didn’t respond\",\n \"Model wait interrupted\",\n \"Model started responding\",\n ],\n};\n\nfunction startupDuration(durationMs: number | null): string | null {\n if (durationMs === null || !Number.isFinite(durationMs) || durationMs < 0) return null;\n if (durationMs < 1_000) return `${Math.round(durationMs)}ms`;\n if (durationMs < 60_000) return `${(durationMs / 1_000).toFixed(1)}s`;\n const minutes = Math.floor(durationMs / 60_000);\n const seconds = Math.round((durationMs % 60_000) / 1_000);\n return `${minutes}m ${String(seconds).padStart(2, \"0\")}s`;\n}\n"],"mappings":"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","names":[]}
package/dist/realtime.js CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  PickerBackHeader,
5
5
  PickerNavRow
6
6
  } from "./chunk-R4KRSFGA.js";
7
- import {
8
- useEmbeddedRealtimeSession
9
- } from "./chunk-NEXLLTP4.js";
10
7
  import {
11
8
  usePortalTokenSource,
12
9
  usePortalTokenStyle
13
10
  } from "./chunk-VZTQ73XT.js";
11
+ import {
12
+ useEmbeddedRealtimeSession
13
+ } from "./chunk-NEXLLTP4.js";
14
14
  import "./chunk-VRBL3F5E.js";
15
15
  import {
16
16
  cn
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  BUILT_IN_TURN_SUMMARY_FACET_IDS,
3
+ HumanInputForm,
3
4
  HumanInputSurface,
4
5
  MessageTimeline,
5
6
  QueueSurface,
@@ -12,27 +13,23 @@ import {
12
13
  sessionChromeGoalPillLabel,
13
14
  sessionChromeGoalPillState,
14
15
  userMessageLikelyNeedsDisclosure
15
- } from "./chunk-7W6PVVTK.js";
16
+ } from "./chunk-HO6X4FHG.js";
16
17
  import "./chunk-YBM6CQRU.js";
17
18
  import "./chunk-5M6VTFJ5.js";
18
19
  import "./chunk-ZYRGEKWS.js";
19
20
  import "./chunk-NOSYBS2D.js";
20
21
  import "./chunk-PAJU5KKL.js";
21
- import "./chunk-7BWFU6H4.js";
22
+ import "./chunk-JVYQRRC4.js";
23
+ import "./chunk-Z7WBDB4E.js";
22
24
  import "./chunk-R4KRSFGA.js";
25
+ import "./chunk-VZTQ73XT.js";
23
26
  import {
24
27
  createOlderHistoryLoadReceipt
25
- } from "./chunk-BXJOQFCD.js";
28
+ } from "./chunk-HUMIRRYI.js";
26
29
  import "./chunk-6J56T3MG.js";
27
30
  import "./chunk-PLYRL5ER.js";
28
31
  import "./chunk-KZ73RL76.js";
29
32
  import "./chunk-NEXLLTP4.js";
30
- import {
31
- HumanInputForm
32
- } from "./chunk-YAIPFO5D.js";
33
- import "./chunk-JVYQRRC4.js";
34
- import "./chunk-HQV2I5HV.js";
35
- import "./chunk-VZTQ73XT.js";
36
33
  import "./chunk-VRBL3F5E.js";
37
34
  import "./chunk-22KP6LR5.js";
38
35
  export {
package/dist/session.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  useSession,
29
29
  useSessionEvents,
30
30
  useTurnQueue
31
- } from "./chunk-BXJOQFCD.js";
31
+ } from "./chunk-HUMIRRYI.js";
32
32
  import {
33
33
  FILE_ONLY_MESSAGE_TEXT,
34
34
  composeSendInput,
@@ -173,6 +173,7 @@ export type StartupPhaseItem = {
173
173
  durationMs: number | null;
174
174
  /** Sandbox origin and rig marker outcomes refine the settled label only. */
175
175
  outcome: "created" | "restored" | "resumed" | "skipped" | null;
176
+ blockedReason?: "rotation_in_progress" | undefined;
176
177
  occurredAt: string;
177
178
  };
178
179
  /**
@@ -352,6 +353,8 @@ export type AuthNeededItem = {
352
353
  export type TurnOutcome = "complete" | "failed" | "cancelled";
353
354
  export type TurnEndItem = {
354
355
  kind: "turn-end";
356
+ /** Keep an existing answer visible when a recorded input wait ends without final output. */
357
+ preserveWaitResponse?: true;
355
358
  id: string;
356
359
  turnId: string | null;
357
360
  outcome: TurnOutcome;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "3.8.0-canary.1",
3
+ "version": "4.0.1-canary.0",
4
4
  "description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -78,10 +78,6 @@
78
78
  "types": "./dist/clipboard.d.ts",
79
79
  "import": "./dist/clipboard.js"
80
80
  },
81
- "./chat": {
82
- "types": "./dist/chat.tsx",
83
- "import": "./dist/chat.tsx"
84
- },
85
81
  "./styles.css": {
86
82
  "types": "./styles/index.d.ts",
87
83
  "style": "./styles/index.css",
@@ -124,7 +120,7 @@
124
120
  "@dnd-kit/utilities": "3.2.2",
125
121
  "@fontsource-variable/noto-sans-arabic": "5.2.10",
126
122
  "@fontsource-variable/noto-sans-jp": "5.2.10",
127
- "@opengeni/sdk": "^3.8.0-canary.1",
123
+ "@opengeni/sdk": "^4.0.1-canary.0",
128
124
  "clsx": "^2.1.1",
129
125
  "dequal": "2.0.3",
130
126
  "lucide-react": "^1.8.0",
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  HumanInputAnswer,
3
3
  HumanInputQuestion,
4
+ SkillRecord,
4
5
  SessionHumanInputRequest,
5
6
  SubmitHumanInputResponseRequest,
6
7
  } from "@opengeni/sdk";
@@ -64,6 +65,10 @@ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
64
65
  };
65
66
 
66
67
  export type HumanInputFormProps = {
68
+ /** Scoped immutable revision reader. Saving a Skill fails closed without its full preview. */
69
+ loadSkillReview?:
70
+ | ((reference: NonNullable<HumanInputQuestion["skillReview"]>) => Promise<SkillRecord>)
71
+ | undefined;
67
72
  request: Pick<SessionHumanInputRequest, "id" | "questions" | "allowSkip" | "expiresAt">;
68
73
  onSubmit: (response: SubmitHumanInputResponseRequest) => void | Promise<void>;
69
74
  submitting?: boolean | undefined;
@@ -98,6 +103,7 @@ export function HumanInputForm(props: HumanInputFormProps) {
98
103
  function HumanInputRequestForm({
99
104
  request,
100
105
  onSubmit,
106
+ loadSkillReview,
101
107
  submitting = false,
102
108
  error,
103
109
  title,
@@ -142,6 +148,116 @@ function HumanInputRequestForm({
142
148
  const submissionInFlight = useRef(false);
143
149
  const submissionGeneration = useRef(0);
144
150
  const busy = submitting || submittingInternally;
151
+ const reviewIdentity = JSON.stringify(
152
+ request.questions
153
+ .filter((question) => question.skillReview)
154
+ .map((question) => ({
155
+ id: question.id,
156
+ reference: {
157
+ sourceOperationId: question.skillReview!.sourceOperationId,
158
+ skillId: question.skillReview!.skillId,
159
+ revisionId: question.skillReview!.revisionId,
160
+ expectedRevisionId: question.skillReview!.expectedRevisionId,
161
+ expectedScopeVersion: question.skillReview!.expectedScopeVersion,
162
+ },
163
+ })),
164
+ );
165
+ const [reviewReload, setReviewReload] = useState(0);
166
+ const [reviews, setReviews] = useState<{
167
+ loader: typeof loadSkillReview;
168
+ identity: string;
169
+ records: Record<string, SkillRecord>;
170
+ error: string | null;
171
+ } | null>(null);
172
+ useEffect(() => {
173
+ let current = true;
174
+ const questions = JSON.parse(reviewIdentity) as Array<{
175
+ id: string;
176
+ reference: NonNullable<HumanInputQuestion["skillReview"]>;
177
+ }>;
178
+ if (!questions.length) return;
179
+ setReviews(null);
180
+ void Promise.all(
181
+ questions.map(async (question) => {
182
+ if (!loadSkillReview)
183
+ throw new Error(
184
+ "This client cannot preview Skill files. Open this request in OpenGeni to review it.",
185
+ );
186
+ const reference = question.reference;
187
+ const record = await loadSkillReview(reference);
188
+ if (
189
+ record.id !== reference.skillId ||
190
+ record.revisionId !== reference.revisionId ||
191
+ !record.files.some((file) => file.path === "SKILL.md")
192
+ ) {
193
+ throw new Error("The requested Skill revision could not be verified.");
194
+ }
195
+ return [question.id, record] as const;
196
+ }),
197
+ )
198
+ .then((records) => {
199
+ if (current)
200
+ setReviews({
201
+ loader: loadSkillReview,
202
+ identity: reviewIdentity,
203
+ records: Object.fromEntries(records),
204
+ error: null,
205
+ });
206
+ })
207
+ .catch((cause) => {
208
+ if (current)
209
+ setReviews({
210
+ loader: loadSkillReview,
211
+ identity: reviewIdentity,
212
+ records: {},
213
+ error: cause instanceof Error ? cause.message : "Could not load the Skill files.",
214
+ });
215
+ });
216
+ return () => {
217
+ current = false;
218
+ };
219
+ }, [loadSkillReview, reviewIdentity, reviewReload]);
220
+ const visibleReviews =
221
+ reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
222
+ const preview = (question: HumanInputQuestion) => {
223
+ if (!question.skillReview) return null;
224
+ const record = visibleReviews?.records[question.id];
225
+ return (
226
+ <div className="mb-3 min-w-0 space-y-2" data-skill-review="">
227
+ {record ? (
228
+ <>
229
+ <p className="text-og-sm">
230
+ {record.title ?? "Skill"} · {record.scope} · {record.files.length} files
231
+ </p>
232
+ <p className="text-og-xs text-og-fg-muted">
233
+ Saving activates these exact files. No additional review is required.
234
+ </p>
235
+ {record.files.map((file) => (
236
+ <details key={file.path} open={file.path === "SKILL.md"}>
237
+ <summary className="cursor-pointer break-all text-og-sm">{file.path}</summary>
238
+ <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-og-md bg-og-surface-1 p-2 text-og-xs">
239
+ {file.content}
240
+ </pre>
241
+ </details>
242
+ ))}
243
+ </>
244
+ ) : visibleReviews?.error ? (
245
+ <>
246
+ <p role="alert" className="text-og-sm">
247
+ {visibleReviews.error}
248
+ </p>
249
+ <button type="button" onClick={() => setReviewReload((value) => value + 1)}>
250
+ Retry preview
251
+ </button>
252
+ </>
253
+ ) : (
254
+ <p role="status" className="text-og-sm">
255
+ Loading the exact Skill files…
256
+ </p>
257
+ )}
258
+ </div>
259
+ );
260
+ };
145
261
 
146
262
  useEffect(() => {
147
263
  if (collapsed) {
@@ -232,6 +348,18 @@ function HumanInputRequestForm({
232
348
  }
233
349
  return;
234
350
  }
351
+ const unreviewedSave = request.questions.find(
352
+ (question) =>
353
+ question.skillReview &&
354
+ !visibleReviews?.records[question.id] &&
355
+ result.answers.some(
356
+ (answer) => answer.questionId === question.id && answer.values.includes("save"),
357
+ ),
358
+ );
359
+ if (unreviewedSave) {
360
+ setValidationErrors({ [unreviewedSave.id]: "Load the exact Skill files before saving." });
361
+ return;
362
+ }
235
363
  await submitResponse({ outcome: "answered", answers: result.answers });
236
364
  };
237
365
 
@@ -365,6 +493,7 @@ function HumanInputRequestForm({
365
493
  // Title already carries the question; only render the control + help extras.
366
494
  return (
367
495
  <div key={question.id} data-human-input-question={question.id}>
496
+ {preview(question)}
368
497
  <QuestionControls
369
498
  question={question}
370
499
  questionNumber={null}
@@ -389,6 +518,7 @@ function HumanInputRequestForm({
389
518
  data-human-input-question={question.id}
390
519
  className="flex flex-col gap-1.5"
391
520
  >
521
+ {preview(question)}
392
522
  <QuestionControls
393
523
  question={question}
394
524
  questionNumber={index + 1}
@@ -643,62 +773,64 @@ function QuestionControls({
643
773
  </label>
644
774
  );
645
775
  })}
646
- <div
647
- className={cn(
648
- "flex items-start gap-2.5 rounded-og-md px-2.5 py-2 transition-colors",
649
- draft.otherSelected
650
- ? "bg-og-status-waiting/12 text-og-fg"
651
- : "text-og-fg hover:bg-og-surface-1/80",
652
- )}
653
- >
654
- <input
655
- id={otherChoiceId}
656
- type={question.kind === "single_select" ? "radio" : "checkbox"}
657
- name={question.kind === "single_select" ? fieldId : undefined}
658
- aria-labelledby={otherLabelId}
659
- checked={draft.otherSelected}
660
- autoFocus={autoFocus && firstOption && question.options.length === 0}
661
- onChange={(event) =>
662
- onUpdate((current) => ({
663
- ...current,
664
- otherSelected: event.target.checked,
665
- ...(question.kind === "single_select" && event.target.checked
666
- ? { values: [] }
667
- : {}),
668
- }))
669
- }
670
- className="mt-2 accent-og-accent"
671
- />
672
- <span className="min-w-0 flex-1">
673
- <label
674
- id={otherLabelId}
675
- htmlFor={otherChoiceId}
676
- className="block text-og-sm font-medium"
677
- >
678
- {messages.other}
679
- </label>
680
- <label htmlFor={otherTextId} className="sr-only">
681
- {messages.other} answer for {visibleLabel}
682
- </label>
776
+ {!question.skillReview ? (
777
+ <div
778
+ className={cn(
779
+ "flex items-start gap-2.5 rounded-og-md px-2.5 py-2 transition-colors",
780
+ draft.otherSelected
781
+ ? "bg-og-status-waiting/12 text-og-fg"
782
+ : "text-og-fg hover:bg-og-surface-1/80",
783
+ )}
784
+ >
683
785
  <input
684
- id={otherTextId}
685
- type="text"
686
- value={draft.other}
687
- disabled={busy}
688
- placeholder="Type a value…"
689
- onClick={() => onUpdate(selectOtherDraft)}
690
- onFocus={() => onUpdate(selectOtherDraft)}
691
- onChange={(event) => {
692
- const other = event.target.value;
786
+ id={otherChoiceId}
787
+ type={question.kind === "single_select" ? "radio" : "checkbox"}
788
+ name={question.kind === "single_select" ? fieldId : undefined}
789
+ aria-labelledby={otherLabelId}
790
+ checked={draft.otherSelected}
791
+ autoFocus={autoFocus && firstOption && question.options.length === 0}
792
+ onChange={(event) =>
693
793
  onUpdate((current) => ({
694
- ...selectOtherDraft(current),
695
- other,
696
- }));
697
- }}
698
- className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
794
+ ...current,
795
+ otherSelected: event.target.checked,
796
+ ...(question.kind === "single_select" && event.target.checked
797
+ ? { values: [] }
798
+ : {}),
799
+ }))
800
+ }
801
+ className="mt-2 accent-og-accent"
699
802
  />
700
- </span>
701
- </div>
803
+ <span className="min-w-0 flex-1">
804
+ <label
805
+ id={otherLabelId}
806
+ htmlFor={otherChoiceId}
807
+ className="block text-og-sm font-medium"
808
+ >
809
+ {messages.other}
810
+ </label>
811
+ <label htmlFor={otherTextId} className="sr-only">
812
+ {messages.other} answer for {visibleLabel}
813
+ </label>
814
+ <input
815
+ id={otherTextId}
816
+ type="text"
817
+ value={draft.other}
818
+ disabled={busy}
819
+ placeholder="Type a value…"
820
+ onClick={() => onUpdate(selectOtherDraft)}
821
+ onFocus={() => onUpdate(selectOtherDraft)}
822
+ onChange={(event) => {
823
+ const other = event.target.value;
824
+ onUpdate((current) => ({
825
+ ...selectOtherDraft(current),
826
+ other,
827
+ }));
828
+ }}
829
+ className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
830
+ />
831
+ </span>
832
+ </div>
833
+ ) : null}
702
834
  </div>
703
835
  )}
704
836
  {error ? (
@@ -9,6 +9,7 @@ import {
9
9
  } from "./human-input-form";
10
10
 
11
11
  export type HumanInputSurfaceProps = {
12
+ loadSkillReview?: HumanInputFormProps["loadSkillReview"];
12
13
  requests: SessionHumanInputRequest[];
13
14
  onSubmit: (requestId: string, response: SubmitHumanInputResponseRequest) => void | Promise<void>;
14
15
  respondingRequestId?: string | null | undefined;
@@ -28,6 +29,7 @@ export type HumanInputSurfaceProps = {
28
29
  export function HumanInputSurface({
29
30
  requests,
30
31
  onSubmit,
32
+ loadSkillReview,
31
33
  respondingRequestId = null,
32
34
  error,
33
35
  messages,
@@ -68,6 +70,7 @@ export function HumanInputSurface({
68
70
 
69
71
  const formProps: HumanInputFormProps = {
70
72
  request: active,
73
+ loadSkillReview,
71
74
  submitting: respondingRequestId !== null,
72
75
  error: error ?? null,
73
76
  progressLabel,
@@ -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}
@@ -3702,6 +3711,32 @@ function formatVideoDuration(seconds: number): string {
3702
3711
 
3703
3712
  function NoticeRow({ item }: { item: NoticeItem }) {
3704
3713
  const enter = useEntranceAnimation();
3714
+ if (item.recordedOutcome) {
3715
+ return (
3716
+ <details
3717
+ className="group text-og-sm text-og-fg-muted"
3718
+ role="note"
3719
+ data-og-recorded-outcome="wait"
3720
+ >
3721
+ <summary className="flex cursor-pointer list-none items-center gap-2 py-1 [&::-webkit-details-marker]:hidden">
3722
+ <ChevronRightIcon
3723
+ aria-hidden
3724
+ className="size-3.5 transition-transform group-open:rotate-90"
3725
+ />
3726
+ <span>
3727
+ Wait recorded ·{" "}
3728
+ <time dateTime={item.occurredAt}>
3729
+ {new Date(item.occurredAt).toLocaleString(undefined, {
3730
+ dateStyle: "medium",
3731
+ timeStyle: "short",
3732
+ })}
3733
+ </time>
3734
+ </span>
3735
+ </summary>
3736
+ <p className="mt-1 whitespace-pre-wrap break-words pl-5 text-og-fg-muted">{item.text}</p>
3737
+ </details>
3738
+ );
3739
+ }
3705
3740
  const tone =
3706
3741
  item.tone === "failed"
3707
3742
  ? "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 ? (