@opengeni/react 1.4.4 → 1.4.5-canary.37485542607001

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 (249) hide show
  1. package/README.md +17 -6
  2. package/dist/artifacts.d.ts +1 -0
  3. package/dist/artifacts.js +22 -15
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{browser-viewer-5EEAS3IU.js → browser-viewer-CV23VIHB.js} +8 -6
  6. package/dist/{chunk-XS67YG5R.js → chunk-4O7OE7ON.js} +45 -4
  7. package/dist/chunk-4O7OE7ON.js.map +1 -0
  8. package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
  9. package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
  10. package/dist/{chunk-QMMCJGFI.js → chunk-6BAMPGBV.js} +6 -3
  11. package/dist/chunk-6BAMPGBV.js.map +1 -0
  12. package/dist/{chunk-I6OY3MWC.js → chunk-6FOTSVWU.js} +2 -2
  13. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  14. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  15. package/dist/chunk-BOFEQC6K.js +162 -0
  16. package/dist/chunk-BOFEQC6K.js.map +1 -0
  17. package/dist/{chunk-2OQA3CPB.js → chunk-CESBPQP5.js} +19 -5
  18. package/dist/chunk-CESBPQP5.js.map +1 -0
  19. package/dist/{chunk-YVFRBXST.js → chunk-GMNGUPMX.js} +2 -2
  20. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  21. package/dist/chunk-I5EGRXXG.js.map +1 -0
  22. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  23. package/dist/chunk-IZV5FTJC.js +55 -0
  24. package/dist/chunk-IZV5FTJC.js.map +1 -0
  25. package/dist/chunk-JDJJMBB3.js +1883 -0
  26. package/dist/chunk-JDJJMBB3.js.map +1 -0
  27. package/dist/chunk-LHF6BVM4.js +83 -0
  28. package/dist/chunk-LHF6BVM4.js.map +1 -0
  29. package/dist/{chunk-C5BF4L26.js → chunk-LSMWZSCJ.js} +7 -7
  30. package/dist/chunk-LTKGTXUQ.js +174 -0
  31. package/dist/chunk-LTKGTXUQ.js.map +1 -0
  32. package/dist/chunk-N7S72EXK.js +25 -0
  33. package/dist/chunk-N7S72EXK.js.map +1 -0
  34. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  35. package/dist/chunk-PEHTX3V7.js.map +1 -0
  36. package/dist/chunk-R5R5RQQD.js +51 -0
  37. package/dist/chunk-R5R5RQQD.js.map +1 -0
  38. package/dist/{chunk-IYQCWGU5.js → chunk-RGTZ2VDR.js} +35 -62
  39. package/dist/chunk-RGTZ2VDR.js.map +1 -0
  40. package/dist/chunk-RI42H5IU.js +326 -0
  41. package/dist/chunk-RI42H5IU.js.map +1 -0
  42. package/dist/{chunk-EAOSEIXB.js → chunk-RRRS5D5T.js} +727 -1983
  43. package/dist/chunk-RRRS5D5T.js.map +1 -0
  44. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  45. package/dist/chunk-SHLZMAJN.js.map +1 -0
  46. package/dist/{chunk-IUHHPMAX.js → chunk-SZOCY5VG.js} +18 -87
  47. package/dist/chunk-SZOCY5VG.js.map +1 -0
  48. package/dist/{chunk-L2J3PU7A.js → chunk-VYIPNDUZ.js} +29 -100
  49. package/dist/chunk-VYIPNDUZ.js.map +1 -0
  50. package/dist/{chunk-G6YRCIYJ.js → chunk-YK6QMGVL.js} +55 -60
  51. package/dist/chunk-YK6QMGVL.js.map +1 -0
  52. package/dist/chunk-ZIB4H5LN.js +101 -0
  53. package/dist/chunk-ZIB4H5LN.js.map +1 -0
  54. package/dist/client.d.ts +9 -3
  55. package/dist/clipboard.js +1 -1
  56. package/dist/components/browser-viewer.d.ts +6 -0
  57. package/dist/components/composer.d.ts +3 -69
  58. package/dist/components/human-input-form.d.ts +3 -32
  59. package/dist/components/markdown.d.ts +8 -0
  60. package/dist/components/model-policy-picker.d.ts +4 -37
  61. package/dist/components/opper-mark.d.ts +7 -0
  62. package/dist/components/provider-recovery-notice.d.ts +15 -0
  63. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  64. package/dist/components/sandbox-workspace.d.ts +13 -0
  65. package/dist/components/session-chrome.d.ts +5 -3
  66. package/dist/components/session-conversation.d.ts +15 -3
  67. package/dist/composer-messages.d.ts +74 -0
  68. package/dist/composer-messages.js +9 -0
  69. package/dist/composer.js +15 -10
  70. package/dist/{computer-viewer-ELNLZGHI.js → computer-viewer-K4CKYRX6.js} +4 -4
  71. package/dist/connect-setup.d.ts +2 -0
  72. package/dist/connect.d.ts +1 -1
  73. package/dist/connect.js +21 -6
  74. package/dist/connect.js.map +1 -1
  75. package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-K7SOZVVZ.js} +6 -4
  76. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-K7SOZVVZ.js.map} +1 -1
  77. package/dist/hooks/shared-machines-list.d.ts +4 -0
  78. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  79. package/dist/hooks/use-goal.d.ts +2 -1
  80. package/dist/hooks/use-new-session-draft.d.ts +74 -0
  81. package/dist/human-input-model.d.ts +40 -0
  82. package/dist/index.d.ts +4 -1
  83. package/dist/index.js +116 -73
  84. package/dist/index.js.map +1 -1
  85. package/dist/interaction.js +5 -5
  86. package/dist/{latest-question-2POFPYNB.js → latest-question-27HGRPVF.js} +3 -3
  87. package/dist/lib/clipboard.d.ts +7 -0
  88. package/dist/lib/provider-recovery.d.ts +49 -0
  89. package/dist/lib/transient-retry.d.ts +25 -0
  90. package/dist/machines.js +2 -2
  91. package/dist/model-mark-paths.d.ts +34 -0
  92. package/dist/model-mark-paths.js +16 -0
  93. package/dist/model-picker-model.d.ts +111 -0
  94. package/dist/model-picker-model.js +25 -0
  95. package/dist/model-picker-model.js.map +1 -0
  96. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-I5KPC3D6.js} +6 -4
  97. package/dist/model-policy-picker-menu-I5KPC3D6.js.map +1 -0
  98. package/dist/model-policy.d.ts +10 -0
  99. package/dist/model-policy.js +5 -1
  100. package/dist/new-session-draft.d.ts +7 -0
  101. package/dist/new-session-draft.js +639 -0
  102. package/dist/new-session-draft.js.map +1 -0
  103. package/dist/organization-model.d.ts +32 -0
  104. package/dist/organization-model.js +65 -0
  105. package/dist/organization-model.js.map +1 -0
  106. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  107. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  108. package/dist/queue-presentation-model.d.ts +72 -0
  109. package/dist/queue-presentation-model.js +38 -0
  110. package/dist/queue-presentation-model.js.map +1 -0
  111. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  112. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  113. package/dist/realtime.js +5 -3
  114. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-2DGDO6NI.js} +5 -5
  115. package/dist/sandbox-files-2DGDO6NI.js.map +1 -0
  116. package/dist/sandbox-label-model.d.ts +23 -0
  117. package/dist/sandbox-label-model.js +28 -0
  118. package/dist/sandbox-label-model.js.map +1 -0
  119. package/dist/session-agents-model.d.ts +10 -0
  120. package/dist/session-agents-model.js +25 -0
  121. package/dist/session-agents-model.js.map +1 -0
  122. package/dist/session-attention-model.d.ts +8 -0
  123. package/dist/session-attention-model.js +46 -0
  124. package/dist/session-attention-model.js.map +1 -0
  125. package/dist/session-feedback-model.d.ts +20 -0
  126. package/dist/session-feedback-model.js +56 -0
  127. package/dist/session-feedback-model.js.map +1 -0
  128. package/dist/session-list-model.d.ts +119 -0
  129. package/dist/session-list-model.js +244 -0
  130. package/dist/session-list-model.js.map +1 -0
  131. package/dist/session-status-model.d.ts +19 -0
  132. package/dist/session-ui.js +26 -17
  133. package/dist/session-ui.js.map +1 -1
  134. package/dist/session.js +8 -6
  135. package/dist/testing/session-scenarios.d.ts +10 -0
  136. package/dist/testing/session-scenarios.js +303 -0
  137. package/dist/testing/session-scenarios.js.map +1 -0
  138. package/dist/timeline/genie-copy.d.ts +3 -0
  139. package/dist/timeline/genie-loading.d.ts +11 -0
  140. package/dist/timeline/index.d.ts +1 -1
  141. package/dist/timeline/notice-presentation.d.ts +7 -0
  142. package/dist/timeline/patch-apply-command.d.ts +18 -0
  143. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  144. package/dist/timeline/presented-image.d.ts +6 -1
  145. package/dist/timeline/question-nav-model.d.ts +23 -0
  146. package/dist/timeline/registry.d.ts +10 -1
  147. package/dist/timeline/retained-loaders.d.ts +1 -1
  148. package/dist/timeline/tool-presentation.d.ts +172 -0
  149. package/dist/timeline/turn-summary-model.d.ts +90 -0
  150. package/dist/timeline/turn-summary.d.ts +4 -74
  151. package/dist/timeline/work-presentation.d.ts +42 -0
  152. package/dist/timeline-model.d.ts +24 -0
  153. package/dist/timeline-model.js +152 -0
  154. package/dist/timeline-model.js.map +1 -0
  155. package/package.json +55 -3
  156. package/src/artifacts.ts +1 -0
  157. package/src/client.ts +9 -3
  158. package/src/components/browser-viewer.tsx +23 -2
  159. package/src/components/claude-mark.tsx +2 -1
  160. package/src/components/composer.tsx +8 -146
  161. package/src/components/grok-mark.tsx +2 -1
  162. package/src/components/human-input-form.tsx +13 -124
  163. package/src/components/markdown.tsx +19 -1
  164. package/src/components/message-timeline.tsx +45 -99
  165. package/src/components/model-mark.tsx +2 -1
  166. package/src/components/model-policy-picker-menu.tsx +30 -85
  167. package/src/components/model-policy-picker.tsx +20 -150
  168. package/src/components/opper-mark.tsx +18 -0
  169. package/src/components/provider-recovery-notice.tsx +41 -0
  170. package/src/components/queue-surface-implementation.tsx +20 -349
  171. package/src/components/remark-soft-line-breaks.ts +166 -0
  172. package/src/components/sandbox-workspace.tsx +17 -1
  173. package/src/components/session-chrome.tsx +114 -99
  174. package/src/components/session-conversation.tsx +55 -6
  175. package/src/components/session-status.tsx +17 -16
  176. package/src/composer-messages.ts +150 -0
  177. package/src/connect-setup.tsx +45 -11
  178. package/src/connect.ts +1 -1
  179. package/src/hooks/shared-machines-list.ts +34 -1
  180. package/src/hooks/use-client-config-flags.ts +8 -0
  181. package/src/hooks/use-goal.ts +66 -10
  182. package/src/hooks/use-new-session-draft.ts +813 -0
  183. package/src/human-input-model.ts +147 -0
  184. package/src/index.ts +16 -0
  185. package/src/lib/clipboard.ts +29 -0
  186. package/src/lib/format.ts +7 -0
  187. package/src/lib/provider-recovery.ts +202 -0
  188. package/src/lib/transient-retry.ts +99 -0
  189. package/src/model-mark-paths.ts +53 -0
  190. package/src/model-picker-model.ts +330 -0
  191. package/src/model-policy.ts +75 -5
  192. package/src/new-session-draft.ts +22 -0
  193. package/src/organization-model.ts +118 -0
  194. package/src/queue-presentation-model.ts +464 -0
  195. package/src/sandbox-label-model.ts +41 -0
  196. package/src/session-agents-model.ts +33 -0
  197. package/src/session-attention-model.ts +59 -0
  198. package/src/session-feedback-model.ts +95 -0
  199. package/src/session-list-model.ts +377 -0
  200. package/src/session-status-model.ts +62 -0
  201. package/src/testing/session-scenarios.ts +326 -0
  202. package/src/timeline/activity-rail.tsx +54 -24
  203. package/src/timeline/genie-copy.ts +19 -0
  204. package/src/timeline/genie-loading.tsx +15 -19
  205. package/src/timeline/index.ts +1 -0
  206. package/src/timeline/notice-presentation.ts +22 -0
  207. package/src/timeline/patch-apply-command.tsx +135 -0
  208. package/src/timeline/platform-activity-presentation.ts +122 -0
  209. package/src/timeline/platform-activity-row.tsx +6 -96
  210. package/src/timeline/presented-image.ts +16 -2
  211. package/src/timeline/projection.ts +12 -1
  212. package/src/timeline/question-nav-model.ts +28 -0
  213. package/src/timeline/registry.ts +11 -1
  214. package/src/timeline/retained-loaders.ts +9 -4
  215. package/src/timeline/rolling-activity.tsx +7 -19
  216. package/src/timeline/tool-presentation.ts +1316 -0
  217. package/src/timeline/tool-renderers.tsx +254 -1024
  218. package/src/timeline/turn-summary-model.ts +338 -0
  219. package/src/timeline/turn-summary.tsx +21 -326
  220. package/src/timeline/work-presentation.ts +139 -0
  221. package/src/timeline-model.ts +108 -0
  222. package/styles/compiled.css +17 -4
  223. package/styles/connect.css +4 -0
  224. package/styles/responsive.css +11 -2
  225. package/dist/chunk-2OQA3CPB.js.map +0 -1
  226. package/dist/chunk-CVYCLFMH.js.map +0 -1
  227. package/dist/chunk-EAOSEIXB.js.map +0 -1
  228. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  229. package/dist/chunk-HXYOSSQO.js +0 -117
  230. package/dist/chunk-HXYOSSQO.js.map +0 -1
  231. package/dist/chunk-IUHHPMAX.js.map +0 -1
  232. package/dist/chunk-IYQCWGU5.js.map +0 -1
  233. package/dist/chunk-JH7V627O.js.map +0 -1
  234. package/dist/chunk-L2J3PU7A.js.map +0 -1
  235. package/dist/chunk-LVS3MH5T.js.map +0 -1
  236. package/dist/chunk-PHBFJCEU.js.map +0 -1
  237. package/dist/chunk-QMMCJGFI.js.map +0 -1
  238. package/dist/chunk-XS67YG5R.js.map +0 -1
  239. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  240. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  241. /package/dist/{browser-viewer-5EEAS3IU.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  242. /package/dist/{chunk-I6OY3MWC.js.map → chunk-6FOTSVWU.js.map} +0 -0
  243. /package/dist/{chunk-YVFRBXST.js.map → chunk-GMNGUPMX.js.map} +0 -0
  244. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  245. /package/dist/{chunk-C5BF4L26.js.map → chunk-LSMWZSCJ.js.map} +0 -0
  246. /package/dist/{computer-viewer-ELNLZGHI.js.map → composer-messages.js.map} +0 -0
  247. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  248. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-27HGRPVF.js.map} +0 -0
  249. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -0,0 +1,338 @@
1
+ // Pure turn-summary model shared by the web and native renderers: facet definitions,
2
+ // the summary context, status types and duration formatting. No DOM, no styling.
3
+ import type { ReactNode } from "react";
4
+ import {
5
+ applyPatchOpsFromToolItem,
6
+ isApplyPatch,
7
+ mediaPreviewFact,
8
+ retainedScreenshotMetadata,
9
+ screenshotDataUrl,
10
+ } from "./parsers";
11
+ import { rawTypeOf } from "./registry";
12
+ import { mcpToolLeaf } from "./tool-display-name";
13
+ import type { ActivityItem, ToolCallItem, TurnOutcome } from "./types";
14
+
15
+ export const BUILT_IN_TURN_SUMMARY_FACET_IDS = [
16
+ "steps",
17
+ "files",
18
+ "commands",
19
+ "screenshots",
20
+ "memories",
21
+ "compacted",
22
+ "duration",
23
+ ] as const;
24
+
25
+ export type BuiltInTurnSummaryFacetId = (typeof BUILT_IN_TURN_SUMMARY_FACET_IDS)[number];
26
+
27
+ export type TurnSummaryContext = Readonly<{
28
+ /** Every normalized activity item folded into this summary. */
29
+ items: readonly ActivityItem[];
30
+ /** Tool calls from `items`, retained in timeline order for convenient aggregation. */
31
+ toolCalls: readonly ToolCallItem[];
32
+ /** The settled turn verdict, or absent for a neutral/incomplete cluster. */
33
+ outcome: TurnOutcome | undefined;
34
+ /** The bounded failure reason rendered by the enclosing summary, when present. */
35
+ failureText: string | undefined;
36
+ /** Total turn duration when the enclosing group has both valid timestamps. */
37
+ durationMs: number | undefined;
38
+ /** False when any projected activity is still running or streaming. */
39
+ settled: boolean;
40
+ /**
41
+ * Adjacent compaction landmarks next to this fold. Landmark remains the
42
+ * primary UI; this is a secondary chip signal only.
43
+ */
44
+ contextCompactionCount: number;
45
+ }>;
46
+
47
+ export type TurnSummaryFacetResult = Readonly<{
48
+ icon?: ReactNode;
49
+ content: ReactNode;
50
+ ariaLabel?: string;
51
+ title?: string;
52
+ }>;
53
+
54
+ export type TurnSummaryFacet = Readonly<{
55
+ /** Stable identity used for removal and deterministic de-duplication. */
56
+ id: string;
57
+ /** Return null when this facet has nothing useful to show. */
58
+ summarize(context: TurnSummaryContext): TurnSummaryFacetResult | null;
59
+ }>;
60
+
61
+ type ModifyTurnSummaryFacets = Readonly<{
62
+ /** Appended after the remaining built-ins, in supplied order. */
63
+ add?: readonly TurnSummaryFacet[];
64
+ /** Built-ins to omit before custom facets are appended. */
65
+ remove?: readonly BuiltInTurnSummaryFacetId[];
66
+ replace?: never;
67
+ }>;
68
+
69
+ type ReplaceTurnSummaryFacets = Readonly<{
70
+ /** The complete ordered facet list. Mutually exclusive with add/remove. */
71
+ replace: readonly TurnSummaryFacet[];
72
+ add?: never;
73
+ remove?: never;
74
+ }>;
75
+
76
+ export type TurnSummaryFacetConfiguration = ModifyTurnSummaryFacets | ReplaceTurnSummaryFacets;
77
+
78
+ export type TurnSummaryOptions = Readonly<{
79
+ /**
80
+ * Readable per-turn presentation. Assistant progress stays fully formatted;
81
+ * each turn has its own Working / Worked row and rolling latest step.
82
+ * Ordinary tip-follow and manual scrolling are unchanged.
83
+ */
84
+ rolling?: boolean;
85
+ facets?: TurnSummaryFacetConfiguration;
86
+ }>;
87
+
88
+ /**
89
+ * Exchange status shown in place of the plain facet line. `working` and
90
+ * `waiting` run a live clock from `since`; `worked` shows the settled span.
91
+ */
92
+ export type TurnSummaryStatus = Readonly<{
93
+ kind: "working" | "waiting" | "worked";
94
+ /** Replaces the default "Working" / "Waiting" wording. */
95
+ label?: string | undefined;
96
+ /** Live clock start for `working` and `waiting`. */
97
+ since?: string | undefined;
98
+ /** Settled span for `worked`. */
99
+ durationMs?: number | undefined;
100
+ /** Live step preview under the row while it is collapsed. */
101
+ preview?: ReactNode;
102
+ }>;
103
+
104
+ /** Elapsed wall time with seconds below an hour: "14s", "2m 14s", "1h 05m". */
105
+ export function formatElapsed(durationMs: number): string {
106
+ const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
107
+ if (totalSeconds < 60) {
108
+ return `${totalSeconds}s`;
109
+ }
110
+ const totalMinutes = Math.floor(totalSeconds / 60);
111
+ if (totalMinutes < 60) {
112
+ const seconds = totalSeconds % 60;
113
+ return seconds ? `${totalMinutes}m ${seconds}s` : `${totalMinutes}m`;
114
+ }
115
+ const hours = Math.floor(totalMinutes / 60);
116
+ return `${hours}h ${String(totalMinutes % 60).padStart(2, "0")}m`;
117
+ }
118
+
119
+ export function createTurnSummaryContext(
120
+ items: ActivityItem[],
121
+ outcome: TurnOutcome | undefined,
122
+ failureText: string | undefined,
123
+ durationMs: number | undefined,
124
+ contextCompactionCount: number,
125
+ ): TurnSummaryContext {
126
+ const itemSnapshot = Object.freeze([...items]);
127
+ const toolCalls = Object.freeze(
128
+ itemSnapshot.filter((item): item is ToolCallItem => item.kind === "tool-call"),
129
+ );
130
+ const settled = itemSnapshot.every((item) => {
131
+ if (item.kind === "reasoning" || item.kind === "agent-message") {
132
+ return !item.streaming;
133
+ }
134
+ if (
135
+ item.kind === "tool-call" ||
136
+ item.kind === "worker" ||
137
+ item.kind === "sandbox" ||
138
+ item.kind === "startup-phase"
139
+ ) {
140
+ return item.status !== "running";
141
+ }
142
+ return true;
143
+ });
144
+ return Object.freeze({
145
+ items: itemSnapshot,
146
+ toolCalls,
147
+ outcome,
148
+ failureText,
149
+ durationMs,
150
+ settled,
151
+ contextCompactionCount: Math.max(0, Math.floor(contextCompactionCount)),
152
+ });
153
+ }
154
+
155
+ export const BUILT_IN_TURN_SUMMARY_FACETS: readonly TurnSummaryFacet[] = Object.freeze([
156
+ {
157
+ id: "steps",
158
+ summarize: ({ items }) => {
159
+ // Progress notes narrate steps; they are not steps themselves.
160
+ const count = items.filter(
161
+ (item) => item.kind !== "startup-phase" && item.kind !== "agent-message",
162
+ ).length;
163
+ return count
164
+ ? { content: `${count} ${count === 1 ? "step" : "steps"}` }
165
+ : items.some((item) => item.kind === "startup-phase")
166
+ ? { content: "Preparation" }
167
+ : null;
168
+ },
169
+ },
170
+ {
171
+ id: "files",
172
+ summarize: ({ toolCalls }) => {
173
+ let files = 0;
174
+ for (const item of toolCalls) {
175
+ if (isApplyPatch(item)) {
176
+ files += applyPatchOpsFromToolItem(item).length;
177
+ }
178
+ }
179
+ return files ? { content: `${files} ${files === 1 ? "file" : "files"} edited` } : null;
180
+ },
181
+ },
182
+ {
183
+ id: "commands",
184
+ summarize: ({ toolCalls }) => {
185
+ const commands = toolCalls.filter((item) => item.name === "exec_command").length;
186
+ return commands
187
+ ? { content: `${commands} ${commands === 1 ? "command" : "commands"}` }
188
+ : null;
189
+ },
190
+ },
191
+ {
192
+ id: "screenshots",
193
+ summarize: ({ toolCalls }) => {
194
+ let screenshots = 0;
195
+ for (const item of toolCalls) {
196
+ const leaf = mcpToolLeaf(item.name);
197
+ if (
198
+ (rawTypeOf(item) === "computer_call" ||
199
+ item.name === "computer_call" ||
200
+ item.name === "computer_screenshot" ||
201
+ leaf === "browser_screenshot" ||
202
+ leaf === "browser_observe" ||
203
+ leaf === "browser_act") &&
204
+ (retainedScreenshotMetadata(item.output) !== null ||
205
+ screenshotDataUrl(item.output) !== null ||
206
+ mediaPreviewFact(item.output) !== null)
207
+ ) {
208
+ screenshots += 1;
209
+ }
210
+ }
211
+ return screenshots
212
+ ? {
213
+ content: `${screenshots} ${screenshots === 1 ? "screenshot" : "screenshots"}`,
214
+ }
215
+ : null;
216
+ },
217
+ },
218
+ {
219
+ id: "memories",
220
+ summarize: ({ items }) => {
221
+ let saved = 0;
222
+ let updated = 0;
223
+ for (const item of items) {
224
+ if (item.kind !== "memory") {
225
+ continue;
226
+ }
227
+ if (item.variant === "corrected") {
228
+ updated += 1;
229
+ } else {
230
+ saved += 1;
231
+ }
232
+ }
233
+ const parts: string[] = [];
234
+ if (saved) {
235
+ parts.push(`${saved} ${saved === 1 ? "memory" : "memories"} saved`);
236
+ }
237
+ if (updated) {
238
+ parts.push(`${updated} ${updated === 1 ? "memory" : "memories"} updated`);
239
+ }
240
+ return parts.length > 0 ? { content: parts.join(" · ") } : null;
241
+ },
242
+ },
243
+ {
244
+ id: "compacted",
245
+ summarize: ({ contextCompactionCount }) =>
246
+ contextCompactionCount > 0
247
+ ? {
248
+ content:
249
+ contextCompactionCount === 1 ? "compacted" : `${contextCompactionCount} compacts`,
250
+ ariaLabel:
251
+ contextCompactionCount === 1
252
+ ? "Conversation history compacted"
253
+ : `${contextCompactionCount} conversation history compactions`,
254
+ }
255
+ : null,
256
+ },
257
+ {
258
+ id: "duration",
259
+ summarize: ({ durationMs }) => {
260
+ const duration = formatDurationFacet(durationMs);
261
+ return duration ? { content: duration } : null;
262
+ },
263
+ },
264
+ ]);
265
+
266
+ export function resolveTurnSummaryFacets(
267
+ configuration: TurnSummaryFacetConfiguration | undefined,
268
+ ): readonly TurnSummaryFacet[] {
269
+ const requested: readonly TurnSummaryFacet[] = configuration?.replace ?? [
270
+ ...BUILT_IN_TURN_SUMMARY_FACETS.filter(
271
+ (facet) => !configuration?.remove?.includes(facet.id as BuiltInTurnSummaryFacetId),
272
+ ),
273
+ ...(configuration?.add ?? []),
274
+ ];
275
+ const seen = new Set<string>();
276
+ return requested.filter((facet) => {
277
+ if (!facet.id || seen.has(facet.id)) {
278
+ return false;
279
+ }
280
+ seen.add(facet.id);
281
+ return true;
282
+ });
283
+ }
284
+
285
+ export function formatDurationFacet(durationMs: number | undefined): string | null {
286
+ if (durationMs === undefined || !Number.isFinite(durationMs) || durationMs < 1000) {
287
+ return null;
288
+ }
289
+ const totalSeconds = Math.floor(durationMs / 1000);
290
+ if (totalSeconds < 60) {
291
+ return `${totalSeconds}s`;
292
+ }
293
+ const totalMinutes = Math.floor(totalSeconds / 60);
294
+ if (totalMinutes < 60) {
295
+ return `${totalMinutes}m`;
296
+ }
297
+ const hours = Math.floor(totalMinutes / 60);
298
+ const minutes = totalMinutes % 60;
299
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
300
+ }
301
+
302
+ function hasFacetContent(content: ReactNode): boolean {
303
+ return content !== null && content !== undefined && content !== false && content !== "";
304
+ }
305
+
306
+ /**
307
+ * The facets a turn header shows. With a readable status line, duration lives in
308
+ * the status, preparation-only turns drop the step count, and a live or waiting
309
+ * status keeps only the step count among the built-ins. A failing custom facet is
310
+ * skipped rather than breaking the header.
311
+ */
312
+ export function selectTurnSummaryFacets(
313
+ definitions: readonly TurnSummaryFacet[],
314
+ context: TurnSummaryContext,
315
+ statusKind: TurnSummaryStatus["kind"] | undefined,
316
+ ): Array<{ facet: TurnSummaryFacet; result: TurnSummaryFacetResult }> {
317
+ return definitions.flatMap((facet) => {
318
+ if (
319
+ statusKind &&
320
+ (facet.id === "duration" ||
321
+ (facet === BUILT_IN_TURN_SUMMARY_FACETS[0] &&
322
+ context.items.every(
323
+ (item) => item.kind === "startup-phase" || item.kind === "agent-message",
324
+ )) ||
325
+ (statusKind !== "worked" &&
326
+ facet.id !== "steps" &&
327
+ BUILT_IN_TURN_SUMMARY_FACET_IDS.includes(facet.id as BuiltInTurnSummaryFacetId)))
328
+ ) {
329
+ return [];
330
+ }
331
+ try {
332
+ const result = facet.summarize(context);
333
+ return result && hasFacetContent(result.content) ? [{ facet, result }] : [];
334
+ } catch {
335
+ return [];
336
+ }
337
+ });
338
+ }