conductor-oss 0.18.2 → 0.18.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 (254) hide show
  1. package/README.md +1 -21
  2. package/package.json +5 -5
  3. package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
  4. package/web/.next/standalone/packages/web/.next/app-path-routes-manifest.json +1 -3
  5. package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
  6. package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
  7. package/web/.next/standalone/packages/web/.next/routes-manifest.json +6 -22
  8. package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
  9. package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
  10. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
  16. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
  19. package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +4 -4
  20. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  21. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  23. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js +1 -1
  27. package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
  28. package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js +1 -1
  29. package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js.nft.json +1 -1
  30. package/web/.next/standalone/packages/web/.next/server/app/api/app-update/route.js +1 -1
  31. package/web/.next/standalone/packages/web/.next/server/app/api/app-update/route.js.nft.json +1 -1
  32. package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js +1 -1
  33. package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
  34. package/web/.next/standalone/packages/web/.next/server/app/api/auth/session/route.js +1 -1
  35. package/web/.next/standalone/packages/web/.next/server/app/api/auth/session/route.js.nft.json +1 -1
  36. package/web/.next/standalone/packages/web/.next/server/app/api/boards/comments/route.js +1 -1
  37. package/web/.next/standalone/packages/web/.next/server/app/api/boards/comments/route.js.nft.json +1 -1
  38. package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js +1 -1
  39. package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
  40. package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js +1 -1
  41. package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/open/route.js +1 -1
  43. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/open/route.js.nft.json +1 -1
  44. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js +1 -1
  45. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
  46. package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js +1 -1
  47. package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
  48. package/web/.next/standalone/packages/web/.next/server/app/api/executor/health/route.js +1 -1
  49. package/web/.next/standalone/packages/web/.next/server/app/api/executor/health/route.js.nft.json +1 -1
  50. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js +1 -1
  51. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
  52. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/pick-directory/route.js +1 -1
  53. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/pick-directory/route.js.nft.json +1 -1
  54. package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js +1 -1
  55. package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
  56. package/web/.next/standalone/packages/web/.next/server/app/api/github/webhook/route.js +1 -1
  57. package/web/.next/standalone/packages/web/.next/server/app/api/github/webhook/route.js.nft.json +1 -1
  58. package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js +1 -1
  59. package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
  60. package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js +1 -1
  61. package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
  62. package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js +1 -1
  63. package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
  64. package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js +1 -1
  65. package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
  66. package/web/.next/standalone/packages/web/.next/server/app/api/remote-access/route.js +1 -1
  67. package/web/.next/standalone/packages/web/.next/server/app/api/remote-access/route.js.nft.json +1 -1
  68. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/[id]/route.js +1 -1
  69. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/[id]/route.js.nft.json +1 -1
  70. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js +1 -1
  71. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
  72. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js +1 -1
  73. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
  74. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/archive/route.js +1 -1
  75. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  76. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js +1 -1
  77. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
  78. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js +1 -1
  79. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
  80. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js +1 -1
  81. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -1
  82. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js +1 -1
  83. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
  84. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js +1 -1
  85. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
  86. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/interrupt/route.js +1 -1
  87. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/interrupt/route.js.nft.json +1 -1
  88. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js +1 -1
  89. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
  90. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js +1 -1
  91. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
  92. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/dom/route.js +1 -1
  93. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/dom/route.js.nft.json +1 -1
  94. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/route.js +1 -1
  95. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/route.js.nft.json +1 -1
  96. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/screenshot/route.js +1 -1
  97. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/screenshot/route.js.nft.json +1 -1
  98. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js +1 -1
  99. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
  100. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js +1 -1
  101. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  102. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js +1 -1
  103. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js.nft.json +1 -1
  104. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/token/route/app-paths-manifest.json +3 -0
  105. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/token/route.js +10 -0
  106. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/token/route.js.nft.json +1 -0
  107. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/token/route_client-reference-manifest.js +2 -0
  108. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js +1 -1
  109. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
  110. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js +1 -1
  111. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
  112. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js +1 -1
  113. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
  114. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js +1 -1
  115. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  116. package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
  117. package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
  118. package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
  119. package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
  120. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/react-loadable-manifest.json +1 -1
  121. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
  122. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  123. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  124. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
  125. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
  126. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
  127. package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
  128. package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
  129. package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
  130. package/web/.next/standalone/packages/web/.next/server/app-paths-manifest.json +1 -3
  131. package/web/.next/standalone/packages/web/.next/server/chunks/26076_server_app_api_sessions_[id]_terminal_token_route_actions_9c4b3c06.js +3 -0
  132. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__63017d21._.js +3 -0
  133. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__f3d09d5c._.js → [root-of-the-server]__9279c912._.js} +1 -1
  134. package/web/.next/standalone/packages/web/.next/server/chunks/_2c837d66._.js +80 -0
  135. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__379d412d._.js +1 -1
  136. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__da08a50a._.js → [root-of-the-server]__443ba186._.js} +2 -2
  137. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__a565f9a3._.js → [root-of-the-server]__742dad30._.js} +2 -2
  138. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__749fe4b2._.js +1 -1
  139. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__992cdcf8._.js → [root-of-the-server]__a8fa29c1._.js} +2 -2
  140. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
  141. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_532f707d._.js +1 -1
  142. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
  143. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
  144. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_62d206cc._.js → _9bf43d8d._.js} +2 -2
  145. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
  146. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
  147. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_5646ec2d._.js → node_modules_91aa5708._.js} +1 -1
  148. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_3964db17._.js +3 -0
  149. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_5c863a0e._.js +3 -0
  150. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_6d2fa1ea._.js → node_modules_be1275d0._.js} +1 -1
  151. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_sessions_SessionTerminal_tsx_eaf9458b._.js +1 -1
  152. package/web/.next/standalone/packages/web/.next/server/functions-config-manifest.json +2 -4
  153. package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
  154. package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
  155. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
  156. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
  157. package/web/.next/standalone/packages/web/.next/static/chunks/{c1e720eabb98af26.js → 2037d1500c64fbef.js} +1 -1
  158. package/web/.next/standalone/packages/web/.next/static/chunks/{58a9b117e5684e7c.js → 3ad6d404d5657604.js} +1 -1
  159. package/web/.next/standalone/packages/web/.next/static/chunks/4d288f280972fd06.js +1 -0
  160. package/web/.next/standalone/packages/web/.next/static/chunks/65bc9229d60adf9f.css +4 -0
  161. package/web/.next/standalone/packages/web/.next/static/chunks/97e7e5343941de65.js +1 -0
  162. package/web/.next/{static/chunks/8d05dc3b261207bb.js → standalone/packages/web/.next/static/chunks/ab8cea4266d5034c.js} +1 -1
  163. package/web/.next/standalone/packages/web/.next/static/chunks/{655db4d21daaca4d.js → b2b84b9e8ccbeafa.js} +1 -1
  164. package/web/.next/standalone/packages/web/.next/static/chunks/b9a43bac36046bf9.js +138 -0
  165. package/web/.next/{static/chunks/9331c73d4edcd945.js → standalone/packages/web/.next/static/chunks/d1cbb83a98e765b5.js} +1 -1
  166. package/web/.next/standalone/packages/web/.next/static/chunks/{301802e8e898dd01.js → f2fea305b6822999.js} +1 -1
  167. package/web/.next/standalone/packages/web/.next/static/chunks/f48f57293e98e0d8.js +1 -0
  168. package/web/.next/standalone/packages/web/.next/static/chunks/fe52c44944adc7f2.js +1 -0
  169. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/terminal/token/route.ts +13 -0
  170. package/web/.next/standalone/packages/web/src/components/sessions/SessionTerminal.tsx +77 -39
  171. package/web/.next/standalone/packages/web/src/components/sessions/sessionTerminalUtils.test.ts +0 -122
  172. package/web/.next/standalone/packages/web/src/components/sessions/sessionTerminalUtils.ts +0 -220
  173. package/web/.next/standalone/packages/web/src/components/sessions/terminal/terminalApi.ts +89 -87
  174. package/web/.next/standalone/packages/web/src/components/sessions/terminal/terminalCache.ts +2 -73
  175. package/web/.next/standalone/packages/web/src/components/sessions/terminal/terminalConstants.ts +0 -8
  176. package/web/.next/standalone/packages/web/src/components/sessions/terminal/terminalTypes.ts +0 -19
  177. package/web/.next/standalone/packages/web/src/components/sessions/terminal/ttydClient.ts +122 -27
  178. package/web/.next/standalone/packages/web/src/components/sessions/terminal/useTtydConnection.ts +9 -12
  179. package/web/.next/standalone/packages/web/src/lib/sessionState.ts +0 -473
  180. package/web/.next/static/chunks/{c1e720eabb98af26.js → 2037d1500c64fbef.js} +1 -1
  181. package/web/.next/static/chunks/{58a9b117e5684e7c.js → 3ad6d404d5657604.js} +1 -1
  182. package/web/.next/static/chunks/4d288f280972fd06.js +1 -0
  183. package/web/.next/static/chunks/65bc9229d60adf9f.css +4 -0
  184. package/web/.next/static/chunks/97e7e5343941de65.js +1 -0
  185. package/web/.next/{standalone/packages/web/.next/static/chunks/8d05dc3b261207bb.js → static/chunks/ab8cea4266d5034c.js} +1 -1
  186. package/web/.next/static/chunks/{655db4d21daaca4d.js → b2b84b9e8ccbeafa.js} +1 -1
  187. package/web/.next/static/chunks/b9a43bac36046bf9.js +138 -0
  188. package/web/.next/{standalone/packages/web/.next/static/chunks/9331c73d4edcd945.js → static/chunks/d1cbb83a98e765b5.js} +1 -1
  189. package/web/.next/static/chunks/{301802e8e898dd01.js → f2fea305b6822999.js} +1 -1
  190. package/web/.next/static/chunks/f48f57293e98e0d8.js +1 -0
  191. package/web/.next/static/chunks/fe52c44944adc7f2.js +1 -0
  192. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route/app-paths-manifest.json +0 -3
  193. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route.js +0 -10
  194. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route.js.nft.json +0 -1
  195. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route_client-reference-manifest.js +0 -2
  196. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route/app-paths-manifest.json +0 -3
  197. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route/build-manifest.json +0 -11
  198. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route/server-reference-manifest.json +0 -4
  199. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js +0 -10
  200. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.map +0 -5
  201. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.nft.json +0 -1
  202. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route_client-reference-manifest.js +0 -2
  203. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/app-paths-manifest.json +0 -3
  204. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/build-manifest.json +0 -11
  205. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/server-reference-manifest.json +0 -4
  206. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js +0 -10
  207. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js.map +0 -5
  208. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js.nft.json +0 -1
  209. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route_client-reference-manifest.js +0 -2
  210. package/web/.next/standalone/packages/web/.next/server/chunks/26076_server_app_api_sessions_[id]_terminal_connection_route_actions_46c114ee.js +0 -3
  211. package/web/.next/standalone/packages/web/.next/server/chunks/29f24__next-internal_server_app_api_sessions_[id]_feed_stream_route_actions_1262f517.js +0 -3
  212. package/web/.next/standalone/packages/web/.next/server/chunks/43d70_next-internal_server_app_api_sessions_[id]_output_stream_route_actions_9bfa500e.js +0 -3
  213. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__1029f927._.js +0 -3
  214. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__d74c0f7a._.js +0 -3
  215. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__ddad8d14._.js +0 -3
  216. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__ede5c8ca._.js +0 -3
  217. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__f56e5b36._.js +0 -3
  218. package/web/.next/standalone/packages/web/.next/server/chunks/_24c4e75d._.js +0 -80
  219. package/web/.next/standalone/packages/web/.next/server/chunks/_3d39aff4._.js +0 -80
  220. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_307d7608._.js +0 -3
  221. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_3ed93faf._.js +0 -3
  222. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_4f296b1d._.js +0 -3
  223. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_599a1810._.js +0 -3
  224. package/web/.next/standalone/packages/web/.next/static/chunks/06eb75e40dff98f1.css +0 -4
  225. package/web/.next/standalone/packages/web/.next/static/chunks/1382eff030c401e3.js +0 -1
  226. package/web/.next/standalone/packages/web/.next/static/chunks/1684a3f76eefe776.js +0 -1
  227. package/web/.next/standalone/packages/web/.next/static/chunks/267e541b481c3c75.js +0 -1
  228. package/web/.next/standalone/packages/web/.next/static/chunks/810a3d36795ae9fd.js +0 -138
  229. package/web/.next/standalone/packages/web/.next/static/chunks/a8cd591e904d769e.js +0 -1
  230. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/feed/stream/route.ts +0 -80
  231. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/output/stream/route.ts +0 -80
  232. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/terminal/connection/route.test.ts +0 -343
  233. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/terminal/connection/route.ts +0 -120
  234. package/web/.next/standalone/packages/web/src/components/Dashboard.tsx +0 -3444
  235. package/web/.next/standalone/packages/web/src/components/TerminalView.tsx +0 -770
  236. package/web/.next/standalone/packages/web/src/components/sessions/ChatPanel.tsx +0 -2097
  237. package/web/.next/standalone/packages/web/src/hooks/useSessionFeed.ts +0 -10
  238. package/web/.next/standalone/packages/web/src/hooks/useSessionOutputStream.ts +0 -166
  239. package/web/.next/standalone/packages/web/src/lib/chatFeed.ts +0 -196
  240. package/web/.next/static/chunks/06eb75e40dff98f1.css +0 -4
  241. package/web/.next/static/chunks/1382eff030c401e3.js +0 -1
  242. package/web/.next/static/chunks/1684a3f76eefe776.js +0 -1
  243. package/web/.next/static/chunks/267e541b481c3c75.js +0 -1
  244. package/web/.next/static/chunks/810a3d36795ae9fd.js +0 -138
  245. package/web/.next/static/chunks/a8cd591e904d769e.js +0 -1
  246. /package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/{feed/stream → terminal/token}/route/build-manifest.json +0 -0
  247. /package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/{feed/stream → terminal/token}/route/server-reference-manifest.json +0 -0
  248. /package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/{feed/stream → terminal/token}/route.js.map +0 -0
  249. /package/web/.next/standalone/packages/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_buildManifest.js +0 -0
  250. /package/web/.next/standalone/packages/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_clientMiddlewareManifest.json +0 -0
  251. /package/web/.next/standalone/packages/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_ssgManifest.js +0 -0
  252. /package/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_buildManifest.js +0 -0
  253. /package/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_clientMiddlewareManifest.json +0 -0
  254. /package/web/.next/static/{FHjp9qazH2xWUCRt6mqg4 → O7I3Iz18_tPidBRAWeKh8}/_ssgManifest.js +0 -0
@@ -1,770 +0,0 @@
1
- "use client";
2
-
3
- import { useCallback, useEffect, useRef, useState } from "react";
4
- import type { FitAddon as XFitAddon } from "@xterm/addon-fit";
5
- import type { SearchAddon as XSearchAddon } from "@xterm/addon-search";
6
- import type { ITerminalOptions, Terminal as XTerminal } from "@xterm/xterm";
7
- import { AlertCircle, ChevronDown, Loader2, RefreshCw, Search, X } from "lucide-react";
8
- import { Button } from "@/components/ui/Button";
9
- import { TERMINAL_FONT_FAMILY, getTerminalTheme } from "@/components/terminal/xtermTheme";
10
-
11
- interface TerminalViewProps {
12
- sessionId: string;
13
- }
14
-
15
- type TerminalConnectionInfo = {
16
- transport: "websocket" | "snapshot";
17
- wsUrl: string | null;
18
- pollIntervalMs: number;
19
- fallbackReason: string | null;
20
- };
21
-
22
- type TerminalSnapshot = {
23
- snapshot: string;
24
- source: string;
25
- live: boolean;
26
- restored: boolean;
27
- };
28
-
29
- const READ_ONLY_SCROLLBACK = 4000;
30
- const LIVE_TERMINAL_SNAPSHOT_LINES = 1200;
31
- const READ_ONLY_TERMINAL_SNAPSHOT_LINES = 6000;
32
- const RECONNECT_BASE_DELAY_MS = 300;
33
- const RECONNECT_MAX_DELAY_MS = 1600;
34
- const DEFAULT_REMOTE_POLL_INTERVAL_MS = 700;
35
- const MANAGED_SCROLL_PRIVATE_MODES = new Set([1000, 1002, 1003, 1005, 1006, 1015, 1047, 1048, 1049]);
36
-
37
- async function fetchTerminalConnection(sessionId: string): Promise<TerminalConnectionInfo> {
38
- const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/terminal/connection`, {
39
- cache: "no-store",
40
- });
41
- const data = (await response.json().catch(() => null)) as {
42
- transport?: "websocket" | "snapshot";
43
- wsUrl?: string | null;
44
- pollIntervalMs?: number;
45
- fallbackReason?: string | null;
46
- error?: string;
47
- } | null;
48
- if (!response.ok) {
49
- throw new Error(data?.error ?? `Failed to resolve terminal connection: ${response.status}`);
50
- }
51
- const transport = data?.transport === "snapshot" ? "snapshot" : "websocket";
52
- const pollIntervalMs = typeof data?.pollIntervalMs === "number" && Number.isFinite(data.pollIntervalMs) && data.pollIntervalMs >= 100
53
- ? Math.round(data.pollIntervalMs)
54
- : DEFAULT_REMOTE_POLL_INTERVAL_MS;
55
- const fallbackReason = typeof data?.fallbackReason === "string" && data.fallbackReason.trim().length > 0
56
- ? data.fallbackReason.trim()
57
- : null;
58
-
59
- if (transport === "websocket") {
60
- if (typeof data?.wsUrl !== "string" || data.wsUrl.trim().length === 0) {
61
- throw new Error("Terminal connection did not include a websocket URL");
62
- }
63
- return {
64
- transport,
65
- wsUrl: data.wsUrl.trim(),
66
- pollIntervalMs,
67
- fallbackReason,
68
- };
69
- }
70
-
71
- return {
72
- transport,
73
- wsUrl: null,
74
- pollIntervalMs,
75
- fallbackReason,
76
- };
77
- }
78
-
79
- async function fetchTerminalSnapshot(sessionId: string, lines: number): Promise<TerminalSnapshot> {
80
- const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/terminal/snapshot?lines=${lines}`, {
81
- cache: "no-store",
82
- });
83
- const data = (await response.json().catch(() => null)) as
84
- | { snapshot?: string; source?: string; live?: boolean; restored?: boolean; error?: string }
85
- | null;
86
- if (!response.ok) {
87
- throw new Error(data?.error ?? `Failed to resolve terminal snapshot: ${response.status}`);
88
- }
89
- return {
90
- snapshot: typeof data?.snapshot === "string" ? data.snapshot : "",
91
- source: typeof data?.source === "string" ? data.source : "empty",
92
- live: data?.live === true,
93
- restored: data?.restored === true,
94
- };
95
- }
96
-
97
- async function fetchLiveTerminalSnapshot(sessionId: string, lines: number): Promise<TerminalSnapshot> {
98
- const response = await fetch(
99
- `/api/sessions/${encodeURIComponent(sessionId)}/terminal/snapshot?lines=${lines}&live=1`,
100
- {
101
- cache: "no-store",
102
- },
103
- );
104
- const data = (await response.json().catch(() => null)) as
105
- | { snapshot?: string; source?: string; live?: boolean; restored?: boolean; error?: string }
106
- | null;
107
- if (!response.ok) {
108
- throw new Error(data?.error ?? `Failed to resolve terminal snapshot: ${response.status}`);
109
- }
110
- return {
111
- snapshot: typeof data?.snapshot === "string" ? data.snapshot : "",
112
- source: typeof data?.source === "string" ? data.source : "empty",
113
- live: data?.live === true,
114
- restored: data?.restored === true,
115
- };
116
- }
117
-
118
- function buildTerminalSocketUrl(baseUrl: string, cols: number, rows: number): string {
119
- const url = new URL(baseUrl);
120
- url.searchParams.set("cols", String(Math.max(1, cols)));
121
- url.searchParams.set("rows", String(Math.max(1, rows)));
122
- return url.toString();
123
- }
124
-
125
- function normalizeTerminalSnapshot(snapshot: string): string {
126
- return snapshot.replace(/\r?\n/g, "\r\n");
127
- }
128
-
129
- function terminalHasRenderedContent(term: XTerminal): boolean {
130
- const buffer = term.buffer.active;
131
- if (buffer.baseY > 0) {
132
- return true;
133
- }
134
-
135
- for (let row = 0; row < term.rows; row += 1) {
136
- const line = buffer.getLine(row);
137
- if (!line) {
138
- continue;
139
- }
140
- if (line.translateToString(true).trim().length > 0) {
141
- return true;
142
- }
143
- }
144
-
145
- return false;
146
- }
147
-
148
- function getTerminalViewportOptions(width: number): Pick<ITerminalOptions, "fontFamily" | "fontSize" | "lineHeight"> {
149
- if (width < 420) {
150
- return {
151
- fontFamily: "'SF Mono', Menlo, Monaco, monospace",
152
- fontSize: 10,
153
- lineHeight: 1,
154
- };
155
- }
156
-
157
- if (width < 640) {
158
- return {
159
- fontFamily: "'SF Mono', Menlo, Monaco, monospace",
160
- fontSize: 12,
161
- lineHeight: 1.08,
162
- };
163
- }
164
-
165
- return {
166
- fontFamily: TERMINAL_FONT_FAMILY,
167
- fontSize: 16,
168
- lineHeight: 1.1,
169
- };
170
- }
171
-
172
- export function TerminalView({ sessionId }: TerminalViewProps) {
173
- const containerRef = useRef<HTMLDivElement>(null);
174
- const termRef = useRef<XTerminal | null>(null);
175
- const fitRef = useRef<XFitAddon | null>(null);
176
- const searchRef = useRef<XSearchAddon | null>(null);
177
- const socketRef = useRef<WebSocket | null>(null);
178
- const reconnectTimerRef = useRef<number | null>(null);
179
- const reconnectCountRef = useRef(0);
180
- const connectAttemptRef = useRef(0);
181
- const hasConnectedOnceRef = useRef(false);
182
- const reconnectNoticeWrittenRef = useRef(false);
183
- const snapshotAppliedRef = useRef<string | null>(null);
184
- const liveOutputStartedRef = useRef(false);
185
-
186
- const [terminalReady, setTerminalReady] = useState(false);
187
- const [transportMode, setTransportMode] = useState<"websocket" | "snapshot">("websocket");
188
- const [socketBaseUrl, setSocketBaseUrl] = useState<string | null>(null);
189
- const [connectionState, setConnectionState] = useState<"connecting" | "live" | "closed" | "error">("connecting");
190
- const [transportError, setTransportError] = useState<string | null>(null);
191
- const [pollIntervalMs, setPollIntervalMs] = useState(DEFAULT_REMOTE_POLL_INTERVAL_MS);
192
- const [reconnectToken, setReconnectToken] = useState(0);
193
- const [searchOpen, setSearchOpen] = useState(false);
194
- const [searchQuery, setSearchQuery] = useState("");
195
- const [showScrollToBottom, setShowScrollToBottom] = useState(false);
196
- const [snapshotReady, setSnapshotReady] = useState(false);
197
- const [snapshotAnsi, setSnapshotAnsi] = useState("");
198
-
199
- const clearReconnectTimer = useCallback(() => {
200
- if (reconnectTimerRef.current !== null) {
201
- window.clearTimeout(reconnectTimerRef.current);
202
- reconnectTimerRef.current = null;
203
- }
204
- }, []);
205
-
206
- const scheduleReconnect = useCallback(() => {
207
- clearReconnectTimer();
208
- reconnectCountRef.current += 1;
209
- const delay = Math.min(
210
- RECONNECT_MAX_DELAY_MS,
211
- RECONNECT_BASE_DELAY_MS * reconnectCountRef.current,
212
- );
213
- reconnectTimerRef.current = window.setTimeout(() => {
214
- setReconnectToken((value) => value + 1);
215
- }, delay);
216
- }, [clearReconnectTimer]);
217
-
218
- const requestReconnect = useCallback(() => {
219
- clearReconnectTimer();
220
- setTransportError(null);
221
- setConnectionState("connecting");
222
- setSocketBaseUrl(null);
223
- setReconnectToken((value) => value + 1);
224
- }, [clearReconnectTimer]);
225
-
226
- const updateScrollState = useCallback(() => {
227
- const term = termRef.current;
228
- if (!term) {
229
- setShowScrollToBottom(false);
230
- return;
231
- }
232
- const buffer = term.buffer.active;
233
- setShowScrollToBottom(buffer.viewportY < buffer.baseY);
234
- }, []);
235
-
236
- const runSearch = useCallback((direction: "next" | "prev") => {
237
- const addon = searchRef.current;
238
- if (!addon || searchQuery.trim().length === 0) {
239
- return;
240
- }
241
- if (direction === "next") {
242
- addon.findNext(searchQuery, { incremental: true, caseSensitive: false });
243
- } else {
244
- addon.findPrevious(searchQuery, { incremental: true, caseSensitive: false });
245
- }
246
- }, [searchQuery]);
247
-
248
- const scrollToBottom = useCallback(() => {
249
- const term = termRef.current;
250
- if (!term) {
251
- return;
252
- }
253
- term.scrollToBottom();
254
- updateScrollState();
255
- term.focus();
256
- }, [updateScrollState]);
257
-
258
- const closeSearch = useCallback(() => {
259
- setSearchOpen(false);
260
- setSearchQuery("");
261
- termRef.current?.focus();
262
- }, []);
263
-
264
- useEffect(() => {
265
- hasConnectedOnceRef.current = false;
266
- reconnectNoticeWrittenRef.current = false;
267
- snapshotAppliedRef.current = null;
268
- liveOutputStartedRef.current = false;
269
- reconnectCountRef.current = 0;
270
- connectAttemptRef.current = 0;
271
- termRef.current?.reset();
272
- setTransportMode("websocket");
273
- setSocketBaseUrl(null);
274
- setConnectionState("connecting");
275
- setTransportError(null);
276
- setPollIntervalMs(DEFAULT_REMOTE_POLL_INTERVAL_MS);
277
- updateScrollState();
278
- }, [sessionId, updateScrollState]);
279
-
280
- useEffect(() => {
281
- let mounted = true;
282
- setSnapshotReady(false);
283
- setSnapshotAnsi("");
284
-
285
- void (async () => {
286
- try {
287
- const snapshot = await fetchLiveTerminalSnapshot(sessionId, LIVE_TERMINAL_SNAPSHOT_LINES);
288
- if (!mounted) return;
289
- setSnapshotAnsi(snapshot.snapshot);
290
- } catch {
291
- try {
292
- const fallbackSnapshot = await fetchTerminalSnapshot(sessionId, READ_ONLY_TERMINAL_SNAPSHOT_LINES);
293
- if (!mounted) return;
294
- setSnapshotAnsi(fallbackSnapshot.snapshot);
295
- } catch {
296
- if (!mounted) return;
297
- setSnapshotAnsi("");
298
- }
299
- } finally {
300
- if (mounted) {
301
- setSnapshotReady(true);
302
- }
303
- }
304
- })();
305
-
306
- return () => {
307
- mounted = false;
308
- };
309
- }, [sessionId]);
310
-
311
- useEffect(() => {
312
- let term: XTerminal | null = null;
313
- let fit: XFitAddon | null = null;
314
- let resizeObserver: ResizeObserver | null = null;
315
- let scrollDisposable: { dispose: () => void } | null = null;
316
- let mounted = true;
317
-
318
- async function init() {
319
- if (!containerRef.current || !mounted) return;
320
-
321
- const [xtermMod, fitMod, searchMod] = await Promise.all([
322
- import("@xterm/xterm"),
323
- import("@xterm/addon-fit"),
324
- import("@xterm/addon-search"),
325
- ]);
326
-
327
- if (!mounted || !containerRef.current) return;
328
-
329
- const isLight = document.documentElement.classList.contains("light");
330
- const viewportOptions = getTerminalViewportOptions(window.innerWidth);
331
- const terminalOptions: ITerminalOptions & { scrollbar?: { showScrollbar: boolean } } = {
332
- cursorBlink: false,
333
- cursorStyle: "underline",
334
- disableStdin: true,
335
- scrollback: READ_ONLY_SCROLLBACK,
336
- fontSize: viewportOptions.fontSize,
337
- drawBoldTextInBrightColors: true,
338
- fontFamily: viewportOptions.fontFamily,
339
- lineHeight: viewportOptions.lineHeight,
340
- convertEol: true,
341
- theme: getTerminalTheme(isLight),
342
- scrollbar: {
343
- showScrollbar: false,
344
- },
345
- };
346
- term = new xtermMod.Terminal(terminalOptions);
347
- const registerManagedScrollMode = (final: "h" | "l") => {
348
- term?.parser.registerCsiHandler({ prefix: "?", final }, (params) => {
349
- const hasManagedMode = params.some((param) => (
350
- Array.isArray(param)
351
- ? param.some((value) => MANAGED_SCROLL_PRIVATE_MODES.has(value))
352
- : MANAGED_SCROLL_PRIVATE_MODES.has(param)
353
- ));
354
- if (!hasManagedMode) {
355
- return false;
356
- }
357
- return true;
358
- });
359
- };
360
- const handleManagedWheel = (event: WheelEvent): boolean => {
361
- const normalizedDelta = event.deltaMode === WheelEvent.DOM_DELTA_LINE
362
- ? event.deltaY
363
- : event.deltaY / 14;
364
- const scrollLines = normalizedDelta === 0
365
- ? 0
366
- : normalizedDelta > 0
367
- ? Math.max(1, Math.round(normalizedDelta))
368
- : Math.min(-1, Math.round(normalizedDelta));
369
- if (scrollLines === 0) {
370
- return false;
371
- }
372
- event.preventDefault();
373
- event.stopPropagation();
374
- term?.scrollLines(scrollLines);
375
- updateScrollState();
376
- return false;
377
- };
378
- registerManagedScrollMode("h");
379
- registerManagedScrollMode("l");
380
- term.attachCustomWheelEventHandler(handleManagedWheel);
381
-
382
- fit = new fitMod.FitAddon();
383
- term.loadAddon(fit);
384
- const searchAddon = new searchMod.SearchAddon();
385
- term.loadAddon(searchAddon);
386
- term.open(containerRef.current);
387
- fit.fit();
388
-
389
- termRef.current = term;
390
- fitRef.current = fit;
391
- searchRef.current = searchAddon;
392
- setTerminalReady(true);
393
- updateScrollState();
394
- scrollDisposable = term.onScroll(() => {
395
- updateScrollState();
396
- });
397
-
398
- resizeObserver = new ResizeObserver(() => {
399
- if (!fit || !mounted || !term) return;
400
- try {
401
- const nextViewportOptions = getTerminalViewportOptions(window.innerWidth);
402
- term.options.fontFamily = nextViewportOptions.fontFamily;
403
- term.options.fontSize = nextViewportOptions.fontSize;
404
- term.options.lineHeight = nextViewportOptions.lineHeight;
405
- fit.fit();
406
- } catch {
407
- // Container may be hidden while switching tabs.
408
- }
409
- });
410
-
411
- resizeObserver.observe(containerRef.current);
412
- }
413
-
414
- void init();
415
-
416
- return () => {
417
- mounted = false;
418
- scrollDisposable?.dispose();
419
- if (resizeObserver) resizeObserver.disconnect();
420
- if (term) term.dispose();
421
- termRef.current = null;
422
- fitRef.current = null;
423
- searchRef.current = null;
424
- setTerminalReady(false);
425
- };
426
- }, [sessionId, updateScrollState]);
427
-
428
- useEffect(() => {
429
- let mounted = true;
430
- void (async () => {
431
- try {
432
- setSocketBaseUrl(null);
433
- const connection = await fetchTerminalConnection(sessionId);
434
- if (!mounted) return;
435
- setTransportMode(connection.transport);
436
- setPollIntervalMs(connection.pollIntervalMs);
437
- setSocketBaseUrl(connection.wsUrl);
438
- setTransportError(connection.fallbackReason);
439
- setConnectionState("connecting");
440
- } catch (err) {
441
- if (!mounted) return;
442
- setTransportError(err instanceof Error ? err.message : "Failed to resolve terminal connection");
443
- setConnectionState("error");
444
- }
445
- })();
446
-
447
- return () => {
448
- mounted = false;
449
- };
450
- }, [reconnectToken, sessionId]);
451
-
452
- useEffect(() => {
453
- if (!terminalReady || !snapshotReady) {
454
- return;
455
- }
456
-
457
- const term = termRef.current;
458
- if (!term) {
459
- return;
460
- }
461
-
462
- if (transportMode === "snapshot") {
463
- const previousBaseY = term.buffer.active.baseY;
464
- const previousViewportY = term.buffer.active.viewportY;
465
- const scrollGap = Math.max(0, previousBaseY - previousViewportY);
466
- const shouldFollow = scrollGap <= 2;
467
- term.reset();
468
- if (snapshotAnsi.length > 0) {
469
- term.write(normalizeTerminalSnapshot(snapshotAnsi), () => {
470
- if (termRef.current !== term) {
471
- return;
472
- }
473
- if (shouldFollow) {
474
- try {
475
- term.scrollToBottom();
476
- } catch {
477
- return;
478
- }
479
- } else {
480
- const nextBaseY = term.buffer.active.baseY;
481
- const targetViewportY = Math.max(0, nextBaseY - scrollGap);
482
- const delta = targetViewportY - term.buffer.active.viewportY;
483
- if (delta !== 0) {
484
- try {
485
- term.scrollLines(delta);
486
- } catch {
487
- return;
488
- }
489
- }
490
- }
491
- updateScrollState();
492
- });
493
- return;
494
- }
495
-
496
- updateScrollState();
497
- return;
498
- }
499
-
500
- if (snapshotAppliedRef.current === sessionId) {
501
- return;
502
- }
503
-
504
- if (liveOutputStartedRef.current || terminalHasRenderedContent(term)) {
505
- snapshotAppliedRef.current = sessionId;
506
- updateScrollState();
507
- return;
508
- }
509
-
510
- snapshotAppliedRef.current = sessionId;
511
- if (snapshotAnsi.length > 0) {
512
- term.reset();
513
- term.write(normalizeTerminalSnapshot(snapshotAnsi), () => {
514
- if (termRef.current !== term) {
515
- return;
516
- }
517
- updateScrollState();
518
- try {
519
- term.focus();
520
- } catch {
521
- // Terminal may have been disposed while the write callback was queued.
522
- }
523
- });
524
- return;
525
- }
526
-
527
- updateScrollState();
528
- }, [sessionId, snapshotAnsi, snapshotReady, terminalReady, transportMode, updateScrollState]);
529
-
530
- useEffect(() => {
531
- if (
532
- !snapshotReady
533
- || !terminalReady
534
- || transportMode !== "snapshot"
535
- ) {
536
- return;
537
- }
538
-
539
- let cancelled = false;
540
- let timer: number | null = null;
541
- let inFlight = false;
542
-
543
- const poll = async () => {
544
- if (cancelled || inFlight) return;
545
- inFlight = true;
546
- try {
547
- const snapshot = await fetchLiveTerminalSnapshot(sessionId, LIVE_TERMINAL_SNAPSHOT_LINES);
548
- if (cancelled) return;
549
- setConnectionState("live");
550
- if (snapshot.snapshot !== snapshotAnsi) {
551
- setSnapshotAnsi(snapshot.snapshot);
552
- }
553
- } catch (error) {
554
- if (cancelled) return;
555
- setTransportError(error instanceof Error ? error.message : "Terminal polling failed");
556
- setConnectionState("error");
557
- } finally {
558
- inFlight = false;
559
- if (!cancelled) {
560
- timer = window.setTimeout(() => {
561
- void poll();
562
- }, pollIntervalMs);
563
- }
564
- }
565
- };
566
-
567
- void poll();
568
-
569
- return () => {
570
- cancelled = true;
571
- if (timer !== null) {
572
- window.clearTimeout(timer);
573
- }
574
- };
575
- }, [pollIntervalMs, sessionId, snapshotAnsi, snapshotReady, terminalReady, transportMode]);
576
-
577
- useEffect(() => {
578
- if (!terminalReady || !snapshotReady || !socketBaseUrl || !termRef.current || transportMode !== "websocket") return;
579
-
580
- const term = termRef.current;
581
- const socketUrl = buildTerminalSocketUrl(socketBaseUrl, term.cols, term.rows);
582
- const attemptId = connectAttemptRef.current + 1;
583
- connectAttemptRef.current = attemptId;
584
- clearReconnectTimer();
585
- setConnectionState("connecting");
586
-
587
- const socket = new WebSocket(socketUrl);
588
- socket.binaryType = "arraybuffer";
589
- socketRef.current = socket;
590
-
591
- socket.onopen = () => {
592
- if (connectAttemptRef.current !== attemptId) return;
593
- reconnectCountRef.current = 0;
594
- setTransportError(null);
595
- setConnectionState("live");
596
- const wasReconnect = hasConnectedOnceRef.current;
597
- hasConnectedOnceRef.current = true;
598
- reconnectNoticeWrittenRef.current = false;
599
- if (wasReconnect) {
600
- term.writeln("\r\n\x1b[90m[Reconnected]\x1b[0m");
601
- }
602
- try {
603
- fitRef.current?.fit();
604
- term.refresh(0, Math.max(0, term.rows - 1));
605
- } catch {
606
- // Best-effort renderer recovery.
607
- }
608
- updateScrollState();
609
- };
610
-
611
- socket.onmessage = (event) => {
612
- if (connectAttemptRef.current !== attemptId) return;
613
-
614
- if (typeof event.data === "string") {
615
- try {
616
- const payload = JSON.parse(event.data) as { type?: string; error?: string };
617
- if (payload.type === "error") {
618
- setTransportError(payload.error || "Terminal connection failed");
619
- setConnectionState("error");
620
- } else if (payload.type === "exit") {
621
- setConnectionState("closed");
622
- }
623
- } catch {
624
- setTransportError("Received an invalid terminal event");
625
- setConnectionState("error");
626
- }
627
- return;
628
- }
629
-
630
- if (event.data instanceof ArrayBuffer) {
631
- liveOutputStartedRef.current = true;
632
- const shouldFollow = term.buffer.active.viewportY >= term.buffer.active.baseY;
633
- term.write(new Uint8Array(event.data), () => {
634
- if (termRef.current !== term) {
635
- return;
636
- }
637
- if (shouldFollow) {
638
- try {
639
- term.scrollToBottom();
640
- } catch {
641
- return;
642
- }
643
- }
644
- updateScrollState();
645
- });
646
- }
647
- };
648
-
649
- socket.onclose = () => {
650
- if (connectAttemptRef.current !== attemptId) return;
651
- socketRef.current = null;
652
- if (termRef.current && hasConnectedOnceRef.current && !reconnectNoticeWrittenRef.current) {
653
- reconnectNoticeWrittenRef.current = true;
654
- termRef.current.writeln("\r\n\x1b[90m[Connection lost. Reconnecting...]\x1b[0m");
655
- }
656
- setConnectionState("connecting");
657
- setSocketBaseUrl(null);
658
- scheduleReconnect();
659
- };
660
-
661
- socket.onerror = () => {
662
- if (connectAttemptRef.current !== attemptId) return;
663
- setTransportError("Terminal connection failed");
664
- setConnectionState("error");
665
- };
666
-
667
- return () => {
668
- if (socketRef.current === socket) {
669
- socketRef.current = null;
670
- }
671
- socket.close();
672
- };
673
- }, [clearReconnectTimer, scheduleReconnect, snapshotReady, socketBaseUrl, terminalReady, transportMode, updateScrollState]);
674
-
675
- useEffect(() => () => {
676
- clearReconnectTimer();
677
- socketRef.current?.close();
678
- }, [clearReconnectTimer]);
679
-
680
- return (
681
- <div className="group/terminal relative flex h-full min-h-0 flex-col overflow-hidden rounded-[14px] border border-white/10 bg-[#060404] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]">
682
- {searchOpen ? (
683
- <div className="absolute right-2 top-2 z-10 flex max-w-[calc(100%-1rem)] items-center rounded bg-[#141010]/95 pl-2 pr-0.5 shadow-lg ring-1 ring-white/10 backdrop-blur sm:right-3 sm:top-3 sm:max-w-[calc(100%-1.5rem)]">
684
- <Search className="h-3.5 w-3.5 text-[#8e847d]" />
685
- <input
686
- value={searchQuery}
687
- onChange={(event) => setSearchQuery(event.target.value)}
688
- onKeyDown={(event) => {
689
- if (event.key === "Enter") {
690
- event.preventDefault();
691
- runSearch(event.shiftKey ? "prev" : "next");
692
- } else if (event.key === "Escape") {
693
- event.preventDefault();
694
- closeSearch();
695
- }
696
- }}
697
- placeholder="Find"
698
- className="h-6 w-20 min-w-0 bg-transparent px-2 text-[11px] text-[#efe8e1] outline-none placeholder:text-[#7d746e] sm:w-28 sm:text-[12px]"
699
- />
700
- <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={() => runSearch("prev")} aria-label="Find previous">
701
- <span className="text-[11px]">↑</span>
702
- </Button>
703
- <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={() => runSearch("next")} aria-label="Find next">
704
- <span className="text-[11px]">↓</span>
705
- </Button>
706
- <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={closeSearch} aria-label="Close search">
707
- <X className="h-3.5 w-3.5" />
708
- </Button>
709
- </div>
710
- ) : (
711
- <div className={`absolute right-2 top-2 z-10 flex items-center gap-1.5 transition-opacity sm:right-3 sm:top-3 sm:gap-2 ${
712
- connectionState === "live" && transportMode === "websocket"
713
- ? "opacity-0 group-hover/terminal:opacity-100 focus-within:opacity-100"
714
- : "opacity-100"
715
- }`}>
716
- {connectionState !== "live" || transportMode === "snapshot" ? (
717
- <Button
718
- type="button"
719
- size="icon"
720
- variant="ghost"
721
- className={`pointer-events-auto h-7 w-7 rounded-full border backdrop-blur-sm sm:h-8 sm:w-8 ${
722
- transportError
723
- ? "border-[#ff8f7a]/25 bg-[#2a1616]/92 text-[#ff8f7a] hover:bg-[#351b1b]"
724
- : "border-white/10 bg-[#141010]/92 text-[#c9c0b7] hover:bg-[#201818]"
725
- }`}
726
- onClick={requestReconnect}
727
- aria-label="Reconnect terminal"
728
- >
729
- {connectionState === "connecting"
730
- ? <Loader2 className="h-3.5 w-3.5 animate-spin" />
731
- : transportError
732
- ? <AlertCircle className="h-3.5 w-3.5" />
733
- : <RefreshCw className="h-3.5 w-3.5" />}
734
- </Button>
735
- ) : null}
736
- <Button
737
- type="button"
738
- size="icon"
739
- variant="ghost"
740
- className="pointer-events-auto h-7 w-7 rounded-full border border-white/10 bg-[#141010]/92 text-[#c9c0b7] backdrop-blur-sm hover:bg-[#201818] sm:h-8 sm:w-8"
741
- onClick={() => setSearchOpen(true)}
742
- aria-label="Search terminal"
743
- >
744
- <Search className="h-3.5 w-3.5" />
745
- </Button>
746
- </div>
747
- )}
748
-
749
- <div className="min-h-0 flex-1 overflow-hidden px-0.5 pb-1 pt-2 sm:px-1.5 sm:pb-1.5 sm:pt-3">
750
- <div ref={containerRef} className="h-full w-full overflow-hidden" />
751
- </div>
752
-
753
- {showScrollToBottom ? (
754
- <div className="pointer-events-none absolute bottom-4 left-1/2 z-10 -translate-x-1/2">
755
- <Button
756
- type="button"
757
- size="sm"
758
- variant="ghost"
759
- className="pointer-events-auto h-9 rounded-full border border-white/10 bg-[#141010]/92 px-3 text-[#efe8e1] shadow-[0_14px_28px_rgba(0,0,0,0.38)] backdrop-blur-sm hover:bg-[#201818]"
760
- onClick={scrollToBottom}
761
- aria-label="Scroll to bottom"
762
- >
763
- <ChevronDown className="h-4 w-4" />
764
- <span className="ml-1 text-[11px] uppercase tracking-[0.16em]">Jump to latest</span>
765
- </Button>
766
- </div>
767
- ) : null}
768
- </div>
769
- );
770
- }