@opengeni/react 7.4.0 → 7.5.0-canary.36919050519001

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 (210) hide show
  1. package/README.md +124 -27
  2. package/dist/artifacts-document.js +3 -2
  3. package/dist/artifacts-presentation.js +3 -2
  4. package/dist/artifacts-spreadsheet.js +2 -2
  5. package/dist/artifacts.d.ts +1 -0
  6. package/dist/artifacts.js +516 -1413
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/{browser-viewer-2E6R2Y4W.js → browser-viewer-Z7S7QEJL.js} +6 -5
  9. package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
  10. package/dist/{chunk-Y3QTZN2F.js → chunk-2VFNCX4E.js} +6 -6
  11. package/dist/{chunk-UDD6DLLT.js → chunk-3IJJEYW4.js} +15 -3
  12. package/dist/chunk-3IJJEYW4.js.map +1 -0
  13. package/dist/chunk-4RVYOY4O.js +379 -0
  14. package/dist/chunk-4RVYOY4O.js.map +1 -0
  15. package/dist/chunk-5CVXZA3Y.js +292 -0
  16. package/dist/chunk-5CVXZA3Y.js.map +1 -0
  17. package/dist/{chunk-5YK2ZDIG.js → chunk-5J4FOW6Q.js} +2 -10
  18. package/dist/chunk-5J4FOW6Q.js.map +1 -0
  19. package/dist/{chunk-KVERXCFY.js → chunk-5KYZIQFY.js} +40 -3
  20. package/dist/chunk-5KYZIQFY.js.map +1 -0
  21. package/dist/{chunk-KNDIXZN6.js → chunk-7UAMYXJY.js} +25 -18
  22. package/dist/{chunk-KNDIXZN6.js.map → chunk-7UAMYXJY.js.map} +1 -1
  23. package/dist/{chunk-SEEVCT36.js → chunk-A4UJB2MM.js} +2 -2
  24. package/dist/{chunk-X4HIN6TV.js → chunk-AFLPWUQ7.js} +71 -162
  25. package/dist/chunk-AFLPWUQ7.js.map +1 -0
  26. package/dist/chunk-B3V6TG77.js +41 -0
  27. package/dist/chunk-B3V6TG77.js.map +1 -0
  28. package/dist/{chunk-XEJEOAOV.js → chunk-B7WMS3CS.js} +51 -40
  29. package/dist/chunk-B7WMS3CS.js.map +1 -0
  30. package/dist/{chunk-S5JQ4R22.js → chunk-BDKR6BAR.js} +54 -24
  31. package/dist/chunk-BDKR6BAR.js.map +1 -0
  32. package/dist/{chunk-4MDLEHXL.js → chunk-DYWBUX5Q.js} +24 -12
  33. package/dist/chunk-DYWBUX5Q.js.map +1 -0
  34. package/dist/{chunk-YZ3YYMOM.js → chunk-E3G7SAYZ.js} +252 -624
  35. package/dist/chunk-E3G7SAYZ.js.map +1 -0
  36. package/dist/chunk-JT4JW7BC.js +54 -0
  37. package/dist/chunk-JT4JW7BC.js.map +1 -0
  38. package/dist/{chunk-5AZSK6MV.js → chunk-LWNLETQT.js} +5 -5
  39. package/dist/{chunk-USXVQACU.js → chunk-M7LBYP63.js} +5 -4
  40. package/dist/{chunk-USXVQACU.js.map → chunk-M7LBYP63.js.map} +1 -1
  41. package/dist/{chunk-L4OSXQ6H.js → chunk-NBOHNRLJ.js} +1289 -1060
  42. package/dist/chunk-NBOHNRLJ.js.map +1 -0
  43. package/dist/chunk-RHREZ7BO.js +11 -0
  44. package/dist/chunk-RHREZ7BO.js.map +1 -0
  45. package/dist/chunk-SCOTPE2I.js +589 -0
  46. package/dist/chunk-SCOTPE2I.js.map +1 -0
  47. package/dist/chunk-WEHMPOXK.js +39 -0
  48. package/dist/chunk-WEHMPOXK.js.map +1 -0
  49. package/dist/chunk-WRHIZYZF.js +988 -0
  50. package/dist/chunk-WRHIZYZF.js.map +1 -0
  51. package/dist/{chunk-3E2AITTZ.js → chunk-WUISZ3WU.js} +32 -28
  52. package/dist/{chunk-3E2AITTZ.js.map → chunk-WUISZ3WU.js.map} +1 -1
  53. package/dist/chunk-XCHWDJ22.js +107 -0
  54. package/dist/chunk-XCHWDJ22.js.map +1 -0
  55. package/dist/{chunk-DUEBTL3N.js → chunk-Y2PRMYLC.js} +4 -3
  56. package/dist/{chunk-DUEBTL3N.js.map → chunk-Y2PRMYLC.js.map} +1 -1
  57. package/dist/chunk-YBXQDQFX.js +1772 -0
  58. package/dist/chunk-YBXQDQFX.js.map +1 -0
  59. package/dist/chunk-YV4L3ILF.js +9 -0
  60. package/dist/chunk-YV4L3ILF.js.map +1 -0
  61. package/dist/{chunk-V2ABIVYJ.js → chunk-ZXSEDZP7.js} +70 -6
  62. package/dist/chunk-ZXSEDZP7.js.map +1 -0
  63. package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
  64. package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
  65. package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
  66. package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
  67. package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
  68. package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
  69. package/dist/components/artifacts/index.d.ts +7 -0
  70. package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
  71. package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
  72. package/dist/components/artifacts/site-view.d.ts +23 -0
  73. package/dist/components/browser-select-control.d.ts +3 -1
  74. package/dist/components/code-editor.d.ts +1 -4
  75. package/dist/components/desktop-viewer.d.ts +1 -1
  76. package/dist/components/message-timeline.d.ts +20 -3
  77. package/dist/components/open-geni-links.d.ts +16 -0
  78. package/dist/components/session-conversation.d.ts +20 -1
  79. package/dist/components/timeline-pill-geometry.d.ts +8 -0
  80. package/dist/components/timeline-pill-placement.d.ts +24 -0
  81. package/dist/components/usage-limit-notice.d.ts +49 -0
  82. package/dist/components/usage-member-list.d.ts +91 -0
  83. package/dist/components/usage-meter.d.ts +73 -0
  84. package/dist/composer.js +5 -5
  85. package/dist/{computer-viewer-WMJE6OA3.js → computer-viewer-KJWLH3GI.js} +7 -5
  86. package/dist/connect.js +7 -6
  87. package/dist/connect.js.map +1 -1
  88. package/dist/conversation-timeline.d.ts +1 -1
  89. package/dist/desktop.d.ts +7 -0
  90. package/dist/desktop.js +26 -0
  91. package/dist/desktop.js.map +1 -0
  92. package/dist/editor.d.ts +7 -0
  93. package/dist/editor.js +21 -0
  94. package/dist/editor.js.map +1 -0
  95. package/dist/hooks/latest-question.d.ts +1 -1
  96. package/dist/hooks/use-composer.d.ts +4 -0
  97. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  98. package/dist/hooks/use-usage.d.ts +40 -0
  99. package/dist/index.d.ts +7 -2
  100. package/dist/index.js +460 -1386
  101. package/dist/index.js.map +1 -1
  102. package/dist/interaction.js +8 -6
  103. package/dist/interaction.js.map +1 -1
  104. package/dist/{latest-question-QB4EUCW5.js → latest-question-IS445HOW.js} +3 -3
  105. package/dist/lib/format.d.ts +6 -1
  106. package/dist/lib/workbench-peers.d.ts +36 -0
  107. package/dist/machines.js +2 -2
  108. package/dist/{markdown-table-layout-PFUEZ2U4.js → markdown-table-layout-QXSLK4BY.js} +37 -5
  109. package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
  110. package/dist/{queue-surface-implementation-IM2HD3SK.js → queue-surface-implementation-2CAQAIHM.js} +2 -2
  111. package/dist/realtime.js +3 -3
  112. package/dist/{sandbox-files-PXPIZAFG.js → sandbox-files-AEJTWGT4.js} +8 -5
  113. package/dist/sandbox-files-AEJTWGT4.js.map +1 -0
  114. package/dist/session-ui.d.ts +9 -2
  115. package/dist/session-ui.js +39 -20
  116. package/dist/session-ui.js.map +1 -1
  117. package/dist/session.js +4 -4
  118. package/dist/sites.js +4 -289
  119. package/dist/sites.js.map +1 -1
  120. package/dist/terminal.d.ts +8 -0
  121. package/dist/terminal.js +31 -0
  122. package/dist/terminal.js.map +1 -0
  123. package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
  124. package/dist/timeline/genie-loading.d.ts +3 -1
  125. package/dist/timeline/types.d.ts +8 -1
  126. package/dist/{timeline-annotation-selection-ODLVF4OI.js → timeline-annotation-selection-DMLMM2TX.js} +6 -1
  127. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
  128. package/dist/timeline-pill-geometry-Z23V243M.js +110 -0
  129. package/dist/timeline-pill-geometry-Z23V243M.js.map +1 -0
  130. package/dist/usage/allowance-copy.d.ts +25 -0
  131. package/dist/usage/summary.d.ts +35 -0
  132. package/dist/usage.d.ts +13 -0
  133. package/dist/usage.js +1073 -0
  134. package/dist/usage.js.map +1 -0
  135. package/package.json +19 -3
  136. package/src/artifacts.ts +37 -0
  137. package/src/components/artifacts/artifact-chrome.tsx +413 -0
  138. package/src/components/artifacts/artifact-sandbox.tsx +185 -0
  139. package/src/components/artifacts/chat-interactive-block.tsx +260 -0
  140. package/src/components/artifacts/deferred-chat-media.tsx +67 -0
  141. package/src/components/artifacts/editable-artifact-ui.tsx +3 -1
  142. package/src/components/artifacts/editable-artifact-view.tsx +187 -0
  143. package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
  144. package/src/components/artifacts/editable-document.tsx +34 -36
  145. package/src/components/artifacts/editable-presentation.tsx +21 -16
  146. package/src/components/artifacts/index.ts +41 -0
  147. package/src/components/artifacts/latest-task-runner.ts +49 -0
  148. package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
  149. package/src/components/artifacts/site-view.tsx +116 -0
  150. package/src/components/browser-select-control.tsx +4 -1
  151. package/src/components/browser-viewer.tsx +81 -22
  152. package/src/components/code-editor.tsx +13 -16
  153. package/src/components/computer-viewer.tsx +51 -23
  154. package/src/components/desktop-viewer.tsx +1 -1
  155. package/src/components/markdown-table-layout.ts +49 -5
  156. package/src/components/markdown.tsx +5 -2
  157. package/src/components/message-timeline.tsx +557 -516
  158. package/src/components/open-geni-links.tsx +24 -0
  159. package/src/components/sandbox-terminal.tsx +48 -11
  160. package/src/components/session-chrome.tsx +22 -3
  161. package/src/components/session-conversation.tsx +195 -102
  162. package/src/components/timeline-annotation-selection.tsx +7 -0
  163. package/src/components/timeline-pill-geometry.ts +169 -0
  164. package/src/components/timeline-pill-placement.ts +99 -0
  165. package/src/components/usage-limit-notice.tsx +179 -0
  166. package/src/components/usage-member-list.tsx +792 -0
  167. package/src/components/usage-meter.tsx +382 -0
  168. package/src/conversation-timeline.ts +10 -2
  169. package/src/desktop.ts +24 -0
  170. package/src/editor.ts +13 -0
  171. package/src/hooks/use-available-models.ts +1 -1
  172. package/src/hooks/use-composer.ts +57 -2
  173. package/src/hooks/use-desktop-stream.ts +19 -28
  174. package/src/hooks/use-usage.ts +100 -0
  175. package/src/index.ts +22 -0
  176. package/src/lib/format.ts +32 -6
  177. package/src/lib/workbench-peers.ts +62 -0
  178. package/src/session-ui.ts +22 -0
  179. package/src/terminal.ts +26 -0
  180. package/src/timeline/activity-rail.tsx +5 -3
  181. package/src/timeline/allowance-exhausted-row.tsx +86 -0
  182. package/src/timeline/genie-loading.tsx +16 -6
  183. package/src/timeline/projection.ts +78 -4
  184. package/src/timeline/types.ts +8 -0
  185. package/src/usage/allowance-copy.ts +64 -0
  186. package/src/usage/summary.ts +93 -0
  187. package/src/usage.ts +50 -0
  188. package/styles/compiled.css +325 -0
  189. package/dist/chunk-4MDLEHXL.js.map +0 -1
  190. package/dist/chunk-5YK2ZDIG.js.map +0 -1
  191. package/dist/chunk-KVERXCFY.js.map +0 -1
  192. package/dist/chunk-L4OSXQ6H.js.map +0 -1
  193. package/dist/chunk-S5JQ4R22.js.map +0 -1
  194. package/dist/chunk-UDD6DLLT.js.map +0 -1
  195. package/dist/chunk-UGMKPUEM.js +0 -299
  196. package/dist/chunk-UGMKPUEM.js.map +0 -1
  197. package/dist/chunk-V2ABIVYJ.js.map +0 -1
  198. package/dist/chunk-X4HIN6TV.js.map +0 -1
  199. package/dist/chunk-XEJEOAOV.js.map +0 -1
  200. package/dist/chunk-YZ3YYMOM.js.map +0 -1
  201. package/dist/markdown-table-layout-PFUEZ2U4.js.map +0 -1
  202. package/dist/timeline-annotation-selection-ODLVF4OI.js.map +0 -1
  203. /package/dist/{browser-viewer-2E6R2Y4W.js.map → browser-viewer-Z7S7QEJL.js.map} +0 -0
  204. /package/dist/{computer-viewer-WMJE6OA3.js.map → chat-interactive-block-UGBTNB3L.js.map} +0 -0
  205. /package/dist/{chunk-Y3QTZN2F.js.map → chunk-2VFNCX4E.js.map} +0 -0
  206. /package/dist/{chunk-SEEVCT36.js.map → chunk-A4UJB2MM.js.map} +0 -0
  207. /package/dist/{chunk-5AZSK6MV.js.map → chunk-LWNLETQT.js.map} +0 -0
  208. /package/dist/{sandbox-files-PXPIZAFG.js.map → computer-viewer-KJWLH3GI.js.map} +0 -0
  209. /package/dist/{latest-question-QB4EUCW5.js.map → latest-question-IS445HOW.js.map} +0 -0
  210. /package/dist/{queue-surface-implementation-IM2HD3SK.js.map → queue-surface-implementation-2CAQAIHM.js.map} +0 -0
@@ -0,0 +1,988 @@
1
+ import {
2
+ sandboxAcceptsLiveIo
3
+ } from "./chunk-YV4L3ILF.js";
4
+ import {
5
+ STREAM_KIND_PTY,
6
+ STREAM_ROLE_CLIENT,
7
+ decodeStreamFrame,
8
+ decodeStreamOpenAck,
9
+ encodeStreamFrame,
10
+ encodeStreamOpen
11
+ } from "./chunk-5J4FOW6Q.js";
12
+ import {
13
+ sandboxTerminalPeers
14
+ } from "./chunk-JT4JW7BC.js";
15
+ import {
16
+ cn
17
+ } from "./chunk-22KP6LR5.js";
18
+
19
+ // src/hooks/use-terminal-stream.ts
20
+ import {
21
+ TTYD_SUBPROTOCOL,
22
+ terminalSocketUrl,
23
+ ttydAuthFrame,
24
+ ttydInputFrame,
25
+ ttydResizeFrame,
26
+ TtydServerCommand
27
+ } from "@opengeni/sdk";
28
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
29
+ var MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;
30
+ var TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5e3;
31
+ var TERMINAL_RECONNECT_INITIAL_MS = 100;
32
+ var TERMINAL_RECONNECT_MAX_MS = 3e3;
33
+ var RELAY_TAG_OPEN = 1;
34
+ var RELAY_TAG_OPEN_ACK = 2;
35
+ var RELAY_TAG_FRAME = 3;
36
+ function relayDatagram(tag, body) {
37
+ const bytes = new Uint8Array(body.length + 1);
38
+ bytes[0] = tag;
39
+ bytes.set(body, 1);
40
+ return bytes.buffer;
41
+ }
42
+ function relayChannelFromUrl(url) {
43
+ const endpoint = new URL(url);
44
+ const channel = {
45
+ channelId: endpoint.searchParams.get("channel") ?? "",
46
+ workspaceId: endpoint.searchParams.get("ws") ?? "",
47
+ agentId: endpoint.searchParams.get("agent") ?? "",
48
+ kind: STREAM_KIND_PTY,
49
+ port: Number(endpoint.searchParams.get("port") ?? "0")
50
+ };
51
+ if (!channel.channelId || !channel.workspaceId || !channel.agentId || !Number.isSafeInteger(channel.port) || channel.port <= 0) {
52
+ throw new Error("terminal relay endpoint is incomplete");
53
+ }
54
+ return channel;
55
+ }
56
+ function sendRelayInput(socket, channel, sequence, data) {
57
+ socket.send(
58
+ relayDatagram(
59
+ RELAY_TAG_FRAME,
60
+ encodeStreamFrame({
61
+ channelId: channel.channelId,
62
+ seq: sequence.toString(),
63
+ data: new TextEncoder().encode(data),
64
+ producedAtMs: String(Date.now())
65
+ })
66
+ )
67
+ );
68
+ }
69
+ function terminalStreamCredentialUsable(capability, nowMs = Date.now()) {
70
+ if (capability?.transport !== "pty-ws" && capability?.transport !== "relay-pty" || !capability.url) {
71
+ return false;
72
+ }
73
+ if (!capability.expiresAt) return true;
74
+ const expiresAt = Date.parse(capability.expiresAt);
75
+ return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;
76
+ }
77
+ function decodeFrame(data) {
78
+ if (typeof data === "string") {
79
+ return { command: data.charAt(0), payload: data.slice(1) };
80
+ }
81
+ const bytes = new Uint8Array(data);
82
+ const command = bytes.length > 0 ? String.fromCharCode(bytes[0]) : "";
83
+ const payload = bytes.length > 1 ? new TextDecoder().decode(bytes.subarray(1)) : "";
84
+ return { command, payload };
85
+ }
86
+ function closeSocket(socket) {
87
+ try {
88
+ socket?.close();
89
+ } catch {
90
+ }
91
+ }
92
+ function useTerminalStream(options) {
93
+ const { capability, onOutput, onTitle, onReconnectNeeded, initialCols, initialRows } = options;
94
+ const [status, setStatus] = useState("closed");
95
+ const [reconnectGeneration, setReconnectGeneration] = useState(0);
96
+ const wsRef = useRef(null);
97
+ const pendingInputRef = useRef("");
98
+ const socketFailedRef = useRef(false);
99
+ const socketAuthenticatedRef = useRef(false);
100
+ const relayChannelRef = useRef(null);
101
+ const relayIdentityRef = useRef(null);
102
+ const relayInputSequenceRef = useRef(0n);
103
+ const relayOutputSequenceRef = useRef(0n);
104
+ const reconnectAttemptRef = useRef(0);
105
+ const credentialIdentityRef = useRef(null);
106
+ const refreshRequestedRef = useRef(false);
107
+ const intentionalCloseRef = useRef(/* @__PURE__ */ new WeakSet());
108
+ const sizeRef = useRef({
109
+ cols: initialCols ?? 80,
110
+ rows: initialRows ?? 24
111
+ });
112
+ const onOutputRef = useRef(onOutput);
113
+ const onTitleRef = useRef(onTitle);
114
+ const onReconnectNeededRef = useRef(onReconnectNeeded);
115
+ onOutputRef.current = onOutput;
116
+ onTitleRef.current = onTitle;
117
+ onReconnectNeededRef.current = onReconnectNeeded;
118
+ const transport = capability?.transport ?? null;
119
+ const url = capability?.url ?? null;
120
+ const token = capability?.token ?? null;
121
+ const expiresAt = capability?.expiresAt ?? null;
122
+ const transportRef = useRef(transport);
123
+ transportRef.current = transport;
124
+ useEffect(() => {
125
+ if (typeof window === "undefined" || typeof WebSocket === "undefined") return;
126
+ const relay = transport === "relay-pty";
127
+ if (transport !== "pty-ws" && !relay) {
128
+ pendingInputRef.current = "";
129
+ socketAuthenticatedRef.current = false;
130
+ relayIdentityRef.current = null;
131
+ relayInputSequenceRef.current = 0n;
132
+ relayOutputSequenceRef.current = 0n;
133
+ reconnectAttemptRef.current = 0;
134
+ credentialIdentityRef.current = null;
135
+ refreshRequestedRef.current = false;
136
+ setStatus("closed");
137
+ return;
138
+ }
139
+ const credentialIdentity = `${transport}\0${url ?? ""}\0${token ?? ""}\0${expiresAt ?? ""}`;
140
+ if (credentialIdentityRef.current !== credentialIdentity) {
141
+ credentialIdentityRef.current = credentialIdentity;
142
+ reconnectAttemptRef.current = 0;
143
+ refreshRequestedRef.current = false;
144
+ }
145
+ socketFailedRef.current = false;
146
+ socketAuthenticatedRef.current = false;
147
+ if (!url) {
148
+ setStatus("closed");
149
+ return;
150
+ }
151
+ if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {
152
+ setStatus("closed");
153
+ if (!refreshRequestedRef.current) {
154
+ refreshRequestedRef.current = true;
155
+ onReconnectNeededRef.current?.();
156
+ }
157
+ return;
158
+ }
159
+ let disposed = false;
160
+ let reconnectTimer = null;
161
+ let socket;
162
+ let relayChannel = null;
163
+ const relayOutputDecoder = new TextDecoder();
164
+ setStatus("connecting");
165
+ try {
166
+ if (relay) {
167
+ relayChannel = relayChannelFromUrl(url);
168
+ const relayIdentity = [
169
+ relayChannel.workspaceId,
170
+ relayChannel.agentId,
171
+ relayChannel.port,
172
+ relayChannel.channelId
173
+ ].join("\0");
174
+ if (relayIdentityRef.current !== relayIdentity) {
175
+ relayIdentityRef.current = relayIdentity;
176
+ relayInputSequenceRef.current = 0n;
177
+ relayOutputSequenceRef.current = 0n;
178
+ }
179
+ relayChannelRef.current = relayChannel;
180
+ socket = new WebSocket(url);
181
+ } else {
182
+ socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);
183
+ }
184
+ } catch {
185
+ socketFailedRef.current = true;
186
+ setStatus("error");
187
+ return;
188
+ }
189
+ socket.binaryType = "arraybuffer";
190
+ wsRef.current = socket;
191
+ const requestFreshGrant = () => {
192
+ if (disposed || refreshRequestedRef.current || intentionalCloseRef.current.has(socket))
193
+ return;
194
+ refreshRequestedRef.current = true;
195
+ onReconnectNeededRef.current?.();
196
+ };
197
+ const scheduleTransportReconnect = () => {
198
+ if (disposed || reconnectTimer !== null || intentionalCloseRef.current.has(socket)) return;
199
+ if (refreshRequestedRef.current) return;
200
+ if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {
201
+ requestFreshGrant();
202
+ return;
203
+ }
204
+ const attempt = reconnectAttemptRef.current++;
205
+ const delay = Math.min(
206
+ TERMINAL_RECONNECT_INITIAL_MS * 2 ** Math.min(attempt, 8),
207
+ TERMINAL_RECONNECT_MAX_MS
208
+ );
209
+ setStatus("connecting");
210
+ reconnectTimer = setTimeout(() => {
211
+ reconnectTimer = null;
212
+ if (!disposed) setReconnectGeneration((generation) => generation + 1);
213
+ }, delay);
214
+ };
215
+ socket.onopen = () => {
216
+ if (disposed) return;
217
+ if (relay && relayChannel) {
218
+ try {
219
+ socket.send(
220
+ relayDatagram(
221
+ RELAY_TAG_OPEN,
222
+ encodeStreamOpen({
223
+ channel: relayChannel,
224
+ token: token ?? "",
225
+ role: STREAM_ROLE_CLIENT,
226
+ resumeFromSeq: relayOutputSequenceRef.current.toString()
227
+ })
228
+ )
229
+ );
230
+ } catch {
231
+ socketFailedRef.current = false;
232
+ setStatus("connecting");
233
+ closeSocket(socket);
234
+ }
235
+ return;
236
+ }
237
+ try {
238
+ socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));
239
+ socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));
240
+ if (pendingInputRef.current.length > 0) {
241
+ socket.send(ttydInputFrame(pendingInputRef.current));
242
+ pendingInputRef.current = "";
243
+ }
244
+ socketAuthenticatedRef.current = true;
245
+ } catch {
246
+ socketFailedRef.current = false;
247
+ setStatus("connecting");
248
+ closeSocket(socket);
249
+ return;
250
+ }
251
+ setStatus("open");
252
+ reconnectAttemptRef.current = 0;
253
+ refreshRequestedRef.current = false;
254
+ };
255
+ socket.onmessage = (ev) => {
256
+ if (disposed) return;
257
+ if (relay) {
258
+ if (!(ev.data instanceof ArrayBuffer) || !relayChannel) return;
259
+ const bytes = new Uint8Array(ev.data);
260
+ if (bytes.length < 1) return;
261
+ const tag = bytes[0];
262
+ const body = bytes.subarray(1);
263
+ if (tag === RELAY_TAG_OPEN_ACK) {
264
+ try {
265
+ const ack = decodeStreamOpenAck(body);
266
+ if (!ack.accepted) throw new Error(ack.error?.message ?? "terminal relay rejected");
267
+ reconnectAttemptRef.current = 0;
268
+ refreshRequestedRef.current = false;
269
+ socketAuthenticatedRef.current = true;
270
+ if (pendingInputRef.current.length > 0) {
271
+ sendRelayInput(
272
+ socket,
273
+ relayChannel,
274
+ relayInputSequenceRef.current++,
275
+ pendingInputRef.current
276
+ );
277
+ pendingInputRef.current = "";
278
+ }
279
+ setStatus("open");
280
+ } catch {
281
+ socketFailedRef.current = true;
282
+ setStatus("error");
283
+ requestFreshGrant();
284
+ closeSocket(socket);
285
+ }
286
+ } else if (tag === RELAY_TAG_FRAME && socketAuthenticatedRef.current) {
287
+ try {
288
+ const frame = decodeStreamFrame(body);
289
+ const sequence = BigInt(frame.seq);
290
+ if (sequence < relayOutputSequenceRef.current) return;
291
+ relayOutputSequenceRef.current = sequence + 1n;
292
+ if (frame.data.length > 0) {
293
+ onOutputRef.current?.(relayOutputDecoder.decode(frame.data, { stream: true }));
294
+ }
295
+ } catch {
296
+ }
297
+ }
298
+ return;
299
+ }
300
+ const { command, payload } = decodeFrame(ev.data);
301
+ switch (command) {
302
+ case TtydServerCommand.OUTPUT:
303
+ onOutputRef.current?.(payload);
304
+ break;
305
+ case TtydServerCommand.SET_WINDOW_TITLE:
306
+ onTitleRef.current?.(payload);
307
+ break;
308
+ // SET_PREFERENCES ("2") and anything else: ignored.
309
+ default:
310
+ break;
311
+ }
312
+ };
313
+ socket.onerror = () => {
314
+ if (!disposed) {
315
+ socketFailedRef.current = false;
316
+ setStatus("connecting");
317
+ closeSocket(socket);
318
+ }
319
+ };
320
+ socket.onclose = () => {
321
+ if (wsRef.current === socket) wsRef.current = null;
322
+ socketAuthenticatedRef.current = false;
323
+ if (!disposed) {
324
+ if (socketFailedRef.current) setStatus("error");
325
+ else scheduleTransportReconnect();
326
+ }
327
+ };
328
+ return () => {
329
+ disposed = true;
330
+ wsRef.current = null;
331
+ socketAuthenticatedRef.current = false;
332
+ relayChannelRef.current = null;
333
+ if (reconnectTimer !== null) clearTimeout(reconnectTimer);
334
+ socket.onopen = null;
335
+ socket.onmessage = null;
336
+ socket.onerror = null;
337
+ socket.onclose = null;
338
+ closeSocket(socket);
339
+ };
340
+ }, [transport, url, token, expiresAt, reconnectGeneration]);
341
+ const write = useCallback((data) => {
342
+ const currentTransport = transportRef.current;
343
+ const ws = wsRef.current;
344
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
345
+ try {
346
+ const relayChannel = relayChannelRef.current;
347
+ if (currentTransport === "relay-pty" && relayChannel) {
348
+ sendRelayInput(ws, relayChannel, relayInputSequenceRef.current++, data);
349
+ } else {
350
+ ws.send(ttydInputFrame(data));
351
+ }
352
+ } catch {
353
+ socketFailedRef.current = false;
354
+ setStatus("connecting");
355
+ closeSocket(ws);
356
+ }
357
+ } else if ((currentTransport === "pty-ws" || currentTransport === "relay-pty") && !socketFailedRef.current) {
358
+ const nextSize = pendingInputRef.current.length + data.length;
359
+ if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {
360
+ pendingInputRef.current = "";
361
+ socketFailedRef.current = true;
362
+ setStatus("error");
363
+ closeSocket(ws);
364
+ return;
365
+ }
366
+ pendingInputRef.current += data;
367
+ }
368
+ }, []);
369
+ const resize = useCallback((cols, rows) => {
370
+ if (cols <= 0 || rows <= 0) return;
371
+ sizeRef.current = { cols, rows };
372
+ const ws = wsRef.current;
373
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
374
+ try {
375
+ if (transportRef.current === "pty-ws") ws.send(ttydResizeFrame(cols, rows));
376
+ } catch {
377
+ }
378
+ }
379
+ }, []);
380
+ const disconnect = useCallback(() => {
381
+ const ws = wsRef.current;
382
+ wsRef.current = null;
383
+ pendingInputRef.current = "";
384
+ socketFailedRef.current = false;
385
+ socketAuthenticatedRef.current = false;
386
+ relayChannelRef.current = null;
387
+ setStatus("closed");
388
+ if (ws) intentionalCloseRef.current.add(ws);
389
+ closeSocket(ws);
390
+ }, []);
391
+ return useMemo(() => {
392
+ return { connected: status === "open", status, write, resize, disconnect };
393
+ }, [status, write, resize, disconnect]);
394
+ }
395
+
396
+ // src/lib/xterm-theme.ts
397
+ var DARK_ANSI = {
398
+ black: "oklch(0.3 0 0)",
399
+ red: "oklch(0.68 0.17 22)",
400
+ green: "oklch(0.75 0.13 155)",
401
+ yellow: "oklch(0.8 0.11 80)",
402
+ blue: "oklch(0.7 0.14 255)",
403
+ magenta: "oklch(0.72 0.13 305)",
404
+ cyan: "oklch(0.75 0.1 210)",
405
+ white: "oklch(0.8 0 0)",
406
+ brightBlack: "oklch(0.45 0 0)",
407
+ brightRed: "oklch(0.76 0.17 22)",
408
+ brightGreen: "oklch(0.82 0.14 155)",
409
+ brightYellow: "oklch(0.86 0.12 85)",
410
+ brightBlue: "oklch(0.78 0.13 255)",
411
+ brightMagenta: "oklch(0.8 0.13 305)",
412
+ brightCyan: "oklch(0.83 0.1 200)",
413
+ brightWhite: "oklch(0.97 0 0)",
414
+ selectionBackground: "oklch(0.8 0 0 / 0.3)",
415
+ selectionForeground: "oklch(0.985 0 0)",
416
+ selectionInactiveBackground: "oklch(0.72 0 0 / 0.2)"
417
+ };
418
+ var LIGHT_ANSI = {
419
+ black: "oklch(0.35 0 0)",
420
+ red: "oklch(0.52 0.19 22)",
421
+ green: "oklch(0.5 0.13 155)",
422
+ yellow: "oklch(0.56 0.13 80)",
423
+ blue: "oklch(0.52 0.18 255)",
424
+ magenta: "oklch(0.52 0.16 305)",
425
+ cyan: "oklch(0.52 0.1 210)",
426
+ white: "oklch(0.7 0 0)",
427
+ brightBlack: "oklch(0.5 0 0)",
428
+ brightRed: "oklch(0.55 0.2 22)",
429
+ brightGreen: "oklch(0.54 0.14 155)",
430
+ brightYellow: "oklch(0.6 0.14 80)",
431
+ brightBlue: "oklch(0.48 0.19 255)",
432
+ brightMagenta: "oklch(0.54 0.17 305)",
433
+ brightCyan: "oklch(0.54 0.11 210)",
434
+ brightWhite: "oklch(0.3 0 0)",
435
+ selectionBackground: "oklch(0.45 0 0 / 0.2)",
436
+ selectionForeground: "oklch(0.21 0 0)",
437
+ selectionInactiveBackground: "oklch(0.6 0 0 / 0.14)"
438
+ };
439
+ function ogAnsiPalette(mode) {
440
+ return mode === "light" ? LIGHT_ANSI : DARK_ANSI;
441
+ }
442
+ function buildXtermTheme(read, mode) {
443
+ const ansi = ogAnsiPalette(mode);
444
+ const bgFallback = mode === "light" ? "#ffffff" : "#202020";
445
+ const fgFallback = mode === "light" ? "#242424" : "#e6e6e6";
446
+ const bg = read("--og-color-bg") ?? bgFallback;
447
+ const fg = read("--og-color-fg") ?? fgFallback;
448
+ const accent = read("--og-color-accent") ?? ansi.brightBlue;
449
+ return {
450
+ // Match the dock surface the terminal paints on (`--og-color-bg`).
451
+ background: bg,
452
+ foreground: fg,
453
+ cursor: accent,
454
+ cursorAccent: bg,
455
+ selectionBackground: ansi.selectionBackground,
456
+ selectionForeground: ansi.selectionForeground,
457
+ selectionInactiveBackground: ansi.selectionInactiveBackground,
458
+ black: ansi.black,
459
+ red: ansi.red,
460
+ green: ansi.green,
461
+ yellow: ansi.yellow,
462
+ blue: ansi.blue,
463
+ magenta: ansi.magenta,
464
+ cyan: ansi.cyan,
465
+ white: ansi.white,
466
+ brightBlack: ansi.brightBlack,
467
+ brightRed: ansi.brightRed,
468
+ brightGreen: ansi.brightGreen,
469
+ brightYellow: ansi.brightYellow,
470
+ brightBlue: ansi.brightBlue,
471
+ brightMagenta: ansi.brightMagenta,
472
+ brightCyan: ansi.brightCyan,
473
+ brightWhite: ansi.brightWhite
474
+ };
475
+ }
476
+ function detectMode(el, style) {
477
+ const closest = el.closest?.bind(el);
478
+ if (closest) {
479
+ if (closest('[data-og-theme="light"]') || closest(".og-light")) return "light";
480
+ const explicit = closest("[data-og-theme]");
481
+ if (explicit) return explicit.getAttribute("data-og-theme") === "light" ? "light" : "dark";
482
+ }
483
+ const scheme = (style.getPropertyValue("color-scheme").trim() || style.colorScheme || "").toLowerCase();
484
+ if (scheme.includes("light") && !scheme.includes("dark")) return "light";
485
+ return "dark";
486
+ }
487
+ function xtermThemeFromTokens(root) {
488
+ if (typeof window === "undefined" || typeof getComputedStyle === "undefined") return void 0;
489
+ const el = root ?? (typeof document !== "undefined" ? document.querySelector("[data-og-theme]") ?? document.documentElement : null);
490
+ if (!el) return void 0;
491
+ const style = getComputedStyle(el);
492
+ const read = (name) => {
493
+ const value = style.getPropertyValue(name).trim();
494
+ return value || void 0;
495
+ };
496
+ return buildXtermTheme(read, detectMode(el, style));
497
+ }
498
+ var FALLBACK_MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace";
499
+ function resolveTerminalFontFromReader(read, overrides) {
500
+ const rawFamily = overrides?.fontFamily ?? read("--og-font-mono");
501
+ const fontFamily = rawFamily && rawFamily.trim() && !rawFamily.includes("var(") ? rawFamily.trim() : FALLBACK_MONO;
502
+ let fontSize = overrides?.fontSize;
503
+ if (fontSize == null) {
504
+ const raw = read("--og-code-font-size");
505
+ const parsed = raw ? Number.parseFloat(raw) : Number.NaN;
506
+ fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 13;
507
+ }
508
+ return { fontFamily, fontSize, lineHeight: 1.3 };
509
+ }
510
+ function resolveTerminalFont(el, overrides) {
511
+ const style = getComputedStyle(el);
512
+ const read = (name) => {
513
+ const value = style.getPropertyValue(name).trim();
514
+ return value || void 0;
515
+ };
516
+ return resolveTerminalFontFromReader(read, overrides);
517
+ }
518
+
519
+ // src/components/sandbox-terminal.tsx
520
+ import {
521
+ useEffect as useEffect2,
522
+ useLayoutEffect,
523
+ useRef as useRef2,
524
+ useState as useState2,
525
+ useSyncExternalStore
526
+ } from "react";
527
+
528
+ // src/lib/terminal-capability.ts
529
+ function terminalCanAcquirePty(cell) {
530
+ if (!cell?.ptyCapable) return false;
531
+ if (cell.transport === "pty-ws" || cell.transport === "relay-pty") return true;
532
+ return cell.reason === "lease_cold" || cell.reason === "not_provisioned";
533
+ }
534
+
535
+ // src/lib/xterm-renderer.ts
536
+ var ORDER = ["webgl", "canvas", "dom"];
537
+ function nextRendererTier(tier) {
538
+ const i = ORDER.indexOf(tier);
539
+ return i >= 0 && i < ORDER.length - 1 ? ORDER[i + 1] : null;
540
+ }
541
+ async function attachRenderer(prefer, loaders, onTier) {
542
+ const start = Math.max(0, ORDER.indexOf(prefer));
543
+ for (let i = start; i < ORDER.length; i++) {
544
+ const tier = ORDER[i];
545
+ if (tier === "dom") break;
546
+ const loader = tier === "webgl" ? loaders.webgl : loaders.canvas;
547
+ if (!loader) continue;
548
+ try {
549
+ const downgrade = nextRendererTier(tier) ?? "dom";
550
+ await loader(() => {
551
+ void attachRenderer(downgrade, loaders, onTier);
552
+ });
553
+ onTier(tier);
554
+ return tier;
555
+ } catch {
556
+ }
557
+ }
558
+ onTier("dom");
559
+ return "dom";
560
+ }
561
+
562
+ // src/components/sandbox-terminal.tsx
563
+ import { jsx, jsxs } from "react/jsx-runtime";
564
+ function terminalSurfaceState(input) {
565
+ if (!input.ready) return "loading";
566
+ if (input.acceptsInput) return "interactive";
567
+ if (input.inputPending) return "connecting";
568
+ if (input.ptyStatus === "connecting") return "connecting";
569
+ if (input.ptyStatus === "error") return "error";
570
+ if (input.booting) return "waking";
571
+ return "output-only";
572
+ }
573
+ function subscribeTerminalInput(term, sink, enabled) {
574
+ if (!enabled || !term || !sink) return null;
575
+ return term.onData(sink);
576
+ }
577
+ function clearTerminalBootStatus(term) {
578
+ term.write("\r\x1B[2K\x1B[2J\x1B[H");
579
+ }
580
+ function forcedRendererFailures() {
581
+ const raw = typeof globalThis !== "undefined" ? globalThis.__OG_FORCE_RENDERER_FAIL__ : void 0;
582
+ const set = /* @__PURE__ */ new Set();
583
+ if (typeof raw === "string") {
584
+ for (const tier of raw.split(/[\s,]+/)) {
585
+ if (tier === "webgl" || tier === "canvas") set.add(tier);
586
+ }
587
+ }
588
+ return set;
589
+ }
590
+ function SandboxTerminal({
591
+ result,
592
+ terminalCapability,
593
+ theme,
594
+ fontFamily,
595
+ fontSize,
596
+ liveness,
597
+ readOnly,
598
+ placeholder,
599
+ showHeader,
600
+ shell,
601
+ onActivate,
602
+ onReconnectNeeded,
603
+ className
604
+ }) {
605
+ const containerRef = useRef2(null);
606
+ const termRef = useRef2(null);
607
+ const fitRef = useRef2(null);
608
+ const rendererRef = useRef2(null);
609
+ const writtenRef = useRef2(/* @__PURE__ */ new Set());
610
+ const wroteFirehoseRef = useRef2(false);
611
+ const bootActiveRef = useRef2(false);
612
+ const [ready, setReady] = useState2(false);
613
+ const [loadError, setLoadError] = useState2(null);
614
+ const peerRevision = useSyncExternalStore(
615
+ sandboxTerminalPeers.subscribe,
616
+ sandboxTerminalPeers.revision,
617
+ sandboxTerminalPeers.revision
618
+ );
619
+ const [inputReady, setInputReady] = useState2(false);
620
+ const [activated, setActivated] = useState2(false);
621
+ const ptyDescriptor = terminalCapability?.transport === "pty-ws" || terminalCapability?.transport === "relay-pty";
622
+ const ptyAttachable = terminalCanAcquirePty(terminalCapability);
623
+ const {
624
+ status: ptyStatus,
625
+ write: ptyWrite,
626
+ resize: resizePty,
627
+ connected: ptyConnected
628
+ } = useTerminalStream({
629
+ // Connect only after xterm exists. This makes the renderer the sole output
630
+ // owner from the first websocket frame and removes an otherwise unbounded
631
+ // pre-mount output queue during large command bursts.
632
+ capability: ptyAttachable && ready ? {
633
+ transport: terminalCapability.transport,
634
+ url: ptyDescriptor ? terminalCapability.url : null,
635
+ token: ptyDescriptor ? terminalCapability.token : null,
636
+ expiresAt: ptyDescriptor ? terminalCapability.expiresAt : null
637
+ } : null,
638
+ onOutput: (data) => {
639
+ termRef.current?.write(data);
640
+ },
641
+ onReconnectNeeded
642
+ });
643
+ const ptyMode = ptyDescriptor;
644
+ const ptyCapturesInput = ptyAttachable && ptyStatus !== "error";
645
+ const interactive = !readOnly && (ptyAttachable ? ptyCapturesInput : result.write !== null);
646
+ const acceptsInput = inputReady && (!ptyAttachable || ptyConnected);
647
+ const inputPending = inputReady && ptyAttachable && !ptyConnected && activated;
648
+ let terminalModeLabel;
649
+ if (readOnly) terminalModeLabel = "read-only";
650
+ else if (!ptyAttachable) terminalModeLabel = acceptsInput ? null : "output only";
651
+ else if (ptyStatus === "error") terminalModeLabel = "connection error";
652
+ else if (acceptsInput) terminalModeLabel = null;
653
+ else terminalModeLabel = activated ? "connecting" : "connect on input";
654
+ const warm = sandboxAcceptsLiveIo(liveness);
655
+ const booting = activated && !ptyMode && !warm && liveness !== null && liveness !== void 0 && result.chunks.length === 0;
656
+ const surfaceState = terminalSurfaceState({
657
+ ready,
658
+ acceptsInput,
659
+ inputPending,
660
+ ptyStatus,
661
+ booting
662
+ });
663
+ function handleActivate() {
664
+ const term = termRef.current;
665
+ if (term && !readOnly && (ptyAttachable || result.write !== null)) {
666
+ term.options.disableStdin = false;
667
+ term.options.cursorBlink = true;
668
+ }
669
+ term?.focus();
670
+ if (!activated) setActivated(true);
671
+ onActivate?.();
672
+ }
673
+ useEffect2(() => {
674
+ if (typeof window === "undefined") return;
675
+ const el = containerRef.current;
676
+ if (!el) return;
677
+ let disposed = false;
678
+ const waitForSize = (node) => new Promise((resolve) => {
679
+ if (node.clientWidth > 0 && node.clientHeight > 0) return resolve();
680
+ const ro = new ResizeObserver(() => {
681
+ if (node.clientWidth > 0 && node.clientHeight > 0) {
682
+ ro.disconnect();
683
+ resolve();
684
+ }
685
+ });
686
+ ro.observe(node);
687
+ setTimeout(() => {
688
+ ro.disconnect();
689
+ resolve();
690
+ }, 1e3);
691
+ });
692
+ ensureXtermBaseCss();
693
+ setLoadError(null);
694
+ void (async () => {
695
+ const { Terminal, FitAddon, WebLinksAddon, loadWebgl } = await sandboxTerminalPeers.load();
696
+ if (disposed) return;
697
+ await waitForSize(el);
698
+ if (disposed) return;
699
+ const font = resolveTerminalFont(el, { fontFamily, fontSize });
700
+ const initialTheme = theme ?? xtermThemeFromTokens(el);
701
+ const term = new Terminal({
702
+ convertEol: true,
703
+ disableStdin: !interactive,
704
+ cursorBlink: interactive,
705
+ fontFamily: font.fontFamily,
706
+ fontSize: font.fontSize,
707
+ lineHeight: font.lineHeight,
708
+ ...initialTheme ? { theme: initialTheme } : {}
709
+ });
710
+ const fit = new FitAddon();
711
+ term.loadAddon(fit);
712
+ term.loadAddon(
713
+ new WebLinksAddon(
714
+ (_e, uri) => window.open(uri, "_blank", "noopener,noreferrer")
715
+ )
716
+ );
717
+ term.open(el);
718
+ termRef.current = term;
719
+ fitRef.current = fit;
720
+ const failSet = forcedRendererFailures();
721
+ const loaders = {
722
+ webgl: async (onLoss) => {
723
+ if (disposed) throw new Error("Terminal was disposed");
724
+ if (failSet.has("webgl")) throw new Error("forced webgl failure");
725
+ if (!loadWebgl) throw new Error("WebGL terminal renderer is not enabled");
726
+ const { WebglAddon } = await loadWebgl();
727
+ if (disposed) throw new Error("Terminal was disposed");
728
+ const addon = new WebglAddon();
729
+ addon.onContextLoss?.(() => {
730
+ if (disposed) return;
731
+ try {
732
+ addon.dispose();
733
+ } catch {
734
+ }
735
+ onLoss();
736
+ });
737
+ term.loadAddon(addon);
738
+ return addon;
739
+ }
740
+ };
741
+ await attachRenderer("webgl", loaders, (tier) => {
742
+ if (disposed) return;
743
+ rendererRef.current = tier;
744
+ el.setAttribute("data-og-term-renderer", tier);
745
+ });
746
+ if (disposed) return;
747
+ const settle = () => {
748
+ try {
749
+ fit.fit();
750
+ } catch {
751
+ }
752
+ };
753
+ settle();
754
+ requestAnimationFrame(() => {
755
+ if (disposed) return;
756
+ settle();
757
+ el.setAttribute("data-og-term-ready", "true");
758
+ if (typeof globalThis.__OG_TERMINAL_DEBUG__ === "function") {
759
+ globalThis.__OG_TERMINAL_DEBUG__({
760
+ renderer: rendererRef.current,
761
+ fontFamily: term.options.fontFamily,
762
+ fontSize: term.options.fontSize,
763
+ hasVarInFont: String(term.options.fontFamily ?? "").includes("var("),
764
+ theme: term.options.theme,
765
+ term
766
+ });
767
+ }
768
+ setReady(true);
769
+ });
770
+ })().catch((cause) => {
771
+ if (!disposed) setLoadError(cause instanceof Error ? cause : new Error(String(cause)));
772
+ });
773
+ return () => {
774
+ disposed = true;
775
+ termRef.current?.dispose();
776
+ termRef.current = null;
777
+ fitRef.current = null;
778
+ rendererRef.current = null;
779
+ writtenRef.current = /* @__PURE__ */ new Set();
780
+ wroteFirehoseRef.current = false;
781
+ bootActiveRef.current = false;
782
+ setReady(false);
783
+ };
784
+ }, [peerRevision]);
785
+ useLayoutEffect(() => {
786
+ const term = termRef.current;
787
+ if (!ready || !term) return;
788
+ term.options.disableStdin = !interactive;
789
+ term.options.cursorBlink = interactive;
790
+ }, [ready, interactive]);
791
+ useEffect2(() => {
792
+ const term = termRef.current;
793
+ if (!ready || !term) return;
794
+ const next = theme ?? xtermThemeFromTokens(containerRef.current);
795
+ if (next) term.options.theme = next;
796
+ }, [ready, theme]);
797
+ useEffect2(() => {
798
+ const term = termRef.current;
799
+ if (!ready || !term) return;
800
+ if (!booting) {
801
+ if (bootActiveRef.current) {
802
+ bootActiveRef.current = false;
803
+ if (!wroteFirehoseRef.current) clearTerminalBootStatus(term);
804
+ }
805
+ return;
806
+ }
807
+ bootActiveRef.current = true;
808
+ const start = Date.now();
809
+ const paint = () => {
810
+ const s = Math.max(0, Math.round((Date.now() - start) / 1e3));
811
+ term.write(`\r\x1B[2K\x1B[94m\u25CF\x1B[0m \x1B[2mwaking machine \u2014 ${s}s\u2026\x1B[0m`);
812
+ };
813
+ term.write("\r\n");
814
+ paint();
815
+ const id = setInterval(paint, 1e3);
816
+ return () => clearInterval(id);
817
+ }, [ready, booting]);
818
+ useEffect2(() => {
819
+ const term = termRef.current;
820
+ if (!ready || !term || ptyMode) return;
821
+ for (const chunk of result.chunks) {
822
+ if (writtenRef.current.has(chunk.id)) continue;
823
+ writtenRef.current.add(chunk.id);
824
+ wroteFirehoseRef.current = true;
825
+ term.write(chunk.text);
826
+ }
827
+ }, [ready, result.chunks, ptyMode]);
828
+ useLayoutEffect(() => {
829
+ const term = termRef.current;
830
+ if (!ready || !term || !interactive) {
831
+ setInputReady(false);
832
+ return;
833
+ }
834
+ const sink = ptyAttachable ? ptyWrite : result.write;
835
+ if (!sink) {
836
+ setInputReady(false);
837
+ return;
838
+ }
839
+ const sub = subscribeTerminalInput(term, sink, true);
840
+ setInputReady(sub !== null);
841
+ return () => sub?.dispose();
842
+ }, [ready, interactive, ptyAttachable, ptyWrite, result.write]);
843
+ useEffect2(() => {
844
+ const term = termRef.current;
845
+ if (!ready || !term || !ptyMode) return;
846
+ resizePty(term.cols, term.rows);
847
+ const sub = term.onResize(({ cols, rows }) => resizePty(cols, rows));
848
+ return () => sub.dispose();
849
+ }, [ready, ptyMode, ptyConnected, resizePty]);
850
+ useEffect2(() => {
851
+ if (typeof window === "undefined") return;
852
+ const refit = () => {
853
+ try {
854
+ fitRef.current?.fit();
855
+ } catch {
856
+ }
857
+ };
858
+ window.addEventListener("resize", refit);
859
+ let observer = null;
860
+ const el = containerRef.current;
861
+ if (el && typeof ResizeObserver !== "undefined") {
862
+ observer = new ResizeObserver(() => refit());
863
+ observer.observe(el);
864
+ }
865
+ return () => {
866
+ window.removeEventListener("resize", refit);
867
+ observer?.disconnect();
868
+ };
869
+ }, [ready]);
870
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative flex h-full w-full flex-col overflow-hidden", className), children: [
871
+ showHeader && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-og-border px-2 py-1 text-og-xs text-og-fg-subtle", children: [
872
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
873
+ /* @__PURE__ */ jsx(
874
+ "span",
875
+ {
876
+ className: cn(
877
+ "size-1.5 shrink-0 rounded-full",
878
+ result.running ? "bg-og-status-running" : "bg-og-fg-subtle"
879
+ )
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsxs("span", { className: "truncate font-og-mono", children: [
883
+ "pty: ",
884
+ shell ?? "shell"
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
888
+ terminalModeLabel && /* @__PURE__ */ jsx(
889
+ "span",
890
+ {
891
+ className: "rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide",
892
+ role: "status",
893
+ children: terminalModeLabel
894
+ }
895
+ ),
896
+ /* @__PURE__ */ jsx(
897
+ "button",
898
+ {
899
+ type: "button",
900
+ onClick: () => {
901
+ termRef.current?.clear();
902
+ writtenRef.current = /* @__PURE__ */ new Set();
903
+ wroteFirehoseRef.current = false;
904
+ },
905
+ className: "rounded-og-sm px-1.5 py-0.5 text-og-xs hover:text-og-fg pointer-coarse:min-h-11",
906
+ children: "Clear"
907
+ }
908
+ )
909
+ ] })
910
+ ] }),
911
+ /* @__PURE__ */ jsxs(
912
+ "div",
913
+ {
914
+ className: "relative min-h-0 flex-1 bg-og-bg p-3",
915
+ onPointerDownCapture: handleActivate,
916
+ onFocusCapture: handleActivate,
917
+ children: [
918
+ !ready && (loadError ? /* @__PURE__ */ jsx("p", { role: "alert", children: loadError.message }) : placeholder ?? /* @__PURE__ */ jsx(TerminalPlaceholder, {})),
919
+ /* @__PURE__ */ jsx(
920
+ "div",
921
+ {
922
+ ref: containerRef,
923
+ className: "h-full w-full",
924
+ style: { visibility: ready ? "visible" : "hidden" },
925
+ "data-opengeni-terminal": true,
926
+ "data-opengeni-terminal-ready": ready ? "true" : "false",
927
+ "data-opengeni-terminal-interactive": surfaceState === "interactive" ? "true" : "false",
928
+ "data-opengeni-terminal-state": surfaceState,
929
+ "data-opengeni-terminal-status": ptyAttachable ? ptyStatus : "firehose"
930
+ }
931
+ )
932
+ ]
933
+ }
934
+ )
935
+ ] });
936
+ }
937
+ var XTERM_STYLE_ID = "opengeni-xterm-base-css";
938
+ var XTERM_BASE_CSS = `
939
+ .xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}
940
+ .xterm.focus,.xterm:focus{outline:none}
941
+ .xterm .xterm-helpers{position:absolute;top:0;z-index:5}
942
+ .xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}
943
+ .xterm .composition-view{background:var(--og-color-bg);color:var(--og-color-fg);display:none;position:absolute;white-space:nowrap;z-index:1}
944
+ .xterm .composition-view.active{display:block}
945
+ .xterm .xterm-viewport{background-color:transparent;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}
946
+ .xterm .xterm-screen{position:relative}
947
+ .xterm .xterm-screen canvas{position:absolute;left:0;top:0}
948
+ .xterm .xterm-scroll-area{visibility:hidden}
949
+ .xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}
950
+ .xterm.enable-mouse-events{cursor:default}
951
+ .xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}
952
+ .xterm.column-select.focus{cursor:crosshair}
953
+ .xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}
954
+ .xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}
955
+ .xterm .xterm-accessibility-tree{user-select:text;white-space:pre}
956
+ .xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
957
+ .xterm-dim{opacity:1!important}
958
+ .xterm-underline-1{text-decoration:underline}
959
+ .xterm-underline-2{text-decoration:double underline}
960
+ .xterm-underline-3{text-decoration:wavy underline}
961
+ .xterm-underline-4{text-decoration:dotted underline}
962
+ .xterm-underline-5{text-decoration:dashed underline}
963
+ .xterm-overline{text-decoration:overline}
964
+ .xterm-strikethrough{text-decoration:line-through}
965
+ .xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}
966
+ .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}
967
+ .xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}
968
+ .xterm-decoration-top{z-index:2;position:relative}
969
+ `;
970
+ function ensureXtermBaseCss() {
971
+ if (typeof document === "undefined") return;
972
+ if (document.getElementById(XTERM_STYLE_ID)) return;
973
+ const style = document.createElement("style");
974
+ style.id = XTERM_STYLE_ID;
975
+ style.textContent = XTERM_BASE_CSS;
976
+ document.head.appendChild(style);
977
+ }
978
+ function TerminalPlaceholder() {
979
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-og-sm text-og-fg-subtle", children: "Loading terminal\u2026" });
980
+ }
981
+
982
+ export {
983
+ useTerminalStream,
984
+ xtermThemeFromTokens,
985
+ terminalCanAcquirePty,
986
+ SandboxTerminal
987
+ };
988
+ //# sourceMappingURL=chunk-WRHIZYZF.js.map