@opengeni/react 1.4.2 → 1.4.5-canary.37458084490001

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 (240) 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-WGJ43YGI.js → browser-viewer-CV23VIHB.js} +8 -6
  6. package/dist/chunk-3ZC6QPOM.js +174 -0
  7. package/dist/chunk-3ZC6QPOM.js.map +1 -0
  8. package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
  9. package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
  10. package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
  11. package/dist/{chunk-MDIXFKA3.js → chunk-6FOTSVWU.js} +303 -109
  12. package/dist/chunk-6FOTSVWU.js.map +1 -0
  13. package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
  14. package/dist/chunk-7RRBX5EM.js.map +1 -0
  15. package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
  16. package/dist/chunk-7ZB7I3KP.js.map +1 -0
  17. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  18. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  19. package/dist/chunk-BOFEQC6K.js +162 -0
  20. package/dist/chunk-BOFEQC6K.js.map +1 -0
  21. package/dist/{chunk-VCSERYXH.js → chunk-CESBPQP5.js} +309 -162
  22. package/dist/chunk-CESBPQP5.js.map +1 -0
  23. package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
  24. package/dist/chunk-GHJU7XG7.js.map +1 -0
  25. package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
  26. package/dist/chunk-HNVWMDNN.js.map +1 -0
  27. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  28. package/dist/chunk-I5EGRXXG.js.map +1 -0
  29. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  30. package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
  31. package/dist/chunk-IX3K4THH.js.map +1 -0
  32. package/dist/chunk-IZV5FTJC.js +55 -0
  33. package/dist/chunk-IZV5FTJC.js.map +1 -0
  34. package/dist/chunk-JDJJMBB3.js +1883 -0
  35. package/dist/chunk-JDJJMBB3.js.map +1 -0
  36. package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
  37. package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
  38. package/dist/chunk-LHF6BVM4.js +83 -0
  39. package/dist/chunk-LHF6BVM4.js.map +1 -0
  40. package/dist/chunk-N7S72EXK.js +25 -0
  41. package/dist/chunk-N7S72EXK.js.map +1 -0
  42. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  43. package/dist/chunk-PEHTX3V7.js.map +1 -0
  44. package/dist/chunk-R5R5RQQD.js +51 -0
  45. package/dist/chunk-R5R5RQQD.js.map +1 -0
  46. package/dist/chunk-RI42H5IU.js +326 -0
  47. package/dist/chunk-RI42H5IU.js.map +1 -0
  48. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  49. package/dist/chunk-SHLZMAJN.js.map +1 -0
  50. package/dist/{chunk-CREMJMVN.js → chunk-TZQSHCZN.js} +692 -1996
  51. package/dist/chunk-TZQSHCZN.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-46UIN7F3.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-3ZFWWJ6O.js} +6 -4
  76. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.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-computer-session.d.ts +2 -0
  80. package/dist/hooks/use-goal.d.ts +2 -1
  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-SWC6QEVN.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/machines.js +2 -2
  90. package/dist/model-mark-paths.d.ts +34 -0
  91. package/dist/model-mark-paths.js +16 -0
  92. package/dist/model-picker-model.d.ts +111 -0
  93. package/dist/model-picker-model.js +25 -0
  94. package/dist/model-picker-model.js.map +1 -0
  95. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
  96. package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
  97. package/dist/model-policy.d.ts +10 -0
  98. package/dist/model-policy.js +5 -1
  99. package/dist/organization-model.d.ts +32 -0
  100. package/dist/organization-model.js +65 -0
  101. package/dist/organization-model.js.map +1 -0
  102. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  103. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  104. package/dist/queue-presentation-model.d.ts +72 -0
  105. package/dist/queue-presentation-model.js +38 -0
  106. package/dist/queue-presentation-model.js.map +1 -0
  107. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  108. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  109. package/dist/realtime.js +5 -3
  110. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
  111. package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
  112. package/dist/sandbox-label-model.d.ts +23 -0
  113. package/dist/sandbox-label-model.js +28 -0
  114. package/dist/sandbox-label-model.js.map +1 -0
  115. package/dist/session-attention-model.d.ts +8 -0
  116. package/dist/session-attention-model.js +46 -0
  117. package/dist/session-attention-model.js.map +1 -0
  118. package/dist/session-feedback-model.d.ts +20 -0
  119. package/dist/session-feedback-model.js +56 -0
  120. package/dist/session-feedback-model.js.map +1 -0
  121. package/dist/session-list-model.d.ts +96 -0
  122. package/dist/session-list-model.js +219 -0
  123. package/dist/session-list-model.js.map +1 -0
  124. package/dist/session-status-model.d.ts +19 -0
  125. package/dist/session-ui.js +26 -17
  126. package/dist/session-ui.js.map +1 -1
  127. package/dist/session.js +8 -6
  128. package/dist/testing/session-scenarios.d.ts +10 -0
  129. package/dist/testing/session-scenarios.js +303 -0
  130. package/dist/testing/session-scenarios.js.map +1 -0
  131. package/dist/timeline/genie-copy.d.ts +3 -0
  132. package/dist/timeline/genie-loading.d.ts +11 -0
  133. package/dist/timeline/index.d.ts +1 -1
  134. package/dist/timeline/notice-presentation.d.ts +7 -0
  135. package/dist/timeline/patch-apply-command.d.ts +18 -0
  136. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  137. package/dist/timeline/presented-image.d.ts +6 -1
  138. package/dist/timeline/question-nav-model.d.ts +23 -0
  139. package/dist/timeline/registry.d.ts +10 -1
  140. package/dist/timeline/retained-loaders.d.ts +1 -1
  141. package/dist/timeline/tool-presentation.d.ts +172 -0
  142. package/dist/timeline/turn-summary-model.d.ts +90 -0
  143. package/dist/timeline/turn-summary.d.ts +4 -74
  144. package/dist/timeline/work-presentation.d.ts +42 -0
  145. package/dist/timeline-model.d.ts +24 -0
  146. package/dist/timeline-model.js +152 -0
  147. package/dist/timeline-model.js.map +1 -0
  148. package/package.json +47 -3
  149. package/src/artifacts.ts +1 -0
  150. package/src/client.ts +9 -3
  151. package/src/components/browser-viewer.tsx +23 -2
  152. package/src/components/claude-mark.tsx +2 -1
  153. package/src/components/composer.tsx +4 -144
  154. package/src/components/computer-viewer.tsx +139 -30
  155. package/src/components/grok-mark.tsx +2 -1
  156. package/src/components/human-input-form.tsx +13 -124
  157. package/src/components/markdown.tsx +19 -1
  158. package/src/components/message-timeline.tsx +45 -99
  159. package/src/components/model-mark.tsx +2 -1
  160. package/src/components/model-policy-picker-menu.tsx +30 -85
  161. package/src/components/model-policy-picker.tsx +15 -148
  162. package/src/components/opper-mark.tsx +18 -0
  163. package/src/components/provider-recovery-notice.tsx +41 -0
  164. package/src/components/queue-surface-implementation.tsx +20 -349
  165. package/src/components/remark-soft-line-breaks.ts +166 -0
  166. package/src/components/sandbox-workspace.tsx +17 -1
  167. package/src/components/session-chrome.tsx +114 -99
  168. package/src/components/session-conversation.tsx +55 -6
  169. package/src/components/session-list.tsx +1 -1
  170. package/src/components/session-status.tsx +17 -16
  171. package/src/composer-messages.ts +150 -0
  172. package/src/connect-setup.tsx +45 -11
  173. package/src/connect.ts +1 -1
  174. package/src/hooks/shared-machines-list.ts +34 -1
  175. package/src/hooks/use-browser-session.ts +445 -168
  176. package/src/hooks/use-client-config-flags.ts +8 -0
  177. package/src/hooks/use-computer-session.ts +334 -98
  178. package/src/hooks/use-goal.ts +66 -10
  179. package/src/human-input-model.ts +147 -0
  180. package/src/index.ts +16 -0
  181. package/src/lib/clipboard.ts +29 -0
  182. package/src/lib/format.ts +7 -0
  183. package/src/lib/provider-recovery.ts +202 -0
  184. package/src/model-mark-paths.ts +53 -0
  185. package/src/model-picker-model.ts +330 -0
  186. package/src/model-policy.ts +43 -2
  187. package/src/organization-model.ts +118 -0
  188. package/src/queue-presentation-model.ts +464 -0
  189. package/src/sandbox-label-model.ts +41 -0
  190. package/src/session-attention-model.ts +59 -0
  191. package/src/session-feedback-model.ts +95 -0
  192. package/src/session-list-model.ts +327 -0
  193. package/src/session-status-model.ts +62 -0
  194. package/src/testing/session-scenarios.ts +326 -0
  195. package/src/timeline/activity-rail.tsx +5 -17
  196. package/src/timeline/genie-copy.ts +19 -0
  197. package/src/timeline/genie-loading.tsx +13 -18
  198. package/src/timeline/index.ts +1 -0
  199. package/src/timeline/notice-presentation.ts +22 -0
  200. package/src/timeline/patch-apply-command.tsx +135 -0
  201. package/src/timeline/platform-activity-presentation.ts +122 -0
  202. package/src/timeline/platform-activity-row.tsx +6 -96
  203. package/src/timeline/presented-image.ts +16 -2
  204. package/src/timeline/question-nav-model.ts +28 -0
  205. package/src/timeline/registry.ts +11 -1
  206. package/src/timeline/retained-loaders.ts +9 -4
  207. package/src/timeline/rolling-activity.tsx +7 -19
  208. package/src/timeline/tool-presentation.ts +1316 -0
  209. package/src/timeline/tool-renderers.tsx +226 -1025
  210. package/src/timeline/turn-summary-model.ts +338 -0
  211. package/src/timeline/turn-summary.tsx +21 -326
  212. package/src/timeline/work-presentation.ts +139 -0
  213. package/src/timeline-model.ts +108 -0
  214. package/styles/compiled.css +8 -0
  215. package/styles/connect.css +4 -0
  216. package/dist/chunk-CREMJMVN.js.map +0 -1
  217. package/dist/chunk-CVYCLFMH.js.map +0 -1
  218. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  219. package/dist/chunk-HXYOSSQO.js +0 -117
  220. package/dist/chunk-HXYOSSQO.js.map +0 -1
  221. package/dist/chunk-IUHHPMAX.js.map +0 -1
  222. package/dist/chunk-IYQCWGU5.js.map +0 -1
  223. package/dist/chunk-JH7V627O.js.map +0 -1
  224. package/dist/chunk-L2J3PU7A.js.map +0 -1
  225. package/dist/chunk-LVS3MH5T.js.map +0 -1
  226. package/dist/chunk-MDIXFKA3.js.map +0 -1
  227. package/dist/chunk-PHBFJCEU.js.map +0 -1
  228. package/dist/chunk-VCSERYXH.js.map +0 -1
  229. package/dist/chunk-XS67YG5R.js.map +0 -1
  230. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  231. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  232. /package/dist/{browser-viewer-WGJ43YGI.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  233. /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
  234. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  235. /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
  236. /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
  237. /package/dist/{computer-viewer-46UIN7F3.js.map → composer-messages.js.map} +0 -0
  238. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  239. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
  240. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * remark: render a single newline inside prose as a line break.
3
+ *
4
+ * CommonMark folds a lone line ending inside a paragraph into a space, so an
5
+ * agent writing `1\n2\n3` shows "1 2 3". Chat surfaces (ChatGPT, GitHub
6
+ * comments) keep the line, which is what message authors expect. This plugin
7
+ * turns every line ending inside an mdast `text` node into a `break` node — the
8
+ * same result as `remark-breaks` — while leaving code (`code` / `inlineCode`
9
+ * carry `value`, not `text` children), raw HTML, and block structure (lists,
10
+ * tables, blockquotes, headings, blank-line paragraphs) untouched.
11
+ *
12
+ * Unlike `remark-breaks` (built on `mdast-util-find-and-replace`, which drops
13
+ * positions on the split nodes), every split text node keeps a source position.
14
+ * Streaming tip ink (`rehypeStreamReveal`) keys fade-in on those offsets, so a
15
+ * positionless line would render without ink and snap in at the stream tail.
16
+ *
17
+ * Structural local types keep the module dependency-free, like
18
+ * `stream-reveal.ts`; the renderer casts once at the plugin seam.
19
+ */
20
+
21
+ type MdPoint = {
22
+ line?: number | undefined;
23
+ column?: number | undefined;
24
+ offset?: number | undefined;
25
+ };
26
+
27
+ type MdPosition = { start?: MdPoint | undefined; end?: MdPoint | undefined };
28
+
29
+ type MdNode = {
30
+ type: string;
31
+ value?: string | undefined;
32
+ position?: MdPosition | undefined;
33
+ children?: MdNode[] | undefined;
34
+ };
35
+
36
+ type ExactPoint = { line: number; column: number; offset: number };
37
+
38
+ const LINE_ENDING = /\r\n|\r|\n/g;
39
+ const HAS_LINE_ENDING = /[\r\n]/;
40
+ /** Line-start syntax the parser strips from text values: indentation and blockquote markers. */
41
+ const LINE_PREFIX = /[\t >]*/y;
42
+
43
+ export function remarkSoftLineBreaks() {
44
+ return (tree: MdNode, file?: { value?: unknown } | undefined) => {
45
+ walk(tree, typeof file?.value === "string" ? file.value : undefined);
46
+ };
47
+ }
48
+
49
+ function walk(node: MdNode, source: string | undefined): void {
50
+ if (!node.children) {
51
+ return;
52
+ }
53
+ let next: MdNode[] | null = null;
54
+ for (let index = 0; index < node.children.length; index += 1) {
55
+ const child = node.children[index]!;
56
+ if (
57
+ child.type === "text" &&
58
+ typeof child.value === "string" &&
59
+ HAS_LINE_ENDING.test(child.value)
60
+ ) {
61
+ next ??= node.children.slice(0, index);
62
+ next.push(...splitText(child, child.value, source));
63
+ continue;
64
+ }
65
+ walk(child, source);
66
+ next?.push(child);
67
+ }
68
+ if (next !== null) {
69
+ node.children = next;
70
+ }
71
+ }
72
+
73
+ function splitText(node: MdNode, value: string, source: string | undefined): MdNode[] {
74
+ const segments: { value: string; index: number }[] = [];
75
+ let cursor = 0;
76
+ for (const match of value.matchAll(LINE_ENDING)) {
77
+ segments.push({ value: value.slice(cursor, match.index), index: cursor });
78
+ cursor = match.index + match[0].length;
79
+ }
80
+ segments.push({ value: value.slice(cursor), index: cursor });
81
+
82
+ const starts = segmentStarts(node, segments, source);
83
+ const nodeEnd = exactPoint(node.position?.end);
84
+ const out: MdNode[] = [];
85
+ segments.forEach((segment, k) => {
86
+ const start = starts?.[k];
87
+ const isLast = k === segments.length - 1;
88
+ const end =
89
+ start === undefined
90
+ ? undefined
91
+ : isLast && nodeEnd !== undefined
92
+ ? nodeEnd
93
+ : {
94
+ line: start.line,
95
+ column: start.column + segment.value.length,
96
+ offset: start.offset + segment.value.length,
97
+ };
98
+ if (segment.value.length > 0) {
99
+ out.push({
100
+ type: "text",
101
+ value: segment.value,
102
+ ...(start !== undefined && end !== undefined ? { position: { start, end } } : {}),
103
+ });
104
+ }
105
+ if (!isLast) {
106
+ const nextStart = starts?.[k + 1];
107
+ out.push({
108
+ type: "break",
109
+ ...(end !== undefined && nextStart !== undefined
110
+ ? { position: { start: end, end: nextStart } }
111
+ : {}),
112
+ });
113
+ }
114
+ });
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * Source start of each line segment. Text values drop each continuation line's
120
+ * indentation / `>` prefix, so offsets are re-anchored on the real source line
121
+ * when the node's source slice has the same number of line endings; otherwise
122
+ * (e.g. a newline produced by a character reference) fall back to the value
123
+ * index, the same approximation tip ink already makes within one text node.
124
+ */
125
+ function segmentStarts(
126
+ node: MdNode,
127
+ segments: readonly { index: number }[],
128
+ source: string | undefined,
129
+ ): ExactPoint[] | undefined {
130
+ const start = exactPoint(node.position?.start);
131
+ if (start === undefined) {
132
+ return undefined;
133
+ }
134
+ const endOffset = node.position?.end?.offset;
135
+ const lineStarts: number[] = [];
136
+ if (source !== undefined && endOffset !== undefined && endOffset >= start.offset) {
137
+ for (const match of source.slice(start.offset, endOffset).matchAll(LINE_ENDING)) {
138
+ lineStarts.push(start.offset + match.index + match[0].length);
139
+ }
140
+ }
141
+ const anchoredSource = lineStarts.length === segments.length - 1 ? source : undefined;
142
+ return segments.map((segment, k) => {
143
+ if (k === 0) {
144
+ return start;
145
+ }
146
+ if (anchoredSource !== undefined) {
147
+ const lineStart = lineStarts[k - 1]!;
148
+ LINE_PREFIX.lastIndex = lineStart;
149
+ const prefix = LINE_PREFIX.exec(anchoredSource)?.[0].length ?? 0;
150
+ return { line: start.line + k, column: prefix + 1, offset: lineStart + prefix };
151
+ }
152
+ return { line: start.line + k, column: 1, offset: start.offset + segment.index };
153
+ });
154
+ }
155
+
156
+ function exactPoint(point: MdPoint | undefined): ExactPoint | undefined {
157
+ if (
158
+ point === undefined ||
159
+ typeof point.line !== "number" ||
160
+ typeof point.column !== "number" ||
161
+ typeof point.offset !== "number"
162
+ ) {
163
+ return undefined;
164
+ }
165
+ return { line: point.line, column: point.column, offset: point.offset };
166
+ }
@@ -293,6 +293,13 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
293
293
  * attachment, and navigation callbacks are not activated.
294
294
  */
295
295
  surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
296
+ /**
297
+ * Whether this viewer may read the workspace machine fleet
298
+ * (`GET /machines`, permission `enrollments:read`). Defaults to true. Pass
299
+ * false for a viewer without it: the dock then never requests the fleet and
300
+ * the machine chip derives from the session's own capabilities alone.
301
+ */
302
+ machinesEnabled?: boolean | undefined;
296
303
  /** Override the source-driven default tab (e.g. a host landing tab id). When
297
304
  * omitted the workbench picks Changes-vs-Files from capture or live Git. */
298
305
  initialTab?: string | null | undefined;
@@ -397,6 +404,7 @@ export function useSandboxWorkspaceTabs(
397
404
  const initialTab = options.initialTab ?? null;
398
405
  const activeTab = options.activeTab ?? initialTab;
399
406
  const workspaceVisible = options.workspaceVisible ?? true;
407
+ const machinesEnabled = options.machinesEnabled ?? true;
400
408
  const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
401
409
  const surfaceSet = new Set<SandboxWorkspaceSurface>(requestedSurfaces);
402
410
  const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
@@ -465,7 +473,7 @@ export function useSandboxWorkspaceTabs(
465
473
  workspaceId,
466
474
  sessionId,
467
475
  pollIntervalMs: MACHINES_SESSION_POLL_MS,
468
- enabled: machineSurfaceEnabled,
476
+ enabled: machineSurfaceEnabled && machinesEnabled,
469
477
  });
470
478
  const activeMachine: MachineView | null =
471
479
  machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ??
@@ -995,6 +1003,12 @@ export type SandboxWorkspaceProps = ClientOverride & {
995
1003
  * Terminal, Browser, and Desktop. Disabled surfaces remain behaviorally dormant.
996
1004
  */
997
1005
  surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
1006
+ /**
1007
+ * Whether this viewer may read the workspace machine fleet
1008
+ * (`GET /machines`, permission `enrollments:read`). Defaults to true. Pass
1009
+ * false for a viewer without it so the dock never requests the fleet.
1010
+ */
1011
+ machinesEnabled?: boolean | undefined;
998
1012
  /** Host tabs injected BEFORE the workbench tabs (e.g. a "Run" landing tab). */
999
1013
  leadingTabs?: WorkspaceTab[] | undefined;
1000
1014
  /** Host tabs injected AFTER the workbench tabs (e.g. a "Debug" tab). */
@@ -1060,6 +1074,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1060
1074
  events,
1061
1075
  primary,
1062
1076
  surfaces,
1077
+ machinesEnabled,
1063
1078
  leadingTabs,
1064
1079
  trailingTabs,
1065
1080
  initialTab,
@@ -1180,6 +1195,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
1180
1195
  sessionId,
1181
1196
  events,
1182
1197
  ...(surfaces ? { surfaces } : {}),
1198
+ ...(machinesEnabled !== undefined ? { machinesEnabled } : {}),
1183
1199
  ...(initialTab ? { initialTab } : {}),
1184
1200
  activeTab: activeTabHint,
1185
1201
  workspaceVisible: collapsed !== true,
@@ -26,6 +26,8 @@ import { ChildSessionLink } from "./child-session-link";
26
26
  *
27
27
  * Inbox and queue stay separate segments. Queue hover actions wire to
28
28
  * `UseTurnQueueResult` (`editTurn` / `steerTurn` / `moveTurn` / `removeTurn`).
29
+ * The collapsed queue chip previews the latest queued message beside its Steer
30
+ * action; open queue rows keep Steer visible and reveal the rest on hover.
29
31
  * Inbox has no product dismiss API; pass `onDismissIncoming` when the host
30
32
  * wants a visible action (dev harness may use a local dummy).
31
33
  *
@@ -75,6 +77,13 @@ import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
75
77
  import { cn } from "../lib/cn";
76
78
  import { useErrorMessage } from "../lib/error-message";
77
79
  import { formatClockTime } from "../lib/format";
80
+ import {
81
+ countAuthoritativeQueuedTurns,
82
+ isAuthoritativeQueuedTurn,
83
+ QUEUE_REPLACE_DRAFT_COPY,
84
+ queuedTurnPresentation,
85
+ queueNeighborAnchors,
86
+ } from "../queue-presentation-model";
78
87
  import { requestQueueDraftEdit } from "./queue-draft-policy";
79
88
  import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
80
89
  import { TimelineAnnotationsChip, type TimelineAnnotationLike } from "./timeline-annotations";
@@ -143,11 +152,6 @@ type GoalPillState =
143
152
  | "invariant_broken"
144
153
  | "completed";
145
154
 
146
- type QueuedTurnPresentation = {
147
- kind: "prompt" | "realtime_voice" | "realtime_voice_handoff";
148
- text: string;
149
- };
150
-
151
155
  const GOAL_LABEL: Record<GoalPillState, string> = {
152
156
  pursuing: "Pursuing",
153
157
  waiting: "Waiting",
@@ -252,35 +256,7 @@ export function sessionChromeGoalPillExplanation(
252
256
  return null;
253
257
  }
254
258
 
255
- function queuedTurnPresentation(turn: SessionTurn): QueuedTurnPresentation {
256
- const realtimeDelegation = objectValue(turn.metadata.realtimeDelegation);
257
- const inputTranscript = realtimeDelegation?.inputTranscript;
258
- if (typeof inputTranscript === "string" && inputTranscript.trim()) {
259
- return { kind: "realtime_voice", text: inputTranscript.trim() };
260
- }
261
- if (objectValue(turn.metadata.realtimeTailFlush)) {
262
- return { kind: "realtime_voice_handoff", text: "Remaining voice context" };
263
- }
264
- return { kind: "prompt", text: turn.prompt };
265
- }
266
-
267
- function isSteeringTurn(turn: SessionTurn): boolean {
268
- return turn.metadata.delivery === "steer";
269
- }
270
-
271
- function isAuthoritativeQueuedTurn(
272
- turn: SessionTurn,
273
- mutationFor: UseTurnQueueResult["mutationFor"],
274
- ): boolean {
275
- return !isSteeringTurn(turn) && mutationFor(turn.id) !== "steer";
276
- }
277
-
278
- export function countAuthoritativeQueuedTurns(
279
- turns: readonly SessionTurn[],
280
- mutationFor: UseTurnQueueResult["mutationFor"],
281
- ): number {
282
- return turns.filter((turn) => isAuthoritativeQueuedTurn(turn, mutationFor)).length;
283
- }
259
+ export { countAuthoritativeQueuedTurns };
284
260
 
285
261
  function isOptimisticQueuedMessage(
286
262
  message: ComposerOptimisticMessage,
@@ -344,12 +320,6 @@ export function sessionChromeShouldOfferQueue(input: {
344
320
  );
345
321
  }
346
322
 
347
- function objectValue(value: unknown): Record<string, unknown> | null {
348
- return value !== null && typeof value === "object" && !Array.isArray(value)
349
- ? (value as Record<string, unknown>)
350
- : null;
351
- }
352
-
353
323
  /** Select pill state from the goal's authoritative continuation projection. */
354
324
  export function sessionChromeGoalPillState(
355
325
  goalStatus: "active" | "paused" | "completed",
@@ -831,6 +801,22 @@ export function SessionChrome({
831
801
  }, [active, signals]);
832
802
 
833
803
  const open = active !== null;
804
+ // Collapsed queue chip shows the latest queued message so a Send that lands
805
+ // in the queue is visible without opening the list; Steer acts on that
806
+ // message once the server has confirmed it.
807
+ const latestOptimisticQueued = optimisticQueued[optimisticQueued.length - 1];
808
+ const latestQueuedTurn = queuedTurns[queuedTurns.length - 1];
809
+ const queuePeek: { text: string | null; turnId: string | null } | null =
810
+ queue.error || queue.mutationError
811
+ ? null
812
+ : latestOptimisticQueued
813
+ ? { text: latestOptimisticQueued.text.trim() || null, turnId: null }
814
+ : latestQueuedTurn
815
+ ? {
816
+ text: queuedTurnPresentation(latestQueuedTurn).text.trim() || null,
817
+ turnId: latestQueuedTurn.id,
818
+ }
819
+ : null;
834
820
 
835
821
  if (signals.length === 0 && !stopping) return null;
836
822
 
@@ -1056,7 +1042,14 @@ export function SessionChrome({
1056
1042
  <span className={cn("shrink-0", toneClass(signal.tone, selected))}>
1057
1043
  {signal.icon}
1058
1044
  </span>
1059
- <span className={cn("font-medium text-og-fg", "min-w-0 truncate")}>
1045
+ <span
1046
+ className={cn(
1047
+ "font-medium text-og-fg",
1048
+ signal.id === "queue" && queuePeek?.text && !selected
1049
+ ? "shrink-0"
1050
+ : "min-w-0 truncate",
1051
+ )}
1052
+ >
1060
1053
  {signal.id === "goal" ? "Goal · " : null}
1061
1054
  {signal.id === "goal"
1062
1055
  ? goalState === "pursuing"
@@ -1068,6 +1061,21 @@ export function SessionChrome({
1068
1061
  : `${queuedTurns.length + optimisticQueued.length} queued`
1069
1062
  : signal.label}
1070
1063
  </span>
1064
+ {signal.id === "queue" && queuePeek?.text && !selected ? (
1065
+ <AnimatePresence initial={false} mode="popLayout">
1066
+ <motion.span
1067
+ key={queuePeek.text}
1068
+ data-testid="session-chrome-queue-peek"
1069
+ className="min-w-0 max-w-[min(24rem,60vw)] truncate text-og-fg-muted"
1070
+ initial={reduceMotion ? false : { opacity: 0, y: 3 }}
1071
+ animate={{ opacity: 1, y: 0 }}
1072
+ exit={{ opacity: 0, transition: { duration: crossfadeDuration } }}
1073
+ transition={{ duration: crossfadeDuration, ease }}
1074
+ >
1075
+ {queuePeek.text}
1076
+ </motion.span>
1077
+ </AnimatePresence>
1078
+ ) : null}
1071
1079
  </button>
1072
1080
  {signal.id === "goal" && goal && record && !readOnly ? (
1073
1081
  <div className="flex shrink-0 items-center pr-1 pl-0.5">
@@ -1101,19 +1109,27 @@ export function SessionChrome({
1101
1109
  <Trash2Icon className="size-3" />
1102
1110
  </IconAction>
1103
1111
  </div>
1104
- ) : signal.id === "queue" && canMutateQueue && queuedTurns[0] ? (
1112
+ ) : signal.id === "queue" && canMutateQueue && queuePeek && !selected ? (
1105
1113
  <div className="flex shrink-0 items-center pr-1 pl-0.5">
1106
1114
  <IconAction
1107
- label="Steer first queued message"
1115
+ label="Steer latest queued message"
1108
1116
  analyticsAction="steer"
1109
1117
  text="Steer"
1110
1118
  tip={QUEUE_STEER_TIP}
1111
1119
  disabled={
1112
- queue.mutating || Boolean(queue.mutationFor(queuedTurns[0].id))
1120
+ !queuePeek.turnId ||
1121
+ queue.mutating ||
1122
+ Boolean(queue.mutationFor(queuePeek.turnId))
1113
1123
  }
1114
- onClick={() => void queue.steerTurn(queuedTurns[0]!.id)}
1124
+ onClick={() => {
1125
+ if (queuePeek.turnId) void queue.steerTurn(queuePeek.turnId);
1126
+ }}
1115
1127
  >
1116
- <CornerDownRightIcon className="size-3" />
1128
+ {queuePeek.turnId && queue.mutationFor(queuePeek.turnId) === "steer" ? (
1129
+ <Loader2Icon className="size-3 animate-og-spin" />
1130
+ ) : (
1131
+ <CornerDownRightIcon className="size-3" />
1132
+ )}
1117
1133
  </IconAction>
1118
1134
  </div>
1119
1135
  ) : null}
@@ -1426,8 +1442,7 @@ function QueuePanel({
1426
1442
  const voice = presentation.kind !== "prompt";
1427
1443
  const pending = mutationFor(turn.id);
1428
1444
  const settling = !interactiveTurnIds.has(turn.id);
1429
- const beforeUp = index > 0 ? (turns[index - 1]?.id ?? null) : null;
1430
- const beforeDown = index < turns.length - 1 ? (turns[index + 2]?.id ?? null) : null;
1445
+ const { beforeUp, beforeDown } = queueNeighborAnchors(turns, index);
1431
1446
  const showActions = !readOnly && (onEdit || onSteer || onRemove || onMove);
1432
1447
  const confirmingReplace = replaceDraftFor === turn.id;
1433
1448
  return (
@@ -1453,27 +1468,59 @@ function QueuePanel({
1453
1468
  annotations={turn.annotations ?? []}
1454
1469
  />
1455
1470
  {showActions ? (
1456
- <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
1457
- {onMove && turns.length > 1 ? (
1458
- <>
1471
+ <div className="flex shrink-0 items-center gap-0.5">
1472
+ {/* Reorder, edit and delete stay on hover; Steer is always visible. */}
1473
+ <div className="flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 max-sm:opacity-100">
1474
+ {onMove && turns.length > 1 ? (
1475
+ <>
1476
+ <IconAction
1477
+ label={`Move queued prompt ${index + 1} up`}
1478
+ tip="Move up"
1479
+ disabled={settling || pending !== null || index === 0}
1480
+ onClick={() => onMove(turn.id, beforeUp)}
1481
+ >
1482
+ <ArrowUpIcon className="size-3" />
1483
+ </IconAction>
1484
+ <IconAction
1485
+ label={`Move queued prompt ${index + 1} down`}
1486
+ tip="Move down"
1487
+ disabled={settling || pending !== null || index >= turns.length - 1}
1488
+ onClick={() => onMove(turn.id, beforeDown)}
1489
+ >
1490
+ <ArrowDownIcon className="size-3" />
1491
+ </IconAction>
1492
+ </>
1493
+ ) : null}
1494
+ {onEdit ? (
1459
1495
  <IconAction
1460
- label={`Move queued prompt ${index + 1} up`}
1461
- tip="Move up"
1462
- disabled={settling || pending !== null || index === 0}
1463
- onClick={() => onMove(turn.id, beforeUp)}
1496
+ label={`Edit queued prompt ${index + 1}`}
1497
+ tip={QUEUE_EDIT_TIP}
1498
+ disabled={settling || pending !== null}
1499
+ onClick={() => onEdit(turn)}
1464
1500
  >
1465
- <ArrowUpIcon className="size-3" />
1501
+ {pending === "edit" ? (
1502
+ <Loader2Icon className="size-3 animate-og-spin" />
1503
+ ) : (
1504
+ <PencilIcon className="size-3" />
1505
+ )}
1466
1506
  </IconAction>
1507
+ ) : null}
1508
+ {onRemove ? (
1467
1509
  <IconAction
1468
- label={`Move queued prompt ${index + 1} down`}
1469
- tip="Move down"
1470
- disabled={settling || pending !== null || index >= turns.length - 1}
1471
- onClick={() => onMove(turn.id, beforeDown)}
1510
+ label={`Remove queued prompt ${index + 1}`}
1511
+ tip={QUEUE_DELETE_TIP}
1512
+ disabled={settling || pending !== null}
1513
+ onClick={() => onRemove(turn.id)}
1514
+ danger
1472
1515
  >
1473
- <ArrowDownIcon className="size-3" />
1516
+ {pending === "delete" ? (
1517
+ <Loader2Icon className="size-3 animate-og-spin" />
1518
+ ) : (
1519
+ <Trash2Icon className="size-3" />
1520
+ )}
1474
1521
  </IconAction>
1475
- </>
1476
- ) : null}
1522
+ ) : null}
1523
+ </div>
1477
1524
  {onSteer ? (
1478
1525
  <IconAction
1479
1526
  label={`Steer queued prompt ${index + 1}`}
@@ -1490,59 +1537,27 @@ function QueuePanel({
1490
1537
  )}
1491
1538
  </IconAction>
1492
1539
  ) : null}
1493
- {onEdit ? (
1494
- <IconAction
1495
- label={`Edit queued prompt ${index + 1}`}
1496
- tip={QUEUE_EDIT_TIP}
1497
- disabled={settling || pending !== null}
1498
- onClick={() => onEdit(turn)}
1499
- >
1500
- {pending === "edit" ? (
1501
- <Loader2Icon className="size-3 animate-og-spin" />
1502
- ) : (
1503
- <PencilIcon className="size-3" />
1504
- )}
1505
- </IconAction>
1506
- ) : null}
1507
- {onRemove ? (
1508
- <IconAction
1509
- label={`Remove queued prompt ${index + 1}`}
1510
- tip={QUEUE_DELETE_TIP}
1511
- disabled={settling || pending !== null}
1512
- onClick={() => onRemove(turn.id)}
1513
- danger
1514
- >
1515
- {pending === "delete" ? (
1516
- <Loader2Icon className="size-3 animate-og-spin" />
1517
- ) : (
1518
- <Trash2Icon className="size-3" />
1519
- )}
1520
- </IconAction>
1521
- ) : null}
1522
1540
  </div>
1523
1541
  ) : null}
1524
1542
  </div>
1525
1543
  {confirmingReplace ? (
1526
1544
  <div className="rounded-og-sm border border-og-status-waiting/30 bg-og-status-waiting/10 p-2 text-og-xs text-og-fg">
1527
- <p>Your composer already has a draft. Replace it with this queued prompt?</p>
1528
- <p className="mt-0.5 text-og-fg-muted">
1529
- The current draft will be permanently discarded; this queued prompt is preserved
1530
- until you confirm.
1531
- </p>
1545
+ <p>{QUEUE_REPLACE_DRAFT_COPY.title}</p>
1546
+ <p className="mt-0.5 text-og-fg-muted">{QUEUE_REPLACE_DRAFT_COPY.detail}</p>
1532
1547
  <div className="mt-2 flex justify-end gap-1.5">
1533
1548
  <button
1534
1549
  type="button"
1535
1550
  className="rounded-og-sm px-2 py-1 font-medium hover:bg-og-surface-3/70 focus-visible:ring-2 focus-visible:ring-og-accent/40"
1536
1551
  onClick={onCancelReplace}
1537
1552
  >
1538
- Keep current draft
1553
+ {QUEUE_REPLACE_DRAFT_COPY.keep}
1539
1554
  </button>
1540
1555
  <button
1541
1556
  type="button"
1542
1557
  className="rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-2 py-1 font-medium hover:bg-og-primary-hover focus-visible:ring-2 focus-visible:ring-og-accent/40"
1543
1558
  onClick={onConfirmReplace}
1544
1559
  >
1545
- Replace and edit
1560
+ {QUEUE_REPLACE_DRAFT_COPY.replace}
1546
1561
  </button>
1547
1562
  </div>
1548
1563
  </div>