conductor-oss 0.7.0 → 0.8.0

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 (381) hide show
  1. package/README.md +173 -134
  2. package/dist/backend.d.ts.map +1 -1
  3. package/dist/backend.js +18 -2
  4. package/dist/backend.js.map +1 -1
  5. package/dist/commands/dashboard.d.ts.map +1 -1
  6. package/dist/commands/dashboard.js +13 -7
  7. package/dist/commands/dashboard.js.map +1 -1
  8. package/node_modules/@conductor-oss/core/dist/config.d.ts.map +1 -1
  9. package/node_modules/@conductor-oss/core/dist/config.js +6 -1
  10. package/node_modules/@conductor-oss/core/dist/config.js.map +1 -1
  11. package/package.json +8 -6
  12. package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
  13. package/web/.next/standalone/packages/web/.next/app-path-routes-manifest.json +2 -0
  14. package/web/.next/standalone/packages/web/.next/build-manifest.json +7 -7
  15. package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
  16. package/web/.next/standalone/packages/web/.next/routes-manifest.json +44 -1
  17. package/web/.next/standalone/packages/web/.next/server/app/_global-error/page/build-manifest.json +5 -5
  18. package/web/.next/standalone/packages/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
  20. package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +7 -7
  21. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  22. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  23. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  24. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  25. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  26. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/build-manifest.json +5 -5
  27. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
  28. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js +2 -2
  29. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  30. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  31. package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
  32. package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +11 -11
  33. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  34. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  35. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  36. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  37. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  38. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  39. package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js +2 -2
  40. package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
  41. package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js +2 -2
  42. package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js.nft.json +1 -1
  43. package/web/.next/standalone/packages/web/.next/server/app/api/app-update/route.js +2 -2
  44. package/web/.next/standalone/packages/web/.next/server/app/api/app-update/route.js.nft.json +1 -1
  45. package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js +2 -2
  46. package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
  47. package/web/.next/standalone/packages/web/.next/server/app/api/auth/session/route.js +2 -2
  48. package/web/.next/standalone/packages/web/.next/server/app/api/auth/session/route.js.nft.json +1 -1
  49. package/web/.next/standalone/packages/web/.next/server/app/api/boards/comments/route.js +2 -2
  50. package/web/.next/standalone/packages/web/.next/server/app/api/boards/comments/route.js.nft.json +1 -1
  51. package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js +2 -2
  52. package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
  53. package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js +2 -2
  54. package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
  55. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/open/route.js +2 -2
  56. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/open/route.js.nft.json +1 -1
  57. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js +2 -2
  58. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
  59. package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js +2 -2
  60. package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
  61. package/web/.next/standalone/packages/web/.next/server/app/api/executor/health/route.js +2 -2
  62. package/web/.next/standalone/packages/web/.next/server/app/api/executor/health/route.js.nft.json +1 -1
  63. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js +2 -2
  64. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
  65. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/pick-directory/route.js +2 -2
  66. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/pick-directory/route.js.nft.json +1 -1
  67. package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js +2 -2
  68. package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
  69. package/web/.next/standalone/packages/web/.next/server/app/api/github/webhook/route.js +2 -2
  70. package/web/.next/standalone/packages/web/.next/server/app/api/github/webhook/route.js.nft.json +1 -1
  71. package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js +2 -2
  72. package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
  73. package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js +2 -2
  74. package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
  75. package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js +2 -2
  76. package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
  77. package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js +2 -2
  78. package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
  79. package/web/.next/standalone/packages/web/.next/server/app/api/remote-access/route.js +2 -2
  80. package/web/.next/standalone/packages/web/.next/server/app/api/remote-access/route.js.nft.json +1 -1
  81. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/[id]/route.js +2 -2
  82. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/[id]/route.js.nft.json +1 -1
  83. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js +2 -2
  84. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
  85. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js +2 -2
  86. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
  87. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/archive/route.js +2 -2
  88. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  89. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js +2 -2
  90. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
  91. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js +2 -2
  92. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
  93. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js +2 -2
  94. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -1
  95. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route.js +2 -2
  96. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/stream/route.js.nft.json +1 -1
  97. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js +2 -2
  98. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
  99. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js +2 -2
  100. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
  101. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/interrupt/route.js +2 -2
  102. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/interrupt/route.js.nft.json +1 -1
  103. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/keys/route.js +2 -2
  104. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/keys/route.js.nft.json +1 -1
  105. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js +2 -2
  106. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
  107. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js +2 -2
  108. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
  109. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js +2 -2
  110. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.nft.json +1 -1
  111. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/dom/route.js +1 -1
  112. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/dom/route.js.nft.json +1 -1
  113. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/route.js +1 -1
  114. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/route.js.nft.json +1 -1
  115. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/screenshot/route.js +1 -1
  116. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/screenshot/route.js.nft.json +1 -1
  117. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js +2 -2
  118. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
  119. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js +2 -2
  120. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  121. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/send/route.js +2 -2
  122. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
  123. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/app-paths-manifest.json +3 -0
  124. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/build-manifest.json +11 -0
  125. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route/server-reference-manifest.json +4 -0
  126. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js +10 -0
  127. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js.map +5 -0
  128. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route.js.nft.json +1 -0
  129. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/connection/route_client-reference-manifest.js +2 -0
  130. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route/app-paths-manifest.json +3 -0
  131. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route/build-manifest.json +11 -0
  132. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route/server-reference-manifest.json +4 -0
  133. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js +10 -0
  134. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js.map +5 -0
  135. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js.nft.json +1 -0
  136. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route_client-reference-manifest.js +2 -0
  137. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js +2 -2
  138. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
  139. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js +2 -2
  140. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
  141. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js +2 -2
  142. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
  143. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js +2 -2
  144. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  145. package/web/.next/standalone/packages/web/.next/server/app/page/build-manifest.json +5 -5
  146. package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +4 -4
  147. package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
  148. package/web/.next/standalone/packages/web/.next/server/app/page.js +2 -2
  149. package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
  150. package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
  151. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/build-manifest.json +5 -5
  152. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
  153. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js +2 -2
  154. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  155. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  156. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/build-manifest.json +5 -5
  157. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
  158. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
  159. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
  160. package/web/.next/standalone/packages/web/.next/server/app/unlock/page/build-manifest.json +5 -5
  161. package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +14 -14
  162. package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
  163. package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
  164. package/web/.next/standalone/packages/web/.next/server/app-paths-manifest.json +2 -0
  165. package/web/.next/standalone/packages/web/.next/server/chunks/26076_server_app_api_sessions_[id]_terminal_connection_route_actions_46c114ee.js +3 -0
  166. package/web/.next/standalone/packages/web/.next/server/chunks/26076_server_app_api_sessions_[id]_terminal_snapshot_route_actions_cfb4de43.js +3 -0
  167. package/web/.next/standalone/packages/web/.next/server/chunks/730ea_web__next-internal_server_app_api_sessions_[id]_restore_route_actions_41a4c356.js +1 -1
  168. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__b716459f._.js → [root-of-the-server]__08556fc3._.js} +2 -2
  169. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__624bcdc9._.js → [root-of-the-server]__0e496df1._.js} +2 -2
  170. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__9bf61231._.js → [root-of-the-server]__115b433b._.js} +2 -2
  171. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__70929198._.js → [root-of-the-server]__120a0868._.js} +2 -2
  172. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__17d710ef._.js → [root-of-the-server]__1a623333._.js} +2 -2
  173. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__262e987e._.js +1 -1
  174. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__31053ac2._.js → [root-of-the-server]__2713bac9._.js} +2 -2
  175. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__8c576b6a._.js → [root-of-the-server]__2798ac7c._.js} +2 -2
  176. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__747e6415._.js → [root-of-the-server]__27b2bd14._.js} +2 -2
  177. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__a4b1a94c._.js → [root-of-the-server]__29d94573._.js} +2 -2
  178. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__732fc06e._.js → [root-of-the-server]__2b59cba9._.js} +2 -2
  179. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__2ce39d51._.js +1 -1
  180. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__3e507276._.js → [root-of-the-server]__2fea4a4c._.js} +2 -2
  181. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__59635ca5._.js → [root-of-the-server]__36559d88._.js} +2 -2
  182. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__f0ec1021._.js → [root-of-the-server]__4117784b._.js} +2 -2
  183. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__543bc377._.js → [root-of-the-server]__43b35d39._.js} +2 -2
  184. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__ba70a88e._.js → [root-of-the-server]__444bf146._.js} +2 -2
  185. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__fd0a681b._.js → [root-of-the-server]__45483f4b._.js} +2 -2
  186. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__98f4a7bd._.js → [root-of-the-server]__4644a89e._.js} +2 -2
  187. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__4c78886c._.js +3 -0
  188. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__e7aaee2d._.js → [root-of-the-server]__4cea5dca._.js} +2 -2
  189. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__33494588._.js → [root-of-the-server]__575c7392._.js} +2 -2
  190. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__eae97345._.js → [root-of-the-server]__57605af7._.js} +2 -2
  191. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__be233a9f._.js → [root-of-the-server]__57c8ca1a._.js} +2 -2
  192. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__5e2b35cd._.js +3 -0
  193. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__8ace49d1._.js → [root-of-the-server]__5f5433df._.js} +2 -2
  194. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__6313c91e._.js +3 -3
  195. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__3dc4c761._.js → [root-of-the-server]__6a8b5dc7._.js} +2 -2
  196. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__3318d8a1._.js → [root-of-the-server]__717081d9._.js} +2 -2
  197. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__ae73aa49._.js → [root-of-the-server]__75668566._.js} +2 -2
  198. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__b4039db5._.js → [root-of-the-server]__7a6210dc._.js} +2 -2
  199. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__34e636e5._.js → [root-of-the-server]__7ca5b93b._.js} +2 -2
  200. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__b53b70e7._.js → [root-of-the-server]__8462ddf1._.js} +2 -2
  201. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__3937a996._.js → [root-of-the-server]__9e363e9c._.js} +2 -2
  202. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__4c5f7cab._.js → [root-of-the-server]__a0467c5b._.js} +2 -2
  203. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__9576223c._.js → [root-of-the-server]__a8d68307._.js} +2 -2
  204. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__82e86bb6._.js → [root-of-the-server]__ac79a67b._.js} +2 -2
  205. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__bcd0c846._.js → [root-of-the-server]__af5be1d9._.js} +2 -2
  206. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__b3cd93ca._.js +3 -0
  207. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__540ebc7f._.js → [root-of-the-server]__b7314f2a._.js} +2 -2
  208. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__bb2675cd._.js → [root-of-the-server]__bcbc6330._.js} +2 -2
  209. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__fe4c1384._.js → [root-of-the-server]__bfbdd808._.js} +2 -2
  210. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__b00db558._.js → [root-of-the-server]__c205eff4._.js} +2 -2
  211. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__c455995a._.js → [root-of-the-server]__c39ceb71._.js} +2 -2
  212. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__c9cb9ac6._.js +3 -0
  213. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__cff9ddef._.js +1 -1
  214. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__7c87a52f._.js → [root-of-the-server]__d65b8403._.js} +2 -2
  215. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__eb6e6bb9._.js → [root-of-the-server]__d7b9cb55._.js} +2 -2
  216. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__53daf5c6._.js → [root-of-the-server]__df103a4a._.js} +2 -2
  217. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__ea845f9f._.js +1 -1
  218. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__8ee7749b._.js → [root-of-the-server]__f5a16619._.js} +2 -2
  219. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__9940de77._.js → [root-of-the-server]__f62d8a1f._.js} +2 -2
  220. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__5df70a62._.js → [root-of-the-server]__f7c7ed71._.js} +2 -2
  221. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__520aa408._.js → [root-of-the-server]__fa5bbdc8._.js} +2 -2
  222. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__e385846c._.js → [root-of-the-server]__fed9223f._.js} +2 -2
  223. package/web/.next/standalone/packages/web/.next/server/chunks/_2c837d66._.js +5 -5
  224. package/web/.next/standalone/packages/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_eaad1f33.js +1 -1
  225. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__000b8c99._.js +1 -1
  226. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__4f387f9d._.js → [root-of-the-server]__10a850c6._.js} +2 -2
  227. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__7829c78d._.js → [root-of-the-server]__17f5cb6a._.js} +2 -2
  228. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__29d8d063._.js +1 -1
  229. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__99387ae7._.js → [root-of-the-server]__6647f7ec._.js} +2 -2
  230. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__7bb2062e._.js +3 -0
  231. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__a509d17d._.js → [root-of-the-server]__9f45b36b._.js} +2 -2
  232. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__bc8d015f._.js → [root-of-the-server]__f8f50c0b._.js} +2 -2
  233. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
  234. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_367680b9._.js → _1f11ad88._.js} +2 -2
  235. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_1fc34e32._.js +3 -0
  236. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_230c0c14._.js +4 -4
  237. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_4a52b10d._.js +3 -0
  238. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_b6d31783._.js → _532f707d._.js} +3 -3
  239. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
  240. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_6cfd06c2._.js +1 -1
  241. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
  242. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b214b154._.js +1 -1
  243. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b8358e63._.js +3 -0
  244. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
  245. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_8982bbf3._.js → _de3b7194._.js} +2 -2
  246. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
  247. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_226037d7._.js +3 -0
  248. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_3c8da5a6._.js → node_modules_@clerk_nextjs_dist_esm_app-router_aed062ea._.js} +2 -2
  249. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@radix-ui_d03fdd57._.js +3 -0
  250. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@xterm_addon-fit_lib_addon-fit_mjs_9b7cb17b._.js +3 -0
  251. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@xterm_addon-search_lib_addon-search_mjs_8d69d9d7._.js +11 -0
  252. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@xterm_xterm_lib_xterm_mjs_a1a7b9e9._.js +20 -0
  253. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_afb6e107._.js +3 -0
  254. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_refresh-cw_50f1c1e7.js +3 -0
  255. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_next_dist_2df23979._.js +1 -1
  256. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_cd51dad4.js +2 -2
  257. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_board_WorkspaceKanban_tsx_735b7999._.js +1 -1
  258. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_sessions_SessionDetail_tsx_5fcad270._.js +6 -0
  259. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_features_dashboard_DashboardClient_tsx_81ae42b0._.js +1 -1
  260. package/web/.next/standalone/packages/web/.next/server/functions-config-manifest.json +3 -1
  261. package/web/.next/standalone/packages/web/.next/server/middleware-build-manifest.js +5 -5
  262. package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
  263. package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
  264. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
  265. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +15 -15
  266. package/web/.next/standalone/packages/web/.next/static/chunks/0cdc18649f440c71.js +1 -0
  267. package/web/.next/standalone/packages/web/.next/static/chunks/0f7eb6d5a59ee3ae.js +1 -0
  268. package/web/.next/standalone/packages/web/.next/static/chunks/12d33f33839d69eb.js +1 -0
  269. package/web/.next/standalone/packages/web/.next/static/chunks/146ec959503d40d4.js +1 -0
  270. package/web/.next/standalone/packages/web/.next/static/chunks/1fabe72716ad1fda.css +3 -0
  271. package/web/.next/standalone/packages/web/.next/static/chunks/2d61dae0bfb5ab41.js +1 -0
  272. package/web/.next/standalone/packages/web/.next/static/chunks/{c4c79d5d0b280aeb.js → 2f236954d6a65e12.js} +1 -1
  273. package/web/.next/standalone/packages/web/.next/static/chunks/3cdf839644f79d43.js +18 -0
  274. package/web/.next/standalone/packages/web/.next/static/chunks/3e3880b9a3d2d1d5.js +6 -0
  275. package/web/.next/standalone/packages/web/.next/static/chunks/486ac8976a12f1e5.js +1 -0
  276. package/web/.next/standalone/packages/web/.next/static/chunks/6850a6cda159c98d.js +1 -0
  277. package/web/.next/{static/chunks/06bdd070b8d25946.js → standalone/packages/web/.next/static/chunks/69be39811437728d.js} +1 -1
  278. package/web/.next/standalone/packages/web/.next/static/chunks/6cec31da5f555e99.js +1 -0
  279. package/web/.next/standalone/packages/web/.next/static/chunks/70d1e7a526f0f4e2.js +9 -0
  280. package/web/.next/standalone/packages/web/.next/static/chunks/{ba2aa8b54e912820.js → 723acbe92d3f9e7e.js} +1 -1
  281. package/web/.next/standalone/packages/web/.next/static/chunks/739c6f71243d76d8.js +1 -0
  282. package/web/.next/standalone/packages/web/.next/static/chunks/a6ec1ab37eb7b410.js +1 -0
  283. package/web/.next/standalone/packages/web/.next/static/chunks/af8bfb72187b7cbf.js +1 -0
  284. package/web/.next/standalone/packages/web/.next/static/chunks/c993a1a4204fce88.js +4 -0
  285. package/web/.next/standalone/packages/web/.next/static/chunks/ca3e38b4495a3d39.js +1 -0
  286. package/web/.next/standalone/packages/web/.next/static/chunks/{45bd2451c2b0790c.js → caf02f8136b16992.js} +2 -2
  287. package/web/.next/standalone/packages/web/.next/static/chunks/d4269c59429734ad.js +1 -0
  288. package/web/.next/standalone/packages/web/.next/static/chunks/{28eac764d6544827.js → e5bcec03f80af56a.js} +1 -1
  289. package/web/.next/standalone/packages/web/.next/static/chunks/{turbopack-49f4119854e8206e.js → turbopack-26870d017b23fcf0.js} +1 -1
  290. package/web/.next/standalone/packages/web/next.config.ts +16 -0
  291. package/web/.next/standalone/packages/web/package.json +3 -1
  292. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/terminal/connection/route.ts +64 -0
  293. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/terminal/snapshot/route.ts +14 -0
  294. package/web/.next/standalone/packages/web/src/app/globals.css +12 -4
  295. package/web/.next/standalone/packages/web/src/components/Dashboard.tsx +1 -5
  296. package/web/.next/standalone/packages/web/src/components/TerminalView.tsx +528 -100
  297. package/web/.next/standalone/packages/web/src/components/board/WorkspaceKanban.tsx +3 -3
  298. package/web/.next/standalone/packages/web/src/components/layout/AppShell.tsx +3 -3
  299. package/web/.next/standalone/packages/web/src/components/layout/Sidebar.tsx +29 -18
  300. package/web/.next/standalone/packages/web/src/components/layout/TopBar.tsx +3 -3
  301. package/web/.next/standalone/packages/web/src/components/sessions/ChatPanel.tsx +61 -5
  302. package/web/.next/standalone/packages/web/src/components/sessions/SessionDetail.tsx +99 -46
  303. package/web/.next/standalone/packages/web/src/components/sessions/SessionPreview.tsx +231 -233
  304. package/web/.next/standalone/packages/web/src/components/sessions/SessionTerminal.tsx +1212 -0
  305. package/web/.next/standalone/packages/web/src/components/sessions/terminalInsert.ts +6 -0
  306. package/web/.next/standalone/packages/web/src/components/terminal/xtermTheme.ts +70 -0
  307. package/web/.next/standalone/packages/web/src/components/ui/Tabs.tsx +2 -2
  308. package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +10 -12
  309. package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +2 -2
  310. package/web/.next/standalone/packages/web/src/lib/devPreviewBrowser.ts +15 -3
  311. package/web/.next/static/chunks/0cdc18649f440c71.js +1 -0
  312. package/web/.next/static/chunks/0f7eb6d5a59ee3ae.js +1 -0
  313. package/web/.next/static/chunks/12d33f33839d69eb.js +1 -0
  314. package/web/.next/static/chunks/146ec959503d40d4.js +1 -0
  315. package/web/.next/static/chunks/1fabe72716ad1fda.css +3 -0
  316. package/web/.next/static/chunks/2d61dae0bfb5ab41.js +1 -0
  317. package/web/.next/static/chunks/{c4c79d5d0b280aeb.js → 2f236954d6a65e12.js} +1 -1
  318. package/web/.next/static/chunks/3cdf839644f79d43.js +18 -0
  319. package/web/.next/static/chunks/3e3880b9a3d2d1d5.js +6 -0
  320. package/web/.next/static/chunks/486ac8976a12f1e5.js +1 -0
  321. package/web/.next/static/chunks/6850a6cda159c98d.js +1 -0
  322. package/web/.next/{standalone/packages/web/.next/static/chunks/06bdd070b8d25946.js → static/chunks/69be39811437728d.js} +1 -1
  323. package/web/.next/static/chunks/6cec31da5f555e99.js +1 -0
  324. package/web/.next/static/chunks/70d1e7a526f0f4e2.js +9 -0
  325. package/web/.next/static/chunks/{ba2aa8b54e912820.js → 723acbe92d3f9e7e.js} +1 -1
  326. package/web/.next/static/chunks/739c6f71243d76d8.js +1 -0
  327. package/web/.next/static/chunks/a6ec1ab37eb7b410.js +1 -0
  328. package/web/.next/static/chunks/af8bfb72187b7cbf.js +1 -0
  329. package/web/.next/static/chunks/c993a1a4204fce88.js +4 -0
  330. package/web/.next/static/chunks/ca3e38b4495a3d39.js +1 -0
  331. package/web/.next/static/chunks/{45bd2451c2b0790c.js → caf02f8136b16992.js} +2 -2
  332. package/web/.next/static/chunks/d4269c59429734ad.js +1 -0
  333. package/web/.next/static/chunks/{28eac764d6544827.js → e5bcec03f80af56a.js} +1 -1
  334. package/web/.next/static/chunks/{turbopack-49f4119854e8206e.js → turbopack-26870d017b23fcf0.js} +1 -1
  335. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__29091976._.js +0 -3
  336. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +0 -3
  337. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__bc9ab6c6._.js +0 -3
  338. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__d1efcbf0._.js +0 -3
  339. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_e1c18705._.js +0 -3
  340. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_af0671be._.js +0 -3
  341. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@radix-ui_02f0d3f0._.js +0 -3
  342. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_message-square_2e76b8d9.js +0 -3
  343. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_ui_Tooltip_tsx_6becc8ca._.js +0 -3
  344. package/web/.next/standalone/packages/web/.next/static/chunks/07f3b9a104ce0ff4.js +0 -1
  345. package/web/.next/standalone/packages/web/.next/static/chunks/1004422d31074d62.js +0 -1
  346. package/web/.next/standalone/packages/web/.next/static/chunks/2bc11b604b131190.js +0 -1
  347. package/web/.next/standalone/packages/web/.next/static/chunks/30ababddac1b82dc.js +0 -1
  348. package/web/.next/standalone/packages/web/.next/static/chunks/5f2995585c1ac66a.js +0 -1
  349. package/web/.next/standalone/packages/web/.next/static/chunks/64f0033534ca1ad3.css +0 -3
  350. package/web/.next/standalone/packages/web/.next/static/chunks/7daf09cc7388f426.js +0 -1
  351. package/web/.next/standalone/packages/web/.next/static/chunks/a28bcda8dd6b68ee.js +0 -1
  352. package/web/.next/standalone/packages/web/.next/static/chunks/aabc80195233b4da.js +0 -1
  353. package/web/.next/standalone/packages/web/.next/static/chunks/b31c8692e1da69ef.js +0 -6
  354. package/web/.next/standalone/packages/web/.next/static/chunks/b6069d0eb07a978f.js +0 -1
  355. package/web/.next/standalone/packages/web/.next/static/chunks/c2caa064748bb0b5.js +0 -1
  356. package/web/.next/standalone/packages/web/.next/static/chunks/c5f8e486166298c2.js +0 -1
  357. package/web/.next/standalone/packages/web/.next/static/chunks/d6a81d2f8b98b4be.js +0 -1
  358. package/web/.next/standalone/packages/web/.next/static/chunks/e9d0e95bbaa0f4fb.js +0 -1
  359. package/web/.next/standalone/packages/web/.next/static/chunks/ebaa0c535c4135f1.js +0 -1
  360. package/web/.next/static/chunks/07f3b9a104ce0ff4.js +0 -1
  361. package/web/.next/static/chunks/1004422d31074d62.js +0 -1
  362. package/web/.next/static/chunks/2bc11b604b131190.js +0 -1
  363. package/web/.next/static/chunks/30ababddac1b82dc.js +0 -1
  364. package/web/.next/static/chunks/5f2995585c1ac66a.js +0 -1
  365. package/web/.next/static/chunks/64f0033534ca1ad3.css +0 -3
  366. package/web/.next/static/chunks/7daf09cc7388f426.js +0 -1
  367. package/web/.next/static/chunks/a28bcda8dd6b68ee.js +0 -1
  368. package/web/.next/static/chunks/aabc80195233b4da.js +0 -1
  369. package/web/.next/static/chunks/b31c8692e1da69ef.js +0 -6
  370. package/web/.next/static/chunks/b6069d0eb07a978f.js +0 -1
  371. package/web/.next/static/chunks/c2caa064748bb0b5.js +0 -1
  372. package/web/.next/static/chunks/c5f8e486166298c2.js +0 -1
  373. package/web/.next/static/chunks/d6a81d2f8b98b4be.js +0 -1
  374. package/web/.next/static/chunks/e9d0e95bbaa0f4fb.js +0 -1
  375. package/web/.next/static/chunks/ebaa0c535c4135f1.js +0 -1
  376. /package/web/.next/standalone/packages/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_buildManifest.js +0 -0
  377. /package/web/.next/standalone/packages/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_clientMiddlewareManifest.json +0 -0
  378. /package/web/.next/standalone/packages/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_ssgManifest.js +0 -0
  379. /package/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_buildManifest.js +0 -0
  380. /package/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_clientMiddlewareManifest.json +0 -0
  381. /package/web/.next/static/{X1r0VPbixkgEYAELz0R1i → 0erIuL9LSGhZ5_YeWB5Mc}/_ssgManifest.js +0 -0
@@ -0,0 +1,1212 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import type { FitAddon as XFitAddon } from "@xterm/addon-fit";
6
+ import type { SearchAddon as XSearchAddon } from "@xterm/addon-search";
7
+ import type { ITerminalOptions, IDisposable, Terminal as XTerminal } from "@xterm/xterm";
8
+ import { AlertCircle, ChevronDown, Loader2, Paperclip, RefreshCw, Search, X } from "lucide-react";
9
+ import { Button } from "@/components/ui/Button";
10
+ import { SUPERSET_TERMINAL_FONT_FAMILY, getSupersetLikeTerminalTheme } from "@/components/terminal/xtermTheme";
11
+ import { useSessionFeed } from "@/hooks/useSessionFeed";
12
+ import type { TerminalInsertRequest } from "./terminalInsert";
13
+
14
+ interface SessionTerminalProps {
15
+ sessionId: string;
16
+ agentName: string;
17
+ projectId: string;
18
+ sessionModel: string;
19
+ sessionReasoningEffort: string;
20
+ sessionState: string;
21
+ active: boolean;
22
+ pendingInsert: TerminalInsertRequest | null;
23
+ }
24
+
25
+ type TerminalConnectionInfo = {
26
+ wsUrl: string;
27
+ };
28
+
29
+ type TerminalSnapshot = {
30
+ snapshot: string;
31
+ source: string;
32
+ live: boolean;
33
+ restored: boolean;
34
+ };
35
+
36
+ type TerminalServerEvent =
37
+ | { type: "ready"; sessionId: string }
38
+ | { type: "ack"; sessionId: string; action: string }
39
+ | { type: "exit"; sessionId: string; exitCode: number }
40
+ | { type: "pong"; sessionId: string }
41
+ | { type: "error"; sessionId: string; error: string };
42
+
43
+ const LIVE_TERMINAL_STATUSES = new Set(["queued", "spawning", "running", "working", "needs_input", "stuck"]);
44
+ const RESUMABLE_STATUSES = new Set(["done", "needs_input", "stuck", "errored", "terminated", "killed"]);
45
+ const RECONNECT_BASE_DELAY_MS = 300;
46
+ const RECONNECT_MAX_DELAY_MS = 1600;
47
+ const RENDERER_RECOVERY_THROTTLE_MS = 120;
48
+ const LIVE_TERMINAL_SCROLLBACK = 200000;
49
+ const READ_ONLY_TERMINAL_SNAPSHOT_LINES = 200000;
50
+ const MANAGED_SCROLL_PRIVATE_MODES = new Set([1000, 1002, 1003, 1005, 1006, 1015, 1047, 1048, 1049]);
51
+
52
+ function shellEscapePath(path: string): string {
53
+ return `'${path.replace(/'/g, "'\\''")}'`;
54
+ }
55
+
56
+ function shellEscapePaths(paths: string[]): string {
57
+ return paths.map(shellEscapePath).join(" ");
58
+ }
59
+
60
+ async function uploadAttachments(files: File[]): Promise<string[]> {
61
+ if (!files.length) return [];
62
+
63
+ const uploadedPaths = await Promise.all(files.map(async (file) => {
64
+ const formData = new FormData();
65
+ formData.append("file", file);
66
+
67
+ const response = await fetch("/api/attachments", {
68
+ method: "POST",
69
+ body: formData,
70
+ });
71
+
72
+ if (!response.ok) {
73
+ throw new Error(`Failed to upload ${file.name}`);
74
+ }
75
+
76
+ const payload = await response.json();
77
+ const record = payload && typeof payload === "object" ? payload as Record<string, unknown> : null;
78
+ const nested = record?.attachment && typeof record.attachment === "object"
79
+ ? record.attachment as Record<string, unknown>
80
+ : null;
81
+
82
+ for (const candidate of [
83
+ record?.absolutePath,
84
+ record?.path,
85
+ record?.filePath,
86
+ nested?.absolutePath,
87
+ nested?.path,
88
+ nested?.filePath,
89
+ ]) {
90
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
91
+ return candidate.trim();
92
+ }
93
+ }
94
+
95
+ throw new Error(`Attachment response for ${file.name} did not include a file path`);
96
+ }));
97
+
98
+ return uploadedPaths.filter(Boolean);
99
+ }
100
+
101
+ async function fetchTerminalConnection(sessionId: string): Promise<TerminalConnectionInfo> {
102
+ const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/terminal/connection`, {
103
+ cache: "no-store",
104
+ });
105
+ const data = (await response.json().catch(() => null)) as { wsUrl?: string; error?: string } | null;
106
+ if (!response.ok || typeof data?.wsUrl !== "string" || data.wsUrl.trim().length === 0) {
107
+ throw new Error(data?.error ?? `Failed to resolve terminal connection: ${response.status}`);
108
+ }
109
+ return { wsUrl: data.wsUrl.trim() };
110
+ }
111
+
112
+ async function fetchTerminalSnapshot(sessionId: string, lines: number): Promise<TerminalSnapshot> {
113
+ const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/terminal/snapshot?lines=${lines}`, {
114
+ cache: "no-store",
115
+ });
116
+ const data = (await response.json().catch(() => null)) as
117
+ | { snapshot?: string; source?: string; live?: boolean; restored?: boolean; error?: string }
118
+ | null;
119
+ if (!response.ok) {
120
+ throw new Error(data?.error ?? `Failed to resolve terminal snapshot: ${response.status}`);
121
+ }
122
+ return {
123
+ snapshot: typeof data?.snapshot === "string" ? data.snapshot : "",
124
+ source: typeof data?.source === "string" ? data.source : "empty",
125
+ live: data?.live === true,
126
+ restored: data?.restored === true,
127
+ };
128
+ }
129
+
130
+ function buildTerminalSocketUrl(baseUrl: string, cols: number, rows: number): string {
131
+ const url = new URL(baseUrl);
132
+ url.searchParams.set("cols", String(Math.max(1, cols)));
133
+ url.searchParams.set("rows", String(Math.max(1, rows)));
134
+ return url.toString();
135
+ }
136
+
137
+ function normalizeTerminalSnapshot(snapshot: string): string {
138
+ return snapshot.replace(/\r?\n/g, "\r\n");
139
+ }
140
+
141
+ function terminalHasRenderedContent(term: XTerminal): boolean {
142
+ const buffer = term.buffer.active;
143
+ if (buffer.baseY > 0) {
144
+ return true;
145
+ }
146
+
147
+ for (let row = 0; row < term.rows; row += 1) {
148
+ const line = buffer.getLine(row);
149
+ if (!line) {
150
+ continue;
151
+ }
152
+ if (line.translateToString(true).trim().length > 0) {
153
+ return true;
154
+ }
155
+ }
156
+
157
+ return false;
158
+ }
159
+
160
+ function getTerminalViewportOptions(width: number): Pick<ITerminalOptions, "fontFamily" | "fontSize" | "lineHeight"> {
161
+ if (width < 420) {
162
+ return {
163
+ fontFamily: "'SF Mono', Menlo, Monaco, monospace",
164
+ fontSize: 11,
165
+ lineHeight: 1,
166
+ };
167
+ }
168
+
169
+ if (width < 640) {
170
+ return {
171
+ fontFamily: "'SF Mono', Menlo, Monaco, monospace",
172
+ fontSize: 13,
173
+ lineHeight: 1.08,
174
+ };
175
+ }
176
+
177
+ return {
178
+ fontFamily: SUPERSET_TERMINAL_FONT_FAMILY,
179
+ fontSize: 17,
180
+ lineHeight: 1.06,
181
+ };
182
+ }
183
+
184
+ export function SessionTerminal({
185
+ sessionId,
186
+ agentName,
187
+ projectId,
188
+ sessionModel,
189
+ sessionReasoningEffort,
190
+ sessionState,
191
+ active,
192
+ pendingInsert,
193
+ }: SessionTerminalProps) {
194
+ const router = useRouter();
195
+ const { sessionStatus, refresh } = useSessionFeed(sessionId);
196
+ const containerRef = useRef<HTMLDivElement>(null);
197
+ const termRef = useRef<XTerminal | null>(null);
198
+ const fitRef = useRef<XFitAddon | null>(null);
199
+ const searchRef = useRef<XSearchAddon | null>(null);
200
+ const resizeObserverRef = useRef<ResizeObserver | null>(null);
201
+ const socketRef = useRef<WebSocket | null>(null);
202
+ const reconnectTimerRef = useRef<number | null>(null);
203
+ const reconnectCountRef = useRef(0);
204
+ const connectAttemptRef = useRef(0);
205
+ const inputDisposableRef = useRef<IDisposable | null>(null);
206
+ const scrollDisposableRef = useRef<IDisposable | null>(null);
207
+ const fileInputRef = useRef<HTMLInputElement>(null);
208
+ const resumeTextareaRef = useRef<HTMLTextAreaElement>(null);
209
+ const latestStatusRef = useRef(sessionState);
210
+ const activeRef = useRef(active);
211
+ const hasConnectedOnceRef = useRef(false);
212
+ const reconnectNoticeWrittenRef = useRef(false);
213
+ const snapshotAppliedRef = useRef<string | null>(null);
214
+ const liveOutputStartedRef = useRef(false);
215
+ const previousLiveTerminalRef = useRef(false);
216
+ const recoveryFrameRef = useRef<number | null>(null);
217
+ const recoveryTimerRef = useRef<number | null>(null);
218
+ const recoveryLastRunRef = useRef(0);
219
+ const recoveryPendingResizeRef = useRef(false);
220
+ const visibilityRecoveryTimersRef = useRef<number[]>([]);
221
+ const lastAppliedInsertNonceRef = useRef<number>(0);
222
+
223
+ const [terminalReady, setTerminalReady] = useState(false);
224
+ const [socketBaseUrl, setSocketBaseUrl] = useState<string | null>(null);
225
+ const [connectionState, setConnectionState] = useState<"connecting" | "live" | "closed" | "error">("connecting");
226
+ const [transportError, setTransportError] = useState<string | null>(null);
227
+ const [reconnectToken, setReconnectToken] = useState(0);
228
+ const [message, setMessage] = useState("");
229
+ const [attachments, setAttachments] = useState<Array<{ file: File }>>([]);
230
+ const [sending, setSending] = useState(false);
231
+ const [sendError, setSendError] = useState<string | null>(null);
232
+ const [dragActive, setDragActive] = useState(false);
233
+ const [searchOpen, setSearchOpen] = useState(false);
234
+ const [searchQuery, setSearchQuery] = useState("");
235
+ const [showScrollToBottom, setShowScrollToBottom] = useState(false);
236
+ const [snapshotReady, setSnapshotReady] = useState(false);
237
+ const [snapshotAnsi, setSnapshotAnsi] = useState("");
238
+
239
+ const normalizedSessionStatus = useMemo(
240
+ () => {
241
+ const candidate = typeof sessionStatus === "string" && sessionStatus.trim().length > 0
242
+ ? sessionStatus
243
+ : sessionState;
244
+ return candidate.trim().toLowerCase();
245
+ },
246
+ [sessionState, sessionStatus],
247
+ );
248
+ latestStatusRef.current = normalizedSessionStatus;
249
+ activeRef.current = active;
250
+
251
+ const expectsLiveTerminal = LIVE_TERMINAL_STATUSES.has(normalizedSessionStatus);
252
+ const showResumeRail = RESUMABLE_STATUSES.has(normalizedSessionStatus) && !expectsLiveTerminal;
253
+ const railPlaceholder = normalizedSessionStatus === "done"
254
+ ? "Continue the session..."
255
+ : normalizedSessionStatus === "needs_input" || normalizedSessionStatus === "stuck"
256
+ ? "Answer the agent and resume..."
257
+ : "Restart this session with a follow-up...";
258
+
259
+ const normalizeWhitespaceOnlyDraft = useCallback(() => {
260
+ setMessage((current) => (current.trim().length === 0 ? "" : current));
261
+ }, []);
262
+
263
+ const clearReconnectTimer = useCallback(() => {
264
+ if (reconnectTimerRef.current !== null) {
265
+ window.clearTimeout(reconnectTimerRef.current);
266
+ reconnectTimerRef.current = null;
267
+ }
268
+ }, []);
269
+
270
+ const scheduleReconnect = useCallback(() => {
271
+ clearReconnectTimer();
272
+ reconnectCountRef.current += 1;
273
+ const delay = Math.min(
274
+ RECONNECT_MAX_DELAY_MS,
275
+ RECONNECT_BASE_DELAY_MS * reconnectCountRef.current,
276
+ );
277
+ reconnectTimerRef.current = window.setTimeout(() => {
278
+ setReconnectToken((value) => value + 1);
279
+ }, delay);
280
+ }, [clearReconnectTimer]);
281
+
282
+ const requestReconnect = useCallback(() => {
283
+ clearReconnectTimer();
284
+ setTransportError(null);
285
+ setConnectionState("connecting");
286
+ setSocketBaseUrl(null);
287
+ setReconnectToken((value) => value + 1);
288
+ }, [clearReconnectTimer]);
289
+
290
+ const sendResize = useCallback(() => {
291
+ const term = termRef.current;
292
+ const socket = socketRef.current;
293
+ if (!term || !socket || socket.readyState !== WebSocket.OPEN) return;
294
+ socket.send(JSON.stringify({
295
+ type: "resize",
296
+ cols: Math.max(1, term.cols),
297
+ rows: Math.max(1, term.rows),
298
+ }));
299
+ }, []);
300
+
301
+ const sendTerminalKeys = useCallback((data: string) => {
302
+ const socket = socketRef.current;
303
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
304
+ throw new Error("Terminal is not connected");
305
+ }
306
+ socket.send(JSON.stringify({ type: "keys", keys: data }));
307
+ }, []);
308
+
309
+ const updateScrollState = useCallback(() => {
310
+ const term = termRef.current;
311
+ if (!term) {
312
+ setShowScrollToBottom(false);
313
+ return;
314
+ }
315
+ const buffer = term.buffer.active;
316
+ setShowScrollToBottom(buffer.viewportY < buffer.baseY);
317
+ }, []);
318
+
319
+ const clearScheduledRecovery = useCallback(() => {
320
+ if (recoveryFrameRef.current !== null) {
321
+ window.cancelAnimationFrame(recoveryFrameRef.current);
322
+ recoveryFrameRef.current = null;
323
+ }
324
+ if (recoveryTimerRef.current !== null) {
325
+ window.clearTimeout(recoveryTimerRef.current);
326
+ recoveryTimerRef.current = null;
327
+ }
328
+ recoveryPendingResizeRef.current = false;
329
+ }, []);
330
+
331
+ const clearVisibilityRecoveryTimers = useCallback(() => {
332
+ for (const timer of visibilityRecoveryTimersRef.current) {
333
+ window.clearTimeout(timer);
334
+ }
335
+ visibilityRecoveryTimersRef.current = [];
336
+ }, []);
337
+
338
+ const runRendererRecovery = useCallback((forceResize: boolean) => {
339
+ const term = termRef.current;
340
+ const fit = fitRef.current;
341
+ const container = containerRef.current;
342
+ if (!term || !fit || !container) {
343
+ return;
344
+ }
345
+
346
+ const style = window.getComputedStyle(container);
347
+ if (style.display === "none" || style.visibility === "hidden") {
348
+ return;
349
+ }
350
+
351
+ const rect = container.getBoundingClientRect();
352
+ if (rect.width <= 1 || rect.height <= 1) {
353
+ return;
354
+ }
355
+
356
+ const previousCols = term.cols;
357
+ const previousRows = term.rows;
358
+ const wasAtBottom = term.buffer.active.viewportY >= term.buffer.active.baseY;
359
+
360
+ try {
361
+ fit.fit();
362
+ } catch {
363
+ return;
364
+ }
365
+
366
+ if (forceResize) {
367
+ term.refresh(0, Math.max(0, term.rows - 1));
368
+ }
369
+
370
+ if (forceResize || term.cols !== previousCols || term.rows !== previousRows) {
371
+ sendResize();
372
+ }
373
+
374
+ if (wasAtBottom) {
375
+ term.scrollToBottom();
376
+ }
377
+
378
+ updateScrollState();
379
+ if (activeRef.current) {
380
+ term.focus();
381
+ }
382
+ }, [sendResize, updateScrollState]);
383
+
384
+ const scheduleRendererRecovery = useCallback((forceResize: boolean) => {
385
+ recoveryPendingResizeRef.current ||= forceResize;
386
+ if (recoveryFrameRef.current !== null) {
387
+ return;
388
+ }
389
+
390
+ recoveryFrameRef.current = window.requestAnimationFrame(() => {
391
+ recoveryFrameRef.current = null;
392
+
393
+ const now = Date.now();
394
+ if (now - recoveryLastRunRef.current < RENDERER_RECOVERY_THROTTLE_MS) {
395
+ const remaining = RENDERER_RECOVERY_THROTTLE_MS - (now - recoveryLastRunRef.current);
396
+ if (recoveryTimerRef.current !== null) {
397
+ window.clearTimeout(recoveryTimerRef.current);
398
+ }
399
+ recoveryTimerRef.current = window.setTimeout(() => {
400
+ recoveryTimerRef.current = null;
401
+ scheduleRendererRecovery(recoveryPendingResizeRef.current);
402
+ }, remaining + 1);
403
+ return;
404
+ }
405
+
406
+ recoveryLastRunRef.current = now;
407
+ const shouldForceResize = recoveryPendingResizeRef.current;
408
+ recoveryPendingResizeRef.current = false;
409
+ runRendererRecovery(shouldForceResize);
410
+ });
411
+ }, [runRendererRecovery]);
412
+
413
+ const queueResumeAttachments = useCallback((files: File[]) => {
414
+ if (!files.length) return;
415
+ setAttachments((current) => [
416
+ ...current,
417
+ ...files.map((file) => ({ file })),
418
+ ]);
419
+ }, []);
420
+
421
+ const injectFilesIntoTerminal = useCallback(async (files: File[]) => {
422
+ const uploadedPaths = await uploadAttachments(files);
423
+ if (!uploadedPaths.length) return;
424
+ const escaped = shellEscapePaths(uploadedPaths);
425
+ sendTerminalKeys(escaped);
426
+ }, [sendTerminalKeys]);
427
+
428
+ const handleIncomingFiles = useCallback(async (files: File[]) => {
429
+ if (!files.length) return;
430
+ setSendError(null);
431
+ try {
432
+ if (expectsLiveTerminal && connectionState === "live") {
433
+ await injectFilesIntoTerminal(files);
434
+ return;
435
+ }
436
+ queueResumeAttachments(files);
437
+ } catch (err) {
438
+ setSendError(err instanceof Error ? err.message : "Failed to process files");
439
+ }
440
+ }, [connectionState, expectsLiveTerminal, injectFilesIntoTerminal, queueResumeAttachments]);
441
+
442
+ useEffect(() => {
443
+ const wasLiveTerminal = previousLiveTerminalRef.current;
444
+ previousLiveTerminalRef.current = expectsLiveTerminal;
445
+ if (wasLiveTerminal && !expectsLiveTerminal) {
446
+ snapshotAppliedRef.current = null;
447
+ liveOutputStartedRef.current = false;
448
+ }
449
+ }, [expectsLiveTerminal]);
450
+
451
+ useEffect(() => {
452
+ hasConnectedOnceRef.current = false;
453
+ reconnectNoticeWrittenRef.current = false;
454
+ snapshotAppliedRef.current = null;
455
+ liveOutputStartedRef.current = false;
456
+ reconnectCountRef.current = 0;
457
+ connectAttemptRef.current = 0;
458
+ lastAppliedInsertNonceRef.current = 0;
459
+ clearReconnectTimer();
460
+ clearScheduledRecovery();
461
+ socketRef.current?.close();
462
+ socketRef.current = null;
463
+ setSocketBaseUrl(null);
464
+ setConnectionState("connecting");
465
+ setTransportError(null);
466
+ setMessage("");
467
+ setAttachments([]);
468
+ setSending(false);
469
+ setSendError(null);
470
+ setDragActive(false);
471
+ setSearchOpen(false);
472
+ setSearchQuery("");
473
+ setShowScrollToBottom(false);
474
+ setSnapshotReady(false);
475
+ setSnapshotAnsi("");
476
+ termRef.current?.reset();
477
+ updateScrollState();
478
+ }, [clearReconnectTimer, clearScheduledRecovery, sessionId, updateScrollState]);
479
+
480
+ useEffect(() => {
481
+ let mounted = true;
482
+ setSnapshotReady(false);
483
+ setSnapshotAnsi("");
484
+
485
+ if (expectsLiveTerminal) {
486
+ setSnapshotReady(true);
487
+ return () => {
488
+ mounted = false;
489
+ };
490
+ }
491
+
492
+ void (async () => {
493
+ try {
494
+ const snapshot = await fetchTerminalSnapshot(sessionId, READ_ONLY_TERMINAL_SNAPSHOT_LINES);
495
+ if (!mounted) return;
496
+ setSnapshotAnsi(snapshot.snapshot);
497
+ } catch {
498
+ if (!mounted) return;
499
+ setSnapshotAnsi("");
500
+ } finally {
501
+ if (mounted) {
502
+ setSnapshotReady(true);
503
+ }
504
+ }
505
+ })();
506
+
507
+ return () => {
508
+ mounted = false;
509
+ };
510
+ }, [expectsLiveTerminal, sessionId]);
511
+
512
+ useEffect(() => {
513
+ let mounted = true;
514
+ void (async () => {
515
+ try {
516
+ setSocketBaseUrl(null);
517
+ const connection = await fetchTerminalConnection(sessionId);
518
+ if (!mounted) return;
519
+ setSocketBaseUrl(connection.wsUrl);
520
+ setTransportError(null);
521
+ } catch (err) {
522
+ if (!mounted) return;
523
+ setTransportError(err instanceof Error ? err.message : "Failed to resolve terminal connection");
524
+ setConnectionState("error");
525
+ }
526
+ })();
527
+
528
+ return () => {
529
+ mounted = false;
530
+ };
531
+ }, [reconnectToken, sessionId]);
532
+
533
+ useEffect(() => {
534
+ let term: XTerminal | null = null;
535
+ let fit: XFitAddon | null = null;
536
+ let mounted = true;
537
+
538
+ async function init() {
539
+ if (!containerRef.current || !mounted) return;
540
+
541
+ const [xtermMod, fitMod, searchMod] = await Promise.all([
542
+ import("@xterm/xterm"),
543
+ import("@xterm/addon-fit"),
544
+ import("@xterm/addon-search"),
545
+ ]);
546
+
547
+ if (!mounted || !containerRef.current) return;
548
+
549
+ const isLight = document.documentElement.classList.contains("light");
550
+ const viewportOptions = getTerminalViewportOptions(window.innerWidth);
551
+ const terminalOptions: ITerminalOptions & { scrollbar?: { showScrollbar: boolean } } = {
552
+ allowTransparency: false,
553
+ cursorBlink: true,
554
+ cursorStyle: "block",
555
+ disableStdin: false,
556
+ drawBoldTextInBrightColors: true,
557
+ fontFamily: viewportOptions.fontFamily,
558
+ fontSize: viewportOptions.fontSize,
559
+ fastScrollSensitivity: 4,
560
+ lineHeight: viewportOptions.lineHeight,
561
+ scrollSensitivity: 1.1,
562
+ scrollback: LIVE_TERMINAL_SCROLLBACK,
563
+ theme: getSupersetLikeTerminalTheme(isLight),
564
+ scrollbar: {
565
+ showScrollbar: false,
566
+ },
567
+ };
568
+ term = new xtermMod.Terminal(terminalOptions);
569
+ const registerManagedScrollMode = (final: "h" | "l") => {
570
+ term?.parser.registerCsiHandler({ prefix: "?", final }, (params) => {
571
+ const hasManagedMode = params.some((param) => (
572
+ Array.isArray(param)
573
+ ? param.some((value) => MANAGED_SCROLL_PRIVATE_MODES.has(value))
574
+ : MANAGED_SCROLL_PRIVATE_MODES.has(param)
575
+ ));
576
+ if (!hasManagedMode) {
577
+ return false;
578
+ }
579
+ return true;
580
+ });
581
+ };
582
+ const handleManagedWheel = (event: WheelEvent): boolean => {
583
+ const normalizedDelta = event.deltaMode === WheelEvent.DOM_DELTA_LINE
584
+ ? event.deltaY
585
+ : event.deltaY / 14;
586
+ const scrollLines = normalizedDelta === 0
587
+ ? 0
588
+ : normalizedDelta > 0
589
+ ? Math.max(1, Math.round(normalizedDelta))
590
+ : Math.min(-1, Math.round(normalizedDelta));
591
+ if (scrollLines === 0) {
592
+ return false;
593
+ }
594
+ event.preventDefault();
595
+ event.stopPropagation();
596
+ term?.scrollLines(scrollLines);
597
+ updateScrollState();
598
+ return false;
599
+ };
600
+ registerManagedScrollMode("h");
601
+ registerManagedScrollMode("l");
602
+ term.attachCustomWheelEventHandler(handleManagedWheel);
603
+
604
+ fit = new fitMod.FitAddon();
605
+ term.loadAddon(fit);
606
+ const searchAddon = new searchMod.SearchAddon();
607
+ term.loadAddon(searchAddon);
608
+ term.open(containerRef.current);
609
+ fit.fit();
610
+
611
+ termRef.current = term;
612
+ fitRef.current = fit;
613
+ searchRef.current = searchAddon;
614
+ setTerminalReady(true);
615
+ updateScrollState();
616
+
617
+ inputDisposableRef.current = term.onData((data) => {
618
+ const socket = socketRef.current;
619
+ if (!socket || socket.readyState !== WebSocket.OPEN) return;
620
+ socket.send(JSON.stringify({ type: "keys", keys: data }));
621
+ });
622
+ scrollDisposableRef.current = term.onScroll(() => {
623
+ updateScrollState();
624
+ });
625
+
626
+ resizeObserverRef.current = new ResizeObserver(() => {
627
+ if (!activeRef.current || !term) {
628
+ return;
629
+ }
630
+ try {
631
+ const nextViewportOptions = getTerminalViewportOptions(window.innerWidth);
632
+ term.options.fontFamily = nextViewportOptions.fontFamily;
633
+ term.options.fontSize = nextViewportOptions.fontSize;
634
+ term.options.lineHeight = nextViewportOptions.lineHeight;
635
+ } catch {
636
+ return;
637
+ }
638
+ scheduleRendererRecovery(true);
639
+ });
640
+ resizeObserverRef.current.observe(containerRef.current);
641
+ }
642
+
643
+ void init();
644
+
645
+ return () => {
646
+ mounted = false;
647
+ inputDisposableRef.current?.dispose();
648
+ inputDisposableRef.current = null;
649
+ scrollDisposableRef.current?.dispose();
650
+ scrollDisposableRef.current = null;
651
+ resizeObserverRef.current?.disconnect();
652
+ resizeObserverRef.current = null;
653
+ if (term) term.dispose();
654
+ termRef.current = null;
655
+ fitRef.current = null;
656
+ searchRef.current = null;
657
+ setTerminalReady(false);
658
+ };
659
+ }, [scheduleRendererRecovery, sendResize, updateScrollState]);
660
+
661
+ useEffect(() => {
662
+ if (!active) {
663
+ return;
664
+ }
665
+
666
+ clearVisibilityRecoveryTimers();
667
+ const frameHandle = window.requestAnimationFrame(() => {
668
+ scheduleRendererRecovery(true);
669
+ visibilityRecoveryTimersRef.current.push(window.setTimeout(() => {
670
+ scheduleRendererRecovery(true);
671
+ }, 48));
672
+ visibilityRecoveryTimersRef.current.push(window.setTimeout(() => {
673
+ scheduleRendererRecovery(true);
674
+ }, 140));
675
+ });
676
+
677
+ return () => {
678
+ window.cancelAnimationFrame(frameHandle);
679
+ clearVisibilityRecoveryTimers();
680
+ };
681
+ }, [active, clearVisibilityRecoveryTimers, scheduleRendererRecovery]);
682
+
683
+ useEffect(() => {
684
+ if (!terminalReady || !snapshotReady) {
685
+ return;
686
+ }
687
+
688
+ const term = termRef.current;
689
+ if (!term || snapshotAppliedRef.current === sessionId) {
690
+ return;
691
+ }
692
+
693
+ if (expectsLiveTerminal && (liveOutputStartedRef.current || terminalHasRenderedContent(term))) {
694
+ snapshotAppliedRef.current = sessionId;
695
+ updateScrollState();
696
+ return;
697
+ }
698
+
699
+ snapshotAppliedRef.current = sessionId;
700
+ if (snapshotAnsi.length > 0) {
701
+ term.reset();
702
+ term.write(normalizeTerminalSnapshot(snapshotAnsi), () => {
703
+ if (termRef.current !== term) {
704
+ return;
705
+ }
706
+ updateScrollState();
707
+ if (activeRef.current) {
708
+ try {
709
+ term.focus();
710
+ } catch {
711
+ // Terminal may have been disposed while the write callback was queued.
712
+ }
713
+ }
714
+ });
715
+ return;
716
+ }
717
+
718
+ updateScrollState();
719
+ }, [sessionId, snapshotAnsi, snapshotReady, terminalReady, updateScrollState]);
720
+
721
+ useEffect(() => {
722
+ const handleVisibilityChange = () => {
723
+ if (document.hidden) {
724
+ return;
725
+ }
726
+ normalizeWhitespaceOnlyDraft();
727
+ scheduleRendererRecovery(false);
728
+ };
729
+
730
+ const handleWindowFocus = () => {
731
+ normalizeWhitespaceOnlyDraft();
732
+ scheduleRendererRecovery(false);
733
+ };
734
+
735
+ document.addEventListener("visibilitychange", handleVisibilityChange);
736
+ window.addEventListener("focus", handleWindowFocus);
737
+
738
+ return () => {
739
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
740
+ window.removeEventListener("focus", handleWindowFocus);
741
+ };
742
+ }, [normalizeWhitespaceOnlyDraft, scheduleRendererRecovery]);
743
+
744
+ useEffect(() => {
745
+ if (!terminalReady || !socketBaseUrl || !termRef.current || !expectsLiveTerminal) return;
746
+
747
+ const term = termRef.current;
748
+ const socketUrl = buildTerminalSocketUrl(socketBaseUrl, term.cols, term.rows);
749
+ const attemptId = connectAttemptRef.current + 1;
750
+ connectAttemptRef.current = attemptId;
751
+ clearReconnectTimer();
752
+ setConnectionState("connecting");
753
+
754
+ const socket = new WebSocket(socketUrl);
755
+ socket.binaryType = "arraybuffer";
756
+ socketRef.current = socket;
757
+
758
+ socket.onopen = () => {
759
+ if (connectAttemptRef.current !== attemptId) return;
760
+ reconnectCountRef.current = 0;
761
+ setTransportError(null);
762
+ setConnectionState("live");
763
+ const wasReconnect = hasConnectedOnceRef.current;
764
+ hasConnectedOnceRef.current = true;
765
+ reconnectNoticeWrittenRef.current = false;
766
+ if (wasReconnect) {
767
+ term.writeln("\r\n\x1b[90m[Reconnected]\x1b[0m");
768
+ }
769
+ updateScrollState();
770
+ scheduleRendererRecovery(true);
771
+ };
772
+
773
+ socket.onmessage = (event) => {
774
+ if (connectAttemptRef.current !== attemptId) return;
775
+
776
+ if (typeof event.data === "string") {
777
+ try {
778
+ const payload = JSON.parse(event.data) as TerminalServerEvent;
779
+ if (payload.type === "error") {
780
+ setTransportError(payload.error);
781
+ setConnectionState("error");
782
+ } else if (payload.type === "exit") {
783
+ setConnectionState("closed");
784
+ }
785
+ } catch {
786
+ setTransportError("Received an invalid terminal event");
787
+ setConnectionState("error");
788
+ }
789
+ return;
790
+ }
791
+
792
+ if (event.data instanceof ArrayBuffer) {
793
+ liveOutputStartedRef.current = true;
794
+ const shouldFollow = term.buffer.active.viewportY >= term.buffer.active.baseY;
795
+ term.write(new Uint8Array(event.data), () => {
796
+ if (termRef.current !== term) {
797
+ return;
798
+ }
799
+ if (shouldFollow) {
800
+ try {
801
+ term.scrollToBottom();
802
+ } catch {
803
+ return;
804
+ }
805
+ }
806
+ updateScrollState();
807
+ });
808
+ }
809
+ };
810
+
811
+ socket.onclose = () => {
812
+ if (connectAttemptRef.current !== attemptId) return;
813
+ socketRef.current = null;
814
+ const shouldRetry = LIVE_TERMINAL_STATUSES.has(latestStatusRef.current);
815
+ if (shouldRetry) {
816
+ const currentTerm = termRef.current;
817
+ if (currentTerm && hasConnectedOnceRef.current && !reconnectNoticeWrittenRef.current) {
818
+ reconnectNoticeWrittenRef.current = true;
819
+ currentTerm.writeln("\r\n\x1b[90m[Connection lost. Reconnecting...]\x1b[0m");
820
+ }
821
+ setConnectionState("connecting");
822
+ setSocketBaseUrl(null);
823
+ scheduleReconnect();
824
+ return;
825
+ }
826
+ setConnectionState("closed");
827
+ };
828
+
829
+ socket.onerror = () => {
830
+ if (connectAttemptRef.current !== attemptId) return;
831
+ setTransportError("Terminal connection failed");
832
+ setConnectionState("error");
833
+ };
834
+
835
+ return () => {
836
+ if (socketRef.current === socket) {
837
+ socketRef.current = null;
838
+ }
839
+ socket.close();
840
+ };
841
+ }, [clearReconnectTimer, expectsLiveTerminal, reconnectToken, scheduleReconnect, scheduleRendererRecovery, socketBaseUrl, terminalReady, updateScrollState]);
842
+
843
+ useEffect(() => {
844
+ if (!terminalReady || !snapshotReady || !expectsLiveTerminal) {
845
+ return;
846
+ }
847
+
848
+ const socket = socketRef.current;
849
+ if (socket && (socket.readyState === WebSocket.CONNECTING || socket.readyState === WebSocket.OPEN)) {
850
+ return;
851
+ }
852
+
853
+ if (connectionState !== "closed" && connectionState !== "error") {
854
+ return;
855
+ }
856
+
857
+ if (reconnectTimerRef.current !== null) {
858
+ return;
859
+ }
860
+
861
+ scheduleReconnect();
862
+ }, [connectionState, expectsLiveTerminal, scheduleReconnect, snapshotReady, terminalReady]);
863
+
864
+ useEffect(() => () => {
865
+ clearReconnectTimer();
866
+ clearScheduledRecovery();
867
+ clearVisibilityRecoveryTimers();
868
+ socketRef.current?.close();
869
+ }, [clearReconnectTimer, clearScheduledRecovery, clearVisibilityRecoveryTimers]);
870
+
871
+ useEffect(() => {
872
+ const container = containerRef.current;
873
+ if (!container) return;
874
+
875
+ const handlePaste = (event: ClipboardEvent) => {
876
+ const clipboard = event.clipboardData;
877
+ if (!clipboard) return;
878
+ const files = Array.from(clipboard.files ?? []);
879
+ const hasText = (clipboard.getData("text/plain") ?? "").length > 0;
880
+ if (!files.length || hasText) {
881
+ return;
882
+ }
883
+
884
+ event.preventDefault();
885
+ void handleIncomingFiles(files);
886
+ };
887
+
888
+ container.addEventListener("paste", handlePaste, { capture: true });
889
+ return () => {
890
+ container.removeEventListener("paste", handlePaste, { capture: true });
891
+ };
892
+ }, [handleIncomingFiles]);
893
+
894
+ const handleSend = useCallback(async () => {
895
+ const trimmedMessage = message.trim();
896
+ if (!trimmedMessage && attachments.length === 0) return;
897
+
898
+ setSending(true);
899
+ setSendError(null);
900
+
901
+ try {
902
+ const attachmentPaths = await uploadAttachments(attachments.map((attachment) => attachment.file));
903
+
904
+ const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/send`, {
905
+ method: "POST",
906
+ headers: {
907
+ "Content-Type": "application/json",
908
+ },
909
+ body: JSON.stringify({
910
+ message: trimmedMessage,
911
+ attachments: attachmentPaths,
912
+ model: sessionModel || null,
913
+ reasoningEffort: sessionReasoningEffort || null,
914
+ projectId: projectId || null,
915
+ }),
916
+ });
917
+
918
+ const data = (await response.json().catch(() => null)) as
919
+ | { error?: string; sessionId?: string | null }
920
+ | null;
921
+
922
+ if (!response.ok) {
923
+ throw new Error(data?.error ?? `Failed to send message: ${response.status}`);
924
+ }
925
+
926
+ setMessage("");
927
+ setAttachments([]);
928
+ if (data?.sessionId && data.sessionId !== sessionId) {
929
+ router.push(`/sessions/${encodeURIComponent(data.sessionId)}`);
930
+ return;
931
+ }
932
+ setReconnectToken((value) => value + 1);
933
+ await refresh();
934
+ } catch (err) {
935
+ setSendError(err instanceof Error ? err.message : "Failed to resume session");
936
+ } finally {
937
+ setSending(false);
938
+ }
939
+ }, [attachments, message, projectId, refresh, router, sessionId, sessionModel, sessionReasoningEffort]);
940
+
941
+ useEffect(() => {
942
+ if (!pendingInsert || pendingInsert.nonce <= lastAppliedInsertNonceRef.current) {
943
+ return;
944
+ }
945
+
946
+ lastAppliedInsertNonceRef.current = pendingInsert.nonce;
947
+ setSendError(null);
948
+
949
+ if (expectsLiveTerminal && connectionState === "live") {
950
+ try {
951
+ const inlineText = pendingInsert.inlineText.trim();
952
+ if (inlineText.length > 0) {
953
+ sendTerminalKeys(`${inlineText} `);
954
+ }
955
+ } catch (err) {
956
+ setSendError(err instanceof Error ? err.message : "Failed to insert preview context into terminal");
957
+ }
958
+ return;
959
+ }
960
+
961
+ const draftText = pendingInsert.draftText.trim();
962
+ if (draftText.length === 0) {
963
+ return;
964
+ }
965
+
966
+ setMessage((current) => (current.trim().length > 0 ? `${current}\n\n${draftText}` : draftText));
967
+ }, [connectionState, expectsLiveTerminal, pendingInsert, sendTerminalKeys]);
968
+
969
+ const runSearch = useCallback((direction: "next" | "prev") => {
970
+ const addon = searchRef.current;
971
+ if (!addon || searchQuery.trim().length === 0) {
972
+ return;
973
+ }
974
+ if (direction === "next") {
975
+ addon.findNext(searchQuery, { incremental: true, caseSensitive: false });
976
+ } else {
977
+ addon.findPrevious(searchQuery, { incremental: true, caseSensitive: false });
978
+ }
979
+ }, [searchQuery]);
980
+
981
+ const scrollToBottom = useCallback(() => {
982
+ const term = termRef.current;
983
+ if (!term) {
984
+ return;
985
+ }
986
+ term.scrollToBottom();
987
+ updateScrollState();
988
+ if (activeRef.current) {
989
+ term.focus();
990
+ }
991
+ }, [updateScrollState]);
992
+
993
+ const closeSearch = useCallback(() => {
994
+ setSearchOpen(false);
995
+ setSearchQuery("");
996
+ if (activeRef.current) {
997
+ termRef.current?.focus();
998
+ }
999
+ }, []);
1000
+
1001
+ return (
1002
+ <div
1003
+ 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)]"
1004
+ onDragOver={(event) => {
1005
+ event.preventDefault();
1006
+ setDragActive(true);
1007
+ }}
1008
+ onDragLeave={(event) => {
1009
+ if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;
1010
+ setDragActive(false);
1011
+ }}
1012
+ onDrop={(event) => {
1013
+ event.preventDefault();
1014
+ setDragActive(false);
1015
+ const files = Array.from(event.dataTransfer.files ?? []);
1016
+ const plainText = event.dataTransfer.getData("text/plain").trim();
1017
+ if (files.length > 0) {
1018
+ void handleIncomingFiles(files);
1019
+ return;
1020
+ }
1021
+ if (!plainText) return;
1022
+ try {
1023
+ if (expectsLiveTerminal && connectionState === "live") {
1024
+ const payload = plainText.startsWith("/") ? shellEscapePath(plainText) : plainText;
1025
+ sendTerminalKeys(payload);
1026
+ return;
1027
+ }
1028
+ setMessage((current) => current.length > 0 ? `${current}\n${plainText}` : plainText);
1029
+ } catch (err) {
1030
+ setSendError(err instanceof Error ? err.message : "Failed to write drop payload");
1031
+ }
1032
+ }}
1033
+ >
1034
+ {searchOpen ? (
1035
+ <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)]">
1036
+ <Search className="h-3.5 w-3.5 text-[#8e847d]" />
1037
+ <input
1038
+ value={searchQuery}
1039
+ onChange={(event) => setSearchQuery(event.target.value)}
1040
+ onKeyDown={(event) => {
1041
+ if (event.key === "Enter") {
1042
+ event.preventDefault();
1043
+ runSearch(event.shiftKey ? "prev" : "next");
1044
+ } else if (event.key === "Escape") {
1045
+ event.preventDefault();
1046
+ closeSearch();
1047
+ }
1048
+ }}
1049
+ placeholder="Find"
1050
+ 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]"
1051
+ />
1052
+ <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={() => runSearch("prev")} aria-label="Find previous">
1053
+ <span className="text-[11px]">↑</span>
1054
+ </Button>
1055
+ <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={() => runSearch("next")} aria-label="Find next">
1056
+ <span className="text-[11px]">↓</span>
1057
+ </Button>
1058
+ <Button type="button" size="icon" variant="ghost" className="h-6 w-6 text-[#c9c0b7]" onClick={closeSearch} aria-label="Close search">
1059
+ <X className="h-3.5 w-3.5" />
1060
+ </Button>
1061
+ </div>
1062
+ ) : (
1063
+ <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 ${
1064
+ connectionState === "live" ? "opacity-0 group-hover/terminal:opacity-100 focus-within:opacity-100" : "opacity-100"
1065
+ }`}>
1066
+ {connectionState !== "live" ? (
1067
+ <Button
1068
+ type="button"
1069
+ size="icon"
1070
+ variant="ghost"
1071
+ className={`pointer-events-auto h-7 w-7 rounded-full border backdrop-blur-sm sm:h-8 sm:w-8 ${
1072
+ transportError
1073
+ ? "border-[#ff8f7a]/25 bg-[#2a1616]/92 text-[#ff8f7a] hover:bg-[#351b1b]"
1074
+ : "border-white/10 bg-[#141010]/92 text-[#c9c0b7] hover:bg-[#201818]"
1075
+ }`}
1076
+ onClick={requestReconnect}
1077
+ aria-label="Reconnect"
1078
+ >
1079
+ {connectionState === "connecting"
1080
+ ? <Loader2 className="h-3.5 w-3.5 animate-spin" />
1081
+ : transportError
1082
+ ? <AlertCircle className="h-3.5 w-3.5" />
1083
+ : <RefreshCw className="h-3.5 w-3.5" />}
1084
+ </Button>
1085
+ ) : null}
1086
+ <Button
1087
+ type="button"
1088
+ size="icon"
1089
+ variant="ghost"
1090
+ 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"
1091
+ onClick={() => setSearchOpen(true)}
1092
+ aria-label="Search terminal"
1093
+ >
1094
+ <Search className="h-3.5 w-3.5" />
1095
+ </Button>
1096
+ </div>
1097
+ )}
1098
+
1099
+ <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">
1100
+ <div ref={containerRef} className="h-full w-full overflow-hidden" />
1101
+ </div>
1102
+
1103
+ {showScrollToBottom ? (
1104
+ <div className={`pointer-events-none absolute left-1/2 z-10 -translate-x-1/2 ${showResumeRail ? "bottom-24" : "bottom-4"}`}>
1105
+ <Button
1106
+ type="button"
1107
+ size="sm"
1108
+ variant="ghost"
1109
+ 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]"
1110
+ onClick={scrollToBottom}
1111
+ aria-label="Scroll to bottom"
1112
+ >
1113
+ <ChevronDown className="h-4 w-4" />
1114
+ <span className="ml-1 text-[11px] uppercase tracking-[0.16em]">Jump to latest</span>
1115
+ </Button>
1116
+ </div>
1117
+ ) : null}
1118
+
1119
+ {dragActive ? (
1120
+ <div className="pointer-events-none absolute inset-4 z-10 flex items-center justify-center rounded-[18px] border border-dashed border-white/20 bg-black/55">
1121
+ <span className="rounded-full border border-white/10 bg-white/6 px-4 py-2 text-[12px] text-[#efe8e1]">
1122
+ {expectsLiveTerminal
1123
+ ? "Drop files or screenshots to insert uploaded paths into the terminal"
1124
+ : "Drop files or screenshots to attach them before resuming"}
1125
+ </span>
1126
+ </div>
1127
+ ) : null}
1128
+
1129
+ {showResumeRail ? (
1130
+ <div className="border-t border-white/8 bg-[#0b0808]/98 px-3 py-3">
1131
+ {attachments.length > 0 ? (
1132
+ <div className="mb-2 flex flex-wrap gap-2">
1133
+ {attachments.map(({ file }) => (
1134
+ <button
1135
+ key={`${file.name}-${file.lastModified}`}
1136
+ type="button"
1137
+ className="inline-flex items-center gap-1 rounded-full border border-white/12 bg-white/6 px-2.5 py-1 text-[11px] text-[#d7cec7]"
1138
+ onClick={() => {
1139
+ setAttachments((current) => current.filter((attachment) => attachment.file !== file));
1140
+ }}
1141
+ >
1142
+ <Paperclip className="h-3 w-3" />
1143
+ {file.name}
1144
+ </button>
1145
+ ))}
1146
+ </div>
1147
+ ) : null}
1148
+
1149
+ <div className="flex items-end gap-2">
1150
+ <textarea
1151
+ ref={resumeTextareaRef}
1152
+ value={message}
1153
+ onChange={(event) => setMessage(event.target.value)}
1154
+ onFocus={() => {
1155
+ normalizeWhitespaceOnlyDraft();
1156
+ }}
1157
+ onKeyDown={(event) => {
1158
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1159
+ event.preventDefault();
1160
+ void handleSend();
1161
+ }
1162
+ }}
1163
+ placeholder={railPlaceholder}
1164
+ className="min-h-[52px] flex-1 resize-none rounded-[14px] border border-white/10 bg-black/35 px-3 py-2 text-[13px] text-[#efe8e1] outline-none placeholder:text-[#7d746e] focus:border-white/20"
1165
+ />
1166
+ <input
1167
+ ref={fileInputRef}
1168
+ type="file"
1169
+ className="hidden"
1170
+ multiple
1171
+ onChange={(event) => {
1172
+ const files = Array.from(event.target.files ?? []);
1173
+ if (files.length > 0) {
1174
+ queueResumeAttachments(files);
1175
+ }
1176
+ event.target.value = "";
1177
+ }}
1178
+ />
1179
+ <button
1180
+ type="button"
1181
+ className="rounded-full border border-white/12 bg-white/6 p-2 text-[#d7cec7] transition hover:bg-white/10"
1182
+ onClick={() => fileInputRef.current?.click()}
1183
+ aria-label="Attach files"
1184
+ >
1185
+ <Paperclip className="h-4 w-4" />
1186
+ </button>
1187
+ <button
1188
+ type="button"
1189
+ className="rounded-full border border-[#f3f0ea]/12 bg-[#f3f0ea] px-4 py-2 text-[12px] font-medium text-[#0d0909] transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-50"
1190
+ disabled={sending || (!message.trim() && attachments.length === 0)}
1191
+ onClick={() => {
1192
+ void handleSend();
1193
+ }}
1194
+ >
1195
+ {sending ? "Starting..." : "Resume"}
1196
+ </button>
1197
+ </div>
1198
+
1199
+ {sendError ? (
1200
+ <p className="mt-2 text-[12px] text-[#ff8f7a]">{sendError}</p>
1201
+ ) : null}
1202
+ </div>
1203
+ ) : sendError ? (
1204
+ <div className="absolute bottom-3 left-3 rounded-full border border-[#ff8f7a]/30 bg-[#1d1111]/90 px-3 py-1.5 text-[12px] text-[#ff8f7a] backdrop-blur-sm">
1205
+ {sendError}
1206
+ </div>
1207
+ ) : null}
1208
+ </div>
1209
+ );
1210
+ }
1211
+
1212
+ export default SessionTerminal;