@opengeni/react 1.4.0 → 1.4.4

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 (218) hide show
  1. package/README.md +8 -0
  2. package/dist/accounts.js +2 -0
  3. package/dist/accounts.js.map +1 -1
  4. package/dist/artifacts-document.js +5 -4
  5. package/dist/artifacts-presentation.js +5 -4
  6. package/dist/artifacts-spreadsheet.js +4 -3
  7. package/dist/artifacts.js +14 -13
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/browser-viewer-5EEAS3IU.js +17 -0
  10. package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
  11. package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
  12. package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
  13. package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
  14. package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
  15. package/dist/{chunk-HDJWDK7W.js → chunk-2OQA3CPB.js} +300 -166
  16. package/dist/{chunk-HDJWDK7W.js.map → chunk-2OQA3CPB.js.map} +1 -1
  17. package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
  18. package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
  19. package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
  20. package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
  21. package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
  22. package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
  23. package/dist/{chunk-MBMPHOMA.js → chunk-55DXD22O.js} +3 -2
  24. package/dist/{chunk-MBMPHOMA.js.map → chunk-55DXD22O.js.map} +1 -1
  25. package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
  26. package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
  27. package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
  28. package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
  29. package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
  30. package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
  31. package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
  32. package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
  33. package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
  34. package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
  35. package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
  36. package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
  37. package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
  38. package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
  39. package/dist/{chunk-LJ2UTJJE.js → chunk-EAOSEIXB.js} +182 -97
  40. package/dist/chunk-EAOSEIXB.js.map +1 -0
  41. package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
  42. package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
  43. package/dist/{chunk-WZMYFJD3.js → chunk-G6YRCIYJ.js} +16 -7
  44. package/dist/{chunk-WZMYFJD3.js.map → chunk-G6YRCIYJ.js.map} +1 -1
  45. package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
  46. package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
  47. package/dist/{chunk-CJUO7J5Z.js → chunk-HXYOSSQO.js} +3 -2
  48. package/dist/{chunk-CJUO7J5Z.js.map → chunk-HXYOSSQO.js.map} +1 -1
  49. package/dist/{chunk-GYVRFZDY.js → chunk-I6OY3MWC.js} +310 -115
  50. package/dist/chunk-I6OY3MWC.js.map +1 -0
  51. package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
  52. package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
  53. package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
  54. package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
  55. package/dist/{chunk-LN6TCXD3.js → chunk-IUHHPMAX.js} +184 -48
  56. package/dist/chunk-IUHHPMAX.js.map +1 -0
  57. package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
  58. package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
  59. package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
  60. package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
  61. package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
  62. package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
  63. package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
  64. package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
  65. package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
  66. package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
  67. package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
  68. package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
  69. package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
  70. package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
  71. package/dist/chunk-M357MHH3.js +2 -0
  72. package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
  73. package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
  74. package/dist/chunk-NX4ADH73.js +14 -0
  75. package/dist/chunk-NX4ADH73.js.map +1 -0
  76. package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
  77. package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
  78. package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
  79. package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
  80. package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
  81. package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
  82. package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
  83. package/dist/chunk-PHBFJCEU.js.map +1 -0
  84. package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
  85. package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
  86. package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
  87. package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
  88. package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
  89. package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
  90. package/dist/{chunk-OA6HFRQP.js → chunk-QMMCJGFI.js} +4 -3
  91. package/dist/{chunk-OA6HFRQP.js.map → chunk-QMMCJGFI.js.map} +1 -1
  92. package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
  93. package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
  94. package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
  95. package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
  96. package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
  97. package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
  98. package/dist/{chunk-AWZLIMKX.js → chunk-T46QZH3E.js} +3 -1
  99. package/dist/{chunk-AWZLIMKX.js.map → chunk-T46QZH3E.js.map} +1 -1
  100. package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
  101. package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
  102. package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
  103. package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
  104. package/dist/{chunk-H5X55XH7.js → chunk-UDPH5SLV.js} +4 -3
  105. package/dist/{chunk-H5X55XH7.js.map → chunk-UDPH5SLV.js.map} +1 -1
  106. package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
  107. package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
  108. package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
  109. package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
  110. package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
  111. package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
  112. package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
  113. package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
  114. package/dist/chunk-YVFRBXST.js +1420 -0
  115. package/dist/chunk-YVFRBXST.js.map +1 -0
  116. package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
  117. package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
  118. package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
  119. package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
  120. package/dist/clipboard.js +3 -2
  121. package/dist/components/composer-transcription-control.d.ts +4 -0
  122. package/dist/components/session-conversation.d.ts +6 -0
  123. package/dist/composer-voice-events.d.ts +8 -0
  124. package/dist/composer.js +15 -13
  125. package/dist/computer-viewer-ELNLZGHI.js +20 -0
  126. package/dist/connect.js +15 -12
  127. package/dist/connect.js.map +1 -1
  128. package/dist/desktop.js +6 -5
  129. package/dist/desktop.js.map +1 -1
  130. package/dist/diffs.js +2 -1
  131. package/dist/diffs.js.map +1 -1
  132. package/dist/editor.js +4 -3
  133. package/dist/editor.js.map +1 -1
  134. package/dist/embedded-voice-NIL4RDX3.js +69 -0
  135. package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
  136. package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
  137. package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
  138. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  139. package/dist/hooks/use-computer-session.d.ts +2 -0
  140. package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
  141. package/dist/hooks/use-voice-input.d.ts +8 -1
  142. package/dist/index.js +47 -45
  143. package/dist/index.js.map +1 -1
  144. package/dist/interaction.js +14 -13
  145. package/dist/interaction.js.map +1 -1
  146. package/dist/{latest-question-GPYM2JNU.js → latest-question-2POFPYNB.js} +5 -4
  147. package/dist/{latest-question-GPYM2JNU.js.map → latest-question-2POFPYNB.js.map} +1 -1
  148. package/dist/machines.js +8 -7
  149. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
  150. package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
  151. package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
  152. package/dist/model-policy.js +2 -1
  153. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
  154. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
  155. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
  156. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
  157. package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
  158. package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
  159. package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
  160. package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
  161. package/dist/realtime/embedded-voice.d.ts +20 -0
  162. package/dist/realtime.js +22 -1353
  163. package/dist/realtime.js.map +1 -1
  164. package/dist/sandbox-files-34CJJNOY.js +21 -0
  165. package/dist/session-ui.js +32 -30
  166. package/dist/session-ui.js.map +1 -1
  167. package/dist/session.js +10 -9
  168. package/dist/sites.js +2 -1
  169. package/dist/terminal.js +6 -5
  170. package/dist/terminal.js.map +1 -1
  171. package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
  172. package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
  173. package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
  174. package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
  175. package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
  176. package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
  177. package/dist/usage.js +6 -5
  178. package/dist/usage.js.map +1 -1
  179. package/dist/voice-recording-owner.d.ts +7 -0
  180. package/package.json +3 -3
  181. package/src/components/composer-transcription-control.tsx +109 -27
  182. package/src/components/computer-viewer.tsx +139 -30
  183. package/src/components/markdown.tsx +7 -2
  184. package/src/components/message-timeline.tsx +10 -1
  185. package/src/components/session-conversation.tsx +44 -0
  186. package/src/components/session-list.tsx +1 -1
  187. package/src/components/session-proxy-scope.tsx +26 -5
  188. package/src/composer-voice-events.ts +12 -0
  189. package/src/connection-discovery.tsx +4 -1
  190. package/src/hooks/use-browser-session.ts +445 -168
  191. package/src/hooks/use-client-config-flags.ts +8 -0
  192. package/src/hooks/use-computer-session.ts +334 -98
  193. package/src/hooks/use-goal.ts +12 -1
  194. package/src/hooks/use-realtime-voice-models.ts +67 -0
  195. package/src/hooks/use-voice-input.ts +109 -18
  196. package/src/realtime/embedded-voice.tsx +81 -0
  197. package/src/realtime/realtime-control.tsx +82 -17
  198. package/src/voice-recording-owner.ts +22 -0
  199. package/styles/compiled.css +77 -18
  200. package/styles/index.css +49 -0
  201. package/styles/responsive.css +61 -4
  202. package/styles/session-capability.css +1 -1
  203. package/dist/browser-viewer-SHYKQRNX.js +0 -16
  204. package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
  205. package/dist/chunk-GYVRFZDY.js.map +0 -1
  206. package/dist/chunk-I7IWRQLN.js.map +0 -1
  207. package/dist/chunk-IWIDV7SB.js +0 -1
  208. package/dist/chunk-LJ2UTJJE.js.map +0 -1
  209. package/dist/chunk-LN6TCXD3.js.map +0 -1
  210. package/dist/computer-viewer-6SRGWRUX.js +0 -19
  211. package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
  212. package/dist/sandbox-files-GORZOUUJ.js +0 -20
  213. /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-5EEAS3IU.js.map} +0 -0
  214. /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
  215. /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
  216. /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-ELNLZGHI.js.map} +0 -0
  217. /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
  218. /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/format.ts"],"sourcesContent":["import { formatErrorMessage, OpenGeniApiError } from \"@opengeni/sdk\";\n\nconst clockTimeFormatter = new Intl.DateTimeFormat(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n});\n\n/** Local finished-at stamp for message footers: \"Aug 2, 3:42 PM\" / locale-equivalent. */\nexport function formatClockTime(iso: string): string {\n const then = new Date(iso);\n if (Number.isNaN(then.getTime())) {\n return \"\";\n }\n return clockTimeFormatter.format(then);\n}\n\n/** Compact relative time: \"now\", \"42s\", \"7m\", \"3h\", \"2d\", then a date. */\nexport function formatRelativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) {\n return \"\";\n }\n const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1000));\n if (seconds < 10) {\n return \"now\";\n }\n if (seconds < 60) {\n return `${seconds}s`;\n }\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes}m`;\n }\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours}h`;\n }\n const days = Math.floor(hours / 24);\n if (days < 14) {\n return `${days}d`;\n }\n return new Date(iso).toLocaleDateString();\n}\n\n/** Human-readable byte size: \"512 B\", \"8.0 KB\", \"1.4 MB\", \"3 GB\". */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes} B`;\n }\n const units = [\"KB\", \"MB\", \"GB\"] as const;\n let value = bytes / 1024;\n for (const unit of units) {\n if (value < 1024 || unit === \"GB\") {\n return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`;\n }\n value /= 1024;\n }\n return `${bytes} B`;\n}\n\n/** Single-line preview of arbitrary text, for tiles and collapsed rows. */\nexport function truncate(text: string, maxLength: number): string {\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n if (collapsed.length <= maxLength) {\n return collapsed;\n }\n return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;\n}\n\n/** Render an unknown payload as readable text (pretty JSON when possible). */\nexport function stringifyPayload(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const parsed = tryParseJson(value);\n if (parsed !== undefined && typeof parsed === \"object\") {\n return stringifyPayload(parsed);\n }\n return value;\n }\n try {\n return JSON.stringify(value, null, 2) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** JSON.parse that returns `undefined` instead of throwing. */\nexport function tryParseJson(text: string): unknown {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\") && !trimmed.startsWith('\"')) {\n return undefined;\n }\n try {\n return JSON.parse(trimmed) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The canonical credit-death sentence. Credit exhaustion is the one failure a\n * user can fix themselves — the copy must say what happened (empty balance),\n * what to do (add credits), and what is safe (nothing was lost). Crucially it\n * must NOT say \"send a message to revive\": a revive turn burns credits the\n * workspace no longer has.\n */\nexport const CREDIT_EXHAUSTION_MESSAGE =\n \"Out of credits — this workspace's balance is empty. Add credits to continue; the conversation is preserved.\";\n\n/** A usage ceiling refused the send; the default allowance wording, plus what is safe. */\nexport const COMPOSER_MEMBER_ALLOWANCE_MESSAGE =\n \"Usage limit reached. A workspace admin can raise this limit. Your draft is preserved.\";\nexport const COMPOSER_WORKSPACE_ALLOWANCE_MESSAGE =\n \"Workspace usage limit reached. An organization admin can raise the workspace budget. Your draft is preserved.\";\n\n/**\n * Actionable composer copy for an edge rejection before a turn is accepted.\n * Unlike an in-flight credit exhaustion, this path has not consumed the\n * actor-private draft or any finalized attachment.\n */\nexport const COMPOSER_PAYMENT_REQUIRED_MESSAGE =\n \"Your organization doesn't have enough credits to send this message. Add credits or choose a model with another payment source. Your message and attachments are saved.\";\n\n/** Trusted, composer-owned guidance; unlike remote diagnostic prose, this is UI copy. */\nexport class ComposerStateError extends Error {}\n\n/** A restored request has omitted credentials and must be reconciled, not replayed. */\nexport class ComposerReconciliationRequiredError extends ComposerStateError {\n constructor() {\n super(\n \"Opengeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.\",\n );\n }\n}\n\nexport class ComposerWorkspaceControlUnavailableError extends Error {\n constructor() {\n super(\"@opengeni/react: workspace-scoped resume requires setWorkspaceInferenceState.\");\n }\n}\n\n/**\n * The send named a model that is no longer in the live catalog (retired or\n * removed). Retrying the same send cannot succeed; the person must choose\n * another model, and the typed message stays recoverable via Edit message.\n */\nexport const COMPOSER_MODEL_UNAVAILABLE_MESSAGE =\n \"This chat's model is no longer available. Choose another model to continue. Your message is saved.\";\n\n/** `details.code` the API sets on a 422 for a model missing from the live catalog. */\nexport const MODEL_UNAVAILABLE_DETAIL_CODE = \"model_unavailable\";\n\n/**\n * Is this send refusal \"the model is no longer available\"? Reads the typed\n * `details.code`, and also the historical message so a refusal persisted\n * before a reload (only its text survives) or from an older API is recognized.\n */\nexport function isModelUnavailableSubmissionError(error: Error): boolean {\n if (error instanceof OpenGeniApiError && error.details?.code === MODEL_UNAVAILABLE_DETAIL_CODE) {\n return true;\n }\n return (\n error.message === COMPOSER_MODEL_UNAVAILABLE_MESSAGE ||\n /^OpenGeni API 422: model is not available: /.test(error.message)\n );\n}\n\nexport function composerSubmissionErrorMessage(error: Error): string {\n if (error instanceof ComposerReconciliationRequiredError) {\n return \"This client cannot safely retry this uncertain request after remount; reconcile the session before sending again.\";\n }\n if (error instanceof ComposerWorkspaceControlUnavailableError) {\n return \"This client cannot resume the workspace. Ask your administrator for a control-capable client.\";\n }\n if (error instanceof ComposerStateError) return error.message;\n if (error instanceof OpenGeniApiError && error.outcomeUnknown) return formatErrorMessage(error);\n if (isModelUnavailableSubmissionError(error)) return COMPOSER_MODEL_UNAVAILABLE_MESSAGE;\n if (error instanceof OpenGeniApiError && error.code === \"allowance_exhausted\") {\n // OpenGeniAllowanceExhaustedError carries the scope; read it structurally\n // so this startup-path helper adds no SDK or wording imports.\n const message =\n (error as { scope?: unknown }).scope === \"workspace\"\n ? COMPOSER_WORKSPACE_ALLOWANCE_MESSAGE\n : COMPOSER_MEMBER_ALLOWANCE_MESSAGE;\n return composerErrorReference(error, message);\n }\n return isComposerCreditRefusal(error)\n ? composerErrorReference(error, COMPOSER_PAYMENT_REQUIRED_MESSAGE)\n : formatErrorMessage(error);\n}\n\nfunction composerErrorReference(error: Error, message: string): string {\n return error instanceof OpenGeniApiError && error.correlationId\n ? `${message} Reference: ${error.correlationId}.`\n : message;\n}\n\n/** These definitive refusals need a payment, allowance or model change, not an unchanged retry. */\nexport function composerSubmissionCanRetry(error: Error): boolean {\n return !(\n isComposerCreditRefusal(error) ||\n isModelUnavailableSubmissionError(error) ||\n (error instanceof OpenGeniApiError && error.code === \"allowance_exhausted\")\n );\n}\n\nfunction isComposerCreditRefusal(error: Error): boolean {\n return (\n (error instanceof OpenGeniApiError &&\n error.status === 402 &&\n (error.code === \"payment_required\" || error.code === \"insufficient_credits\")) ||\n isCreditExhaustion(error.message)\n );\n}\n\n/**\n * Does this failure/completion payload (or raw error string) mean the\n * workspace ran out of OpenGeni credits? Matches the engine's\n * \"insufficient OpenGeni credits\" text (case-insensitive, substring — it\n * arrives both bare and wrapped in \"Activity task failed: …\") and the\n * budget-exhausted segment limit the engine stamps on a turn it ended early.\n */\nexport function isCreditExhaustion(\n input: { error?: string | null; detail?: string | null; segmentLimit?: string | null } | string,\n): boolean {\n if (typeof input === \"string\") {\n return input.toLowerCase().includes(\"insufficient opengeni credits\");\n }\n if (input.segmentLimit === \"budget_exhausted\") {\n return true;\n }\n for (const text of [input.error, input.detail]) {\n if (typeof text === \"string\" && text.toLowerCase().includes(\"insufficient opengeni credits\")) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Humanize engine/provider failure text before it reaches the timeline or a\n * failure banner. Raw provider errors leak the wrong audience's instructions —\n * \"Incorrect API key … find your API key at platform.openai.com\" tells a\n * managed-deployment USER to fix credentials only an OPERATOR controls (and is\n * flatly wrong for Azure or subscription-backed engines). Auth, quota, and\n * credit-exhaustion failures collapse to one neutral, honest sentence; every\n * other reason passes through untouched. Raw payloads stay available in the\n * debug surfaces.\n */\nexport function humanizeFailureReason(reason: string | null): string | null {\n if (!reason) {\n return reason;\n }\n if (isCreditExhaustion(reason)) {\n return CREDIT_EXHAUSTION_MESSAGE;\n }\n const normalized = reason.toLowerCase();\n const authFailure =\n normalized.includes(\"incorrect api key\") ||\n normalized.includes(\"invalid api key\") ||\n normalized.includes(\"invalid_api_key\") ||\n normalized.includes(\"platform.openai.com/account/api-keys\") ||\n (normalized.includes(\"401\") &&\n (normalized.includes(\"api key\") || normalized.includes(\"unauthorized\")));\n if (authFailure) {\n return \"The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.\";\n }\n const quotaFailure =\n normalized.includes(\"insufficient_quota\") || normalized.includes(\"exceeded your current quota\");\n if (quotaFailure) {\n return \"The model provider refused the request: this deployment's provider quota is exhausted.\";\n }\n return reason;\n}\n\n/** Project failure diagnostics without changing the stored event, including legacy retry wrappers. */\nexport function presentFailure(payload: Record<string, unknown>): {\n reason: string | null;\n safetyRefusal: boolean;\n} {\n const databaseFailure =\n payload.code === \"db_deadlock\" ||\n payload.code === \"db_serialization_failure\" ||\n payload.code === \"db_failure\" ||\n (typeof payload.sqlState === \"string\" &&\n /^[0-9A-Z]{5}$/.test(payload.sqlState) &&\n payload.database !== null &&\n typeof payload.database === \"object\" &&\n !Array.isArray(payload.database));\n if (databaseFailure) {\n return { reason: \"The service encountered a database error.\", safetyRefusal: false };\n }\n const text = (key: string): string | null => {\n const value = payload[key];\n return typeof value === \"string\" && value.trim() ? value : null;\n };\n const message = text(\"error\") ?? text(\"message\");\n const detail = text(\"lastRetryableError\") ?? text(\"detail\");\n const safetyRefusal =\n payload.code === \"provider_safety_refusal\" ||\n [message, detail].some(\n (value) =>\n value !== null && /\\bthis request was blocked by our safety systems\\b/i.test(value),\n );\n if (safetyRefusal) {\n return {\n reason: `The model provider blocked this request.${detail || message ? ` ${detail ?? message}` : \"\"}`,\n safetyRefusal: true,\n };\n }\n return {\n reason:\n detail && detail !== message\n ? [humanizeFailureReason(message), humanizeFailureReason(detail)].filter(Boolean).join(\" \")\n : humanizeFailureReason(message),\n safetyRefusal: false,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/format.ts"],"sourcesContent":["import { formatErrorMessage, OpenGeniApiError } from \"@opengeni/sdk\";\n\nconst clockTimeFormatter = new Intl.DateTimeFormat(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n});\n\n/** Local finished-at stamp for message footers: \"Aug 2, 3:42 PM\" / locale-equivalent. */\nexport function formatClockTime(iso: string): string {\n const then = new Date(iso);\n if (Number.isNaN(then.getTime())) {\n return \"\";\n }\n return clockTimeFormatter.format(then);\n}\n\n/** Compact relative time: \"now\", \"42s\", \"7m\", \"3h\", \"2d\", then a date. */\nexport function formatRelativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) {\n return \"\";\n }\n const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1000));\n if (seconds < 10) {\n return \"now\";\n }\n if (seconds < 60) {\n return `${seconds}s`;\n }\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes}m`;\n }\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours}h`;\n }\n const days = Math.floor(hours / 24);\n if (days < 14) {\n return `${days}d`;\n }\n return new Date(iso).toLocaleDateString();\n}\n\n/** Human-readable byte size: \"512 B\", \"8.0 KB\", \"1.4 MB\", \"3 GB\". */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes} B`;\n }\n const units = [\"KB\", \"MB\", \"GB\"] as const;\n let value = bytes / 1024;\n for (const unit of units) {\n if (value < 1024 || unit === \"GB\") {\n return `${value.toFixed(value < 10 ? 1 : 0)} ${unit}`;\n }\n value /= 1024;\n }\n return `${bytes} B`;\n}\n\n/** Single-line preview of arbitrary text, for tiles and collapsed rows. */\nexport function truncate(text: string, maxLength: number): string {\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n if (collapsed.length <= maxLength) {\n return collapsed;\n }\n return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;\n}\n\n/** Render an unknown payload as readable text (pretty JSON when possible). */\nexport function stringifyPayload(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const parsed = tryParseJson(value);\n if (parsed !== undefined && typeof parsed === \"object\") {\n return stringifyPayload(parsed);\n }\n return value;\n }\n try {\n return JSON.stringify(value, null, 2) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** JSON.parse that returns `undefined` instead of throwing. */\nexport function tryParseJson(text: string): unknown {\n const trimmed = text.trim();\n if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\") && !trimmed.startsWith('\"')) {\n return undefined;\n }\n try {\n return JSON.parse(trimmed) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The canonical credit-death sentence. Credit exhaustion is the one failure a\n * user can fix themselves — the copy must say what happened (empty balance),\n * what to do (add credits), and what is safe (nothing was lost). Crucially it\n * must NOT say \"send a message to revive\": a revive turn burns credits the\n * workspace no longer has.\n */\nexport const CREDIT_EXHAUSTION_MESSAGE =\n \"Out of credits — this workspace's balance is empty. Add credits to continue; the conversation is preserved.\";\n\n/** A usage ceiling refused the send; the default allowance wording, plus what is safe. */\nexport const COMPOSER_MEMBER_ALLOWANCE_MESSAGE =\n \"Usage limit reached. A workspace admin can raise this limit. Your draft is preserved.\";\nexport const COMPOSER_WORKSPACE_ALLOWANCE_MESSAGE =\n \"Workspace usage limit reached. An organization admin can raise the workspace budget. Your draft is preserved.\";\n\n/**\n * Actionable composer copy for an edge rejection before a turn is accepted.\n * Unlike an in-flight credit exhaustion, this path has not consumed the\n * actor-private draft or any finalized attachment.\n */\nexport const COMPOSER_PAYMENT_REQUIRED_MESSAGE =\n \"Your organization doesn't have enough credits to send this message. Add credits or choose a model with another payment source. Your message and attachments are saved.\";\n\n/** Trusted, composer-owned guidance; unlike remote diagnostic prose, this is UI copy. */\nexport class ComposerStateError extends Error {}\n\n/** A restored request has omitted credentials and must be reconciled, not replayed. */\nexport class ComposerReconciliationRequiredError extends ComposerStateError {\n constructor() {\n super(\n \"Opengeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.\",\n );\n }\n}\n\nexport class ComposerWorkspaceControlUnavailableError extends Error {\n constructor() {\n super(\"@opengeni/react: workspace-scoped resume requires setWorkspaceInferenceState.\");\n }\n}\n\n/**\n * The send named a model that is no longer in the live catalog (retired or\n * removed). Retrying the same send cannot succeed; the person must choose\n * another model, and the typed message stays recoverable via Edit message.\n */\nexport const COMPOSER_MODEL_UNAVAILABLE_MESSAGE =\n \"This chat's model is no longer available. Choose another model to continue. Your message is saved.\";\n\n/** `details.code` the API sets on a 422 for a model missing from the live catalog. */\nexport const MODEL_UNAVAILABLE_DETAIL_CODE = \"model_unavailable\";\n\n/**\n * Is this send refusal \"the model is no longer available\"? Reads the typed\n * `details.code`, and also the historical message so a refusal persisted\n * before a reload (only its text survives) or from an older API is recognized.\n */\nexport function isModelUnavailableSubmissionError(error: Error): boolean {\n if (error instanceof OpenGeniApiError && error.details?.code === MODEL_UNAVAILABLE_DETAIL_CODE) {\n return true;\n }\n return (\n error.message === COMPOSER_MODEL_UNAVAILABLE_MESSAGE ||\n /^OpenGeni API 422: model is not available: /.test(error.message)\n );\n}\n\nexport function composerSubmissionErrorMessage(error: Error): string {\n if (error instanceof ComposerReconciliationRequiredError) {\n return \"This client cannot safely retry this uncertain request after remount; reconcile the session before sending again.\";\n }\n if (error instanceof ComposerWorkspaceControlUnavailableError) {\n return \"This client cannot resume the workspace. Ask your administrator for a control-capable client.\";\n }\n if (error instanceof ComposerStateError) return error.message;\n if (error instanceof OpenGeniApiError && error.outcomeUnknown) return formatErrorMessage(error);\n if (isModelUnavailableSubmissionError(error)) return COMPOSER_MODEL_UNAVAILABLE_MESSAGE;\n if (error instanceof OpenGeniApiError && error.code === \"allowance_exhausted\") {\n // OpenGeniAllowanceExhaustedError carries the scope; read it structurally\n // so this startup-path helper adds no SDK or wording imports.\n const message =\n (error as { scope?: unknown }).scope === \"workspace\"\n ? COMPOSER_WORKSPACE_ALLOWANCE_MESSAGE\n : COMPOSER_MEMBER_ALLOWANCE_MESSAGE;\n return composerErrorReference(error, message);\n }\n return isComposerCreditRefusal(error)\n ? composerErrorReference(error, COMPOSER_PAYMENT_REQUIRED_MESSAGE)\n : formatErrorMessage(error);\n}\n\nfunction composerErrorReference(error: Error, message: string): string {\n return error instanceof OpenGeniApiError && error.correlationId\n ? `${message} Reference: ${error.correlationId}.`\n : message;\n}\n\n/** These definitive refusals need a payment, allowance or model change, not an unchanged retry. */\nexport function composerSubmissionCanRetry(error: Error): boolean {\n return !(\n isComposerCreditRefusal(error) ||\n isModelUnavailableSubmissionError(error) ||\n (error instanceof OpenGeniApiError && error.code === \"allowance_exhausted\")\n );\n}\n\nfunction isComposerCreditRefusal(error: Error): boolean {\n return (\n (error instanceof OpenGeniApiError &&\n error.status === 402 &&\n (error.code === \"payment_required\" || error.code === \"insufficient_credits\")) ||\n isCreditExhaustion(error.message)\n );\n}\n\n/**\n * Does this failure/completion payload (or raw error string) mean the\n * workspace ran out of OpenGeni credits? Matches the engine's\n * \"insufficient OpenGeni credits\" text (case-insensitive, substring — it\n * arrives both bare and wrapped in \"Activity task failed: …\") and the\n * budget-exhausted segment limit the engine stamps on a turn it ended early.\n */\nexport function isCreditExhaustion(\n input: { error?: string | null; detail?: string | null; segmentLimit?: string | null } | string,\n): boolean {\n if (typeof input === \"string\") {\n return input.toLowerCase().includes(\"insufficient opengeni credits\");\n }\n if (input.segmentLimit === \"budget_exhausted\") {\n return true;\n }\n for (const text of [input.error, input.detail]) {\n if (typeof text === \"string\" && text.toLowerCase().includes(\"insufficient opengeni credits\")) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Humanize engine/provider failure text before it reaches the timeline or a\n * failure banner. Raw provider errors leak the wrong audience's instructions —\n * \"Incorrect API key … find your API key at platform.openai.com\" tells a\n * managed-deployment USER to fix credentials only an OPERATOR controls (and is\n * flatly wrong for Azure or subscription-backed engines). Auth, quota, and\n * credit-exhaustion failures collapse to one neutral, honest sentence; every\n * other reason passes through untouched. Raw payloads stay available in the\n * debug surfaces.\n */\nexport function humanizeFailureReason(reason: string | null): string | null {\n if (!reason) {\n return reason;\n }\n if (isCreditExhaustion(reason)) {\n return CREDIT_EXHAUSTION_MESSAGE;\n }\n const normalized = reason.toLowerCase();\n const authFailure =\n normalized.includes(\"incorrect api key\") ||\n normalized.includes(\"invalid api key\") ||\n normalized.includes(\"invalid_api_key\") ||\n normalized.includes(\"platform.openai.com/account/api-keys\") ||\n (normalized.includes(\"401\") &&\n (normalized.includes(\"api key\") || normalized.includes(\"unauthorized\")));\n if (authFailure) {\n return \"The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.\";\n }\n const quotaFailure =\n normalized.includes(\"insufficient_quota\") || normalized.includes(\"exceeded your current quota\");\n if (quotaFailure) {\n return \"The model provider refused the request: this deployment's provider quota is exhausted.\";\n }\n return reason;\n}\n\n/** Project failure diagnostics without changing the stored event, including legacy retry wrappers. */\nexport function presentFailure(payload: Record<string, unknown>): {\n reason: string | null;\n safetyRefusal: boolean;\n} {\n const databaseFailure =\n payload.code === \"db_deadlock\" ||\n payload.code === \"db_serialization_failure\" ||\n payload.code === \"db_failure\" ||\n (typeof payload.sqlState === \"string\" &&\n /^[0-9A-Z]{5}$/.test(payload.sqlState) &&\n payload.database !== null &&\n typeof payload.database === \"object\" &&\n !Array.isArray(payload.database));\n if (databaseFailure) {\n return { reason: \"The service encountered a database error.\", safetyRefusal: false };\n }\n const text = (key: string): string | null => {\n const value = payload[key];\n return typeof value === \"string\" && value.trim() ? value : null;\n };\n const message = text(\"error\") ?? text(\"message\");\n const detail = text(\"lastRetryableError\") ?? text(\"detail\");\n const safetyRefusal =\n payload.code === \"provider_safety_refusal\" ||\n [message, detail].some(\n (value) =>\n value !== null && /\\bthis request was blocked by our safety systems\\b/i.test(value),\n );\n if (safetyRefusal) {\n return {\n reason: `The model provider blocked this request.${detail || message ? ` ${detail ?? message}` : \"\"}`,\n safetyRefusal: true,\n };\n }\n return {\n reason:\n detail && detail !== message\n ? [humanizeFailureReason(message), humanizeFailureReason(detail)].filter(Boolean).join(\" \")\n : humanizeFailureReason(message),\n safetyRefusal: false,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-M357MHH3.js.map
@@ -1,6 +1,7 @@
1
+ "use client";
1
2
  import {
2
3
  sandboxAcceptsLiveIo
3
- } from "./chunk-YV4L3ILF.js";
4
+ } from "./chunk-QV3DJVRZ.js";
4
5
  import {
5
6
  STREAM_KIND_PTY,
6
7
  STREAM_ROLE_CLIENT,
@@ -8,13 +9,13 @@ import {
8
9
  decodeStreamOpenAck,
9
10
  encodeStreamFrame,
10
11
  encodeStreamOpen
11
- } from "./chunk-5J4FOW6Q.js";
12
+ } from "./chunk-TNEZNMSN.js";
12
13
  import {
13
14
  sandboxTerminalPeers
14
- } from "./chunk-JT4JW7BC.js";
15
+ } from "./chunk-5I4VRWBB.js";
15
16
  import {
16
17
  cn
17
- } from "./chunk-22KP6LR5.js";
18
+ } from "./chunk-TXS65BB2.js";
18
19
 
19
20
  // src/hooks/use-terminal-stream.ts
20
21
  import {
@@ -985,4 +986,4 @@ export {
985
986
  terminalCanAcquirePty,
986
987
  SandboxTerminal
987
988
  };
988
- //# sourceMappingURL=chunk-WRHIZYZF.js.map
989
+ //# sourceMappingURL=chunk-MEQM4LI3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/use-terminal-stream.ts","../src/lib/xterm-theme.ts","../src/components/sandbox-terminal.tsx","../src/lib/terminal-capability.ts","../src/lib/xterm-renderer.ts"],"sourcesContent":["import {\n TTYD_SUBPROTOCOL,\n TerminalCapability,\n terminalSocketUrl,\n ttydAuthFrame,\n ttydInputFrame,\n ttydResizeFrame,\n TtydServerCommand,\n} from \"@opengeni/sdk\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n decodeStreamFrame,\n decodeStreamOpenAck,\n encodeStreamFrame,\n encodeStreamOpen,\n type RelayChannel,\n STREAM_KIND_PTY,\n STREAM_ROLE_CLIENT,\n} from \"../lib/relay-wire\";\n\n/** The ttyd connection lifecycle as surfaced to the component. */\nexport type TerminalStreamStatus = \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport type UseTerminalStreamOptions = {\n /** The Terminal cell of the negotiated capabilities (`capabilities.Terminal`).\n * The stream connects ONLY when `transport === \"pty-ws\"` and `url` is set; on a\n * cold box (`transport === \"sse-events\"` / no url) it stays idle and the caller\n * falls back to the Channel-A read-only firehose. */\n capability: Pick<TerminalCapability, \"transport\" | \"url\" | \"token\" | \"expiresAt\"> | null;\n /** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */\n onOutput?: ((data: string) => void) | undefined;\n /** Called when ttyd sends a SET_WINDOW_TITLE frame. */\n onTitle?: ((title: string) => void) | undefined;\n /** Requests a fresh negotiated terminal grant after an unexpected transport\n * failure. Intentional disconnects and effect cleanup never call it. */\n onReconnectNeeded?: (() => void) | undefined;\n /** Initial PTY size to seed the ttyd auth frame + first resize. */\n initialCols?: number | undefined;\n initialRows?: number | undefined;\n};\n\nexport type UseTerminalStreamResult = {\n /** True once the ttyd socket is open (and the auth frame has been sent). */\n connected: boolean;\n status: TerminalStreamStatus;\n /** Pipe a keystroke/paste to PTY stdin. Input during CONNECTING is bounded and\n * replayed only after ttyd authentication succeeds. */\n write: (data: string) => void;\n /** Tell ttyd the PTY window changed size (on xterm fit/resize). */\n resize: (cols: number, rows: number) => void;\n /** Tear the socket down (the effect also tears down on unmount / url change). */\n disconnect: () => void;\n};\n\n// Keystrokes entered during the websocket handshake are held until ttyd has\n// accepted its auth/resize frames. Bound the queue by UTF-16 code units (a hard\n// <=128 KiB string payload in current JS engines) so a paste cannot turn a slow\n// handshake into unbounded renderer memory. Overflow rejects the whole pending\n// input rather than ever executing a truncated shell command.\nexport const MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;\nconst TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5_000;\nconst TERMINAL_RECONNECT_INITIAL_MS = 100;\nconst TERMINAL_RECONNECT_MAX_MS = 3_000;\nconst RELAY_TAG_OPEN = 1;\nconst RELAY_TAG_OPEN_ACK = 2;\nconst RELAY_TAG_FRAME = 3;\n\nfunction relayDatagram(tag: number, body: Uint8Array): ArrayBuffer {\n const bytes = new Uint8Array(body.length + 1);\n bytes[0] = tag;\n bytes.set(body, 1);\n return bytes.buffer;\n}\n\nfunction relayChannelFromUrl(url: string): RelayChannel {\n const endpoint = new URL(url);\n const channel: RelayChannel = {\n channelId: endpoint.searchParams.get(\"channel\") ?? \"\",\n workspaceId: endpoint.searchParams.get(\"ws\") ?? \"\",\n agentId: endpoint.searchParams.get(\"agent\") ?? \"\",\n kind: STREAM_KIND_PTY,\n port: Number(endpoint.searchParams.get(\"port\") ?? \"0\"),\n };\n if (\n !channel.channelId ||\n !channel.workspaceId ||\n !channel.agentId ||\n !Number.isSafeInteger(channel.port) ||\n channel.port <= 0\n ) {\n throw new Error(\"terminal relay endpoint is incomplete\");\n }\n return channel;\n}\n\nfunction sendRelayInput(\n socket: WebSocket,\n channel: RelayChannel,\n sequence: bigint,\n data: string,\n): void {\n socket.send(\n relayDatagram(\n RELAY_TAG_FRAME,\n encodeStreamFrame({\n channelId: channel.channelId,\n seq: sequence.toString(),\n data: new TextEncoder().encode(data),\n producedAtMs: String(Date.now()),\n }),\n ),\n );\n}\n\n/** A bearer must remain valid long enough to finish a new websocket handshake.\n * Existing open sockets are unaffected: this is evaluated only when the\n * credential identity changes and the connection effect attempts a new socket. */\nexport function terminalStreamCredentialUsable(\n capability: Pick<TerminalCapability, \"transport\" | \"url\" | \"expiresAt\"> | null | undefined,\n nowMs = Date.now(),\n): boolean {\n if (\n (capability?.transport !== \"pty-ws\" && capability?.transport !== \"relay-pty\") ||\n !capability.url\n ) {\n return false;\n }\n if (!capability.expiresAt) return true;\n const expiresAt = Date.parse(capability.expiresAt);\n return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;\n}\n\n/** Decode an inbound ttyd frame's payload (everything after the 1-char command).\n * ttyd may send either a text frame (string) or a binary frame (ArrayBuffer);\n * for binary we slice off the first byte (the command) and utf-8 decode the rest. */\nfunction decodeFrame(data: string | ArrayBuffer): { command: string; payload: string } {\n if (typeof data === \"string\") {\n return { command: data.charAt(0), payload: data.slice(1) };\n }\n const bytes = new Uint8Array(data);\n const command = bytes.length > 0 ? String.fromCharCode(bytes[0]!) : \"\";\n const payload = bytes.length > 1 ? new TextDecoder().decode(bytes.subarray(1)) : \"\";\n return { command, payload };\n}\n\nfunction closeSocket(socket: WebSocket | null): void {\n try {\n socket?.close();\n } catch {\n // Already closed or rejected by the host WebSocket implementation.\n }\n}\n\n/**\n * Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,\n * symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped\n * stream token is already embedded in the minted tunnel `url`; the WebSocket is\n * opened with the REQUIRED ttyd subprotocol \"tty\".\n *\n * ttyd wire protocol (see `@opengeni/sdk/terminal`):\n * - first frame: `JSON.stringify({ AuthToken: \"\" })` (+ optional columns/rows).\n * - client→server: INPUT = \"0\"+data ; RESIZE = \"1\"+JSON({columns,rows}).\n * - server→client: \"0\" = OUTPUT (→ xterm) ; \"1\" = SET_WINDOW_TITLE ;\n * \"2\" = SET_PREFERENCES (ignored). Binary frames are decoded the same way.\n *\n * On a `url`/`token` rotation (a box rollover folds a fresh address into the cell)\n * the effect re-runs: the old socket closes and a fresh one connects — a brief\n * terminal blink, acceptable on rollover (mirrors the desktop's RFB hot-swap).\n * SSR-safe: the socket open lives in `useEffect`, so a server render is a no-op.\n */\nexport function useTerminalStream(options: UseTerminalStreamOptions): UseTerminalStreamResult {\n const { capability, onOutput, onTitle, onReconnectNeeded, initialCols, initialRows } = options;\n const [status, setStatus] = useState<TerminalStreamStatus>(\"closed\");\n const [reconnectGeneration, setReconnectGeneration] = useState(0);\n const wsRef = useRef<WebSocket | null>(null);\n const pendingInputRef = useRef(\"\");\n const socketFailedRef = useRef(false);\n const socketAuthenticatedRef = useRef(false);\n const relayChannelRef = useRef<RelayChannel | null>(null);\n const relayIdentityRef = useRef<string | null>(null);\n const relayInputSequenceRef = useRef(0n);\n const relayOutputSequenceRef = useRef(0n);\n const reconnectAttemptRef = useRef(0);\n const credentialIdentityRef = useRef<string | null>(null);\n const refreshRequestedRef = useRef(false);\n const intentionalCloseRef = useRef<WeakSet<WebSocket>>(new WeakSet());\n // Latest size, so a resize() before the socket opens is replayed on open, and a\n // reconnect seeds the right geometry.\n const sizeRef = useRef<{ cols: number; rows: number }>({\n cols: initialCols ?? 80,\n rows: initialRows ?? 24,\n });\n // Keep the callbacks current without re-running the connect effect on every\n // render (the parent passes fresh closures each time).\n const onOutputRef = useRef(onOutput);\n const onTitleRef = useRef(onTitle);\n const onReconnectNeededRef = useRef(onReconnectNeeded);\n onOutputRef.current = onOutput;\n onTitleRef.current = onTitle;\n onReconnectNeededRef.current = onReconnectNeeded;\n\n const transport = capability?.transport ?? null;\n const url = capability?.url ?? null;\n const token = capability?.token ?? null;\n const expiresAt = capability?.expiresAt ?? null;\n const transportRef = useRef(transport);\n transportRef.current = transport;\n\n useEffect(() => {\n // SSR / no WebSocket / not a live pty-ws cell: stay closed; the caller falls\n // back to the Channel-A read-only firehose.\n if (typeof window === \"undefined\" || typeof WebSocket === \"undefined\") return;\n const relay = transport === \"relay-pty\";\n if (transport !== \"pty-ws\" && !relay) {\n // A transport downgrade is a real semantic boundary. Do not replay input\n // captured for a PTY if this surface later changes back from firehose mode.\n pendingInputRef.current = \"\";\n socketAuthenticatedRef.current = false;\n relayIdentityRef.current = null;\n relayInputSequenceRef.current = 0n;\n relayOutputSequenceRef.current = 0n;\n reconnectAttemptRef.current = 0;\n credentialIdentityRef.current = null;\n refreshRequestedRef.current = false;\n setStatus(\"closed\");\n return;\n }\n const credentialIdentity = `${transport}\\u0000${url ?? \"\"}\\u0000${token ?? \"\"}\\u0000${expiresAt ?? \"\"}`;\n if (credentialIdentityRef.current !== credentialIdentity) {\n credentialIdentityRef.current = credentialIdentity;\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n }\n // A changed/cleared credential is a new connection attempt boundary. It may\n // receive input while its exact grant is pending even if the prior socket\n // failed; the unchanged failed identity remains fenced because this effect\n // does not re-run for a status-only render.\n socketFailedRef.current = false;\n socketAuthenticatedRef.current = false;\n if (!url) {\n // Exact grants arrive asynchronously. Preserve the bounded first input\n // while the descriptor remains pty-ws, but never open without a credential.\n setStatus(\"closed\");\n return;\n }\n if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {\n // A descriptor from an older rolling server may still carry a bearer that\n // aged out before this surface mounted. Ask once for the exact fresh grant.\n setStatus(\"closed\");\n if (!refreshRequestedRef.current) {\n refreshRequestedRef.current = true;\n onReconnectNeededRef.current?.();\n }\n return;\n }\n\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let socket: WebSocket;\n let relayChannel: RelayChannel | null = null;\n const relayOutputDecoder = new TextDecoder();\n setStatus(\"connecting\");\n try {\n if (relay) {\n relayChannel = relayChannelFromUrl(url);\n const relayIdentity = [\n relayChannel.workspaceId,\n relayChannel.agentId,\n relayChannel.port,\n relayChannel.channelId,\n ].join(\"\\u0000\");\n if (relayIdentityRef.current !== relayIdentity) {\n relayIdentityRef.current = relayIdentity;\n relayInputSequenceRef.current = 0n;\n relayOutputSequenceRef.current = 0n;\n }\n relayChannelRef.current = relayChannel;\n socket = new WebSocket(url);\n } else {\n // The ttyd \"tty\" subprotocol is REQUIRED — ttyd rejects a handshake without\n // it. The scoped token is already in the tunnel `url`.\n socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);\n }\n } catch {\n socketFailedRef.current = true;\n setStatus(\"error\");\n return;\n }\n socket.binaryType = \"arraybuffer\";\n wsRef.current = socket;\n\n const requestFreshGrant = () => {\n if (disposed || refreshRequestedRef.current || intentionalCloseRef.current.has(socket))\n return;\n refreshRequestedRef.current = true;\n onReconnectNeededRef.current?.();\n };\n\n const scheduleTransportReconnect = () => {\n if (disposed || reconnectTimer !== null || intentionalCloseRef.current.has(socket)) return;\n if (refreshRequestedRef.current) return;\n if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {\n requestFreshGrant();\n return;\n }\n const attempt = reconnectAttemptRef.current++;\n const delay = Math.min(\n TERMINAL_RECONNECT_INITIAL_MS * 2 ** Math.min(attempt, 8),\n TERMINAL_RECONNECT_MAX_MS,\n );\n setStatus(\"connecting\");\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n if (!disposed) setReconnectGeneration((generation) => generation + 1);\n }, delay);\n };\n\n socket.onopen = () => {\n if (disposed) return;\n if (relay && relayChannel) {\n try {\n socket.send(\n relayDatagram(\n RELAY_TAG_OPEN,\n encodeStreamOpen({\n channel: relayChannel,\n token: token ?? \"\",\n role: STREAM_ROLE_CLIENT,\n resumeFromSeq: relayOutputSequenceRef.current.toString(),\n }),\n ),\n );\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n }\n return;\n }\n // ttyd's required first frame: the auth message (empty token — the gate is\n // the tunnel url + scoped stream token, not a ttyd -c credential), seeded\n // with the current PTY geometry. Then an explicit resize to be safe.\n try {\n socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));\n socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));\n if (pendingInputRef.current.length > 0) {\n // One websocket frame makes the pre-open queue all-or-nothing from this\n // client's perspective; never execute a prefix of a buffered command.\n socket.send(ttydInputFrame(pendingInputRef.current));\n pendingInputRef.current = \"\";\n }\n socketAuthenticatedRef.current = true;\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n return;\n }\n setStatus(\"open\");\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n };\n\n socket.onmessage = (ev: MessageEvent) => {\n if (disposed) return;\n if (relay) {\n if (!(ev.data instanceof ArrayBuffer) || !relayChannel) return;\n const bytes = new Uint8Array(ev.data);\n if (bytes.length < 1) return;\n const tag = bytes[0];\n const body = bytes.subarray(1);\n if (tag === RELAY_TAG_OPEN_ACK) {\n try {\n const ack = decodeStreamOpenAck(body);\n if (!ack.accepted) throw new Error(ack.error?.message ?? \"terminal relay rejected\");\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n socketAuthenticatedRef.current = true;\n if (pendingInputRef.current.length > 0) {\n sendRelayInput(\n socket,\n relayChannel,\n relayInputSequenceRef.current++,\n pendingInputRef.current,\n );\n pendingInputRef.current = \"\";\n }\n setStatus(\"open\");\n } catch {\n socketFailedRef.current = true;\n setStatus(\"error\");\n requestFreshGrant();\n closeSocket(socket);\n }\n } else if (tag === RELAY_TAG_FRAME && socketAuthenticatedRef.current) {\n try {\n const frame = decodeStreamFrame(body);\n const sequence = BigInt(frame.seq);\n if (sequence < relayOutputSequenceRef.current) return;\n relayOutputSequenceRef.current = sequence + 1n;\n if (frame.data.length > 0) {\n onOutputRef.current?.(relayOutputDecoder.decode(frame.data, { stream: true }));\n }\n } catch {\n // Ignore one malformed frame; the live PTY remains usable.\n }\n }\n return;\n }\n const { command, payload } = decodeFrame(ev.data as string | ArrayBuffer);\n switch (command) {\n case TtydServerCommand.OUTPUT:\n onOutputRef.current?.(payload);\n break;\n case TtydServerCommand.SET_WINDOW_TITLE:\n onTitleRef.current?.(payload);\n break;\n // SET_PREFERENCES (\"2\") and anything else: ignored.\n default:\n break;\n }\n };\n\n socket.onerror = () => {\n if (!disposed) {\n // A network/relay outage is retryable with the same credential and PTY.\n // Keep accepting bounded input while the transport comes back.\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n }\n };\n socket.onclose = () => {\n if (wsRef.current === socket) wsRef.current = null;\n socketAuthenticatedRef.current = false;\n if (!disposed) {\n if (socketFailedRef.current) setStatus(\"error\");\n else scheduleTransportReconnect();\n }\n };\n\n return () => {\n disposed = true;\n wsRef.current = null;\n socketAuthenticatedRef.current = false;\n relayChannelRef.current = null;\n if (reconnectTimer !== null) clearTimeout(reconnectTimer);\n // Drop handlers so an in-flight close/error doesn't mutate state post-unmount.\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n closeSocket(socket);\n };\n // A url/token change (rotation) re-runs this effect → close old, open new.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transport, url, token, expiresAt, reconnectGeneration]);\n\n const write = useCallback((data: string) => {\n const currentTransport = transportRef.current;\n const ws = wsRef.current;\n if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {\n try {\n const relayChannel = relayChannelRef.current;\n if (currentTransport === \"relay-pty\" && relayChannel) {\n sendRelayInput(ws, relayChannel, relayInputSequenceRef.current++, data);\n } else {\n ws.send(ttydInputFrame(data));\n }\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(ws);\n }\n } else if (\n (currentTransport === \"pty-ws\" || currentTransport === \"relay-pty\") &&\n !socketFailedRef.current\n ) {\n const nextSize = pendingInputRef.current.length + data.length;\n if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {\n pendingInputRef.current = \"\";\n socketFailedRef.current = true;\n setStatus(\"error\");\n closeSocket(ws);\n return;\n }\n pendingInputRef.current += data;\n }\n }, []);\n const resize = useCallback((cols: number, rows: number) => {\n if (cols <= 0 || rows <= 0) return;\n sizeRef.current = { cols, rows };\n const ws = wsRef.current;\n if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {\n try {\n // Self-hosted relay PTYs carry raw input/output frames. Resize remains\n // an out-of-band control operation; do not send ttyd framing into them.\n if (transportRef.current === \"pty-ws\") ws.send(ttydResizeFrame(cols, rows));\n } catch {\n // socket raced closed — geometry is replayed on the next open.\n }\n }\n }, []);\n const disconnect = useCallback(() => {\n const ws = wsRef.current;\n wsRef.current = null;\n pendingInputRef.current = \"\";\n socketFailedRef.current = false;\n socketAuthenticatedRef.current = false;\n relayChannelRef.current = null;\n setStatus(\"closed\");\n if (ws) intentionalCloseRef.current.add(ws);\n closeSocket(ws);\n }, []);\n\n return useMemo<UseTerminalStreamResult>(() => {\n return { connected: status === \"open\", status, write, resize, disconnect };\n }, [status, write, resize, disconnect]);\n}\n","import type { XtermTheme } from \"../components/sandbox-terminal\";\n\n/* ----------------------------------------------------------------------------\n xterm theming + font resolution from the `--og-*` token system.\n\n xterm paints glyphs into a canvas (WebGL / 2D) and builds its font strings by\n hand, so it can consume NEITHER CSS custom properties (`var(--og-font-mono)`\n never resolves in a canvas font string) NOR a partial palette (an unset ANSI\n slot falls back to xterm's stock VGA color). This module resolves both to\n concrete values: a full 16-color ANSI ITheme designed against the og palette\n for BOTH themes, and a concrete monospace family + px size.\n\n The color-building + font-resolution cores are pure over a token `read`\n function so they unit-test without a DOM; thin `*FromTokens` / `resolve*`\n wrappers do the `getComputedStyle` read on the client.\n -------------------------------------------------------------------------- */\n\nexport type ThemeMode = \"dark\" | \"light\";\n\n/** Reads a resolved token value (already `getComputedStyle`-flattened). */\nexport type TokenReader = (name: string) => string | undefined;\n\n/**\n * The 16 ANSI colors + selection/cursor treatment, designed against the og\n * OKLCH palette. Hues are borrowed from the og status ramp so terminal color\n * reads as part of the same system: red≈status-failed(22), green≈status-idle\n * (155), yellow≈status-running(80), magenta(305), cyan≈200. The ANSI blue and\n * magenta slots stay chromatic because program output means them; the app\n * chrome around the terminal is neutral. Dark uses lifted lightness on the\n * dark neutral ground; light uses deeper, more saturated inks on the\n * near-white ground. Bright variants add lightness + a touch of chroma.\n * `black`/`white` and the selection are neutral grey ramp stops (never pure\n * #000/#fff — that clashes with the calm neutral ground).\n */\nexport type AnsiPalette = {\n black: string;\n red: string;\n green: string;\n yellow: string;\n blue: string;\n magenta: string;\n cyan: string;\n white: string;\n brightBlack: string;\n brightRed: string;\n brightGreen: string;\n brightYellow: string;\n brightBlue: string;\n brightMagenta: string;\n brightCyan: string;\n brightWhite: string;\n /** Selection wash + its foreground; sits over the terminal ground. */\n selectionBackground: string;\n selectionForeground: string;\n selectionInactiveBackground: string;\n};\n\nconst DARK_ANSI: AnsiPalette = {\n black: \"oklch(0.3 0 0)\",\n red: \"oklch(0.68 0.17 22)\",\n green: \"oklch(0.75 0.13 155)\",\n yellow: \"oklch(0.8 0.11 80)\",\n blue: \"oklch(0.7 0.14 255)\",\n magenta: \"oklch(0.72 0.13 305)\",\n cyan: \"oklch(0.75 0.1 210)\",\n white: \"oklch(0.8 0 0)\",\n brightBlack: \"oklch(0.45 0 0)\",\n brightRed: \"oklch(0.76 0.17 22)\",\n brightGreen: \"oklch(0.82 0.14 155)\",\n brightYellow: \"oklch(0.86 0.12 85)\",\n brightBlue: \"oklch(0.78 0.13 255)\",\n brightMagenta: \"oklch(0.8 0.13 305)\",\n brightCyan: \"oklch(0.83 0.1 200)\",\n brightWhite: \"oklch(0.97 0 0)\",\n selectionBackground: \"oklch(0.8 0 0 / 0.3)\",\n selectionForeground: \"oklch(0.985 0 0)\",\n selectionInactiveBackground: \"oklch(0.72 0 0 / 0.2)\",\n};\n\nconst LIGHT_ANSI: AnsiPalette = {\n black: \"oklch(0.35 0 0)\",\n red: \"oklch(0.52 0.19 22)\",\n green: \"oklch(0.5 0.13 155)\",\n yellow: \"oklch(0.56 0.13 80)\",\n blue: \"oklch(0.52 0.18 255)\",\n magenta: \"oklch(0.52 0.16 305)\",\n cyan: \"oklch(0.52 0.1 210)\",\n white: \"oklch(0.7 0 0)\",\n brightBlack: \"oklch(0.5 0 0)\",\n brightRed: \"oklch(0.55 0.2 22)\",\n brightGreen: \"oklch(0.54 0.14 155)\",\n brightYellow: \"oklch(0.6 0.14 80)\",\n brightBlue: \"oklch(0.48 0.19 255)\",\n brightMagenta: \"oklch(0.54 0.17 305)\",\n brightCyan: \"oklch(0.54 0.11 210)\",\n brightWhite: \"oklch(0.3 0 0)\",\n selectionBackground: \"oklch(0.45 0 0 / 0.2)\",\n selectionForeground: \"oklch(0.21 0 0)\",\n selectionInactiveBackground: \"oklch(0.6 0 0 / 0.14)\",\n};\n\n/** The designed ANSI-16 palette for a theme mode. Pure. */\nexport function ogAnsiPalette(mode: ThemeMode): AnsiPalette {\n return mode === \"light\" ? LIGHT_ANSI : DARK_ANSI;\n}\n\n/**\n * Build a COMPLETE xterm ITheme from token values. Background/foreground/cursor\n * come from the live `--og-*` tokens (so an embedder's rebrand flows through);\n * the ANSI-16 + selection come from the designed palette for `mode`. Every ANSI\n * slot is always set — xterm never falls back to its stock VGA colors. Pure.\n */\nexport function buildXtermTheme(read: TokenReader, mode: ThemeMode): XtermTheme {\n const ansi = ogAnsiPalette(mode);\n const bgFallback = mode === \"light\" ? \"#ffffff\" : \"#202020\";\n const fgFallback = mode === \"light\" ? \"#242424\" : \"#e6e6e6\";\n const bg = read(\"--og-color-bg\") ?? bgFallback;\n const fg = read(\"--og-color-fg\") ?? fgFallback;\n const accent = read(\"--og-color-accent\") ?? ansi.brightBlue;\n return {\n // Match the dock surface the terminal paints on (`--og-color-bg`).\n background: bg,\n foreground: fg,\n cursor: accent,\n cursorAccent: bg,\n selectionBackground: ansi.selectionBackground,\n selectionForeground: ansi.selectionForeground,\n selectionInactiveBackground: ansi.selectionInactiveBackground,\n black: ansi.black,\n red: ansi.red,\n green: ansi.green,\n yellow: ansi.yellow,\n blue: ansi.blue,\n magenta: ansi.magenta,\n cyan: ansi.cyan,\n white: ansi.white,\n brightBlack: ansi.brightBlack,\n brightRed: ansi.brightRed,\n brightGreen: ansi.brightGreen,\n brightYellow: ansi.brightYellow,\n brightBlue: ansi.brightBlue,\n brightMagenta: ansi.brightMagenta,\n brightCyan: ansi.brightCyan,\n brightWhite: ansi.brightWhite,\n };\n}\n\n/** Which og theme is active for `el`, matching the `use-theme-type` convention\n * (nearest `data-og-theme` / `.og-light` ancestor; else the computed\n * `color-scheme`; else dark, the first-class default). */\nfunction detectMode(el: Element, style: CSSStyleDeclaration): ThemeMode {\n const closest = el.closest?.bind(el);\n if (closest) {\n if (closest('[data-og-theme=\"light\"]') || closest(\".og-light\")) return \"light\";\n const explicit = closest(\"[data-og-theme]\");\n if (explicit) return explicit.getAttribute(\"data-og-theme\") === \"light\" ? \"light\" : \"dark\";\n }\n const scheme = (\n style.getPropertyValue(\"color-scheme\").trim() ||\n style.colorScheme ||\n \"\"\n ).toLowerCase();\n if (scheme.includes(\"light\") && !scheme.includes(\"dark\")) return \"light\";\n return \"dark\";\n}\n\n/**\n * Derive a COMPLETE xterm `ITheme` from the live `--og-*` token system on the\n * DOM. Reads the COMPUTED values so xterm gets concrete colors, detects the\n * active theme mode, and fills every ANSI slot. Call on mount + re-derive on a\n * `data-og-theme` flip. SSR-safe: returns undefined off the DOM.\n *\n * `root` should be an element INSIDE the themed subtree (e.g. the terminal\n * container) so nested `[data-og-theme]` themes resolve correctly; when omitted\n * it falls back to the nearest `[data-og-theme]` element, then documentElement.\n */\nexport function xtermThemeFromTokens(root?: HTMLElement | null): XtermTheme | undefined {\n if (typeof window === \"undefined\" || typeof getComputedStyle === \"undefined\") return undefined;\n const el =\n root ??\n (typeof document !== \"undefined\"\n ? ((document.querySelector(\"[data-og-theme]\") as HTMLElement | null) ??\n document.documentElement)\n : null);\n if (!el) return undefined;\n const style = getComputedStyle(el);\n const read: TokenReader = (name) => {\n const value = style.getPropertyValue(name).trim();\n return value || undefined;\n };\n return buildXtermTheme(read, detectMode(el, style));\n}\n\n// ── Font resolution ──────────────────────────────────────────────────────────\n\nexport type ResolvedTerminalFont = { fontFamily: string; fontSize: number; lineHeight: number };\n\n/** Concrete fallback if the token is unset or (defensively) still a `var()`. */\nconst FALLBACK_MONO = \"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace\";\n\nexport type FontOverrides = { fontFamily?: string | undefined; fontSize?: number | undefined };\n\n/**\n * Resolve xterm's font from token values. The critical bug this fixes:\n * `fontFamily: \"var(--og-font-mono)\"` is measured by xterm via a canvas font\n * string where `var()` does NOT resolve, so xterm silently mis-measures every\n * glyph. Here the family is a CONCRETE stack; a `var(` in the input is treated\n * as unresolved and replaced with the fallback. Pure over a token `read`.\n */\nexport function resolveTerminalFontFromReader(\n read: TokenReader,\n overrides?: FontOverrides,\n): ResolvedTerminalFont {\n const rawFamily = overrides?.fontFamily ?? read(\"--og-font-mono\");\n const fontFamily =\n rawFamily && rawFamily.trim() && !rawFamily.includes(\"var(\") ? rawFamily.trim() : FALLBACK_MONO;\n\n let fontSize = overrides?.fontSize;\n if (fontSize == null) {\n const raw = read(\"--og-code-font-size\");\n const parsed = raw ? Number.parseFloat(raw) : Number.NaN;\n fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 13;\n }\n\n return { fontFamily, fontSize, lineHeight: 1.3 };\n}\n\n/** DOM wrapper: resolve the terminal font from an element's computed tokens. */\nexport function resolveTerminalFont(\n el: HTMLElement,\n overrides?: FontOverrides,\n): ResolvedTerminalFont {\n const style = getComputedStyle(el);\n const read: TokenReader = (name) => {\n const value = style.getPropertyValue(name).trim();\n return value || undefined;\n };\n return resolveTerminalFontFromReader(read, overrides);\n}\n","import type { TerminalCapability } from \"@opengeni/sdk\";\nimport {\n type ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { type TerminalStreamStatus, useTerminalStream } from \"../hooks/use-terminal-stream\";\nimport type { UseSandboxTerminalResult } from \"../hooks/use-sandbox-terminal\";\nimport { resolveTerminalFont, xtermThemeFromTokens } from \"../lib/xterm-theme\";\nimport { sandboxAcceptsLiveIo } from \"../lib/sandbox-liveness\";\nimport { terminalCanAcquirePty } from \"../lib/terminal-capability\";\nimport { attachRenderer, type RendererLoaders, type RendererTier } from \"../lib/xterm-renderer\";\nimport { sandboxTerminalPeers } from \"../lib/workbench-peers\";\n\n/**\n * A COMPLETE xterm `ITheme` (subset by intent, but every field xterm colors a\n * cell with is present so it never falls back to its stock VGA palette). Built\n * from the og tokens via `xtermThemeFromTokens`.\n */\nexport type XtermTheme = {\n background?: string;\n foreground?: string;\n cursor?: string;\n cursorAccent?: string;\n selectionBackground?: string;\n selectionForeground?: string;\n selectionInactiveBackground?: string;\n black?: string;\n red?: string;\n green?: string;\n yellow?: string;\n blue?: string;\n magenta?: string;\n cyan?: string;\n white?: string;\n brightBlack?: string;\n brightRed?: string;\n brightGreen?: string;\n brightYellow?: string;\n brightBlue?: string;\n brightMagenta?: string;\n brightCyan?: string;\n brightWhite?: string;\n};\n\nexport type SandboxTerminalProps = {\n /** From `useSandboxTerminal(...)`. */\n result: UseSandboxTerminalResult;\n /**\n * The negotiated Terminal capability cell. When it advertises `transport:\n * \"pty-ws\"` + a live `url` (a warm box with a viewer attached), the terminal is\n * driven by a REAL bidirectional PTY over the Modal tunnel (ttyd-over-websocket)\n * INSTEAD of the broken ptyWrite-over-HTTP path: xterm input → the socket, the\n * socket's output → xterm, xterm resize → the socket. On a cold box / no url\n * (`transport: \"sse-events\"`) it stays on the read-only Channel-A firehose\n * (`result.chunks`). Omit to force the legacy firehose-only behavior.\n */\n terminalCapability?: TerminalCapability | null | undefined;\n /**\n * Full xterm theme. When omitted the terminal self-derives it from the `--og-*`\n * tokens of its own container (correct even inside a nested `data-og-theme`\n * subtree). Passing it lets the host drive re-theme on a theme flip.\n */\n theme?: XtermTheme | undefined;\n fontFamily?: string | undefined;\n fontSize?: number | undefined;\n /**\n * Lease liveness (`cold` | `warming` | `warm` | `draining`). Drives the\n * boot-in-terminal status lines: once the user has engaged the terminal and\n * the box is not yet warm, a styled \"● waking machine — Ns…\" line is rendered\n * INSIDE xterm (no overlay/spinner) until the live PTY connects.\n */\n liveness?: string | undefined;\n /**\n * Force read-only even when the PTY accepts stdin. Default: interactive\n * whenever a live pty-ws stream is connected OR `result.write !== null` (the box\n * advertises an interactive PTY); otherwise the read-only agent firehose.\n */\n readOnly?: boolean | undefined;\n /** Shown on the server / before xterm hydrates (SSR-safe placeholder). */\n placeholder?: ReactNode | undefined;\n /** Render the small status header (pty/shell + running dot + read-only pill). */\n showHeader?: boolean | undefined;\n /** Shell label for the header (e.g. `/bin/bash`). */\n shell?: string | undefined;\n /**\n * Fired the first time the user engages the terminal surface (focus or click).\n * The host wires this to warm the box for the REAL pty-ws terminal (the viewer\n * attach), so a cold box upgrades from the read-only firehose to a live PTY ON\n * INTERACT — never on mere mount (which would force a box spin-up and regress\n * the firehose-only default).\n */\n onActivate?: (() => void) | undefined;\n /** Re-negotiate the terminal capability after an unexpected socket failure. */\n onReconnectNeeded?: (() => void) | undefined;\n className?: string | undefined;\n};\n\n// The xterm.js handle the effect owns. `any`-free structural shape so we can\n// drive it without a hard type dep on the lazily-imported lib.\ntype XtermLike = {\n open: (el: HTMLElement) => void;\n focus: () => void;\n write: (data: string) => void;\n clear: () => void;\n onData: (cb: (data: string) => void) => { dispose: () => void };\n onResize: (cb: (size: { cols: number; rows: number }) => void) => { dispose: () => void };\n loadAddon: (addon: unknown) => void;\n dispose: () => void;\n cols: number;\n rows: number;\n options: {\n theme?: XtermTheme;\n disableStdin?: boolean;\n cursorBlink?: boolean;\n fontFamily?: string;\n fontSize?: number;\n };\n};\ntype FitAddonLike = { fit: () => void };\ntype TerminalPeerBundle = {\n Terminal: new (options: Record<string, unknown>) => XtermLike;\n FitAddon: new () => FitAddonLike;\n WebLinksAddon: new (handler: (event: MouseEvent, uri: string) => void) => unknown;\n loadWebgl?:\n | (() => Promise<{\n WebglAddon: new () => {\n dispose: () => void;\n onContextLoss?: (callback: () => void) => void;\n };\n }>)\n | undefined;\n};\n\nexport type TerminalSurfaceState =\n | \"loading\"\n | \"connecting\"\n | \"error\"\n | \"waking\"\n | \"output-only\"\n | \"interactive\";\n\n/**\n * Stable semantic state for hosts and browser acceptance. The xterm helper\n * textarea exists before the live PTY socket is ready, so its presence alone\n * must never be treated as proof that keystrokes can reach the sandbox.\n */\nexport function terminalSurfaceState(input: {\n ready: boolean;\n acceptsInput: boolean;\n inputPending: boolean;\n ptyStatus: TerminalStreamStatus;\n booting: boolean;\n}): TerminalSurfaceState {\n if (!input.ready) return \"loading\";\n if (input.acceptsInput) return \"interactive\";\n if (input.inputPending) return \"connecting\";\n if (input.ptyStatus === \"connecting\") return \"connecting\";\n if (input.ptyStatus === \"error\") return \"error\";\n if (input.booting) return \"waking\";\n return \"output-only\";\n}\n\nexport function subscribeTerminalInput(\n term: Pick<XtermLike, \"onData\"> | null,\n sink: ((data: string) => void) | null,\n enabled: boolean,\n): { dispose: () => void } | null {\n if (!enabled || !term || !sink) return null;\n return term.onData(sink);\n}\n\n/** Clear the transient wake line and put the first live prompt at cell 1,1.\n * `Terminal.clear()` removes scrollback but deliberately preserves the cursor,\n * which lets the first PTY frame append to the old wake message. */\nexport function clearTerminalBootStatus(term: Pick<XtermLike, \"write\">): void {\n term.write(\"\\r\\x1b[2K\\x1b[2J\\x1b[H\");\n}\n\n/** Debug seam (dev/evidence only, never a typed public API): a global hook the\n * screenshot harness reads to probe the settled renderer tier + resolved font\n * without mounting a real WebGL context in unit tests. */\ntype TerminalDebugInfo = {\n renderer: RendererTier | null;\n fontFamily: string | undefined;\n fontSize: number | undefined;\n hasVarInFont: boolean;\n theme: XtermTheme | undefined;\n /** The live xterm instance (opaque) — the evidence harness reads its buffer to\n * prove screen preservation (E5) and drive the burst-responsiveness probe. */\n term: unknown;\n};\ndeclare global {\n // eslint-disable-next-line no-var\n var __OG_TERMINAL_DEBUG__: ((info: TerminalDebugInfo) => void) | undefined;\n // A space/comma list of renderer tiers to force-fail, so the demo can prove the\n // WebGL→canvas→DOM fallback ladder without a real GPU context loss.\n // eslint-disable-next-line no-var\n var __OG_FORCE_RENDERER_FAIL__: string | undefined;\n}\n\n/** Which renderer tiers are force-failed (demo/evidence only). */\nfunction forcedRendererFailures(): Set<RendererTier> {\n const raw = typeof globalThis !== \"undefined\" ? globalThis.__OG_FORCE_RENDERER_FAIL__ : undefined;\n const set = new Set<RendererTier>();\n if (typeof raw === \"string\") {\n for (const tier of raw.split(/[\\s,]+/)) {\n if (tier === \"webgl\" || tier === \"canvas\") set.add(tier);\n }\n }\n return set;\n}\n\n/**\n * An xterm.js terminal fed by the Channel-A event projection\n * (`useSandboxTerminal`) or a live ttyd PTY. xterm + its addons are lazy-imported\n * inside an effect, so SSR renders the placeholder and the terminal mounts on\n * hydration.\n *\n * Renderer: WebGL (GPU-composited cells) with a canvas → DOM fallback ladder\n * (`attachRenderer`), so bursty output stays smooth but a lost/blocklisted\n * context degrades gracefully. Font + theme are resolved to CONCRETE values\n * from the og tokens BEFORE construction (xterm measures glyphs in a canvas\n * where `var()` never resolves, and colors every cell from a full ANSI palette).\n *\n * The terminal is mounted ONCE and its data source is swapped in place: the\n * read-only firehose (`result.chunks`) → the live PTY. Stdin is toggled via\n * `term.options.disableStdin` at runtime (not a remount), so scrollback and the\n * visible screen survive the projection→PTY handoff.\n *\n * Resizes are tracked with a `ResizeObserver` on the container (not just\n * `window.resize`) so dragging the dock handle refits the grid.\n */\nexport function SandboxTerminal({\n result,\n terminalCapability,\n theme,\n fontFamily,\n fontSize,\n liveness,\n readOnly,\n placeholder,\n showHeader,\n shell,\n onActivate,\n onReconnectNeeded,\n className,\n}: SandboxTerminalProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const termRef = useRef<XtermLike | null>(null);\n const fitRef = useRef<FitAddonLike | null>(null);\n const rendererRef = useRef<RendererTier | null>(null);\n const writtenRef = useRef<Set<string>>(new Set());\n const wroteFirehoseRef = useRef(false);\n const bootActiveRef = useRef(false);\n const [ready, setReady] = useState(false);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const peerRevision = useSyncExternalStore(\n sandboxTerminalPeers.subscribe,\n sandboxTerminalPeers.revision,\n sandboxTerminalPeers.revision,\n );\n const [inputReady, setInputReady] = useState(false);\n const [activated, setActivated] = useState(false);\n\n // ── Interactive transport switch ─────────────────────────────────────────────\n const ptyDescriptor =\n terminalCapability?.transport === \"pty-ws\" || terminalCapability?.transport === \"relay-pty\";\n const ptyAttachable = terminalCanAcquirePty(terminalCapability);\n const {\n status: ptyStatus,\n write: ptyWrite,\n resize: resizePty,\n connected: ptyConnected,\n } = useTerminalStream({\n // Connect only after xterm exists. This makes the renderer the sole output\n // owner from the first websocket frame and removes an otherwise unbounded\n // pre-mount output queue during large command bursts.\n capability:\n ptyAttachable && ready\n ? {\n transport: terminalCapability!.transport,\n url: ptyDescriptor ? terminalCapability!.url : null,\n token: ptyDescriptor ? terminalCapability!.token : null,\n expiresAt: ptyDescriptor ? terminalCapability!.expiresAt : null,\n }\n : null,\n onOutput: (data) => {\n termRef.current?.write(data);\n },\n onReconnectNeeded,\n });\n\n // A pty-ws capability exclusively owns the screen even while connecting or\n // after a visible connection failure; never silently switch one terminal UI\n // between two PTYs. Input is enabled during the handshake because the stream\n // hook buffers it strictly and flushes only after ttyd auth succeeds.\n const ptyMode = ptyDescriptor;\n // Keep xterm stdin live for an attachable descriptor even before its URL has\n // arrived. Focus/click triggers the grant; any immediately following input is\n // bounded in the stream hook instead of disappearing in that HTTP interval.\n const ptyCapturesInput = ptyAttachable && ptyStatus !== \"error\";\n const interactive = !readOnly && (ptyAttachable ? ptyCapturesInput : result.write !== null);\n // Public \"interactive\" means the mounted input subscription can reach a live\n // PTY now. Before that point xterm still captures into the bounded queue, but\n // acceptance and embedders see an explicit connecting state.\n const acceptsInput = inputReady && (!ptyAttachable || ptyConnected);\n const inputPending = inputReady && ptyAttachable && !ptyConnected && activated;\n let terminalModeLabel: string | null;\n if (readOnly) terminalModeLabel = \"read-only\";\n else if (!ptyAttachable) terminalModeLabel = acceptsInput ? null : \"output only\";\n else if (ptyStatus === \"error\") terminalModeLabel = \"connection error\";\n else if (acceptsInput) terminalModeLabel = null;\n else terminalModeLabel = activated ? \"connecting\" : \"connect on input\";\n\n // Boot-in-terminal: after the user engages a not-yet-warm box, show styled\n // status lines INSIDE xterm instead of an overlay — but only when there is no\n // firehose transcript to show yet (otherwise the projected output is shown).\n const warm = sandboxAcceptsLiveIo(liveness);\n const booting =\n activated &&\n !ptyMode &&\n !warm &&\n liveness !== null &&\n liveness !== undefined &&\n result.chunks.length === 0;\n const surfaceState = terminalSurfaceState({\n ready,\n acceptsInput,\n inputPending,\n ptyStatus,\n booting,\n });\n\n function handleActivate() {\n // Capture-phase activation may synchronously negotiate a fresh terminal\n // capability and rerender the shell. Focus xterm before that state change so\n // the user's first keystroke or paste reaches its already-bounded input\n // queue instead of landing on the surrounding dock.\n const term = termRef.current;\n if (term && !readOnly && (ptyAttachable || result.write !== null)) {\n term.options.disableStdin = false;\n term.options.cursorBlink = true;\n }\n term?.focus();\n if (!activated) setActivated(true);\n onActivate?.();\n }\n\n // Mount xterm once (client-only). Never re-mounts on interactive/theme flips —\n // stdin + theme are runtime options synced by the effects below, so the visible\n // screen survives the projection→PTY handoff (E5).\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const el = containerRef.current;\n if (!el) return;\n let disposed = false;\n\n // Wait for a real measured size before opening xterm so the first fit is at\n // the true width (belt-and-braces with the visibility gate below).\n const waitForSize = (node: HTMLElement) =>\n new Promise<void>((resolve) => {\n if (node.clientWidth > 0 && node.clientHeight > 0) return resolve();\n const ro = new ResizeObserver(() => {\n if (node.clientWidth > 0 && node.clientHeight > 0) {\n ro.disconnect();\n resolve();\n }\n });\n ro.observe(node);\n setTimeout(() => {\n ro.disconnect();\n resolve();\n }, 1000);\n });\n\n ensureXtermBaseCss();\n\n setLoadError(null);\n void (async () => {\n const { Terminal, FitAddon, WebLinksAddon, loadWebgl } =\n (await sandboxTerminalPeers.load()) as TerminalPeerBundle;\n if (disposed) return;\n await waitForSize(el);\n if (disposed) return;\n\n // Resolve font + theme to CONCRETE values BEFORE construction. `var()` in a\n // font family never resolves in xterm's canvas measurement, so we read the\n // computed `--og-font-mono` here; the theme is a full ANSI palette.\n const font = resolveTerminalFont(el, { fontFamily, fontSize });\n const initialTheme = theme ?? xtermThemeFromTokens(el);\n\n const term = new Terminal({\n convertEol: true,\n disableStdin: !interactive,\n cursorBlink: interactive,\n fontFamily: font.fontFamily,\n fontSize: font.fontSize,\n lineHeight: font.lineHeight,\n ...(initialTheme ? { theme: initialTheme } : {}),\n }) as unknown as XtermLike;\n const fit = new FitAddon() as unknown as FitAddonLike;\n term.loadAddon(fit);\n term.loadAddon(\n new WebLinksAddon((_e: MouseEvent, uri: string) =>\n window.open(uri, \"_blank\", \"noopener,noreferrer\"),\n ) as unknown,\n );\n term.open(el);\n termRef.current = term;\n fitRef.current = fit;\n\n // Attach the fastest available renderer. For xterm-6 the supported ladder\n // is WebGL → DOM (the standalone 2D-canvas addon targets xterm-5 internals,\n // so it is intentionally NOT in the shipped path). Init failure / context\n // loss steps down to xterm's built-in DOM renderer; the settled tier is\n // exposed for evidence.\n const failSet = forcedRendererFailures();\n const loaders: RendererLoaders = {\n webgl: async (onLoss) => {\n if (disposed) throw new Error(\"Terminal was disposed\");\n if (failSet.has(\"webgl\")) throw new Error(\"forced webgl failure\");\n if (!loadWebgl) throw new Error(\"WebGL terminal renderer is not enabled\");\n const { WebglAddon } = await loadWebgl();\n if (disposed) throw new Error(\"Terminal was disposed\");\n const addon = new WebglAddon() as unknown as {\n dispose: () => void;\n onContextLoss?: (cb: () => void) => void;\n };\n addon.onContextLoss?.(() => {\n if (disposed) return;\n try {\n addon.dispose();\n } catch {\n // already disposed\n }\n onLoss();\n });\n term.loadAddon(addon);\n return addon;\n },\n };\n await attachRenderer(\"webgl\", loaders, (tier) => {\n if (disposed) return;\n rendererRef.current = tier;\n el.setAttribute(\"data-og-term-renderer\", tier);\n });\n if (disposed) return;\n\n // Fit BEFORE reveal so the first visible frame is already correctly sized\n // (no 80×24 rasterize-then-reflow flash — the container stays hidden until\n // now via the `ready` visibility gate).\n const settle = () => {\n try {\n fit.fit();\n } catch {\n // ignore fit before layout\n }\n };\n settle();\n requestAnimationFrame(() => {\n if (disposed) return;\n settle();\n el.setAttribute(\"data-og-term-ready\", \"true\");\n if (typeof globalThis.__OG_TERMINAL_DEBUG__ === \"function\") {\n globalThis.__OG_TERMINAL_DEBUG__({\n renderer: rendererRef.current,\n fontFamily: term.options.fontFamily,\n fontSize: term.options.fontSize,\n hasVarInFont: String(term.options.fontFamily ?? \"\").includes(\"var(\"),\n theme: term.options.theme,\n term,\n });\n }\n setReady(true);\n });\n })().catch((cause: unknown) => {\n if (!disposed) setLoadError(cause instanceof Error ? cause : new Error(String(cause)));\n });\n\n return () => {\n disposed = true;\n termRef.current?.dispose();\n termRef.current = null;\n fitRef.current = null;\n rendererRef.current = null;\n writtenRef.current = new Set();\n wroteFirehoseRef.current = false;\n bootActiveRef.current = false;\n setReady(false);\n };\n // Mount once per peer registration; runtime options sync without a remount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [peerRevision]);\n\n // Sync stdin/cursor at runtime (NOT a remount) when interactivity flips, so the\n // projection→PTY handoff keeps the single Terminal instance + its scrollback.\n useLayoutEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n term.options.disableStdin = !interactive;\n term.options.cursorBlink = interactive;\n }, [ready, interactive]);\n\n // Re-theme live (dark↔light flip) without re-mounting / losing scrollback. Uses\n // the passed theme, or self-derives from the container's tokens.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n const next = theme ?? xtermThemeFromTokens(containerRef.current);\n if (next) term.options.theme = next;\n }, [ready, theme]);\n\n // Boot-in-terminal status lines. While `booting`, paint a styled \"● waking\n // machine — Ns…\" line and tick it. On exit, if those lines were the ONLY\n // content (no firehose transcript), clear them for the incoming live screen —\n // never clear a real transcript (E5 preservation).\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n if (!booting) {\n if (bootActiveRef.current) {\n bootActiveRef.current = false;\n if (!wroteFirehoseRef.current) clearTerminalBootStatus(term);\n }\n return;\n }\n bootActiveRef.current = true;\n const start = Date.now();\n const paint = () => {\n const s = Math.max(0, Math.round((Date.now() - start) / 1000));\n // Bright-blue dot (accent) + dim label; `\\r` + clear-line keeps it on one\n // line so the counter updates in place.\n term.write(`\\r\\x1b[2K\\x1b[94m●\\x1b[0m \\x1b[2mwaking machine — ${s}s…\\x1b[0m`);\n };\n term.write(\"\\r\\n\");\n paint();\n const id = setInterval(paint, 1000);\n return () => clearInterval(id);\n }, [ready, booting]);\n\n // Write new firehose chunks incrementally — ONLY in firehose mode. In PTY mode\n // the ttyd socket owns the screen. Records that a real transcript exists so the\n // boot cleanup never wipes it.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term || ptyMode) return;\n for (const chunk of result.chunks) {\n if (writtenRef.current.has(chunk.id)) continue;\n writtenRef.current.add(chunk.id);\n wroteFirehoseRef.current = true;\n term.write(chunk.text);\n }\n }, [ready, result.chunks, ptyMode]);\n\n // Wire interactive input when allowed. `ptyWrite` is stable across socket\n // status changes, so this subscription does not tear down while a grant opens\n // or rotates and cannot create a first-keystroke gap.\n useLayoutEffect(() => {\n const term = termRef.current;\n if (!ready || !term || !interactive) {\n setInputReady(false);\n return;\n }\n const sink = ptyAttachable ? ptyWrite : result.write;\n if (!sink) {\n setInputReady(false);\n return;\n }\n const sub = subscribeTerminalInput(term, sink, true);\n setInputReady(sub !== null);\n return () => sub?.dispose();\n }, [ready, interactive, ptyAttachable, ptyWrite, result.write]);\n\n // In PTY mode, tell ttyd the window size on the xterm resize event.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term || !ptyMode) return;\n resizePty(term.cols, term.rows);\n const sub = term.onResize(({ cols, rows }) => resizePty(cols, rows));\n return () => sub.dispose();\n }, [ready, ptyMode, ptyConnected, resizePty]);\n\n // Refit on container resize (dock drag) AND window resize.\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const refit = () => {\n try {\n fitRef.current?.fit();\n } catch {\n // ignore\n }\n };\n window.addEventListener(\"resize\", refit);\n let observer: ResizeObserver | null = null;\n const el = containerRef.current;\n if (el && typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(() => refit());\n observer.observe(el);\n }\n return () => {\n window.removeEventListener(\"resize\", refit);\n observer?.disconnect();\n };\n }, [ready]);\n\n return (\n <div className={cn(\"relative flex h-full w-full flex-col overflow-hidden\", className)}>\n {showHeader && (\n <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\">\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <span\n className={cn(\n \"size-1.5 shrink-0 rounded-full\",\n result.running ? \"bg-og-status-running\" : \"bg-og-fg-subtle\",\n )}\n />\n <span className=\"truncate font-og-mono\">pty: {shell ?? \"shell\"}</span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {terminalModeLabel && (\n <span\n className=\"rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide\"\n role=\"status\"\n >\n {terminalModeLabel}\n </span>\n )}\n <button\n type=\"button\"\n onClick={() => {\n termRef.current?.clear();\n writtenRef.current = new Set();\n wroteFirehoseRef.current = false;\n }}\n className=\"rounded-og-sm px-1.5 py-0.5 text-og-xs hover:text-og-fg pointer-coarse:min-h-11\"\n >\n Clear\n </button>\n </span>\n </div>\n )}\n {/* `onPointerDownCapture`/`onFocusCapture` fire on the FIRST user engagement\n with the terminal surface (capture so they win even though xterm's own\n listeners stop propagation). The host warms the box for pty-ws here. The\n 12px inset + `--og-color-bg` ground match the dock surface. */}\n <div\n className=\"relative min-h-0 flex-1 bg-og-bg p-3\"\n onPointerDownCapture={handleActivate}\n onFocusCapture={handleActivate}\n >\n {!ready &&\n (loadError ? (\n <p role=\"alert\">{loadError.message}</p>\n ) : (\n (placeholder ?? <TerminalPlaceholder />)\n ))}\n {/* Kept `visibility:hidden` until the first successful fit so the first\n VISIBLE frame is already correctly sized (no 80×24 flash — E4). */}\n <div\n ref={containerRef}\n className=\"h-full w-full\"\n style={{ visibility: ready ? \"visible\" : \"hidden\" }}\n data-opengeni-terminal\n data-opengeni-terminal-ready={ready ? \"true\" : \"false\"}\n data-opengeni-terminal-interactive={surfaceState === \"interactive\" ? \"true\" : \"false\"}\n data-opengeni-terminal-state={surfaceState}\n data-opengeni-terminal-status={ptyAttachable ? ptyStatus : \"firehose\"}\n />\n </div>\n </div>\n );\n}\n\n// xterm.js ships a stylesheet (`@xterm/xterm/css/xterm.css`) that is REQUIRED for\n// correct layout — without it the `.xterm-helper-textarea` (an off-screen input\n// xterm uses for IME/keystrokes) is NOT clipped and renders as a visible 1-line\n// box in the top-left (the \"weird input box\" bug), and the viewport/screen aren't\n// positioned. The host app never imports that CSS (no global stylesheet here), so\n// we inject the structural rules once, idempotently (keyed by id), the first time\n// any terminal mounts. Scoped under `.xterm` so it can't leak into host styles.\nconst XTERM_STYLE_ID = \"opengeni-xterm-base-css\";\nconst XTERM_BASE_CSS = `\n.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}\n.xterm.focus,.xterm:focus{outline:none}\n.xterm .xterm-helpers{position:absolute;top:0;z-index:5}\n.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}\n.xterm .composition-view{background:var(--og-color-bg);color:var(--og-color-fg);display:none;position:absolute;white-space:nowrap;z-index:1}\n.xterm .composition-view.active{display:block}\n.xterm .xterm-viewport{background-color:transparent;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}\n.xterm .xterm-screen{position:relative}\n.xterm .xterm-screen canvas{position:absolute;left:0;top:0}\n.xterm .xterm-scroll-area{visibility:hidden}\n.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}\n.xterm.enable-mouse-events{cursor:default}\n.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}\n.xterm.column-select.focus{cursor:crosshair}\n.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}\n.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}\n.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}\n.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}\n.xterm-dim{opacity:1!important}\n.xterm-underline-1{text-decoration:underline}\n.xterm-underline-2{text-decoration:double underline}\n.xterm-underline-3{text-decoration:wavy underline}\n.xterm-underline-4{text-decoration:dotted underline}\n.xterm-underline-5{text-decoration:dashed underline}\n.xterm-overline{text-decoration:overline}\n.xterm-strikethrough{text-decoration:line-through}\n.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}\n.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}\n.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}\n.xterm-decoration-top{z-index:2;position:relative}\n`;\n\nfunction ensureXtermBaseCss(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(XTERM_STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = XTERM_STYLE_ID;\n style.textContent = XTERM_BASE_CSS;\n document.head.appendChild(style);\n}\n\nfunction TerminalPlaceholder() {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center text-og-sm text-og-fg-subtle\">\n Loading terminal…\n </div>\n );\n}\n","import type { TerminalCapability } from \"@opengeni/sdk\";\n\n/** Whether an explicit viewer grant can upgrade this cell to a real PTY.\n * Keep acquisition feasibility separate from the current transport: a cold\n * real-PTY backend reports sse-events until the just-in-time grant warms it. */\nexport function terminalCanAcquirePty(cell: TerminalCapability | null | undefined): boolean {\n if (!cell?.ptyCapable) return false;\n if (cell.transport === \"pty-ws\" || cell.transport === \"relay-pty\") return true;\n return cell.reason === \"lease_cold\" || cell.reason === \"not_provisioned\";\n}\n","/* ----------------------------------------------------------------------------\n xterm renderer selection ladder: WebGL → 2D canvas → DOM.\n\n WebGL is the fast path (GPU-composited cells — the whole point of the burst\n responsiveness win). But a WebGL context can fail to init (headless / no GPU /\n blocklisted driver) or be LOST at runtime (GPU reset, tab backgrounded). On\n either, we dispose the addon and step down one tier so the terminal keeps\n painting via the 2D canvas addon, and failing that xterm's built-in DOM\n renderer (no addon).\n\n `attachRenderer` is pure over its `loaders` (each closes over the xterm\n instance and constructs+loads its addon, throwing on failure), so the ladder\n + context-loss downgrade unit-test without a real WebGL context.\n -------------------------------------------------------------------------- */\n\nexport type RendererTier = \"webgl\" | \"canvas\" | \"dom\";\n\nexport type RendererAddon = { dispose: () => void };\n\n/** A loader constructs + `loadAddon`s its renderer, wiring `onLoss` to the\n * addon's context-loss event, and throws if the renderer can't initialize. */\nexport type RendererLoaders = {\n webgl?: ((onLoss: () => void) => Promise<RendererAddon>) | undefined;\n canvas?: (() => Promise<RendererAddon>) | undefined;\n};\n\nconst ORDER: RendererTier[] = [\"webgl\", \"canvas\", \"dom\"];\n\n/** The next lower renderer tier, or null at the bottom (DOM). Pure. */\nexport function nextRendererTier(tier: RendererTier): RendererTier | null {\n const i = ORDER.indexOf(tier);\n return i >= 0 && i < ORDER.length - 1 ? ORDER[i + 1]! : null;\n}\n\n/**\n * Attach the highest renderer tier at or below `prefer` that initializes,\n * reporting the settled tier via `onTier`. On a WebGL/canvas context loss the\n * loader's `onLoss` re-enters at the next tier down, so `onTier` is called again\n * with the downgraded tier. Returns the tier chosen on this pass.\n */\nexport async function attachRenderer(\n prefer: RendererTier,\n loaders: RendererLoaders,\n onTier: (tier: RendererTier) => void,\n): Promise<RendererTier> {\n const start = Math.max(0, ORDER.indexOf(prefer));\n for (let i = start; i < ORDER.length; i++) {\n const tier = ORDER[i]!;\n if (tier === \"dom\") break;\n const loader = tier === \"webgl\" ? loaders.webgl : loaders.canvas;\n if (!loader) continue;\n try {\n const downgrade = nextRendererTier(tier) ?? \"dom\";\n await loader(() => {\n void attachRenderer(downgrade, loaders, onTier);\n });\n onTier(tier);\n return tier;\n } catch {\n // Initialization failed — fall through to the next tier down.\n }\n }\n onTier(\"dom\");\n return \"dom\";\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useRef","useState","useEffect"]}
1
+ {"version":3,"sources":["../src/hooks/use-terminal-stream.ts","../src/lib/xterm-theme.ts","../src/components/sandbox-terminal.tsx","../src/lib/terminal-capability.ts","../src/lib/xterm-renderer.ts"],"sourcesContent":["import {\n TTYD_SUBPROTOCOL,\n TerminalCapability,\n terminalSocketUrl,\n ttydAuthFrame,\n ttydInputFrame,\n ttydResizeFrame,\n TtydServerCommand,\n} from \"@opengeni/sdk\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n decodeStreamFrame,\n decodeStreamOpenAck,\n encodeStreamFrame,\n encodeStreamOpen,\n type RelayChannel,\n STREAM_KIND_PTY,\n STREAM_ROLE_CLIENT,\n} from \"../lib/relay-wire\";\n\n/** The ttyd connection lifecycle as surfaced to the component. */\nexport type TerminalStreamStatus = \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport type UseTerminalStreamOptions = {\n /** The Terminal cell of the negotiated capabilities (`capabilities.Terminal`).\n * The stream connects ONLY when `transport === \"pty-ws\"` and `url` is set; on a\n * cold box (`transport === \"sse-events\"` / no url) it stays idle and the caller\n * falls back to the Channel-A read-only firehose. */\n capability: Pick<TerminalCapability, \"transport\" | \"url\" | \"token\" | \"expiresAt\"> | null;\n /** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */\n onOutput?: ((data: string) => void) | undefined;\n /** Called when ttyd sends a SET_WINDOW_TITLE frame. */\n onTitle?: ((title: string) => void) | undefined;\n /** Requests a fresh negotiated terminal grant after an unexpected transport\n * failure. Intentional disconnects and effect cleanup never call it. */\n onReconnectNeeded?: (() => void) | undefined;\n /** Initial PTY size to seed the ttyd auth frame + first resize. */\n initialCols?: number | undefined;\n initialRows?: number | undefined;\n};\n\nexport type UseTerminalStreamResult = {\n /** True once the ttyd socket is open (and the auth frame has been sent). */\n connected: boolean;\n status: TerminalStreamStatus;\n /** Pipe a keystroke/paste to PTY stdin. Input during CONNECTING is bounded and\n * replayed only after ttyd authentication succeeds. */\n write: (data: string) => void;\n /** Tell ttyd the PTY window changed size (on xterm fit/resize). */\n resize: (cols: number, rows: number) => void;\n /** Tear the socket down (the effect also tears down on unmount / url change). */\n disconnect: () => void;\n};\n\n// Keystrokes entered during the websocket handshake are held until ttyd has\n// accepted its auth/resize frames. Bound the queue by UTF-16 code units (a hard\n// <=128 KiB string payload in current JS engines) so a paste cannot turn a slow\n// handshake into unbounded renderer memory. Overflow rejects the whole pending\n// input rather than ever executing a truncated shell command.\nexport const MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;\nconst TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5_000;\nconst TERMINAL_RECONNECT_INITIAL_MS = 100;\nconst TERMINAL_RECONNECT_MAX_MS = 3_000;\nconst RELAY_TAG_OPEN = 1;\nconst RELAY_TAG_OPEN_ACK = 2;\nconst RELAY_TAG_FRAME = 3;\n\nfunction relayDatagram(tag: number, body: Uint8Array): ArrayBuffer {\n const bytes = new Uint8Array(body.length + 1);\n bytes[0] = tag;\n bytes.set(body, 1);\n return bytes.buffer;\n}\n\nfunction relayChannelFromUrl(url: string): RelayChannel {\n const endpoint = new URL(url);\n const channel: RelayChannel = {\n channelId: endpoint.searchParams.get(\"channel\") ?? \"\",\n workspaceId: endpoint.searchParams.get(\"ws\") ?? \"\",\n agentId: endpoint.searchParams.get(\"agent\") ?? \"\",\n kind: STREAM_KIND_PTY,\n port: Number(endpoint.searchParams.get(\"port\") ?? \"0\"),\n };\n if (\n !channel.channelId ||\n !channel.workspaceId ||\n !channel.agentId ||\n !Number.isSafeInteger(channel.port) ||\n channel.port <= 0\n ) {\n throw new Error(\"terminal relay endpoint is incomplete\");\n }\n return channel;\n}\n\nfunction sendRelayInput(\n socket: WebSocket,\n channel: RelayChannel,\n sequence: bigint,\n data: string,\n): void {\n socket.send(\n relayDatagram(\n RELAY_TAG_FRAME,\n encodeStreamFrame({\n channelId: channel.channelId,\n seq: sequence.toString(),\n data: new TextEncoder().encode(data),\n producedAtMs: String(Date.now()),\n }),\n ),\n );\n}\n\n/** A bearer must remain valid long enough to finish a new websocket handshake.\n * Existing open sockets are unaffected: this is evaluated only when the\n * credential identity changes and the connection effect attempts a new socket. */\nexport function terminalStreamCredentialUsable(\n capability: Pick<TerminalCapability, \"transport\" | \"url\" | \"expiresAt\"> | null | undefined,\n nowMs = Date.now(),\n): boolean {\n if (\n (capability?.transport !== \"pty-ws\" && capability?.transport !== \"relay-pty\") ||\n !capability.url\n ) {\n return false;\n }\n if (!capability.expiresAt) return true;\n const expiresAt = Date.parse(capability.expiresAt);\n return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;\n}\n\n/** Decode an inbound ttyd frame's payload (everything after the 1-char command).\n * ttyd may send either a text frame (string) or a binary frame (ArrayBuffer);\n * for binary we slice off the first byte (the command) and utf-8 decode the rest. */\nfunction decodeFrame(data: string | ArrayBuffer): { command: string; payload: string } {\n if (typeof data === \"string\") {\n return { command: data.charAt(0), payload: data.slice(1) };\n }\n const bytes = new Uint8Array(data);\n const command = bytes.length > 0 ? String.fromCharCode(bytes[0]!) : \"\";\n const payload = bytes.length > 1 ? new TextDecoder().decode(bytes.subarray(1)) : \"\";\n return { command, payload };\n}\n\nfunction closeSocket(socket: WebSocket | null): void {\n try {\n socket?.close();\n } catch {\n // Already closed or rejected by the host WebSocket implementation.\n }\n}\n\n/**\n * Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,\n * symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped\n * stream token is already embedded in the minted tunnel `url`; the WebSocket is\n * opened with the REQUIRED ttyd subprotocol \"tty\".\n *\n * ttyd wire protocol (see `@opengeni/sdk/terminal`):\n * - first frame: `JSON.stringify({ AuthToken: \"\" })` (+ optional columns/rows).\n * - client→server: INPUT = \"0\"+data ; RESIZE = \"1\"+JSON({columns,rows}).\n * - server→client: \"0\" = OUTPUT (→ xterm) ; \"1\" = SET_WINDOW_TITLE ;\n * \"2\" = SET_PREFERENCES (ignored). Binary frames are decoded the same way.\n *\n * On a `url`/`token` rotation (a box rollover folds a fresh address into the cell)\n * the effect re-runs: the old socket closes and a fresh one connects — a brief\n * terminal blink, acceptable on rollover (mirrors the desktop's RFB hot-swap).\n * SSR-safe: the socket open lives in `useEffect`, so a server render is a no-op.\n */\nexport function useTerminalStream(options: UseTerminalStreamOptions): UseTerminalStreamResult {\n const { capability, onOutput, onTitle, onReconnectNeeded, initialCols, initialRows } = options;\n const [status, setStatus] = useState<TerminalStreamStatus>(\"closed\");\n const [reconnectGeneration, setReconnectGeneration] = useState(0);\n const wsRef = useRef<WebSocket | null>(null);\n const pendingInputRef = useRef(\"\");\n const socketFailedRef = useRef(false);\n const socketAuthenticatedRef = useRef(false);\n const relayChannelRef = useRef<RelayChannel | null>(null);\n const relayIdentityRef = useRef<string | null>(null);\n const relayInputSequenceRef = useRef(0n);\n const relayOutputSequenceRef = useRef(0n);\n const reconnectAttemptRef = useRef(0);\n const credentialIdentityRef = useRef<string | null>(null);\n const refreshRequestedRef = useRef(false);\n const intentionalCloseRef = useRef<WeakSet<WebSocket>>(new WeakSet());\n // Latest size, so a resize() before the socket opens is replayed on open, and a\n // reconnect seeds the right geometry.\n const sizeRef = useRef<{ cols: number; rows: number }>({\n cols: initialCols ?? 80,\n rows: initialRows ?? 24,\n });\n // Keep the callbacks current without re-running the connect effect on every\n // render (the parent passes fresh closures each time).\n const onOutputRef = useRef(onOutput);\n const onTitleRef = useRef(onTitle);\n const onReconnectNeededRef = useRef(onReconnectNeeded);\n onOutputRef.current = onOutput;\n onTitleRef.current = onTitle;\n onReconnectNeededRef.current = onReconnectNeeded;\n\n const transport = capability?.transport ?? null;\n const url = capability?.url ?? null;\n const token = capability?.token ?? null;\n const expiresAt = capability?.expiresAt ?? null;\n const transportRef = useRef(transport);\n transportRef.current = transport;\n\n useEffect(() => {\n // SSR / no WebSocket / not a live pty-ws cell: stay closed; the caller falls\n // back to the Channel-A read-only firehose.\n if (typeof window === \"undefined\" || typeof WebSocket === \"undefined\") return;\n const relay = transport === \"relay-pty\";\n if (transport !== \"pty-ws\" && !relay) {\n // A transport downgrade is a real semantic boundary. Do not replay input\n // captured for a PTY if this surface later changes back from firehose mode.\n pendingInputRef.current = \"\";\n socketAuthenticatedRef.current = false;\n relayIdentityRef.current = null;\n relayInputSequenceRef.current = 0n;\n relayOutputSequenceRef.current = 0n;\n reconnectAttemptRef.current = 0;\n credentialIdentityRef.current = null;\n refreshRequestedRef.current = false;\n setStatus(\"closed\");\n return;\n }\n const credentialIdentity = `${transport}\\u0000${url ?? \"\"}\\u0000${token ?? \"\"}\\u0000${expiresAt ?? \"\"}`;\n if (credentialIdentityRef.current !== credentialIdentity) {\n credentialIdentityRef.current = credentialIdentity;\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n }\n // A changed/cleared credential is a new connection attempt boundary. It may\n // receive input while its exact grant is pending even if the prior socket\n // failed; the unchanged failed identity remains fenced because this effect\n // does not re-run for a status-only render.\n socketFailedRef.current = false;\n socketAuthenticatedRef.current = false;\n if (!url) {\n // Exact grants arrive asynchronously. Preserve the bounded first input\n // while the descriptor remains pty-ws, but never open without a credential.\n setStatus(\"closed\");\n return;\n }\n if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {\n // A descriptor from an older rolling server may still carry a bearer that\n // aged out before this surface mounted. Ask once for the exact fresh grant.\n setStatus(\"closed\");\n if (!refreshRequestedRef.current) {\n refreshRequestedRef.current = true;\n onReconnectNeededRef.current?.();\n }\n return;\n }\n\n let disposed = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let socket: WebSocket;\n let relayChannel: RelayChannel | null = null;\n const relayOutputDecoder = new TextDecoder();\n setStatus(\"connecting\");\n try {\n if (relay) {\n relayChannel = relayChannelFromUrl(url);\n const relayIdentity = [\n relayChannel.workspaceId,\n relayChannel.agentId,\n relayChannel.port,\n relayChannel.channelId,\n ].join(\"\\u0000\");\n if (relayIdentityRef.current !== relayIdentity) {\n relayIdentityRef.current = relayIdentity;\n relayInputSequenceRef.current = 0n;\n relayOutputSequenceRef.current = 0n;\n }\n relayChannelRef.current = relayChannel;\n socket = new WebSocket(url);\n } else {\n // The ttyd \"tty\" subprotocol is REQUIRED — ttyd rejects a handshake without\n // it. The scoped token is already in the tunnel `url`.\n socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);\n }\n } catch {\n socketFailedRef.current = true;\n setStatus(\"error\");\n return;\n }\n socket.binaryType = \"arraybuffer\";\n wsRef.current = socket;\n\n const requestFreshGrant = () => {\n if (disposed || refreshRequestedRef.current || intentionalCloseRef.current.has(socket))\n return;\n refreshRequestedRef.current = true;\n onReconnectNeededRef.current?.();\n };\n\n const scheduleTransportReconnect = () => {\n if (disposed || reconnectTimer !== null || intentionalCloseRef.current.has(socket)) return;\n if (refreshRequestedRef.current) return;\n if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {\n requestFreshGrant();\n return;\n }\n const attempt = reconnectAttemptRef.current++;\n const delay = Math.min(\n TERMINAL_RECONNECT_INITIAL_MS * 2 ** Math.min(attempt, 8),\n TERMINAL_RECONNECT_MAX_MS,\n );\n setStatus(\"connecting\");\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n if (!disposed) setReconnectGeneration((generation) => generation + 1);\n }, delay);\n };\n\n socket.onopen = () => {\n if (disposed) return;\n if (relay && relayChannel) {\n try {\n socket.send(\n relayDatagram(\n RELAY_TAG_OPEN,\n encodeStreamOpen({\n channel: relayChannel,\n token: token ?? \"\",\n role: STREAM_ROLE_CLIENT,\n resumeFromSeq: relayOutputSequenceRef.current.toString(),\n }),\n ),\n );\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n }\n return;\n }\n // ttyd's required first frame: the auth message (empty token — the gate is\n // the tunnel url + scoped stream token, not a ttyd -c credential), seeded\n // with the current PTY geometry. Then an explicit resize to be safe.\n try {\n socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));\n socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));\n if (pendingInputRef.current.length > 0) {\n // One websocket frame makes the pre-open queue all-or-nothing from this\n // client's perspective; never execute a prefix of a buffered command.\n socket.send(ttydInputFrame(pendingInputRef.current));\n pendingInputRef.current = \"\";\n }\n socketAuthenticatedRef.current = true;\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n return;\n }\n setStatus(\"open\");\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n };\n\n socket.onmessage = (ev: MessageEvent) => {\n if (disposed) return;\n if (relay) {\n if (!(ev.data instanceof ArrayBuffer) || !relayChannel) return;\n const bytes = new Uint8Array(ev.data);\n if (bytes.length < 1) return;\n const tag = bytes[0];\n const body = bytes.subarray(1);\n if (tag === RELAY_TAG_OPEN_ACK) {\n try {\n const ack = decodeStreamOpenAck(body);\n if (!ack.accepted) throw new Error(ack.error?.message ?? \"terminal relay rejected\");\n reconnectAttemptRef.current = 0;\n refreshRequestedRef.current = false;\n socketAuthenticatedRef.current = true;\n if (pendingInputRef.current.length > 0) {\n sendRelayInput(\n socket,\n relayChannel,\n relayInputSequenceRef.current++,\n pendingInputRef.current,\n );\n pendingInputRef.current = \"\";\n }\n setStatus(\"open\");\n } catch {\n socketFailedRef.current = true;\n setStatus(\"error\");\n requestFreshGrant();\n closeSocket(socket);\n }\n } else if (tag === RELAY_TAG_FRAME && socketAuthenticatedRef.current) {\n try {\n const frame = decodeStreamFrame(body);\n const sequence = BigInt(frame.seq);\n if (sequence < relayOutputSequenceRef.current) return;\n relayOutputSequenceRef.current = sequence + 1n;\n if (frame.data.length > 0) {\n onOutputRef.current?.(relayOutputDecoder.decode(frame.data, { stream: true }));\n }\n } catch {\n // Ignore one malformed frame; the live PTY remains usable.\n }\n }\n return;\n }\n const { command, payload } = decodeFrame(ev.data as string | ArrayBuffer);\n switch (command) {\n case TtydServerCommand.OUTPUT:\n onOutputRef.current?.(payload);\n break;\n case TtydServerCommand.SET_WINDOW_TITLE:\n onTitleRef.current?.(payload);\n break;\n // SET_PREFERENCES (\"2\") and anything else: ignored.\n default:\n break;\n }\n };\n\n socket.onerror = () => {\n if (!disposed) {\n // A network/relay outage is retryable with the same credential and PTY.\n // Keep accepting bounded input while the transport comes back.\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(socket);\n }\n };\n socket.onclose = () => {\n if (wsRef.current === socket) wsRef.current = null;\n socketAuthenticatedRef.current = false;\n if (!disposed) {\n if (socketFailedRef.current) setStatus(\"error\");\n else scheduleTransportReconnect();\n }\n };\n\n return () => {\n disposed = true;\n wsRef.current = null;\n socketAuthenticatedRef.current = false;\n relayChannelRef.current = null;\n if (reconnectTimer !== null) clearTimeout(reconnectTimer);\n // Drop handlers so an in-flight close/error doesn't mutate state post-unmount.\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n closeSocket(socket);\n };\n // A url/token change (rotation) re-runs this effect → close old, open new.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transport, url, token, expiresAt, reconnectGeneration]);\n\n const write = useCallback((data: string) => {\n const currentTransport = transportRef.current;\n const ws = wsRef.current;\n if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {\n try {\n const relayChannel = relayChannelRef.current;\n if (currentTransport === \"relay-pty\" && relayChannel) {\n sendRelayInput(ws, relayChannel, relayInputSequenceRef.current++, data);\n } else {\n ws.send(ttydInputFrame(data));\n }\n } catch {\n socketFailedRef.current = false;\n setStatus(\"connecting\");\n closeSocket(ws);\n }\n } else if (\n (currentTransport === \"pty-ws\" || currentTransport === \"relay-pty\") &&\n !socketFailedRef.current\n ) {\n const nextSize = pendingInputRef.current.length + data.length;\n if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {\n pendingInputRef.current = \"\";\n socketFailedRef.current = true;\n setStatus(\"error\");\n closeSocket(ws);\n return;\n }\n pendingInputRef.current += data;\n }\n }, []);\n const resize = useCallback((cols: number, rows: number) => {\n if (cols <= 0 || rows <= 0) return;\n sizeRef.current = { cols, rows };\n const ws = wsRef.current;\n if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {\n try {\n // Self-hosted relay PTYs carry raw input/output frames. Resize remains\n // an out-of-band control operation; do not send ttyd framing into them.\n if (transportRef.current === \"pty-ws\") ws.send(ttydResizeFrame(cols, rows));\n } catch {\n // socket raced closed — geometry is replayed on the next open.\n }\n }\n }, []);\n const disconnect = useCallback(() => {\n const ws = wsRef.current;\n wsRef.current = null;\n pendingInputRef.current = \"\";\n socketFailedRef.current = false;\n socketAuthenticatedRef.current = false;\n relayChannelRef.current = null;\n setStatus(\"closed\");\n if (ws) intentionalCloseRef.current.add(ws);\n closeSocket(ws);\n }, []);\n\n return useMemo<UseTerminalStreamResult>(() => {\n return { connected: status === \"open\", status, write, resize, disconnect };\n }, [status, write, resize, disconnect]);\n}\n","import type { XtermTheme } from \"../components/sandbox-terminal\";\n\n/* ----------------------------------------------------------------------------\n xterm theming + font resolution from the `--og-*` token system.\n\n xterm paints glyphs into a canvas (WebGL / 2D) and builds its font strings by\n hand, so it can consume NEITHER CSS custom properties (`var(--og-font-mono)`\n never resolves in a canvas font string) NOR a partial palette (an unset ANSI\n slot falls back to xterm's stock VGA color). This module resolves both to\n concrete values: a full 16-color ANSI ITheme designed against the og palette\n for BOTH themes, and a concrete monospace family + px size.\n\n The color-building + font-resolution cores are pure over a token `read`\n function so they unit-test without a DOM; thin `*FromTokens` / `resolve*`\n wrappers do the `getComputedStyle` read on the client.\n -------------------------------------------------------------------------- */\n\nexport type ThemeMode = \"dark\" | \"light\";\n\n/** Reads a resolved token value (already `getComputedStyle`-flattened). */\nexport type TokenReader = (name: string) => string | undefined;\n\n/**\n * The 16 ANSI colors + selection/cursor treatment, designed against the og\n * OKLCH palette. Hues are borrowed from the og status ramp so terminal color\n * reads as part of the same system: red≈status-failed(22), green≈status-idle\n * (155), yellow≈status-running(80), magenta(305), cyan≈200. The ANSI blue and\n * magenta slots stay chromatic because program output means them; the app\n * chrome around the terminal is neutral. Dark uses lifted lightness on the\n * dark neutral ground; light uses deeper, more saturated inks on the\n * near-white ground. Bright variants add lightness + a touch of chroma.\n * `black`/`white` and the selection are neutral grey ramp stops (never pure\n * #000/#fff — that clashes with the calm neutral ground).\n */\nexport type AnsiPalette = {\n black: string;\n red: string;\n green: string;\n yellow: string;\n blue: string;\n magenta: string;\n cyan: string;\n white: string;\n brightBlack: string;\n brightRed: string;\n brightGreen: string;\n brightYellow: string;\n brightBlue: string;\n brightMagenta: string;\n brightCyan: string;\n brightWhite: string;\n /** Selection wash + its foreground; sits over the terminal ground. */\n selectionBackground: string;\n selectionForeground: string;\n selectionInactiveBackground: string;\n};\n\nconst DARK_ANSI: AnsiPalette = {\n black: \"oklch(0.3 0 0)\",\n red: \"oklch(0.68 0.17 22)\",\n green: \"oklch(0.75 0.13 155)\",\n yellow: \"oklch(0.8 0.11 80)\",\n blue: \"oklch(0.7 0.14 255)\",\n magenta: \"oklch(0.72 0.13 305)\",\n cyan: \"oklch(0.75 0.1 210)\",\n white: \"oklch(0.8 0 0)\",\n brightBlack: \"oklch(0.45 0 0)\",\n brightRed: \"oklch(0.76 0.17 22)\",\n brightGreen: \"oklch(0.82 0.14 155)\",\n brightYellow: \"oklch(0.86 0.12 85)\",\n brightBlue: \"oklch(0.78 0.13 255)\",\n brightMagenta: \"oklch(0.8 0.13 305)\",\n brightCyan: \"oklch(0.83 0.1 200)\",\n brightWhite: \"oklch(0.97 0 0)\",\n selectionBackground: \"oklch(0.8 0 0 / 0.3)\",\n selectionForeground: \"oklch(0.985 0 0)\",\n selectionInactiveBackground: \"oklch(0.72 0 0 / 0.2)\",\n};\n\nconst LIGHT_ANSI: AnsiPalette = {\n black: \"oklch(0.35 0 0)\",\n red: \"oklch(0.52 0.19 22)\",\n green: \"oklch(0.5 0.13 155)\",\n yellow: \"oklch(0.56 0.13 80)\",\n blue: \"oklch(0.52 0.18 255)\",\n magenta: \"oklch(0.52 0.16 305)\",\n cyan: \"oklch(0.52 0.1 210)\",\n white: \"oklch(0.7 0 0)\",\n brightBlack: \"oklch(0.5 0 0)\",\n brightRed: \"oklch(0.55 0.2 22)\",\n brightGreen: \"oklch(0.54 0.14 155)\",\n brightYellow: \"oklch(0.6 0.14 80)\",\n brightBlue: \"oklch(0.48 0.19 255)\",\n brightMagenta: \"oklch(0.54 0.17 305)\",\n brightCyan: \"oklch(0.54 0.11 210)\",\n brightWhite: \"oklch(0.3 0 0)\",\n selectionBackground: \"oklch(0.45 0 0 / 0.2)\",\n selectionForeground: \"oklch(0.21 0 0)\",\n selectionInactiveBackground: \"oklch(0.6 0 0 / 0.14)\",\n};\n\n/** The designed ANSI-16 palette for a theme mode. Pure. */\nexport function ogAnsiPalette(mode: ThemeMode): AnsiPalette {\n return mode === \"light\" ? LIGHT_ANSI : DARK_ANSI;\n}\n\n/**\n * Build a COMPLETE xterm ITheme from token values. Background/foreground/cursor\n * come from the live `--og-*` tokens (so an embedder's rebrand flows through);\n * the ANSI-16 + selection come from the designed palette for `mode`. Every ANSI\n * slot is always set — xterm never falls back to its stock VGA colors. Pure.\n */\nexport function buildXtermTheme(read: TokenReader, mode: ThemeMode): XtermTheme {\n const ansi = ogAnsiPalette(mode);\n const bgFallback = mode === \"light\" ? \"#ffffff\" : \"#202020\";\n const fgFallback = mode === \"light\" ? \"#242424\" : \"#e6e6e6\";\n const bg = read(\"--og-color-bg\") ?? bgFallback;\n const fg = read(\"--og-color-fg\") ?? fgFallback;\n const accent = read(\"--og-color-accent\") ?? ansi.brightBlue;\n return {\n // Match the dock surface the terminal paints on (`--og-color-bg`).\n background: bg,\n foreground: fg,\n cursor: accent,\n cursorAccent: bg,\n selectionBackground: ansi.selectionBackground,\n selectionForeground: ansi.selectionForeground,\n selectionInactiveBackground: ansi.selectionInactiveBackground,\n black: ansi.black,\n red: ansi.red,\n green: ansi.green,\n yellow: ansi.yellow,\n blue: ansi.blue,\n magenta: ansi.magenta,\n cyan: ansi.cyan,\n white: ansi.white,\n brightBlack: ansi.brightBlack,\n brightRed: ansi.brightRed,\n brightGreen: ansi.brightGreen,\n brightYellow: ansi.brightYellow,\n brightBlue: ansi.brightBlue,\n brightMagenta: ansi.brightMagenta,\n brightCyan: ansi.brightCyan,\n brightWhite: ansi.brightWhite,\n };\n}\n\n/** Which og theme is active for `el`, matching the `use-theme-type` convention\n * (nearest `data-og-theme` / `.og-light` ancestor; else the computed\n * `color-scheme`; else dark, the first-class default). */\nfunction detectMode(el: Element, style: CSSStyleDeclaration): ThemeMode {\n const closest = el.closest?.bind(el);\n if (closest) {\n if (closest('[data-og-theme=\"light\"]') || closest(\".og-light\")) return \"light\";\n const explicit = closest(\"[data-og-theme]\");\n if (explicit) return explicit.getAttribute(\"data-og-theme\") === \"light\" ? \"light\" : \"dark\";\n }\n const scheme = (\n style.getPropertyValue(\"color-scheme\").trim() ||\n style.colorScheme ||\n \"\"\n ).toLowerCase();\n if (scheme.includes(\"light\") && !scheme.includes(\"dark\")) return \"light\";\n return \"dark\";\n}\n\n/**\n * Derive a COMPLETE xterm `ITheme` from the live `--og-*` token system on the\n * DOM. Reads the COMPUTED values so xterm gets concrete colors, detects the\n * active theme mode, and fills every ANSI slot. Call on mount + re-derive on a\n * `data-og-theme` flip. SSR-safe: returns undefined off the DOM.\n *\n * `root` should be an element INSIDE the themed subtree (e.g. the terminal\n * container) so nested `[data-og-theme]` themes resolve correctly; when omitted\n * it falls back to the nearest `[data-og-theme]` element, then documentElement.\n */\nexport function xtermThemeFromTokens(root?: HTMLElement | null): XtermTheme | undefined {\n if (typeof window === \"undefined\" || typeof getComputedStyle === \"undefined\") return undefined;\n const el =\n root ??\n (typeof document !== \"undefined\"\n ? ((document.querySelector(\"[data-og-theme]\") as HTMLElement | null) ??\n document.documentElement)\n : null);\n if (!el) return undefined;\n const style = getComputedStyle(el);\n const read: TokenReader = (name) => {\n const value = style.getPropertyValue(name).trim();\n return value || undefined;\n };\n return buildXtermTheme(read, detectMode(el, style));\n}\n\n// ── Font resolution ──────────────────────────────────────────────────────────\n\nexport type ResolvedTerminalFont = { fontFamily: string; fontSize: number; lineHeight: number };\n\n/** Concrete fallback if the token is unset or (defensively) still a `var()`. */\nconst FALLBACK_MONO = \"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace\";\n\nexport type FontOverrides = { fontFamily?: string | undefined; fontSize?: number | undefined };\n\n/**\n * Resolve xterm's font from token values. The critical bug this fixes:\n * `fontFamily: \"var(--og-font-mono)\"` is measured by xterm via a canvas font\n * string where `var()` does NOT resolve, so xterm silently mis-measures every\n * glyph. Here the family is a CONCRETE stack; a `var(` in the input is treated\n * as unresolved and replaced with the fallback. Pure over a token `read`.\n */\nexport function resolveTerminalFontFromReader(\n read: TokenReader,\n overrides?: FontOverrides,\n): ResolvedTerminalFont {\n const rawFamily = overrides?.fontFamily ?? read(\"--og-font-mono\");\n const fontFamily =\n rawFamily && rawFamily.trim() && !rawFamily.includes(\"var(\") ? rawFamily.trim() : FALLBACK_MONO;\n\n let fontSize = overrides?.fontSize;\n if (fontSize == null) {\n const raw = read(\"--og-code-font-size\");\n const parsed = raw ? Number.parseFloat(raw) : Number.NaN;\n fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 13;\n }\n\n return { fontFamily, fontSize, lineHeight: 1.3 };\n}\n\n/** DOM wrapper: resolve the terminal font from an element's computed tokens. */\nexport function resolveTerminalFont(\n el: HTMLElement,\n overrides?: FontOverrides,\n): ResolvedTerminalFont {\n const style = getComputedStyle(el);\n const read: TokenReader = (name) => {\n const value = style.getPropertyValue(name).trim();\n return value || undefined;\n };\n return resolveTerminalFontFromReader(read, overrides);\n}\n","import type { TerminalCapability } from \"@opengeni/sdk\";\nimport {\n type ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { type TerminalStreamStatus, useTerminalStream } from \"../hooks/use-terminal-stream\";\nimport type { UseSandboxTerminalResult } from \"../hooks/use-sandbox-terminal\";\nimport { resolveTerminalFont, xtermThemeFromTokens } from \"../lib/xterm-theme\";\nimport { sandboxAcceptsLiveIo } from \"../lib/sandbox-liveness\";\nimport { terminalCanAcquirePty } from \"../lib/terminal-capability\";\nimport { attachRenderer, type RendererLoaders, type RendererTier } from \"../lib/xterm-renderer\";\nimport { sandboxTerminalPeers } from \"../lib/workbench-peers\";\n\n/**\n * A COMPLETE xterm `ITheme` (subset by intent, but every field xterm colors a\n * cell with is present so it never falls back to its stock VGA palette). Built\n * from the og tokens via `xtermThemeFromTokens`.\n */\nexport type XtermTheme = {\n background?: string;\n foreground?: string;\n cursor?: string;\n cursorAccent?: string;\n selectionBackground?: string;\n selectionForeground?: string;\n selectionInactiveBackground?: string;\n black?: string;\n red?: string;\n green?: string;\n yellow?: string;\n blue?: string;\n magenta?: string;\n cyan?: string;\n white?: string;\n brightBlack?: string;\n brightRed?: string;\n brightGreen?: string;\n brightYellow?: string;\n brightBlue?: string;\n brightMagenta?: string;\n brightCyan?: string;\n brightWhite?: string;\n};\n\nexport type SandboxTerminalProps = {\n /** From `useSandboxTerminal(...)`. */\n result: UseSandboxTerminalResult;\n /**\n * The negotiated Terminal capability cell. When it advertises `transport:\n * \"pty-ws\"` + a live `url` (a warm box with a viewer attached), the terminal is\n * driven by a REAL bidirectional PTY over the Modal tunnel (ttyd-over-websocket)\n * INSTEAD of the broken ptyWrite-over-HTTP path: xterm input → the socket, the\n * socket's output → xterm, xterm resize → the socket. On a cold box / no url\n * (`transport: \"sse-events\"`) it stays on the read-only Channel-A firehose\n * (`result.chunks`). Omit to force the legacy firehose-only behavior.\n */\n terminalCapability?: TerminalCapability | null | undefined;\n /**\n * Full xterm theme. When omitted the terminal self-derives it from the `--og-*`\n * tokens of its own container (correct even inside a nested `data-og-theme`\n * subtree). Passing it lets the host drive re-theme on a theme flip.\n */\n theme?: XtermTheme | undefined;\n fontFamily?: string | undefined;\n fontSize?: number | undefined;\n /**\n * Lease liveness (`cold` | `warming` | `warm` | `draining`). Drives the\n * boot-in-terminal status lines: once the user has engaged the terminal and\n * the box is not yet warm, a styled \"● waking machine — Ns…\" line is rendered\n * INSIDE xterm (no overlay/spinner) until the live PTY connects.\n */\n liveness?: string | undefined;\n /**\n * Force read-only even when the PTY accepts stdin. Default: interactive\n * whenever a live pty-ws stream is connected OR `result.write !== null` (the box\n * advertises an interactive PTY); otherwise the read-only agent firehose.\n */\n readOnly?: boolean | undefined;\n /** Shown on the server / before xterm hydrates (SSR-safe placeholder). */\n placeholder?: ReactNode | undefined;\n /** Render the small status header (pty/shell + running dot + read-only pill). */\n showHeader?: boolean | undefined;\n /** Shell label for the header (e.g. `/bin/bash`). */\n shell?: string | undefined;\n /**\n * Fired the first time the user engages the terminal surface (focus or click).\n * The host wires this to warm the box for the REAL pty-ws terminal (the viewer\n * attach), so a cold box upgrades from the read-only firehose to a live PTY ON\n * INTERACT — never on mere mount (which would force a box spin-up and regress\n * the firehose-only default).\n */\n onActivate?: (() => void) | undefined;\n /** Re-negotiate the terminal capability after an unexpected socket failure. */\n onReconnectNeeded?: (() => void) | undefined;\n className?: string | undefined;\n};\n\n// The xterm.js handle the effect owns. `any`-free structural shape so we can\n// drive it without a hard type dep on the lazily-imported lib.\ntype XtermLike = {\n open: (el: HTMLElement) => void;\n focus: () => void;\n write: (data: string) => void;\n clear: () => void;\n onData: (cb: (data: string) => void) => { dispose: () => void };\n onResize: (cb: (size: { cols: number; rows: number }) => void) => { dispose: () => void };\n loadAddon: (addon: unknown) => void;\n dispose: () => void;\n cols: number;\n rows: number;\n options: {\n theme?: XtermTheme;\n disableStdin?: boolean;\n cursorBlink?: boolean;\n fontFamily?: string;\n fontSize?: number;\n };\n};\ntype FitAddonLike = { fit: () => void };\ntype TerminalPeerBundle = {\n Terminal: new (options: Record<string, unknown>) => XtermLike;\n FitAddon: new () => FitAddonLike;\n WebLinksAddon: new (handler: (event: MouseEvent, uri: string) => void) => unknown;\n loadWebgl?:\n | (() => Promise<{\n WebglAddon: new () => {\n dispose: () => void;\n onContextLoss?: (callback: () => void) => void;\n };\n }>)\n | undefined;\n};\n\nexport type TerminalSurfaceState =\n | \"loading\"\n | \"connecting\"\n | \"error\"\n | \"waking\"\n | \"output-only\"\n | \"interactive\";\n\n/**\n * Stable semantic state for hosts and browser acceptance. The xterm helper\n * textarea exists before the live PTY socket is ready, so its presence alone\n * must never be treated as proof that keystrokes can reach the sandbox.\n */\nexport function terminalSurfaceState(input: {\n ready: boolean;\n acceptsInput: boolean;\n inputPending: boolean;\n ptyStatus: TerminalStreamStatus;\n booting: boolean;\n}): TerminalSurfaceState {\n if (!input.ready) return \"loading\";\n if (input.acceptsInput) return \"interactive\";\n if (input.inputPending) return \"connecting\";\n if (input.ptyStatus === \"connecting\") return \"connecting\";\n if (input.ptyStatus === \"error\") return \"error\";\n if (input.booting) return \"waking\";\n return \"output-only\";\n}\n\nexport function subscribeTerminalInput(\n term: Pick<XtermLike, \"onData\"> | null,\n sink: ((data: string) => void) | null,\n enabled: boolean,\n): { dispose: () => void } | null {\n if (!enabled || !term || !sink) return null;\n return term.onData(sink);\n}\n\n/** Clear the transient wake line and put the first live prompt at cell 1,1.\n * `Terminal.clear()` removes scrollback but deliberately preserves the cursor,\n * which lets the first PTY frame append to the old wake message. */\nexport function clearTerminalBootStatus(term: Pick<XtermLike, \"write\">): void {\n term.write(\"\\r\\x1b[2K\\x1b[2J\\x1b[H\");\n}\n\n/** Debug seam (dev/evidence only, never a typed public API): a global hook the\n * screenshot harness reads to probe the settled renderer tier + resolved font\n * without mounting a real WebGL context in unit tests. */\ntype TerminalDebugInfo = {\n renderer: RendererTier | null;\n fontFamily: string | undefined;\n fontSize: number | undefined;\n hasVarInFont: boolean;\n theme: XtermTheme | undefined;\n /** The live xterm instance (opaque) — the evidence harness reads its buffer to\n * prove screen preservation (E5) and drive the burst-responsiveness probe. */\n term: unknown;\n};\ndeclare global {\n // eslint-disable-next-line no-var\n var __OG_TERMINAL_DEBUG__: ((info: TerminalDebugInfo) => void) | undefined;\n // A space/comma list of renderer tiers to force-fail, so the demo can prove the\n // WebGL→canvas→DOM fallback ladder without a real GPU context loss.\n // eslint-disable-next-line no-var\n var __OG_FORCE_RENDERER_FAIL__: string | undefined;\n}\n\n/** Which renderer tiers are force-failed (demo/evidence only). */\nfunction forcedRendererFailures(): Set<RendererTier> {\n const raw = typeof globalThis !== \"undefined\" ? globalThis.__OG_FORCE_RENDERER_FAIL__ : undefined;\n const set = new Set<RendererTier>();\n if (typeof raw === \"string\") {\n for (const tier of raw.split(/[\\s,]+/)) {\n if (tier === \"webgl\" || tier === \"canvas\") set.add(tier);\n }\n }\n return set;\n}\n\n/**\n * An xterm.js terminal fed by the Channel-A event projection\n * (`useSandboxTerminal`) or a live ttyd PTY. xterm + its addons are lazy-imported\n * inside an effect, so SSR renders the placeholder and the terminal mounts on\n * hydration.\n *\n * Renderer: WebGL (GPU-composited cells) with a canvas → DOM fallback ladder\n * (`attachRenderer`), so bursty output stays smooth but a lost/blocklisted\n * context degrades gracefully. Font + theme are resolved to CONCRETE values\n * from the og tokens BEFORE construction (xterm measures glyphs in a canvas\n * where `var()` never resolves, and colors every cell from a full ANSI palette).\n *\n * The terminal is mounted ONCE and its data source is swapped in place: the\n * read-only firehose (`result.chunks`) → the live PTY. Stdin is toggled via\n * `term.options.disableStdin` at runtime (not a remount), so scrollback and the\n * visible screen survive the projection→PTY handoff.\n *\n * Resizes are tracked with a `ResizeObserver` on the container (not just\n * `window.resize`) so dragging the dock handle refits the grid.\n */\nexport function SandboxTerminal({\n result,\n terminalCapability,\n theme,\n fontFamily,\n fontSize,\n liveness,\n readOnly,\n placeholder,\n showHeader,\n shell,\n onActivate,\n onReconnectNeeded,\n className,\n}: SandboxTerminalProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const termRef = useRef<XtermLike | null>(null);\n const fitRef = useRef<FitAddonLike | null>(null);\n const rendererRef = useRef<RendererTier | null>(null);\n const writtenRef = useRef<Set<string>>(new Set());\n const wroteFirehoseRef = useRef(false);\n const bootActiveRef = useRef(false);\n const [ready, setReady] = useState(false);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const peerRevision = useSyncExternalStore(\n sandboxTerminalPeers.subscribe,\n sandboxTerminalPeers.revision,\n sandboxTerminalPeers.revision,\n );\n const [inputReady, setInputReady] = useState(false);\n const [activated, setActivated] = useState(false);\n\n // ── Interactive transport switch ─────────────────────────────────────────────\n const ptyDescriptor =\n terminalCapability?.transport === \"pty-ws\" || terminalCapability?.transport === \"relay-pty\";\n const ptyAttachable = terminalCanAcquirePty(terminalCapability);\n const {\n status: ptyStatus,\n write: ptyWrite,\n resize: resizePty,\n connected: ptyConnected,\n } = useTerminalStream({\n // Connect only after xterm exists. This makes the renderer the sole output\n // owner from the first websocket frame and removes an otherwise unbounded\n // pre-mount output queue during large command bursts.\n capability:\n ptyAttachable && ready\n ? {\n transport: terminalCapability!.transport,\n url: ptyDescriptor ? terminalCapability!.url : null,\n token: ptyDescriptor ? terminalCapability!.token : null,\n expiresAt: ptyDescriptor ? terminalCapability!.expiresAt : null,\n }\n : null,\n onOutput: (data) => {\n termRef.current?.write(data);\n },\n onReconnectNeeded,\n });\n\n // A pty-ws capability exclusively owns the screen even while connecting or\n // after a visible connection failure; never silently switch one terminal UI\n // between two PTYs. Input is enabled during the handshake because the stream\n // hook buffers it strictly and flushes only after ttyd auth succeeds.\n const ptyMode = ptyDescriptor;\n // Keep xterm stdin live for an attachable descriptor even before its URL has\n // arrived. Focus/click triggers the grant; any immediately following input is\n // bounded in the stream hook instead of disappearing in that HTTP interval.\n const ptyCapturesInput = ptyAttachable && ptyStatus !== \"error\";\n const interactive = !readOnly && (ptyAttachable ? ptyCapturesInput : result.write !== null);\n // Public \"interactive\" means the mounted input subscription can reach a live\n // PTY now. Before that point xterm still captures into the bounded queue, but\n // acceptance and embedders see an explicit connecting state.\n const acceptsInput = inputReady && (!ptyAttachable || ptyConnected);\n const inputPending = inputReady && ptyAttachable && !ptyConnected && activated;\n let terminalModeLabel: string | null;\n if (readOnly) terminalModeLabel = \"read-only\";\n else if (!ptyAttachable) terminalModeLabel = acceptsInput ? null : \"output only\";\n else if (ptyStatus === \"error\") terminalModeLabel = \"connection error\";\n else if (acceptsInput) terminalModeLabel = null;\n else terminalModeLabel = activated ? \"connecting\" : \"connect on input\";\n\n // Boot-in-terminal: after the user engages a not-yet-warm box, show styled\n // status lines INSIDE xterm instead of an overlay — but only when there is no\n // firehose transcript to show yet (otherwise the projected output is shown).\n const warm = sandboxAcceptsLiveIo(liveness);\n const booting =\n activated &&\n !ptyMode &&\n !warm &&\n liveness !== null &&\n liveness !== undefined &&\n result.chunks.length === 0;\n const surfaceState = terminalSurfaceState({\n ready,\n acceptsInput,\n inputPending,\n ptyStatus,\n booting,\n });\n\n function handleActivate() {\n // Capture-phase activation may synchronously negotiate a fresh terminal\n // capability and rerender the shell. Focus xterm before that state change so\n // the user's first keystroke or paste reaches its already-bounded input\n // queue instead of landing on the surrounding dock.\n const term = termRef.current;\n if (term && !readOnly && (ptyAttachable || result.write !== null)) {\n term.options.disableStdin = false;\n term.options.cursorBlink = true;\n }\n term?.focus();\n if (!activated) setActivated(true);\n onActivate?.();\n }\n\n // Mount xterm once (client-only). Never re-mounts on interactive/theme flips —\n // stdin + theme are runtime options synced by the effects below, so the visible\n // screen survives the projection→PTY handoff (E5).\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const el = containerRef.current;\n if (!el) return;\n let disposed = false;\n\n // Wait for a real measured size before opening xterm so the first fit is at\n // the true width (belt-and-braces with the visibility gate below).\n const waitForSize = (node: HTMLElement) =>\n new Promise<void>((resolve) => {\n if (node.clientWidth > 0 && node.clientHeight > 0) return resolve();\n const ro = new ResizeObserver(() => {\n if (node.clientWidth > 0 && node.clientHeight > 0) {\n ro.disconnect();\n resolve();\n }\n });\n ro.observe(node);\n setTimeout(() => {\n ro.disconnect();\n resolve();\n }, 1000);\n });\n\n ensureXtermBaseCss();\n\n setLoadError(null);\n void (async () => {\n const { Terminal, FitAddon, WebLinksAddon, loadWebgl } =\n (await sandboxTerminalPeers.load()) as TerminalPeerBundle;\n if (disposed) return;\n await waitForSize(el);\n if (disposed) return;\n\n // Resolve font + theme to CONCRETE values BEFORE construction. `var()` in a\n // font family never resolves in xterm's canvas measurement, so we read the\n // computed `--og-font-mono` here; the theme is a full ANSI palette.\n const font = resolveTerminalFont(el, { fontFamily, fontSize });\n const initialTheme = theme ?? xtermThemeFromTokens(el);\n\n const term = new Terminal({\n convertEol: true,\n disableStdin: !interactive,\n cursorBlink: interactive,\n fontFamily: font.fontFamily,\n fontSize: font.fontSize,\n lineHeight: font.lineHeight,\n ...(initialTheme ? { theme: initialTheme } : {}),\n }) as unknown as XtermLike;\n const fit = new FitAddon() as unknown as FitAddonLike;\n term.loadAddon(fit);\n term.loadAddon(\n new WebLinksAddon((_e: MouseEvent, uri: string) =>\n window.open(uri, \"_blank\", \"noopener,noreferrer\"),\n ) as unknown,\n );\n term.open(el);\n termRef.current = term;\n fitRef.current = fit;\n\n // Attach the fastest available renderer. For xterm-6 the supported ladder\n // is WebGL → DOM (the standalone 2D-canvas addon targets xterm-5 internals,\n // so it is intentionally NOT in the shipped path). Init failure / context\n // loss steps down to xterm's built-in DOM renderer; the settled tier is\n // exposed for evidence.\n const failSet = forcedRendererFailures();\n const loaders: RendererLoaders = {\n webgl: async (onLoss) => {\n if (disposed) throw new Error(\"Terminal was disposed\");\n if (failSet.has(\"webgl\")) throw new Error(\"forced webgl failure\");\n if (!loadWebgl) throw new Error(\"WebGL terminal renderer is not enabled\");\n const { WebglAddon } = await loadWebgl();\n if (disposed) throw new Error(\"Terminal was disposed\");\n const addon = new WebglAddon() as unknown as {\n dispose: () => void;\n onContextLoss?: (cb: () => void) => void;\n };\n addon.onContextLoss?.(() => {\n if (disposed) return;\n try {\n addon.dispose();\n } catch {\n // already disposed\n }\n onLoss();\n });\n term.loadAddon(addon);\n return addon;\n },\n };\n await attachRenderer(\"webgl\", loaders, (tier) => {\n if (disposed) return;\n rendererRef.current = tier;\n el.setAttribute(\"data-og-term-renderer\", tier);\n });\n if (disposed) return;\n\n // Fit BEFORE reveal so the first visible frame is already correctly sized\n // (no 80×24 rasterize-then-reflow flash — the container stays hidden until\n // now via the `ready` visibility gate).\n const settle = () => {\n try {\n fit.fit();\n } catch {\n // ignore fit before layout\n }\n };\n settle();\n requestAnimationFrame(() => {\n if (disposed) return;\n settle();\n el.setAttribute(\"data-og-term-ready\", \"true\");\n if (typeof globalThis.__OG_TERMINAL_DEBUG__ === \"function\") {\n globalThis.__OG_TERMINAL_DEBUG__({\n renderer: rendererRef.current,\n fontFamily: term.options.fontFamily,\n fontSize: term.options.fontSize,\n hasVarInFont: String(term.options.fontFamily ?? \"\").includes(\"var(\"),\n theme: term.options.theme,\n term,\n });\n }\n setReady(true);\n });\n })().catch((cause: unknown) => {\n if (!disposed) setLoadError(cause instanceof Error ? cause : new Error(String(cause)));\n });\n\n return () => {\n disposed = true;\n termRef.current?.dispose();\n termRef.current = null;\n fitRef.current = null;\n rendererRef.current = null;\n writtenRef.current = new Set();\n wroteFirehoseRef.current = false;\n bootActiveRef.current = false;\n setReady(false);\n };\n // Mount once per peer registration; runtime options sync without a remount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [peerRevision]);\n\n // Sync stdin/cursor at runtime (NOT a remount) when interactivity flips, so the\n // projection→PTY handoff keeps the single Terminal instance + its scrollback.\n useLayoutEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n term.options.disableStdin = !interactive;\n term.options.cursorBlink = interactive;\n }, [ready, interactive]);\n\n // Re-theme live (dark↔light flip) without re-mounting / losing scrollback. Uses\n // the passed theme, or self-derives from the container's tokens.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n const next = theme ?? xtermThemeFromTokens(containerRef.current);\n if (next) term.options.theme = next;\n }, [ready, theme]);\n\n // Boot-in-terminal status lines. While `booting`, paint a styled \"● waking\n // machine — Ns…\" line and tick it. On exit, if those lines were the ONLY\n // content (no firehose transcript), clear them for the incoming live screen —\n // never clear a real transcript (E5 preservation).\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term) return;\n if (!booting) {\n if (bootActiveRef.current) {\n bootActiveRef.current = false;\n if (!wroteFirehoseRef.current) clearTerminalBootStatus(term);\n }\n return;\n }\n bootActiveRef.current = true;\n const start = Date.now();\n const paint = () => {\n const s = Math.max(0, Math.round((Date.now() - start) / 1000));\n // Bright-blue dot (accent) + dim label; `\\r` + clear-line keeps it on one\n // line so the counter updates in place.\n term.write(`\\r\\x1b[2K\\x1b[94m●\\x1b[0m \\x1b[2mwaking machine — ${s}s…\\x1b[0m`);\n };\n term.write(\"\\r\\n\");\n paint();\n const id = setInterval(paint, 1000);\n return () => clearInterval(id);\n }, [ready, booting]);\n\n // Write new firehose chunks incrementally — ONLY in firehose mode. In PTY mode\n // the ttyd socket owns the screen. Records that a real transcript exists so the\n // boot cleanup never wipes it.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term || ptyMode) return;\n for (const chunk of result.chunks) {\n if (writtenRef.current.has(chunk.id)) continue;\n writtenRef.current.add(chunk.id);\n wroteFirehoseRef.current = true;\n term.write(chunk.text);\n }\n }, [ready, result.chunks, ptyMode]);\n\n // Wire interactive input when allowed. `ptyWrite` is stable across socket\n // status changes, so this subscription does not tear down while a grant opens\n // or rotates and cannot create a first-keystroke gap.\n useLayoutEffect(() => {\n const term = termRef.current;\n if (!ready || !term || !interactive) {\n setInputReady(false);\n return;\n }\n const sink = ptyAttachable ? ptyWrite : result.write;\n if (!sink) {\n setInputReady(false);\n return;\n }\n const sub = subscribeTerminalInput(term, sink, true);\n setInputReady(sub !== null);\n return () => sub?.dispose();\n }, [ready, interactive, ptyAttachable, ptyWrite, result.write]);\n\n // In PTY mode, tell ttyd the window size on the xterm resize event.\n useEffect(() => {\n const term = termRef.current;\n if (!ready || !term || !ptyMode) return;\n resizePty(term.cols, term.rows);\n const sub = term.onResize(({ cols, rows }) => resizePty(cols, rows));\n return () => sub.dispose();\n }, [ready, ptyMode, ptyConnected, resizePty]);\n\n // Refit on container resize (dock drag) AND window resize.\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const refit = () => {\n try {\n fitRef.current?.fit();\n } catch {\n // ignore\n }\n };\n window.addEventListener(\"resize\", refit);\n let observer: ResizeObserver | null = null;\n const el = containerRef.current;\n if (el && typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(() => refit());\n observer.observe(el);\n }\n return () => {\n window.removeEventListener(\"resize\", refit);\n observer?.disconnect();\n };\n }, [ready]);\n\n return (\n <div className={cn(\"relative flex h-full w-full flex-col overflow-hidden\", className)}>\n {showHeader && (\n <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\">\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <span\n className={cn(\n \"size-1.5 shrink-0 rounded-full\",\n result.running ? \"bg-og-status-running\" : \"bg-og-fg-subtle\",\n )}\n />\n <span className=\"truncate font-og-mono\">pty: {shell ?? \"shell\"}</span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {terminalModeLabel && (\n <span\n className=\"rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide\"\n role=\"status\"\n >\n {terminalModeLabel}\n </span>\n )}\n <button\n type=\"button\"\n onClick={() => {\n termRef.current?.clear();\n writtenRef.current = new Set();\n wroteFirehoseRef.current = false;\n }}\n className=\"rounded-og-sm px-1.5 py-0.5 text-og-xs hover:text-og-fg pointer-coarse:min-h-11\"\n >\n Clear\n </button>\n </span>\n </div>\n )}\n {/* `onPointerDownCapture`/`onFocusCapture` fire on the FIRST user engagement\n with the terminal surface (capture so they win even though xterm's own\n listeners stop propagation). The host warms the box for pty-ws here. The\n 12px inset + `--og-color-bg` ground match the dock surface. */}\n <div\n className=\"relative min-h-0 flex-1 bg-og-bg p-3\"\n onPointerDownCapture={handleActivate}\n onFocusCapture={handleActivate}\n >\n {!ready &&\n (loadError ? (\n <p role=\"alert\">{loadError.message}</p>\n ) : (\n (placeholder ?? <TerminalPlaceholder />)\n ))}\n {/* Kept `visibility:hidden` until the first successful fit so the first\n VISIBLE frame is already correctly sized (no 80×24 flash — E4). */}\n <div\n ref={containerRef}\n className=\"h-full w-full\"\n style={{ visibility: ready ? \"visible\" : \"hidden\" }}\n data-opengeni-terminal\n data-opengeni-terminal-ready={ready ? \"true\" : \"false\"}\n data-opengeni-terminal-interactive={surfaceState === \"interactive\" ? \"true\" : \"false\"}\n data-opengeni-terminal-state={surfaceState}\n data-opengeni-terminal-status={ptyAttachable ? ptyStatus : \"firehose\"}\n />\n </div>\n </div>\n );\n}\n\n// xterm.js ships a stylesheet (`@xterm/xterm/css/xterm.css`) that is REQUIRED for\n// correct layout — without it the `.xterm-helper-textarea` (an off-screen input\n// xterm uses for IME/keystrokes) is NOT clipped and renders as a visible 1-line\n// box in the top-left (the \"weird input box\" bug), and the viewport/screen aren't\n// positioned. The host app never imports that CSS (no global stylesheet here), so\n// we inject the structural rules once, idempotently (keyed by id), the first time\n// any terminal mounts. Scoped under `.xterm` so it can't leak into host styles.\nconst XTERM_STYLE_ID = \"opengeni-xterm-base-css\";\nconst XTERM_BASE_CSS = `\n.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}\n.xterm.focus,.xterm:focus{outline:none}\n.xterm .xterm-helpers{position:absolute;top:0;z-index:5}\n.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}\n.xterm .composition-view{background:var(--og-color-bg);color:var(--og-color-fg);display:none;position:absolute;white-space:nowrap;z-index:1}\n.xterm .composition-view.active{display:block}\n.xterm .xterm-viewport{background-color:transparent;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}\n.xterm .xterm-screen{position:relative}\n.xterm .xterm-screen canvas{position:absolute;left:0;top:0}\n.xterm .xterm-scroll-area{visibility:hidden}\n.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}\n.xterm.enable-mouse-events{cursor:default}\n.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}\n.xterm.column-select.focus{cursor:crosshair}\n.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}\n.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}\n.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}\n.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}\n.xterm-dim{opacity:1!important}\n.xterm-underline-1{text-decoration:underline}\n.xterm-underline-2{text-decoration:double underline}\n.xterm-underline-3{text-decoration:wavy underline}\n.xterm-underline-4{text-decoration:dotted underline}\n.xterm-underline-5{text-decoration:dashed underline}\n.xterm-overline{text-decoration:overline}\n.xterm-strikethrough{text-decoration:line-through}\n.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}\n.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}\n.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}\n.xterm-decoration-top{z-index:2;position:relative}\n`;\n\nfunction ensureXtermBaseCss(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(XTERM_STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = XTERM_STYLE_ID;\n style.textContent = XTERM_BASE_CSS;\n document.head.appendChild(style);\n}\n\nfunction TerminalPlaceholder() {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center text-og-sm text-og-fg-subtle\">\n Loading terminal…\n </div>\n );\n}\n","import type { TerminalCapability } from \"@opengeni/sdk\";\n\n/** Whether an explicit viewer grant can upgrade this cell to a real PTY.\n * Keep acquisition feasibility separate from the current transport: a cold\n * real-PTY backend reports sse-events until the just-in-time grant warms it. */\nexport function terminalCanAcquirePty(cell: TerminalCapability | null | undefined): boolean {\n if (!cell?.ptyCapable) return false;\n if (cell.transport === \"pty-ws\" || cell.transport === \"relay-pty\") return true;\n return cell.reason === \"lease_cold\" || cell.reason === \"not_provisioned\";\n}\n","/* ----------------------------------------------------------------------------\n xterm renderer selection ladder: WebGL → 2D canvas → DOM.\n\n WebGL is the fast path (GPU-composited cells — the whole point of the burst\n responsiveness win). But a WebGL context can fail to init (headless / no GPU /\n blocklisted driver) or be LOST at runtime (GPU reset, tab backgrounded). On\n either, we dispose the addon and step down one tier so the terminal keeps\n painting via the 2D canvas addon, and failing that xterm's built-in DOM\n renderer (no addon).\n\n `attachRenderer` is pure over its `loaders` (each closes over the xterm\n instance and constructs+loads its addon, throwing on failure), so the ladder\n + context-loss downgrade unit-test without a real WebGL context.\n -------------------------------------------------------------------------- */\n\nexport type RendererTier = \"webgl\" | \"canvas\" | \"dom\";\n\nexport type RendererAddon = { dispose: () => void };\n\n/** A loader constructs + `loadAddon`s its renderer, wiring `onLoss` to the\n * addon's context-loss event, and throws if the renderer can't initialize. */\nexport type RendererLoaders = {\n webgl?: ((onLoss: () => void) => Promise<RendererAddon>) | undefined;\n canvas?: (() => Promise<RendererAddon>) | undefined;\n};\n\nconst ORDER: RendererTier[] = [\"webgl\", \"canvas\", \"dom\"];\n\n/** The next lower renderer tier, or null at the bottom (DOM). Pure. */\nexport function nextRendererTier(tier: RendererTier): RendererTier | null {\n const i = ORDER.indexOf(tier);\n return i >= 0 && i < ORDER.length - 1 ? ORDER[i + 1]! : null;\n}\n\n/**\n * Attach the highest renderer tier at or below `prefer` that initializes,\n * reporting the settled tier via `onTier`. On a WebGL/canvas context loss the\n * loader's `onLoss` re-enters at the next tier down, so `onTier` is called again\n * with the downgraded tier. Returns the tier chosen on this pass.\n */\nexport async function attachRenderer(\n prefer: RendererTier,\n loaders: RendererLoaders,\n onTier: (tier: RendererTier) => void,\n): Promise<RendererTier> {\n const start = Math.max(0, ORDER.indexOf(prefer));\n for (let i = start; i < ORDER.length; i++) {\n const tier = ORDER[i]!;\n if (tier === \"dom\") break;\n const loader = tier === \"webgl\" ? loaders.webgl : loaders.canvas;\n if (!loader) continue;\n try {\n const downgrade = nextRendererTier(tier) ?? \"dom\";\n await loader(() => {\n void attachRenderer(downgrade, loaders, onTier);\n });\n onTier(tier);\n return tier;\n } catch {\n // Initialization failed — fall through to the next tier down.\n }\n }\n onTier(\"dom\");\n return \"dom\";\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useRef","useState","useEffect"]}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+
3
+ // src/composer-voice-events.ts
4
+ var COMPOSER_VOICE_INPUT_START_EVENT = "opengeni:composer-voice-input-start";
5
+ function composerVoiceEventScope(element) {
6
+ if (!element) return null;
7
+ return element.closest(".og-composer") ?? element.ownerDocument;
8
+ }
9
+
10
+ export {
11
+ COMPOSER_VOICE_INPUT_START_EVENT,
12
+ composerVoiceEventScope
13
+ };
14
+ //# sourceMappingURL=chunk-NX4ADH73.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/composer-voice-events.ts"],"sourcesContent":["/**\n * Internal DOM signal between the composer's two voice controls: dictation\n * starting supersedes a stale live-voice start failure in the same composer.\n * Not part of the public package surface.\n */\nexport const COMPOSER_VOICE_INPUT_START_EVENT = \"opengeni:composer-voice-input-start\";\n\n/** Closest composer root shared by both voice controls (falls back to the document). */\nexport function composerVoiceEventScope(element: Element | null): EventTarget | null {\n if (!element) return null;\n return element.closest(\".og-composer\") ?? element.ownerDocument;\n}\n"],"mappings":";;;AAKO,IAAM,mCAAmC;AAGzC,SAAS,wBAAwB,SAA6C;AACnF,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,QAAQ,cAAc,KAAK,QAAQ;AACpD;","names":[]}
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  // src/components/open-geni-links.tsx
2
4
  import { createContext, useContext, useMemo } from "react";
3
5
  import { jsx } from "react/jsx-runtime";
@@ -104,4 +106,4 @@ export {
104
106
  sessionLinkResolver,
105
107
  viewerLinkResolver
106
108
  };
107
- //# sourceMappingURL=chunk-XCHWDJ22.js.map
109
+ //# sourceMappingURL=chunk-OGTTUYNT.js.map