@opengeni/react 0.29.0 → 0.32.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 (207) hide show
  1. package/dist/approvals.d.ts +14 -0
  2. package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
  3. package/dist/chunk-4QOQ2DSR.js.map +1 -0
  4. package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
  5. package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
  6. package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
  7. package/dist/chunk-A2WITZTI.js.map +1 -0
  8. package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
  9. package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
  10. package/dist/chunk-KZRUYCL4.js.map +1 -0
  11. package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
  12. package/dist/chunk-TCCE5ZU5.js.map +1 -0
  13. package/dist/chunk-U255M5EZ.js +178 -0
  14. package/dist/chunk-U255M5EZ.js.map +1 -0
  15. package/dist/{chunk-IPVOVOW6.js → chunk-WY2QN2CX.js} +2539 -2174
  16. package/dist/chunk-WY2QN2CX.js.map +1 -0
  17. package/dist/chunk-ZNNE2VES.js +6413 -0
  18. package/dist/chunk-ZNNE2VES.js.map +1 -0
  19. package/dist/client.d.ts +33 -0
  20. package/dist/commands/index.d.ts +3 -0
  21. package/dist/commands/registry.d.ts +41 -0
  22. package/dist/commands/types.d.ts +85 -0
  23. package/dist/components/approval-surface.d.ts +28 -0
  24. package/dist/components/chat-composer.d.ts +51 -0
  25. package/dist/components/code-editor.d.ts +43 -0
  26. package/dist/components/command-palette.d.ts +25 -0
  27. package/dist/components/composer-transcription-control.d.ts +36 -0
  28. package/dist/components/composer.d.ts +277 -0
  29. package/dist/components/copy-button.d.ts +26 -0
  30. package/dist/components/desktop-viewer.d.ts +85 -0
  31. package/dist/components/diff-view.d.ts +38 -0
  32. package/dist/components/enrollment-consent.d.ts +33 -0
  33. package/dist/components/enrollment-device-flow.d.ts +27 -0
  34. package/dist/components/file-browser.d.ts +47 -0
  35. package/dist/components/fleet-tile.d.ts +18 -0
  36. package/dist/components/human-input-form.d.ts +47 -0
  37. package/dist/components/machine-card.d.ts +21 -0
  38. package/dist/components/machine-dock-bar.d.ts +31 -0
  39. package/dist/components/machine-health-pill.d.ts +9 -0
  40. package/dist/components/machine-metrics.d.ts +15 -0
  41. package/dist/components/machine-status-pill.d.ts +54 -0
  42. package/dist/components/machines/health.d.ts +25 -0
  43. package/dist/components/machines/machine-detail.d.ts +14 -0
  44. package/dist/components/machines/metric-history-chart.d.ts +24 -0
  45. package/dist/components/machines/metric-sparkline.d.ts +9 -0
  46. package/dist/components/machines/series.d.ts +24 -0
  47. package/dist/components/machines-dashboard.d.ts +30 -0
  48. package/dist/components/markdown.d.ts +31 -0
  49. package/dist/components/message-timeline.d.ts +98 -0
  50. package/dist/components/model-picker.d.ts +19 -0
  51. package/dist/components/pierre-diff.d.ts +34 -0
  52. package/dist/components/pierre-file.d.ts +27 -0
  53. package/dist/components/queue-draft-policy.d.ts +7 -0
  54. package/dist/components/queue-surface-implementation.d.ts +20 -0
  55. package/dist/components/queue-surface-state.d.ts +12 -0
  56. package/dist/components/queue-surface.d.ts +12 -0
  57. package/dist/components/sandbox-files.d.ts +49 -0
  58. package/dist/components/sandbox-terminal.d.ts +123 -0
  59. package/dist/components/sandbox-workspace.d.ts +128 -0
  60. package/dist/components/session-chrome.d.ts +70 -0
  61. package/dist/components/session-status.d.ts +25 -0
  62. package/dist/components/soften-streaming-markdown.d.ts +13 -0
  63. package/dist/components/stream-reveal.d.ts +37 -0
  64. package/dist/components/tip-follow.d.ts +139 -0
  65. package/dist/components/tooltip.d.ts +16 -0
  66. package/dist/components/workbench-changes.d.ts +34 -0
  67. package/dist/components/workspace-dock.d.ts +35 -0
  68. package/dist/composer.d.ts +11 -7
  69. package/dist/composer.js +9 -6
  70. package/dist/hooks/internal.d.ts +46 -0
  71. package/dist/hooks/use-available-models.d.ts +37 -0
  72. package/dist/hooks/use-billing-usage.d.ts +26 -0
  73. package/dist/hooks/use-codex-accounts.d.ts +69 -0
  74. package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
  75. package/dist/hooks/use-desktop-stream.d.ts +43 -0
  76. package/dist/hooks/use-environments.d.ts +35 -0
  77. package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
  78. package/dist/hooks/use-goal.d.ts +46 -0
  79. package/dist/hooks/use-human-input.d.ts +26 -0
  80. package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
  81. package/dist/hooks/use-machine-chip.d.ts +54 -0
  82. package/dist/hooks/use-machines.d.ts +81 -0
  83. package/dist/hooks/use-packs.d.ts +27 -0
  84. package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
  85. package/dist/hooks/use-rigs.d.ts +87 -0
  86. package/dist/hooks/use-sandbox-files.d.ts +108 -0
  87. package/dist/hooks/use-sandbox-git.d.ts +58 -0
  88. package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
  89. package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
  90. package/dist/hooks/use-session-capabilities.d.ts +83 -0
  91. package/dist/hooks/use-session-control.d.ts +22 -0
  92. package/dist/hooks/use-session-events.d.ts +92 -0
  93. package/dist/hooks/use-session-lineage.d.ts +17 -0
  94. package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
  95. package/dist/hooks/use-session.d.ts +23 -0
  96. package/dist/hooks/use-slash-commands.d.ts +66 -0
  97. package/dist/hooks/use-terminal-stream.d.ts +46 -0
  98. package/dist/hooks/use-transcription.d.ts +73 -0
  99. package/dist/hooks/use-turn-queue.d.ts +44 -0
  100. package/dist/hooks/use-voice-input.d.ts +25 -0
  101. package/dist/hooks/use-windowed-sections.d.ts +42 -0
  102. package/dist/hooks/use-workspace-capture.d.ts +49 -0
  103. package/dist/hooks/use-workspace-edit.d.ts +73 -0
  104. package/dist/hooks/use-workspace-sessions.d.ts +30 -0
  105. package/dist/hooks/use-workspaces.d.ts +22 -0
  106. package/dist/human-input.d.ts +18 -0
  107. package/dist/index.d.ts +144 -2119
  108. package/dist/index.js +421 -324
  109. package/dist/index.js.map +1 -1
  110. package/dist/lib/clipboard.d.ts +7 -0
  111. package/dist/lib/cn.d.ts +3 -0
  112. package/dist/lib/format.d.ts +48 -0
  113. package/dist/lib/git-patch.d.ts +8 -0
  114. package/dist/lib/motion-inspect.d.ts +5 -0
  115. package/dist/lib/motion.d.ts +14 -0
  116. package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
  117. package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
  118. package/dist/lib/relay-wire.d.ts +30 -0
  119. package/dist/lib/use-portal-token-style.d.ts +9 -0
  120. package/dist/lib/use-theme-type.d.ts +5 -0
  121. package/dist/lib/use-unicode-fonts.d.ts +12 -0
  122. package/dist/lib/xterm-renderer.d.ts +19 -0
  123. package/dist/lib/xterm-theme.d.ts +75 -0
  124. package/dist/machines.d.ts +30 -413
  125. package/dist/machines.js +3 -3
  126. package/dist/model-policy.d.ts +32 -0
  127. package/dist/model-policy.js +33 -0
  128. package/dist/model-policy.js.map +1 -0
  129. package/dist/provider.d.ts +16 -0
  130. package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
  131. package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
  132. package/dist/session-context.d.ts +82 -0
  133. package/dist/session-ui.d.ts +10 -7
  134. package/dist/session-ui.js +9 -5
  135. package/dist/session.d.ts +28 -6
  136. package/dist/session.js +11 -11
  137. package/dist/timeline/activity-rail.d.ts +19 -0
  138. package/dist/timeline/disclosure-context.d.ts +14 -0
  139. package/dist/timeline/entrance.d.ts +14 -0
  140. package/dist/timeline/fleet-decision-projection.d.ts +9 -0
  141. package/dist/timeline/fleet-decision-row.d.ts +11 -0
  142. package/dist/timeline/fold-memory.d.ts +15 -0
  143. package/dist/timeline/index.d.ts +18 -0
  144. package/dist/timeline/parsers.d.ts +96 -0
  145. package/dist/timeline/projection.d.ts +38 -0
  146. package/dist/timeline/registry.d.ts +40 -0
  147. package/dist/timeline/screenshot-lightbox.d.ts +26 -0
  148. package/dist/timeline/seen-activity-ids.d.ts +2 -0
  149. package/dist/timeline/shared.d.ts +125 -0
  150. package/dist/timeline/tool-diff.d.ts +8 -0
  151. package/dist/timeline/tool-display-name.d.ts +10 -0
  152. package/dist/timeline/tool-renderers.d.ts +5 -0
  153. package/dist/timeline/turn-summary.d.ts +116 -0
  154. package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
  155. package/dist/types/machines.d.ts +11 -0
  156. package/package.json +8 -4
  157. package/src/client.ts +1 -0
  158. package/src/components/composer-transcription-control.tsx +278 -130
  159. package/src/components/composer.tsx +20 -15
  160. package/src/components/copy-button.tsx +127 -0
  161. package/src/components/markdown.tsx +231 -32
  162. package/src/components/message-timeline.tsx +1304 -378
  163. package/src/components/model-picker.tsx +83 -43
  164. package/src/components/queue-surface-implementation.tsx +1 -1
  165. package/src/components/session-chrome.tsx +942 -0
  166. package/src/components/soften-streaming-markdown.ts +205 -0
  167. package/src/components/stream-reveal.ts +195 -0
  168. package/src/components/tip-follow.ts +459 -0
  169. package/src/components/tooltip.tsx +103 -0
  170. package/src/composer.ts +6 -0
  171. package/src/hooks/internal.ts +30 -4
  172. package/src/hooks/use-available-models.ts +51 -4
  173. package/src/hooks/use-composer.ts +23 -3
  174. package/src/hooks/use-last-started-turn-policy.ts +95 -0
  175. package/src/hooks/use-session-events.ts +359 -5
  176. package/src/hooks/use-voice-input.ts +232 -0
  177. package/src/index.ts +47 -1
  178. package/src/lib/clipboard.ts +58 -0
  179. package/src/lib/format.ts +18 -0
  180. package/src/lib/motion-inspect.ts +5 -0
  181. package/src/lib/motion.ts +80 -0
  182. package/src/model-policy.ts +210 -0
  183. package/src/session-ui.ts +7 -0
  184. package/src/timeline/activity-rail.tsx +46 -7
  185. package/src/timeline/entrance.tsx +10 -0
  186. package/src/timeline/fold-memory.ts +52 -0
  187. package/src/timeline/index.ts +9 -1
  188. package/src/timeline/projection.ts +226 -41
  189. package/src/timeline/seen-activity-ids.ts +19 -0
  190. package/src/timeline/shared.tsx +1 -1
  191. package/src/timeline/tool-renderers.tsx +75 -6
  192. package/src/timeline/turn-summary.tsx +387 -97
  193. package/src/timeline/types.ts +21 -0
  194. package/styles/index.css +99 -7
  195. package/styles/tokens.css +44 -3
  196. package/dist/chunk-AVLMIWYO.js.map +0 -1
  197. package/dist/chunk-CSJBF3QJ.js.map +0 -1
  198. package/dist/chunk-IPVOVOW6.js.map +0 -1
  199. package/dist/chunk-M7X4JZOD.js.map +0 -1
  200. package/dist/chunk-OKXCECLG.js.map +0 -1
  201. package/dist/chunk-ZMNPFZWL.js +0 -3918
  202. package/dist/chunk-ZMNPFZWL.js.map +0 -1
  203. package/dist/composer-BMzimGTO.d.ts +0 -528
  204. package/dist/session-DaONt-dY.d.ts +0 -264
  205. package/dist/session-context-CAjlaeZb.d.ts +0 -84
  206. package/dist/session-ui-B5_roA95.d.ts +0 -259
  207. /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
@@ -0,0 +1,210 @@
1
+ import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from "@opengeni/sdk";
2
+
3
+ export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "byok";
4
+
5
+ export type PickerModelRow = {
6
+ id: string;
7
+ label: string;
8
+ billingClass: PickerBillingClass;
9
+ billingClassLabel: string;
10
+ selectable: boolean;
11
+ unavailableReason: string | null;
12
+ provider: string;
13
+ providerLabel: string;
14
+ catalog: WorkspaceModelCatalogModel;
15
+ };
16
+
17
+ export type LatencyModeId = "standard" | "priority" | "fast";
18
+
19
+ const BILLING_CLASS_ORDER: PickerBillingClass[] = [
20
+ "opengeni_credits",
21
+ "codex_subscription",
22
+ "byok",
23
+ ];
24
+
25
+ const BILLING_CLASS_LABELS: Record<PickerBillingClass, string> = {
26
+ opengeni_credits: "OpenGeni",
27
+ codex_subscription: "Codex",
28
+ byok: "Bring your own key",
29
+ };
30
+
31
+ const AVAILABILITY_REASON_LABELS: Record<string, string> = {
32
+ missing_credential: "Credentials required",
33
+ needs_reauth: "Reconnect required",
34
+ credential_not_ready: "Credential not ready",
35
+ not_entitled: "Not entitled",
36
+ provider_unhealthy: "Provider unavailable",
37
+ policy_blocked: "Blocked by workspace policy",
38
+ unsupported: "Unsupported",
39
+ };
40
+
41
+ export function billingClassForModel(model: ClientModel): PickerBillingClass {
42
+ if (model.credentialSource?.kind === "connected_subscription") {
43
+ return "codex_subscription";
44
+ }
45
+ if (model.credentialSource?.kind === "workspace_connection") {
46
+ return "byok";
47
+ }
48
+ if (model.billing?.upstreamPayer === "connected_subscription") {
49
+ return "codex_subscription";
50
+ }
51
+ if (model.billing?.upstreamPayer === "workspace") {
52
+ return "byok";
53
+ }
54
+ return "opengeni_credits";
55
+ }
56
+
57
+ export function billingClassLabel(billingClass: PickerBillingClass): string {
58
+ return BILLING_CLASS_LABELS[billingClass];
59
+ }
60
+
61
+ export function availabilityReasonLabel(
62
+ reason: WorkspaceModelCatalogModel["availability"]["reason"],
63
+ ): string | null {
64
+ if (!reason) {
65
+ return null;
66
+ }
67
+ return AVAILABILITY_REASON_LABELS[reason] ?? "Unavailable";
68
+ }
69
+
70
+ export function effortOptionsForModel(model: ClientModel): ReasoningEffort[] {
71
+ const efforts = model.capabilities?.reasoning.efforts;
72
+ if (!efforts || efforts.length === 0) {
73
+ return ["low"];
74
+ }
75
+ const order: ReasoningEffort[] = ["none", "minimal", "low", "medium", "high", "xhigh"];
76
+ return order.filter((effort) => efforts.includes(effort));
77
+ }
78
+
79
+ export function defaultEffortForModel(model: ClientModel): ReasoningEffort {
80
+ const options = effortOptionsForModel(model);
81
+ const configured = model.capabilities?.reasoning.defaultEffort;
82
+ if (configured && options.includes(configured)) {
83
+ return configured;
84
+ }
85
+ return options[0] ?? "low";
86
+ }
87
+
88
+ export function coerceReasoningEffortForModel(
89
+ model: ClientModel,
90
+ effort: ReasoningEffort,
91
+ ): ReasoningEffort {
92
+ const options = effortOptionsForModel(model);
93
+ if (options.includes(effort)) {
94
+ return effort;
95
+ }
96
+ return defaultEffortForModel(model);
97
+ }
98
+
99
+ export function runnableLatencyModesForModel(model: ClientModel): LatencyModeId[] {
100
+ const modes = model.capabilities?.latencyModes ?? [];
101
+ return modes.filter((mode) => mode.runnable).map((mode) => mode.id);
102
+ }
103
+
104
+ export function labelLatencyMode(mode: LatencyModeId): string {
105
+ if (mode === "fast") {
106
+ return "Fast";
107
+ }
108
+ if (mode === "priority") {
109
+ return "Priority";
110
+ }
111
+ return "Standard";
112
+ }
113
+
114
+ export function payerSummaryForModel(model: ClientModel): string {
115
+ const billing = model.billing;
116
+ if (!billing) {
117
+ return "Route unknown";
118
+ }
119
+ if (billing.metering === "opengeni_credits") {
120
+ return "OpenGeni credits · automatic managed route";
121
+ }
122
+ if (billing.upstreamPayer === "connected_subscription") {
123
+ return "Codex subscription · external billing";
124
+ }
125
+ if (billing.upstreamPayer === "workspace") {
126
+ return "Workspace credentials · external billing";
127
+ }
128
+ return "Deployment route · external billing";
129
+ }
130
+
131
+ export function advancedSourceSummary(model: ClientModel): string | null {
132
+ const source = model.credentialSource;
133
+ if (!source) {
134
+ return null;
135
+ }
136
+ if (source.kind === "connected_subscription") {
137
+ return "Connected Codex subscription";
138
+ }
139
+ if (source.kind === "workspace_connection") {
140
+ return "Workspace API key connection";
141
+ }
142
+ if (source.kind === "deployment") {
143
+ return source.mechanism === "azure_ad_bearer"
144
+ ? "Deployment Azure identity"
145
+ : "Deployment API key";
146
+ }
147
+ return null;
148
+ }
149
+
150
+ export function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerModelRow[] {
151
+ return models.map((catalog) => {
152
+ const billingClass = billingClassForModel(catalog);
153
+ return {
154
+ id: catalog.id,
155
+ label: catalog.label,
156
+ billingClass,
157
+ billingClassLabel: billingClassLabel(billingClass),
158
+ selectable: catalog.availability.selectable,
159
+ unavailableReason: catalog.availability.selectable
160
+ ? null
161
+ : availabilityReasonLabel(catalog.availability.reason),
162
+ provider: catalog.provider,
163
+ providerLabel: catalog.providerLabel,
164
+ catalog,
165
+ };
166
+ });
167
+ }
168
+
169
+ export function sortPickerRows(rows: PickerModelRow[]): PickerModelRow[] {
170
+ return [...rows].sort((left, right) => {
171
+ const classDelta =
172
+ BILLING_CLASS_ORDER.indexOf(left.billingClass) -
173
+ BILLING_CLASS_ORDER.indexOf(right.billingClass);
174
+ if (classDelta !== 0) {
175
+ return classDelta;
176
+ }
177
+ if (left.selectable !== right.selectable) {
178
+ return left.selectable ? -1 : 1;
179
+ }
180
+ return left.label.localeCompare(right.label);
181
+ });
182
+ }
183
+
184
+ export function findPickerRow(rows: PickerModelRow[], modelId: string): PickerModelRow | null {
185
+ return rows.find((row) => row.id === modelId) ?? null;
186
+ }
187
+
188
+ export function groupPickerRowsByBillingClass(
189
+ rows: PickerModelRow[],
190
+ ): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }> {
191
+ const sorted = sortPickerRows(rows);
192
+ const groups: Array<{
193
+ billingClass: PickerBillingClass;
194
+ label: string;
195
+ rows: PickerModelRow[];
196
+ }> = [];
197
+ for (const row of sorted) {
198
+ const existing = groups.find((group) => group.billingClass === row.billingClass);
199
+ if (existing) {
200
+ existing.rows.push(row);
201
+ continue;
202
+ }
203
+ groups.push({
204
+ billingClass: row.billingClass,
205
+ label: row.billingClassLabel,
206
+ rows: [row],
207
+ });
208
+ }
209
+ return groups;
210
+ }
package/src/session-ui.ts CHANGED
@@ -20,3 +20,10 @@ export type {
20
20
  } from "./timeline/turn-summary";
21
21
  export { QueueSurface } from "./components/queue-surface";
22
22
  export type { QueueSurfaceProps } from "./components/queue-surface";
23
+ export { SessionChrome, sessionChromeGoalPillState } from "./components/session-chrome";
24
+ export type {
25
+ SessionChromeAgentsSignal,
26
+ SessionChromeProps,
27
+ SessionChromeSignalId,
28
+ SessionChromeSignalTone,
29
+ } from "./components/session-chrome";
@@ -5,13 +5,15 @@ import {
5
5
  BrainIcon,
6
6
  SquareTerminalIcon,
7
7
  } from "lucide-react";
8
- import { lazy, Suspense } from "react";
8
+ import { lazy, Suspense, useLayoutEffect, useRef } from "react";
9
9
  import { jsx as rowJsx, jsxs as rowJsxs } from "react/jsx-runtime";
10
+ import { Markdown } from "../components/markdown";
10
11
  import { cn } from "../lib/cn";
11
12
  import { truncate } from "../lib/format";
12
13
  import { defaultToolRegistry } from "./tool-renderers";
13
- import { useEntranceAnimation } from "./entrance";
14
+ import { useEntranceAnimation, useEntranceAnimationLive } from "./entrance";
14
15
  import type { ToolRegistry } from "./registry";
16
+ import { useSeenActivityIds } from "./seen-activity-ids";
15
17
  import { BodyNote, PayloadBlock, ActivityDisclosure } from "./shared";
16
18
  import { toolDisplayName } from "./tool-display-name";
17
19
  import type { ActivityItem, MemoryItem, ReasoningItem, SandboxItem, WorkerItem } from "./types";
@@ -66,7 +68,34 @@ export function ActivityRail({
66
68
  bare,
67
69
  className,
68
70
  }: ActivityRailProps) {
69
- const enter = useEntranceAnimation();
71
+ const enterMounted = useEntranceAnimation();
72
+ // Live gate: rails born during bulk capture enter=false forever; with a
73
+ // seen-id map we still want later live appends to fade (ids gate remounts).
74
+ const enterLive = useEntranceAnimationLive();
75
+ const seenIds = useSeenActivityIds();
76
+ const enter = seenIds ? enterLive : enterMounted;
77
+ const previousIdsRef = useRef<Set<string> | null>(null);
78
+ const enteringIds = new Set<string>();
79
+ if (enter) {
80
+ for (const item of items) {
81
+ if (seenIds) {
82
+ if (!seenIds.has(item.id)) {
83
+ enteringIds.add(item.id);
84
+ }
85
+ } else if (previousIdsRef.current !== null && !previousIdsRef.current.has(item.id)) {
86
+ // Standalone ActivityRail (tests / demo): append-only, no remount map.
87
+ enteringIds.add(item.id);
88
+ }
89
+ }
90
+ }
91
+ useLayoutEffect(() => {
92
+ previousIdsRef.current = new Set(items.map((item) => item.id));
93
+ if (seenIds) {
94
+ for (const item of items) {
95
+ seenIds.add(item.id);
96
+ }
97
+ }
98
+ });
70
99
  return (
71
100
  <div
72
101
  className={cn(
@@ -75,7 +104,9 @@ export function ActivityRail({
75
104
  // so a long rail reads as a few clusters, not a metronome of rows.
76
105
  "flex flex-col gap-0.5",
77
106
  !bare && "border-l-2 border-og-border pl-3 sm:pl-4",
78
- !bare && enter && "animate-og-enter",
107
+ // Whole-rail enter: standalone rails only (no seen-id map). Inside
108
+ // MessageTimeline, unknown ids take per-row enter — remounts stay quiet.
109
+ !bare && enter && !seenIds && previousIdsRef.current === null && "animate-og-enter",
79
110
  className,
80
111
  )}
81
112
  >
@@ -83,7 +114,11 @@ export function ActivityRail({
83
114
  const newFamily = index > 0 && familyOf(item) !== familyOf(items[index - 1]!);
84
115
  const row = renderActivity(item, toolRegistry, onOpenSession, onMemoryClick);
85
116
  return (
86
- <div key={item.id} data-og-timeline-row-anchor="" className={cn(newFamily && "mt-3")}>
117
+ <div
118
+ key={item.id}
119
+ data-og-timeline-row-anchor=""
120
+ className={cn(newFamily && "mt-3", enteringIds.has(item.id) && "animate-og-row-enter")}
121
+ >
87
122
  {row}
88
123
  </div>
89
124
  );
@@ -135,7 +170,9 @@ function renderActivity(
135
170
 
136
171
  function ReasoningRow({ item }: { item: ReasoningItem }) {
137
172
  // Reasoning recedes: a dimmer, lighter-weight title so action rows lead and
138
- // thought rows sit a half-step back in the hierarchy.
173
+ // thought rows sit a half-step back in the hierarchy. Body uses the same
174
+ // GFM markdown path as agent messages — Codex reasoning often carries
175
+ // headings/lists/bold that were previously shown as raw plaintext.
139
176
  return (
140
177
  <ActivityDisclosure
141
178
  icon={<BrainIcon className="size-3.5" />}
@@ -150,7 +187,9 @@ function ReasoningRow({ item }: { item: ReasoningItem }) {
150
187
  running={item.streaming}
151
188
  preview={truncate(item.text, 110)}
152
189
  >
153
- <p className="whitespace-pre-wrap text-og-base leading-6 text-og-fg-muted">{item.text}</p>
190
+ <div className="text-og-base leading-6 text-og-fg-muted [&_strong]:text-og-fg-muted">
191
+ <Markdown streaming={item.streaming}>{item.text}</Markdown>
192
+ </div>
154
193
  </ActivityDisclosure>
155
194
  );
156
195
  }
@@ -18,6 +18,16 @@ const EntranceAnimationContext = createContext(true);
18
18
 
19
19
  export const EntranceAnimationProvider = EntranceAnimationContext.Provider;
20
20
 
21
+ /**
22
+ * Live entrance gate from the nearest provider. Prefer
23
+ * {@link useEntranceAnimation} for elements that must freeze the decision at
24
+ * mount; use this when a stable parent (e.g. ActivityRail) needs to animate
25
+ * later appends after a bulk window clears.
26
+ */
27
+ export function useEntranceAnimationLive(): boolean {
28
+ return useContext(EntranceAnimationContext);
29
+ }
30
+
21
31
  /**
22
32
  * Whether this element should wear the entrance animation. Captured at mount
23
33
  * from the nearest provider (true outside any provider) and stable for the
@@ -0,0 +1,52 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /* ----------------------------------------------------------------------------
4
+ Fold memory
5
+
6
+ Remembers, per durable timeline group id, that a fold reached a RESTING
7
+ state: "closed" when the settle choreography finished its collapse or the
8
+ reader closed the chip by hand, "open" when the reader explicitly expanded
9
+ it. The map lives OUTSIDE the component tree because the timeline
10
+ deliberately remounts chips with fresh keys (the activity→turn wrap, the
11
+ nested-chip key flip around settle chrome) — and a remount must never
12
+ forget that the reader already watched this cluster fold. Re-opening an
13
+ already-settled fold reads as the timeline undoing its own choreography.
14
+
15
+ With no provider the hook returns null and every fold keeps its
16
+ author-chosen default — standalone TurnSummary usage is unchanged.
17
+ -------------------------------------------------------------------------- */
18
+
19
+ export type FoldRestingState = "open" | "closed";
20
+
21
+ const FoldMemoryContext = createContext<Map<string, FoldRestingState> | null>(null);
22
+
23
+ /** Provide a per-timeline resting-state map (MessageTimeline owns one). */
24
+ export const FoldMemoryProvider = FoldMemoryContext.Provider;
25
+
26
+ /** The ancestor fold-memory map, or null when none is mounted (inert). */
27
+ export function useFoldMemory(): Map<string, FoldRestingState> | null {
28
+ return useContext(FoldMemoryContext);
29
+ }
30
+
31
+ /**
32
+ * Single-cluster activity→turn wrap: copy the activity resting state onto the
33
+ * new turn-* key. Those ids differ across the wrap; without this the turn
34
+ * remounts settleFold and re-opens a chip the reader already watched collapse.
35
+ *
36
+ * Multi-cluster wraps deliberately do NOT inherit — the outer turn chip is new
37
+ * and still takes one settle beat; nested clusters keep their own activity-*
38
+ * memory so they stay closed under that beat.
39
+ */
40
+ export function inheritFoldRestingState(
41
+ memory: Map<string, FoldRestingState>,
42
+ targetKey: string,
43
+ sourceKeys: readonly string[],
44
+ ): void {
45
+ if (memory.has(targetKey) || sourceKeys.length !== 1) {
46
+ return;
47
+ }
48
+ const state = memory.get(sourceKeys[0]!);
49
+ if (state !== undefined) {
50
+ memory.set(targetKey, state);
51
+ }
52
+ }
@@ -30,6 +30,7 @@ export type {
30
30
  ActivityItem,
31
31
  AgentMessageItem,
32
32
  AuthNeededItem,
33
+ ContextCompactionItem,
33
34
  FleetDecisionItem,
34
35
  FleetDecisionScoreItem,
35
36
  GoalItem,
@@ -85,8 +86,15 @@ export { LightboxProvider, useLightbox, useLightboxOptional } from "./screenshot
85
86
  // disclosure defaults (opt-in initial-open seed; for screenshot/test instrumentation)
86
87
  export { DisclosureDefaultsProvider, useForcedDefaultOpen } from "./disclosure-context";
87
88
 
89
+ // fold memory (cross-remount resting state so wraps never reopen settled folds)
90
+ export { FoldMemoryProvider, inheritFoldRestingState, useFoldMemory } from "./fold-memory";
91
+ export type { FoldRestingState } from "./fold-memory";
92
+
93
+ // seen activity ids (live tool enter across rail remounts)
94
+ export { SeenActivityIdsProvider, useSeenActivityIds } from "./seen-activity-ids";
95
+
88
96
  // turn-collapse summary chip
89
- export { BUILT_IN_TURN_SUMMARY_FACET_IDS, TurnSummary } from "./turn-summary";
97
+ export { BUILT_IN_TURN_SUMMARY_FACET_IDS, TurnSummary, useTurnSettleOpen } from "./turn-summary";
90
98
  export type {
91
99
  BuiltInTurnSummaryFacetId,
92
100
  TurnOutcome,