vibepulse 0.2.2 → 0.3.1-beta

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 (424) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/fallback-build-manifest.json +2 -2
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +8 -0
  11. package/.next/server/app/_global-error/page.js +1 -1
  12. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  13. package/.next/server/app/_global-error.html +2 -2
  14. package/.next/server/app/_global-error.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/_not-found/page.js +1 -1
  21. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/_not-found.html +1 -1
  24. package/.next/server/app/_not-found.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/api/node/events/route.js +2 -2
  32. package/.next/server/app/api/node/events/route.js.nft.json +1 -1
  33. package/.next/server/app/api/node/sessions/[id]/delete/route.js +2 -2
  34. package/.next/server/app/api/node/sessions/[id]/delete/route.js.nft.json +1 -1
  35. package/.next/server/app/api/node/sessions/[id]/open-editor/route.js +2 -2
  36. package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  37. package/.next/server/app/api/node/sessions/route.js +6 -4
  38. package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  39. package/.next/server/app/api/nodes/route.js +2 -2
  40. package/.next/server/app/api/nodes/route.js.nft.json +1 -1
  41. package/.next/server/app/api/opencode-config/route.js +2 -2
  42. package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  43. package/.next/server/app/api/opencode-config/status/route.js +2 -2
  44. package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  45. package/.next/server/app/api/opencode-events/route.js +3 -3
  46. package/.next/server/app/api/opencode-events/route.js.nft.json +1 -1
  47. package/.next/server/app/api/profiles/[id]/apply/route.js +2 -2
  48. package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  49. package/.next/server/app/api/profiles/[id]/export/route.js +2 -2
  50. package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  51. package/.next/server/app/api/profiles/[id]/route.js +2 -2
  52. package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  53. package/.next/server/app/api/profiles/import/route.js +2 -2
  54. package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  55. package/.next/server/app/api/profiles/route.js +2 -2
  56. package/.next/server/app/api/profiles/route.js.nft.json +1 -1
  57. package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  58. package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  59. package/.next/server/app/api/sessions/[id]/delete/route.js +4 -3
  60. package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  61. package/.next/server/app/api/sessions/[id]/open-editor/route.js +2 -2
  62. package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  63. package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  64. package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  65. package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  66. package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  67. package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  68. package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  69. package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  70. package/.next/server/app/api/sessions/[id]/route.js +2 -2
  71. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  72. package/.next/server/app/api/sessions/route.js +5 -3
  73. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  74. package/.next/server/app/index.html +1 -1
  75. package/.next/server/app/index.rsc +3 -3
  76. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  77. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  78. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  80. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app-paths-manifest.json +1 -0
  83. package/.next/server/chunks/[root-of-the-server]__005eb0c5._.js +3 -0
  84. package/.next/server/chunks/[root-of-the-server]__005eb0c5._.js.map +1 -0
  85. package/.next/server/chunks/[root-of-the-server]__09e90d57._.js +3 -0
  86. package/.next/server/chunks/[root-of-the-server]__09e90d57._.js.map +1 -0
  87. package/.next/server/chunks/[root-of-the-server]__18dd0ce9._.js +3 -0
  88. package/.next/server/chunks/[root-of-the-server]__18dd0ce9._.js.map +1 -0
  89. package/.next/server/chunks/[root-of-the-server]__19468536._.js +3 -0
  90. package/.next/server/chunks/[root-of-the-server]__19468536._.js.map +1 -0
  91. package/.next/server/chunks/[root-of-the-server]__1b87ec42._.js +1 -1
  92. package/.next/server/chunks/[root-of-the-server]__1b87ec42._.js.map +1 -1
  93. package/.next/server/chunks/[root-of-the-server]__2b912935._.js +3 -0
  94. package/.next/server/chunks/[root-of-the-server]__2b912935._.js.map +1 -0
  95. package/.next/server/chunks/[root-of-the-server]__303d3bac._.js +3 -0
  96. package/.next/server/chunks/[root-of-the-server]__303d3bac._.js.map +1 -0
  97. package/.next/server/chunks/[root-of-the-server]__3fac2b91._.js +5 -0
  98. package/.next/server/chunks/[root-of-the-server]__3fac2b91._.js.map +1 -0
  99. package/.next/server/chunks/[root-of-the-server]__43440b8d._.js +3 -0
  100. package/.next/server/chunks/[root-of-the-server]__43440b8d._.js.map +1 -0
  101. package/.next/server/chunks/[root-of-the-server]__438f8bbe._.js +3 -0
  102. package/.next/server/chunks/[root-of-the-server]__438f8bbe._.js.map +1 -0
  103. package/.next/server/chunks/[root-of-the-server]__4a0bfb55._.js +3 -0
  104. package/.next/server/chunks/[root-of-the-server]__4a0bfb55._.js.map +1 -0
  105. package/.next/server/chunks/[root-of-the-server]__534c3949._.js +3 -0
  106. package/.next/server/chunks/[root-of-the-server]__534c3949._.js.map +1 -0
  107. package/.next/server/chunks/[root-of-the-server]__59160266._.js +1 -1
  108. package/.next/server/chunks/[root-of-the-server]__59160266._.js.map +1 -1
  109. package/.next/server/chunks/[root-of-the-server]__6f812da0._.js +3 -0
  110. package/.next/server/chunks/[root-of-the-server]__6f812da0._.js.map +1 -0
  111. package/.next/server/chunks/[root-of-the-server]__71aac504._.js +3 -0
  112. package/.next/server/chunks/[root-of-the-server]__71aac504._.js.map +1 -0
  113. package/.next/server/chunks/[root-of-the-server]__907a8bf2._.js +3 -0
  114. package/.next/server/chunks/[root-of-the-server]__907a8bf2._.js.map +1 -0
  115. package/.next/server/chunks/[root-of-the-server]__92089220._.js +3 -0
  116. package/.next/server/chunks/[root-of-the-server]__92089220._.js.map +1 -0
  117. package/.next/server/chunks/[root-of-the-server]__9b7bc2d0._.js +3 -0
  118. package/.next/server/chunks/[root-of-the-server]__9b7bc2d0._.js.map +1 -0
  119. package/.next/server/chunks/[root-of-the-server]__b2640944._.js +3 -0
  120. package/.next/server/chunks/[root-of-the-server]__b2640944._.js.map +1 -0
  121. package/.next/server/chunks/[root-of-the-server]__c2267cf1._.js +3 -0
  122. package/.next/server/chunks/[root-of-the-server]__c2267cf1._.js.map +1 -0
  123. package/.next/server/chunks/[root-of-the-server]__d7f7e6dd._.js +3 -0
  124. package/.next/server/chunks/{[root-of-the-server]__6924c09d._.js.map → [root-of-the-server]__d7f7e6dd._.js.map} +1 -1
  125. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  126. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
  127. package/.next/server/chunks/[root-of-the-server]__f6d0d488._.js +3 -0
  128. package/.next/server/chunks/{[root-of-the-server]__192ed2f4._.js.map → [root-of-the-server]__f6d0d488._.js.map} +1 -1
  129. package/.next/server/chunks/[root-of-the-server]__fa559e1e._.js +3 -0
  130. package/.next/server/chunks/[root-of-the-server]__fa559e1e._.js.map +1 -0
  131. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  132. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
  133. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7e181e75.js +1 -1
  134. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7e181e75.js.map +1 -1
  135. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  136. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
  137. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  138. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
  139. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_fa835ac3.js +2 -2
  140. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_fa835ac3.js.map +1 -1
  141. package/.next/server/chunks/src_lib_opencodeConfig_ts_8e209941._.js +3 -0
  142. package/.next/server/chunks/src_lib_opencodeConfig_ts_8e209941._.js.map +1 -0
  143. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  144. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
  145. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  146. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
  147. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  148. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
  149. package/.next/server/pages/404.html +1 -1
  150. package/.next/server/pages/500.html +2 -2
  151. package/.next/server/server-reference-manifest.js +1 -1
  152. package/.next/server/server-reference-manifest.json +1 -1
  153. package/.next/standalone/.next/BUILD_ID +1 -1
  154. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  155. package/.next/standalone/.next/build-manifest.json +2 -2
  156. package/.next/standalone/.next/prerender-manifest.json +3 -3
  157. package/.next/standalone/.next/routes-manifest.json +8 -0
  158. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  159. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  161. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  162. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  163. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  164. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  165. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  166. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  167. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  168. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  169. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  170. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  171. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  172. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  173. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  174. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  175. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  176. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  177. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  178. package/.next/standalone/.next/server/app/api/node/events/route.js +2 -2
  179. package/.next/standalone/.next/server/app/api/node/events/route.js.nft.json +1 -1
  180. package/.next/standalone/.next/server/app/api/node/sessions/[id]/delete/route.js +2 -2
  181. package/.next/standalone/.next/server/app/api/node/sessions/[id]/delete/route.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js +2 -2
  183. package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  184. package/.next/standalone/.next/server/app/api/node/sessions/route.js +6 -4
  185. package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  186. package/.next/standalone/.next/server/app/api/nodes/route.js +2 -2
  187. package/.next/standalone/.next/server/app/api/nodes/route.js.nft.json +1 -1
  188. package/.next/standalone/.next/server/app/api/opencode-config/route.js +2 -2
  189. package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  190. package/.next/standalone/.next/server/app/api/opencode-config/status/route.js +2 -2
  191. package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  192. package/.next/standalone/.next/server/app/api/opencode-events/route.js +3 -3
  193. package/.next/standalone/.next/server/app/api/opencode-events/route.js.nft.json +1 -1
  194. package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js +2 -2
  195. package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  196. package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js +2 -2
  197. package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  198. package/.next/standalone/.next/server/app/api/profiles/[id]/route.js +2 -2
  199. package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  200. package/.next/standalone/.next/server/app/api/profiles/import/route.js +2 -2
  201. package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  202. package/.next/standalone/.next/server/app/api/profiles/route.js +2 -2
  203. package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
  204. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  205. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  206. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +4 -3
  207. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  208. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +2 -2
  209. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  210. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  211. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  212. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  213. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  214. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  215. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  216. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  217. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js +2 -2
  218. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  219. package/.next/standalone/.next/server/app/api/sessions/route.js +5 -3
  220. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  221. package/.next/standalone/.next/server/app/index.html +1 -1
  222. package/.next/standalone/.next/server/app/index.rsc +3 -3
  223. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  224. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  225. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  226. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  227. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  228. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  229. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  230. package/.next/standalone/.next/server/chunks/[root-of-the-server]__005eb0c5._.js +3 -0
  231. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09e90d57._.js +3 -0
  232. package/.next/standalone/.next/server/chunks/[root-of-the-server]__18dd0ce9._.js +3 -0
  233. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19468536._.js +3 -0
  234. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b87ec42._.js +1 -1
  235. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2b912935._.js +3 -0
  236. package/.next/standalone/.next/server/chunks/[root-of-the-server]__303d3bac._.js +3 -0
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3fac2b91._.js +5 -0
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__43440b8d._.js +3 -0
  239. package/.next/standalone/.next/server/chunks/[root-of-the-server]__438f8bbe._.js +3 -0
  240. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a0bfb55._.js +3 -0
  241. package/.next/standalone/.next/server/chunks/[root-of-the-server]__534c3949._.js +3 -0
  242. package/.next/standalone/.next/server/chunks/[root-of-the-server]__59160266._.js +1 -1
  243. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6f812da0._.js +3 -0
  244. package/.next/standalone/.next/server/chunks/[root-of-the-server]__71aac504._.js +3 -0
  245. package/.next/standalone/.next/server/chunks/[root-of-the-server]__907a8bf2._.js +3 -0
  246. package/.next/standalone/.next/server/chunks/[root-of-the-server]__92089220._.js +3 -0
  247. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9b7bc2d0._.js +3 -0
  248. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b2640944._.js +3 -0
  249. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c2267cf1._.js +3 -0
  250. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d7f7e6dd._.js +3 -0
  251. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  252. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f6d0d488._.js +3 -0
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fa559e1e._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  255. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7e181e75.js +1 -1
  256. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  257. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  258. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_fa835ac3.js +2 -2
  259. package/.next/standalone/.next/server/chunks/src_lib_opencodeConfig_ts_8e209941._.js +3 -0
  260. package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  261. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  262. package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  263. package/.next/standalone/.next/server/pages/404.html +1 -1
  264. package/.next/standalone/.next/server/pages/500.html +2 -2
  265. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  266. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  267. package/.next/standalone/.next/static/chunks/9e790b67c80f853c.js +13 -0
  268. package/.next/standalone/.next/static/chunks/c3dc8cd80979c971.css +3 -0
  269. package/.next/standalone/AGENTS.md +4 -0
  270. package/.next/standalone/README.md +54 -5
  271. package/.next/standalone/check-hsql.mjs +1 -1
  272. package/.next/standalone/docs/session-status-detection.md +36 -0
  273. package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
  274. package/.next/standalone/eslint.config.mjs +1 -0
  275. package/.next/standalone/package-lock.json +74 -13
  276. package/.next/standalone/package.json +2 -2
  277. package/.next/standalone/src/app/api/node/events/route.ts +3 -3
  278. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
  279. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
  280. package/.next/standalone/src/app/api/node/sessions/[id]/delete/route.ts +6 -5
  281. package/.next/standalone/src/app/api/node/sessions/[id]/open-editor/route.ts +6 -5
  282. package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
  283. package/.next/standalone/src/app/api/node/sessions/route.ts +156 -30
  284. package/.next/standalone/src/app/api/opencode-config/route.test.ts +613 -0
  285. package/.next/standalone/src/app/api/opencode-config/route.ts +336 -185
  286. package/.next/standalone/src/app/api/opencode-events/route.test.ts +77 -1
  287. package/.next/standalone/src/app/api/opencode-events/route.ts +3 -3
  288. package/.next/standalone/src/app/api/opencode-models/route.test.ts +19 -0
  289. package/.next/standalone/src/app/api/opencode-models/route.ts +4 -1
  290. package/.next/standalone/src/app/api/profiles/[id]/apply/route.test.ts +227 -0
  291. package/.next/standalone/src/app/api/profiles/[id]/apply/route.ts +13 -9
  292. package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +126 -0
  293. package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
  294. package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +140 -0
  295. package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +51 -16
  296. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
  297. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
  298. package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
  299. package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
  300. package/.next/standalone/src/app/api/sessions/[id]/route.ts +3 -3
  301. package/.next/standalone/src/app/api/sessions/route.test.ts +1955 -100
  302. package/.next/standalone/src/app/api/sessions/route.ts +361 -986
  303. package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
  304. package/.next/standalone/src/components/KanbanBoard.tsx +106 -19
  305. package/.next/standalone/src/components/ProjectCard.test.tsx +420 -6
  306. package/.next/standalone/src/components/ProjectCard.tsx +238 -86
  307. package/.next/standalone/src/components/SessionCard.test.tsx +259 -8
  308. package/.next/standalone/src/components/SessionCard.tsx +182 -76
  309. package/.next/standalone/src/components/opencode-config/AgentConfigForm.test.tsx +141 -1
  310. package/.next/standalone/src/components/opencode-config/AgentConfigForm.tsx +99 -7
  311. package/.next/standalone/src/components/opencode-config/GeneralSettingsForm.test.tsx +11 -0
  312. package/.next/standalone/src/components/opencode-config/GeneralSettingsForm.tsx +41 -2
  313. package/.next/standalone/src/components/opencode-config/categories/CategoriesManager.tsx +3 -1
  314. package/.next/standalone/src/components/opencode-config/categories/CategoryConfigForm.test.tsx +106 -8
  315. package/.next/standalone/src/components/opencode-config/categories/CategoryConfigForm.tsx +82 -5
  316. package/.next/standalone/src/hooks/useHostSources.test.ts +0 -41
  317. package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
  318. package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
  319. package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
  320. package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
  321. package/.next/standalone/src/lib/fixtures/opencode-config/oh-my-openagent.v4.jsonc +70 -0
  322. package/.next/standalone/src/lib/fixtures/opencode-config/oh-my-openagent.v4.secret-like.jsonc +21 -0
  323. package/.next/standalone/src/lib/fixtures/opencode-config/oh-my-opencode.v3.jsonc +17 -0
  324. package/.next/standalone/src/lib/opencodeConfig.test.ts +430 -3
  325. package/.next/standalone/src/lib/opencodeConfig.ts +157 -4
  326. package/.next/standalone/src/lib/opencodeDiscovery.test.ts +241 -0
  327. package/.next/standalone/src/lib/opencodeDiscovery.ts +164 -9
  328. package/.next/standalone/src/lib/profiles/share.test.ts +92 -0
  329. package/.next/standalone/src/lib/profiles/share.ts +1 -0
  330. package/.next/standalone/src/lib/profiles/storage.test.ts +77 -1
  331. package/.next/standalone/src/lib/profiles/storage.ts +10 -9
  332. package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
  333. package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
  334. package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
  335. package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
  336. package/.next/standalone/src/lib/session-providers/opencodeProvider.test.ts +170 -0
  337. package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +721 -0
  338. package/.next/standalone/src/lib/session-providers/opencodeSdkCompat.ts +92 -0
  339. package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
  340. package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
  341. package/.next/standalone/src/lib/session-providers/types.ts +131 -0
  342. package/.next/standalone/src/lib/transform.test.ts +253 -0
  343. package/.next/standalone/src/lib/transform.ts +96 -37
  344. package/.next/standalone/src/types/index.ts +23 -17
  345. package/.next/standalone/src/types/opencodeConfig.ts +55 -0
  346. package/.next/static/chunks/9e790b67c80f853c.js +13 -0
  347. package/.next/static/chunks/c3dc8cd80979c971.css +3 -0
  348. package/.next/trace +1 -1
  349. package/.next/trace-build +1 -1
  350. package/.next/types/routes.d.ts +2 -1
  351. package/.next/types/validator.ts +9 -0
  352. package/README.md +54 -5
  353. package/package.json +2 -2
  354. package/.next/server/chunks/[root-of-the-server]__1211da38._.js +0 -3
  355. package/.next/server/chunks/[root-of-the-server]__1211da38._.js.map +0 -1
  356. package/.next/server/chunks/[root-of-the-server]__192ed2f4._.js +0 -3
  357. package/.next/server/chunks/[root-of-the-server]__2b526e7a._.js +0 -3
  358. package/.next/server/chunks/[root-of-the-server]__2b526e7a._.js.map +0 -1
  359. package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  360. package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
  361. package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  362. package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
  363. package/.next/server/chunks/[root-of-the-server]__56690af0._.js +0 -3
  364. package/.next/server/chunks/[root-of-the-server]__56690af0._.js.map +0 -1
  365. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +0 -3
  366. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +0 -1
  367. package/.next/server/chunks/[root-of-the-server]__59175de4._.js +0 -3
  368. package/.next/server/chunks/[root-of-the-server]__59175de4._.js.map +0 -1
  369. package/.next/server/chunks/[root-of-the-server]__64fffc02._.js +0 -3
  370. package/.next/server/chunks/[root-of-the-server]__64fffc02._.js.map +0 -1
  371. package/.next/server/chunks/[root-of-the-server]__6924c09d._.js +0 -3
  372. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  373. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
  374. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  375. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
  376. package/.next/server/chunks/[root-of-the-server]__7e757f50._.js +0 -3
  377. package/.next/server/chunks/[root-of-the-server]__7e757f50._.js.map +0 -1
  378. package/.next/server/chunks/[root-of-the-server]__89c5eeab._.js +0 -3
  379. package/.next/server/chunks/[root-of-the-server]__89c5eeab._.js.map +0 -1
  380. package/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js +0 -3
  381. package/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js.map +0 -1
  382. package/.next/server/chunks/[root-of-the-server]__b796d06c._.js +0 -3
  383. package/.next/server/chunks/[root-of-the-server]__b796d06c._.js.map +0 -1
  384. package/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js +0 -3
  385. package/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js.map +0 -1
  386. package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  387. package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
  388. package/.next/server/chunks/[root-of-the-server]__e00a9200._.js +0 -5
  389. package/.next/server/chunks/[root-of-the-server]__e00a9200._.js.map +0 -1
  390. package/.next/server/chunks/[root-of-the-server]__e5df5e5f._.js +0 -3
  391. package/.next/server/chunks/[root-of-the-server]__e5df5e5f._.js.map +0 -1
  392. package/.next/server/chunks/[root-of-the-server]__edbc8d9e._.js +0 -3
  393. package/.next/server/chunks/[root-of-the-server]__edbc8d9e._.js.map +0 -1
  394. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1211da38._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/[root-of-the-server]__192ed2f4._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2b526e7a._.js +0 -3
  397. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  398. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  399. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56690af0._.js +0 -3
  400. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +0 -3
  401. package/.next/standalone/.next/server/chunks/[root-of-the-server]__59175de4._.js +0 -3
  402. package/.next/standalone/.next/server/chunks/[root-of-the-server]__64fffc02._.js +0 -3
  403. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6924c09d._.js +0 -3
  404. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  405. package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  406. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e757f50._.js +0 -3
  407. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c5eeab._.js +0 -3
  408. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js +0 -3
  409. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b796d06c._.js +0 -3
  410. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js +0 -3
  411. package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  412. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e00a9200._.js +0 -5
  413. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e5df5e5f._.js +0 -3
  414. package/.next/standalone/.next/server/chunks/[root-of-the-server]__edbc8d9e._.js +0 -3
  415. package/.next/standalone/.next/static/chunks/65d5354ba0add961.js +0 -13
  416. package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
  417. package/.next/static/chunks/65d5354ba0add961.js +0 -13
  418. package/.next/static/chunks/f42202943f6742e5.css +0 -3
  419. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_buildManifest.js +0 -0
  420. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_clientMiddlewareManifest.json +0 -0
  421. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_ssgManifest.js +0 -0
  422. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_buildManifest.js +0 -0
  423. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_clientMiddlewareManifest.json +0 -0
  424. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → 0WaQ6UjiNBgvh531pJVh0}/_ssgManifest.js +0 -0
@@ -1,152 +1,34 @@
1
- import { createOpencodeClient } from '@opencode-ai/sdk';
2
- import { execSync } from 'child_process';
3
- import path from 'path';
4
- import {
5
- discoverOpencodePortsWithMeta,
6
- discoverOpencodeProcessCwdsWithoutPortWithMeta,
7
- } from '@/lib/opencodeDiscovery';
8
1
  import { readConfig } from '@/lib/opencodeConfig';
2
+ import { claudeCodeLocalSessionProvider } from '@/lib/session-providers/claudeCode';
9
3
  import {
10
- clearSessionForceUnarchived,
11
- markSessionForceUnarchived,
12
- pruneSessionStickyStatusBlocked,
13
- pruneSessionForceUnarchived,
14
- shouldForceSessionUnarchived,
15
- takeSessionStickyStatusBlocked,
16
- } from '@/lib/sessionArchiveOverrides';
17
- import { composeSourceKey, parseSourceKey } from '@/lib/hostIdentity';
4
+ applyStickyBusyStatus,
5
+ applyStickyStatusStabilization,
6
+ getLocalSessionsResult,
7
+ shouldSkipSessionStatusStabilization,
8
+ } from '@/lib/session-providers/localAggregator';
9
+ import { opencodeLocalSessionProvider } from '@/lib/session-providers/opencodeProvider';
10
+ import type {
11
+ ChildEntry,
12
+ EnrichedSession,
13
+ HostAwareFields,
14
+ ProcessHint,
15
+ SessionHostStatus,
16
+ SessionsRouteResult,
17
+ SessionsSuccessPayload,
18
+ SessionSource,
19
+ SourceResultMeta,
20
+ } from '@/lib/session-providers/types';
21
+
22
+ import { parseSourceKey } from '@/lib/hostIdentity';
18
23
  import { createNodeRequestHeaders, NODE_PROTOCOL_VERSION } from '@/lib/nodeProtocol';
24
+ import { composeProviderSourceKey, detectProviderFromRawId, extractProviderRawId } from '@/lib/session-providers/providerIds';
19
25
  import { listNodeRecords, type StoredNodeRecord } from '@/lib/nodeRegistry';
20
26
  import { RUNTIME_ROLE_ENV_VAR } from '@/lib/runtimeMode';
21
- import type { BuiltInHostSource, RemoteHostConfig } from '@/types';
22
-
23
- type SessionLike = {
24
- id: string;
25
- slug?: string;
26
- title?: string;
27
- directory: string;
28
- debugReason?: string;
29
- parentID?: string;
30
- time?: {
31
- created: number;
32
- updated: number;
33
- archived?: number;
34
- };
35
- };
27
+ import type { BuiltInHostSource, RemoteHostConfig, SessionCapabilities, SessionProvider } from '@/types';
36
28
 
37
- const CHILD_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
38
- const CHILD_UNKNOWN_STATE_BUSY_WINDOW_MS = 2 * 60 * 1000;
39
- const CHILD_STATUS_MESSAGE_CHECK_LIMIT = 50;
40
- const STALL_DETECTION_WINDOW_MS = 30 * 1000;
41
- const STATUS_STICKY_RETENTION_MS = 24 * 60 * 60 * 1000;
42
- const STATUS_STICKY_ABSENT_RETENTION_MS = 30 * 60 * 1000;
43
- const DEFAULT_STATUS_STICKY_MAX_ENTRIES = 5000;
44
- const GIT_COMMAND_TIMEOUT_MS = 1200;
45
- const sessionListTimeoutMs = readPositiveTimeoutEnv('OPENCODE_SESSIONS_LIST_TIMEOUT_MS', 6000);
46
- const sessionStatusTimeoutMs = readPositiveTimeoutEnv('OPENCODE_SESSIONS_STATUS_TIMEOUT_MS', 4000);
47
- const sessionMessagesTimeoutMs = readPositiveTimeoutEnv('OPENCODE_SESSIONS_MESSAGES_TIMEOUT_MS', 2500);
48
29
  const nodeSessionsTimeoutMs = readPositiveTimeoutEnv('VIBEPULSE_NODE_SESSIONS_TIMEOUT_MS', 6000);
49
-
50
- type StableRealtimeStatus = 'idle' | 'busy' | 'retry';
51
-
52
- type StatusStickyState = {
53
- lastBusyAt: number;
54
- lastSeenAt: number;
55
- };
56
-
57
- const statusStickyState = new Map<string, StatusStickyState>();
58
-
59
- function clearStickyStatusState(sessionId: string): void {
60
- statusStickyState.delete(sessionId);
61
- statusStickyState.delete(`child:${sessionId}`);
62
- }
63
-
64
- type ChildEntry = HostAwareFields & {
65
- id: string;
66
- slug?: string;
67
- title?: string;
68
- directory?: string;
69
- debugReason?: string;
70
- parentID?: string;
71
- time?: { created: number; updated: number; archived?: number };
72
- realTimeStatus: string;
73
- waitingForUser: boolean;
74
- };
75
-
76
- type EnrichedSession = SessionLike & HostAwareFields & {
77
- projectName: string;
78
- branch: string | null;
79
- realTimeStatus: 'idle' | 'busy' | 'retry';
80
- waitingForUser: boolean;
81
- children: ChildEntry[];
82
- };
83
-
84
- type SessionStatusStabilizationTarget = {
85
- id: string;
86
- time?: {
87
- archived?: number;
88
- };
89
- realTimeStatus: string;
90
- waitingForUser: boolean;
91
- children: Array<{
92
- id: string;
93
- time?: {
94
- archived?: number;
95
- };
96
- realTimeStatus: string;
97
- waitingForUser: boolean;
98
- }>;
99
- };
100
-
101
- type ProcessHint = {
102
- pid: number;
103
- directory: string;
104
- projectName: string;
105
- reason: 'process_without_api_port';
106
- };
107
-
108
- type SessionSource = BuiltInHostSource | (RemoteHostConfig & { hostKind: 'remote' });
109
-
110
- type HostAwareFields = {
111
- hostId?: string;
112
- hostLabel?: string;
113
- hostKind?: SessionSource['hostKind'];
114
- hostBaseUrl?: string;
115
- rawSessionId?: string;
116
- sourceSessionKey?: string;
117
- readOnly?: boolean;
118
- };
119
-
120
- type SessionHostStatus = {
121
- hostId: string;
122
- hostLabel: string;
123
- hostKind: SessionSource['hostKind'];
124
- online: boolean;
125
- degraded?: boolean;
126
- reason?: string;
127
- baseUrl?: string;
128
- };
129
-
130
- type SourceResultMeta = {
131
- online: boolean;
132
- degraded?: boolean;
133
- reason?: string;
134
- };
135
-
136
- type SessionsSuccessPayload = {
137
- sessions: EnrichedSession[];
138
- processHints: ProcessHint[];
139
- failedPorts?: Array<{ port: number; reason: string }>;
140
- degraded?: boolean;
141
- hosts?: SessionHostStatus[];
142
- hostStatuses?: SessionHostStatus[];
143
- };
144
-
145
- type SessionsRouteResult = {
146
- payload: SessionsSuccessPayload | Record<string, unknown>;
147
- status?: number;
148
- sourceMeta?: SourceResultMeta;
149
- };
30
+ const CLAUDE_INFERRED_PARENT_MAX_CREATED_GAP_MS = 60_000;
31
+ const CLAUDE_INFERRED_PARENT_AMBIGUITY_GAP_MS = 5_000;
150
32
 
151
33
  const LOCAL_SOURCE: BuiltInHostSource = {
152
34
  hostId: 'local',
@@ -154,8 +36,16 @@ const LOCAL_SOURCE: BuiltInHostSource = {
154
36
  hostKind: 'local',
155
37
  };
156
38
 
39
+ const LOCAL_POLLING_PROVIDERS = [opencodeLocalSessionProvider, claudeCodeLocalSessionProvider] as const;
40
+
157
41
  export const dynamic = 'force-dynamic';
158
42
 
43
+ export {
44
+ applyStickyBusyStatus,
45
+ applyStickyStatusStabilization,
46
+ shouldSkipSessionStatusStabilization,
47
+ };
48
+
159
49
  export async function GET() {
160
50
  return handleGet();
161
51
  }
@@ -164,14 +54,6 @@ export async function POST(request: Request) {
164
54
  return handlePost(request);
165
55
  }
166
56
 
167
- type MessageStateStatus = string;
168
-
169
- type MessagePart = {
170
- state?: {
171
- status?: unknown;
172
- };
173
- };
174
-
175
57
  function readPositiveTimeoutEnv(name: string, fallback: number): number {
176
58
  const raw = process.env[name];
177
59
  const parsed = Number(raw);
@@ -181,300 +63,6 @@ function readPositiveTimeoutEnv(name: string, fallback: number): number {
181
63
  return fallback;
182
64
  }
183
65
 
184
- function withTimeout<T>(operation: (signal: AbortSignal) => Promise<T>, timeoutMs: number, label: string): Promise<T> {
185
- const timeoutError = new Error(`${label} timed out after ${timeoutMs}ms`);
186
- const timeoutController = new AbortController();
187
- let timeoutHandle: NodeJS.Timeout | undefined;
188
- const timeoutPromise = new Promise<never>((_, reject) => {
189
- timeoutHandle = setTimeout(() => {
190
- timeoutController.abort();
191
- reject(timeoutError);
192
- }, timeoutMs);
193
- });
194
-
195
- const operationPromise = operation(timeoutController.signal).catch((error) => {
196
- if (timeoutController.signal.aborted) {
197
- throw timeoutError;
198
- }
199
-
200
- throw error;
201
- });
202
-
203
- return Promise.race([operationPromise, timeoutPromise]).finally(() => {
204
- if (timeoutHandle) {
205
- clearTimeout(timeoutHandle);
206
- }
207
- });
208
- }
209
-
210
- const WAITING_PART_STATUSES = new Set<string>([
211
- 'awaiting-input',
212
- 'awaiting_input',
213
- 'input-required',
214
- 'input_required',
215
- 'requires-input',
216
- 'requires_input',
217
- 'blocked',
218
- 'paused',
219
- ]);
220
-
221
- function normalizePartStatus(status: string): string {
222
- return status.trim().toLowerCase();
223
- }
224
-
225
- function isWaitingPartStatus(status: string): boolean {
226
- return WAITING_PART_STATUSES.has(normalizePartStatus(status));
227
- }
228
-
229
- function collectPartStatuses(messages: Array<{ parts?: MessagePart[] }>): MessageStateStatus[] {
230
- const partStatuses: MessageStateStatus[] = [];
231
-
232
- for (const message of messages) {
233
- for (const part of message.parts || []) {
234
- const status = part?.state?.status;
235
- if (typeof status === 'string') {
236
- const normalized = normalizePartStatus(status);
237
- if (normalized) {
238
- partStatuses.push(normalized);
239
- }
240
- }
241
- }
242
- }
243
-
244
- return partStatuses;
245
- }
246
-
247
- async function fetchPartStatuses(
248
- client: ReturnType<typeof createOpencodeClient>,
249
- sessionId: string,
250
- timeoutMs: number
251
- ): Promise<MessageStateStatus[]> {
252
- const messagesResult = await withTimeout(
253
- (signal) =>
254
- client.session.messages({
255
- path: { id: sessionId },
256
- query: { limit: 8 },
257
- signal,
258
- }),
259
- timeoutMs,
260
- `session.messages(${sessionId})`
261
- );
262
- const messages = (messagesResult.data || []) as Array<{ parts?: MessagePart[] }>;
263
- return collectPartStatuses(messages);
264
- }
265
-
266
- function getUpdatedAt(session: { time?: { updated?: number; created?: number } }): number {
267
- return session.time?.updated || session.time?.created || 0;
268
- }
269
-
270
- function normalizeRealtimeStatus(value: string | undefined): StableRealtimeStatus {
271
- if (value === 'busy' || value === 'retry') return value;
272
- return 'idle';
273
- }
274
-
275
- export function applyStickyBusyStatus(id: string, status: StableRealtimeStatus, now: number, stickyBusyWindowMs: number): StableRealtimeStatus {
276
- const existing = statusStickyState.get(id) ?? { lastBusyAt: 0, lastSeenAt: now };
277
-
278
- if (status === 'busy') {
279
- existing.lastBusyAt = now;
280
- existing.lastSeenAt = now;
281
- statusStickyState.set(id, existing);
282
- return status;
283
- }
284
-
285
- if (status === 'retry') {
286
- existing.lastSeenAt = now;
287
- statusStickyState.set(id, existing);
288
- return status;
289
- }
290
-
291
- const shouldKeepBusy = existing.lastBusyAt > 0 && now - existing.lastBusyAt <= stickyBusyWindowMs;
292
- existing.lastSeenAt = now;
293
- statusStickyState.set(id, existing);
294
- return shouldKeepBusy ? 'busy' : 'idle';
295
- }
296
-
297
- function getStickyStateMaxEntries(): number {
298
- const raw = Number(process.env.OPENCODE_STATUS_STICKY_MAX_ENTRIES);
299
- if (Number.isFinite(raw) && raw > 0) {
300
- return Math.floor(raw);
301
- }
302
- return DEFAULT_STATUS_STICKY_MAX_ENTRIES;
303
- }
304
-
305
- function pruneStickyState(now: number, activeIds: Set<string>): void {
306
- for (const [id, state] of statusStickyState) {
307
- const ageMs = now - state.lastSeenAt;
308
- const isActive = activeIds.has(id);
309
- if (ageMs > STATUS_STICKY_RETENTION_MS || (!isActive && ageMs > STATUS_STICKY_ABSENT_RETENTION_MS)) {
310
- statusStickyState.delete(id);
311
- }
312
- }
313
-
314
- const maxEntries = getStickyStateMaxEntries();
315
- if (statusStickyState.size <= maxEntries) {
316
- return;
317
- }
318
-
319
- const overflow = statusStickyState.size - maxEntries;
320
- const sortedByLastSeen = Array.from(statusStickyState.entries()).sort((a, b) => a[1].lastSeenAt - b[1].lastSeenAt);
321
-
322
- let removed = 0;
323
- for (const [id] of sortedByLastSeen) {
324
- if (removed >= overflow) break;
325
- if (activeIds.has(id)) continue;
326
- statusStickyState.delete(id);
327
- removed++;
328
- }
329
-
330
- if (removed >= overflow) {
331
- return;
332
- }
333
-
334
- for (const [id] of sortedByLastSeen) {
335
- if (removed >= overflow) break;
336
- if (!statusStickyState.has(id)) continue;
337
- statusStickyState.delete(id);
338
- removed++;
339
- }
340
- }
341
-
342
- function hasRecentActivity(session: { time?: { updated?: number } }, now: number): boolean {
343
- const updatedAt = session.time?.updated;
344
- if (!updatedAt) return false;
345
- return now - updatedAt <= STALL_DETECTION_WINDOW_MS;
346
- }
347
-
348
- function toChildEntry(
349
- child: SessionLike,
350
- status: 'idle' | 'busy' | 'retry',
351
- waitingForUser = false
352
- ): ChildEntry {
353
- return {
354
- id: child.id,
355
- slug: child.slug,
356
- title: child.title,
357
- directory: child.directory,
358
- debugReason: child.debugReason,
359
- parentID: child.parentID,
360
- time: child.time,
361
- realTimeStatus: status,
362
- waitingForUser,
363
- };
364
- }
365
-
366
- function clearSessionStabilizationState(session: SessionStatusStabilizationTarget): void {
367
- clearStickyStatusState(session.id);
368
- clearSessionForceUnarchived(session.id);
369
- for (const child of session.children) {
370
- clearStickyStatusState(`child:${child.id}`);
371
- clearSessionForceUnarchived(child.id);
372
- }
373
- }
374
-
375
- export function shouldSkipSessionStatusStabilization(
376
- session: SessionStatusStabilizationTarget,
377
- now: number
378
- ): boolean {
379
- if (takeSessionStickyStatusBlocked(session.id, now)) {
380
- clearSessionStabilizationState(session);
381
- return true;
382
- }
383
-
384
- if (session.time?.archived) {
385
- clearSessionStabilizationState(session);
386
- return true;
387
- }
388
-
389
- return false;
390
- }
391
-
392
- export function applyStickyStatusStabilization(
393
- session: SessionStatusStabilizationTarget,
394
- stickyNow: number,
395
- stickyBusyDelayMs: number
396
- ): void {
397
- for (const child of session.children) {
398
- if (child.time?.archived) {
399
- clearStickyStatusState(`child:${child.id}`);
400
- clearSessionForceUnarchived(child.id);
401
- continue;
402
- }
403
-
404
- const normalizedChildStatus = normalizeRealtimeStatus(child.realTimeStatus);
405
- const childStatusForStabilization =
406
- child.waitingForUser && normalizedChildStatus === 'idle' ? 'retry' : normalizedChildStatus;
407
- child.realTimeStatus = applyStickyBusyStatus(
408
- `child:${child.id}`,
409
- childStatusForStabilization,
410
- stickyNow,
411
- stickyBusyDelayMs
412
- );
413
-
414
- if (child.realTimeStatus === 'busy' || child.realTimeStatus === 'retry' || child.waitingForUser) {
415
- markSessionForceUnarchived(child.id, stickyNow);
416
- }
417
- }
418
-
419
- const normalizedSessionStatus = normalizeRealtimeStatus(session.realTimeStatus);
420
- const sessionStatusForStabilization =
421
- session.waitingForUser && normalizedSessionStatus === 'idle' ? 'retry' : normalizedSessionStatus;
422
- session.realTimeStatus = applyStickyBusyStatus(
423
- session.id,
424
- sessionStatusForStabilization,
425
- stickyNow,
426
- stickyBusyDelayMs
427
- );
428
-
429
- const hasActiveChildren = session.children.some(
430
- (child) => child.realTimeStatus === 'busy' || child.realTimeStatus === 'retry' || child.waitingForUser
431
- );
432
- const shouldAutoUnarchive =
433
- session.realTimeStatus === 'busy' ||
434
- session.realTimeStatus === 'retry' ||
435
- session.waitingForUser ||
436
- hasActiveChildren;
437
-
438
- if (shouldAutoUnarchive) {
439
- markSessionForceUnarchived(session.id, stickyNow);
440
- }
441
- }
442
- // Get project name from directory path
443
- function getProjectName(directory: string): string {
444
- return path.basename(directory);
445
- }
446
-
447
- // Check if directory is a git repository
448
- function isGitRepo(directory: string): boolean {
449
- try {
450
- const result = execSync('git rev-parse --is-inside-work-tree', {
451
- cwd: directory,
452
- encoding: 'utf-8',
453
- stdio: ['ignore', 'pipe', 'ignore'],
454
- timeout: GIT_COMMAND_TIMEOUT_MS,
455
- });
456
- return result.trim() === 'true';
457
- } catch {
458
- return false;
459
- }
460
- }
461
-
462
- // Get git branch name
463
- function getGitBranch(directory: string): string | null {
464
- if (!isGitRepo(directory)) return null;
465
- try {
466
- const branch = execSync('git branch --show-current', {
467
- cwd: directory,
468
- encoding: 'utf-8',
469
- stdio: ['ignore', 'pipe', 'ignore'],
470
- timeout: GIT_COMMAND_TIMEOUT_MS,
471
- });
472
- return branch.trim() || null;
473
- } catch {
474
- return null;
475
- }
476
- }
477
-
478
66
  async function readStickyBusyDelayMs(): Promise<number> {
479
67
  let stickyBusyDelayMs = 1000; // default 1s
480
68
  try {
@@ -502,6 +90,23 @@ function isRemoteSource(source: SessionSource): source is RemoteHostConfig & { h
502
90
  return source.hostKind === 'remote';
503
91
  }
504
92
 
93
+ function getRemoteClaudeCapabilities(
94
+ capabilities: SessionCapabilities | undefined,
95
+ provider: SessionProvider,
96
+ source: SessionSource
97
+ ): SessionCapabilities | undefined {
98
+ if (!isRemoteSource(source) || provider !== 'claude-code') {
99
+ return capabilities;
100
+ }
101
+
102
+ return {
103
+ openProject: capabilities?.openProject ?? true,
104
+ openEditor: false,
105
+ archive: false,
106
+ delete: false,
107
+ };
108
+ }
109
+
505
110
  function normalizeNodeBaseUrl(baseUrl: string): string | null {
506
111
  const trimmed = baseUrl.trim();
507
112
  if (!trimmed) {
@@ -551,10 +156,26 @@ function isTimeValue(value: unknown): boolean {
551
156
  return (
552
157
  typeof created === 'number' &&
553
158
  typeof updated === 'number' &&
554
- (archived === undefined || typeof archived === 'number')
159
+ (archived === undefined || archived === null || typeof archived === 'number')
555
160
  );
556
161
  }
557
162
 
163
+ function normalizeSessionTimeValue<T extends { created: number; updated: number; archived?: number } | undefined>(time: T): T {
164
+ if (!time) {
165
+ return time;
166
+ }
167
+
168
+ const archived = (time as { archived?: number | null }).archived;
169
+ if (archived === null) {
170
+ return {
171
+ created: time.created,
172
+ updated: time.updated,
173
+ } as T;
174
+ }
175
+
176
+ return time;
177
+ }
178
+
558
179
  function isChildEntryValue(value: unknown): value is ChildEntry {
559
180
  if (!isRecord(value)) {
560
181
  return false;
@@ -784,25 +405,62 @@ function toRawSessionId(value: string): string {
784
405
  }
785
406
  }
786
407
 
787
- function composeSourceKeySafely(hostId: string, sessionId: string): string | undefined {
408
+ function toProviderRawSessionId(value: string): string {
409
+ return extractProviderRawId(toRawSessionId(value));
410
+ }
411
+
412
+ function composeProviderSourceKeySafely(
413
+ hostId: string,
414
+ rawId: string,
415
+ readOnly?: boolean,
416
+ provider?: SessionProvider
417
+ ): string | undefined {
788
418
  try {
789
- return composeSourceKey(hostId, sessionId);
419
+ return composeProviderSourceKey(hostId, rawId, {
420
+ readOnly,
421
+ ...(provider ? { provider } : {}),
422
+ }).sourceKey;
790
423
  } catch {
791
424
  return undefined;
792
425
  }
793
426
  }
794
427
 
795
- function addHostMetadataToChildEntry(child: ChildEntry, source: SessionSource): ChildEntry | null {
796
- const rawSessionId = child.rawSessionId ?? toRawSessionId(child.id);
797
- const rawParentId = child.parentID ? toRawSessionId(child.parentID) : child.parentID;
798
- const sourceSessionKey = composeSourceKeySafely(source.hostId, rawSessionId);
428
+ function addHostMetadataToChildEntry(
429
+ child: ChildEntry,
430
+ source: SessionSource,
431
+ parentSourceSessionKey?: string
432
+ ): ChildEntry | null {
433
+ const rawSessionId = child.rawSessionId ?? toProviderRawSessionId(child.id);
434
+ const rawParentId = child.parentID ? toProviderRawSessionId(child.parentID) : child.parentID;
435
+ const inferredProvider = detectProviderFromRawId(child.id);
436
+ const parentProvider = parentSourceSessionKey ? detectProviderFromRawId(parentSourceSessionKey) : undefined;
437
+ const childProvider = inferredProvider === 'claude-code' ? inferredProvider : (parentProvider ?? inferredProvider);
438
+ const childCapabilities = getRemoteClaudeCapabilities(child.capabilities, childProvider, source);
439
+ const sourceSessionKey = composeProviderSourceKeySafely(source.hostId, rawSessionId, child.readOnly, childProvider);
799
440
  if (!sourceSessionKey) {
800
441
  return null;
801
442
  }
802
443
 
444
+ const parentSourceRawId = parentSourceSessionKey
445
+ ? toProviderRawSessionId(parentSourceSessionKey)
446
+ : undefined;
447
+ const shouldReuseParentSourceKey =
448
+ typeof rawParentId === 'string'
449
+ && typeof parentSourceSessionKey === 'string'
450
+ && parentSourceRawId === rawParentId;
451
+
803
452
  const sourceParentKey = rawParentId
804
- ? (composeSourceKeySafely(source.hostId, rawParentId) ?? undefined)
453
+ ? (
454
+ shouldReuseParentSourceKey
455
+ ? parentSourceSessionKey
456
+ : composeProviderSourceKeySafely(source.hostId, rawParentId, undefined, childProvider) ?? undefined
457
+ )
805
458
  : undefined;
459
+ const normalizedChildProvider = child.provider ?? (childProvider === 'claude-code' ? childProvider : undefined);
460
+ const normalizedChildProviderRawId =
461
+ normalizedChildProvider === 'claude-code'
462
+ ? (child.providerRawId ?? rawSessionId)
463
+ : child.providerRawId;
806
464
 
807
465
  return {
808
466
  ...child,
@@ -812,26 +470,37 @@ function addHostMetadataToChildEntry(child: ChildEntry, source: SessionSource):
812
470
  hostLabel: source.hostLabel,
813
471
  hostKind: source.hostKind,
814
472
  ...(isRemoteSource(source) ? { hostBaseUrl: source.baseUrl } : {}),
473
+ time: normalizeSessionTimeValue(child.time),
815
474
  rawSessionId,
816
475
  sourceSessionKey,
817
- readOnly: false,
476
+ readOnly: child.readOnly ?? false,
477
+ capabilities: childCapabilities,
478
+ ...(normalizedChildProvider ? { provider: normalizedChildProvider } : {}),
479
+ ...(normalizedChildProviderRawId ? { providerRawId: normalizedChildProviderRawId } : {}),
818
480
  };
819
481
  }
820
482
 
821
483
  function addHostMetadataToSession(session: EnrichedSession, source: SessionSource): EnrichedSession | null {
822
- const rawSessionId = session.rawSessionId ?? toRawSessionId(session.id);
823
- const rawParentId = session.parentID ? toRawSessionId(session.parentID) : session.parentID;
824
- const sourceSessionKey = composeSourceKeySafely(source.hostId, rawSessionId);
484
+ const rawSessionId = session.rawSessionId ?? toProviderRawSessionId(session.id);
485
+ const rawParentId = session.parentID ? toProviderRawSessionId(session.parentID) : session.parentID;
486
+ const sessionProvider = detectProviderFromRawId(session.id);
487
+ const sessionCapabilities = getRemoteClaudeCapabilities(session.capabilities, sessionProvider, source);
488
+ const sourceSessionKey = composeProviderSourceKeySafely(source.hostId, rawSessionId, session.readOnly, sessionProvider);
825
489
  if (!sourceSessionKey) {
826
490
  return null;
827
491
  }
828
492
 
829
493
  const sourceParentKey = rawParentId
830
- ? (composeSourceKeySafely(source.hostId, rawParentId) ?? undefined)
494
+ ? (composeProviderSourceKeySafely(source.hostId, rawParentId, undefined, sessionProvider) ?? undefined)
831
495
  : undefined;
496
+ const normalizedSessionProvider = session.provider ?? (sessionProvider === 'claude-code' ? sessionProvider : undefined);
497
+ const normalizedSessionProviderRawId =
498
+ normalizedSessionProvider === 'claude-code'
499
+ ? (session.providerRawId ?? rawSessionId)
500
+ : session.providerRawId;
832
501
  const children: ChildEntry[] = [];
833
502
  for (const child of session.children) {
834
- const enrichedChild = addHostMetadataToChildEntry(child, source);
503
+ const enrichedChild = addHostMetadataToChildEntry(child, source, sourceSessionKey);
835
504
  if (enrichedChild) {
836
505
  children.push(enrichedChild);
837
506
  }
@@ -845,9 +514,13 @@ function addHostMetadataToSession(session: EnrichedSession, source: SessionSourc
845
514
  hostLabel: source.hostLabel,
846
515
  hostKind: source.hostKind,
847
516
  ...(isRemoteSource(source) ? { hostBaseUrl: source.baseUrl } : {}),
517
+ time: normalizeSessionTimeValue(session.time),
848
518
  rawSessionId,
849
519
  sourceSessionKey,
850
- readOnly: false,
520
+ readOnly: session.readOnly ?? false,
521
+ capabilities: sessionCapabilities,
522
+ ...(normalizedSessionProvider ? { provider: normalizedSessionProvider } : {}),
523
+ ...(normalizedSessionProviderRawId ? { providerRawId: normalizedSessionProviderRawId } : {}),
851
524
  children,
852
525
  };
853
526
  }
@@ -873,11 +546,235 @@ function addHostMetadataToPayload(payload: Record<string, unknown>, source: Sess
873
546
 
874
547
  return {
875
548
  ...payload,
876
- sessions,
549
+ sessions: rebuildAggregateClaudeTopology(sessions),
877
550
  ...(payloadDegraded || droppedSessions > 0 ? { degraded: true } : {}),
878
551
  };
879
552
  }
880
553
 
554
+ function toAggregateClaudeChildEntry(session: EnrichedSession): ChildEntry {
555
+ const childEntry = { ...session };
556
+ delete (childEntry as { children?: unknown }).children;
557
+ return childEntry as ChildEntry;
558
+ }
559
+
560
+ function hasClaudeProvider(entry: HostAwareFields): boolean {
561
+ return entry.provider === 'claude-code';
562
+ }
563
+
564
+ function toFiniteTimestamp(value: unknown): number | undefined {
565
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
566
+ }
567
+
568
+ function getCreatedAt(session: EnrichedSession): number | undefined {
569
+ return toFiniteTimestamp(session.time?.created);
570
+ }
571
+
572
+ function getUpdatedAt(session: EnrichedSession): number | undefined {
573
+ return toFiniteTimestamp(session.time?.updated);
574
+ }
575
+
576
+ function hasSameHostIdentity(a: HostAwareFields, b: HostAwareFields): boolean {
577
+ return a.hostId === b.hostId && a.hostKind === b.hostKind;
578
+ }
579
+
580
+ type InferredClaudeParentCandidate = {
581
+ parent: EnrichedSession;
582
+ createdGapMs: number;
583
+ updatedGapMs: number;
584
+ };
585
+
586
+ function inferClaudeParentSession(
587
+ child: EnrichedSession,
588
+ sessions: EnrichedSession[],
589
+ absorbedChildIds: Set<string>
590
+ ): EnrichedSession | undefined {
591
+ if (child.topology?.childSessions === 'authoritative') {
592
+ return undefined;
593
+ }
594
+
595
+ const childCreatedAt = getCreatedAt(child);
596
+ if (childCreatedAt === undefined) {
597
+ return undefined;
598
+ }
599
+
600
+ const childUpdatedAt = getUpdatedAt(child) ?? childCreatedAt;
601
+ const candidates: InferredClaudeParentCandidate[] = [];
602
+
603
+ for (const parent of sessions) {
604
+ if (parent.id === child.id || absorbedChildIds.has(parent.id)) {
605
+ continue;
606
+ }
607
+
608
+ if (!hasClaudeProvider(parent) || !hasSameHostIdentity(parent, child)) {
609
+ continue;
610
+ }
611
+
612
+ if (typeof parent.parentID === 'string') {
613
+ continue;
614
+ }
615
+
616
+ if (parent.directory !== child.directory || parent.projectName !== child.projectName) {
617
+ continue;
618
+ }
619
+
620
+ const parentCreatedAt = getCreatedAt(parent);
621
+ if (parentCreatedAt === undefined || parentCreatedAt > childCreatedAt) {
622
+ continue;
623
+ }
624
+
625
+ const createdGapMs = childCreatedAt - parentCreatedAt;
626
+ if (createdGapMs > CLAUDE_INFERRED_PARENT_MAX_CREATED_GAP_MS) {
627
+ continue;
628
+ }
629
+
630
+ const parentLooksActive =
631
+ parent.realTimeStatus === 'busy' ||
632
+ parent.realTimeStatus === 'retry' ||
633
+ parent.waitingForUser;
634
+ if (!parentLooksActive) {
635
+ continue;
636
+ }
637
+
638
+ const parentUpdatedAt = getUpdatedAt(parent) ?? parentCreatedAt;
639
+ const updatedGapMs = Math.abs(childUpdatedAt - parentUpdatedAt);
640
+
641
+ candidates.push({
642
+ parent,
643
+ createdGapMs,
644
+ updatedGapMs,
645
+ });
646
+ }
647
+
648
+ if (candidates.length === 0) {
649
+ return undefined;
650
+ }
651
+
652
+ candidates.sort((a, b) => {
653
+ if (a.createdGapMs !== b.createdGapMs) {
654
+ return a.createdGapMs - b.createdGapMs;
655
+ }
656
+ return a.updatedGapMs - b.updatedGapMs;
657
+ });
658
+
659
+ const bestCandidate = candidates[0];
660
+ const secondCandidate = candidates[1];
661
+
662
+ if (
663
+ secondCandidate
664
+ && Math.abs(secondCandidate.createdGapMs - bestCandidate.createdGapMs) <= CLAUDE_INFERRED_PARENT_AMBIGUITY_GAP_MS
665
+ ) {
666
+ return undefined;
667
+ }
668
+
669
+ return bestCandidate.parent;
670
+ }
671
+
672
+ function rebuildAggregateClaudeTopology(sessions: EnrichedSession[]): EnrichedSession[] {
673
+ const sessionsById = new Map(sessions.map((session) => [session.id, session]));
674
+ const absorbedChildIds = new Set<string>();
675
+ const resolveVisibleClaudeParent = (
676
+ childSession: EnrichedSession,
677
+ parentSession: EnrichedSession
678
+ ): EnrichedSession | undefined => {
679
+ let cursor: EnrichedSession | undefined = parentSession;
680
+ const visited = new Set<string>([childSession.id]);
681
+
682
+ while (cursor) {
683
+ if (visited.has(cursor.id)) {
684
+ return undefined;
685
+ }
686
+
687
+ visited.add(cursor.id);
688
+
689
+ if (!absorbedChildIds.has(cursor.id)) {
690
+ return cursor;
691
+ }
692
+
693
+ if (typeof cursor.parentID !== 'string') {
694
+ return undefined;
695
+ }
696
+
697
+ cursor = sessionsById.get(cursor.parentID);
698
+ }
699
+
700
+ return undefined;
701
+ };
702
+
703
+ const orderedSessions = [...sessions].sort((a, b) => {
704
+ const createdDiff = (getCreatedAt(b) ?? 0) - (getCreatedAt(a) ?? 0);
705
+ if (createdDiff !== 0) {
706
+ return createdDiff;
707
+ }
708
+
709
+ return (getUpdatedAt(b) ?? 0) - (getUpdatedAt(a) ?? 0);
710
+ });
711
+
712
+ for (const session of orderedSessions) {
713
+ if (!hasClaudeProvider(session) || absorbedChildIds.has(session.id)) {
714
+ continue;
715
+ }
716
+
717
+ let parentSession: EnrichedSession | undefined;
718
+
719
+ if (typeof session.parentID === 'string') {
720
+ const explicitParentSession = sessionsById.get(session.parentID);
721
+ if (explicitParentSession) {
722
+ if (
723
+ explicitParentSession === session
724
+ || !hasClaudeProvider(explicitParentSession)
725
+ || !hasSameHostIdentity(explicitParentSession, session)
726
+ ) {
727
+ continue;
728
+ }
729
+
730
+ const visibleParentSession = resolveVisibleClaudeParent(session, explicitParentSession);
731
+ if (!visibleParentSession) {
732
+ if (absorbedChildIds.has(explicitParentSession.id)) {
733
+ session.parentID = undefined;
734
+ }
735
+ continue;
736
+ }
737
+
738
+ parentSession = visibleParentSession;
739
+ if (session.parentID !== visibleParentSession.id) {
740
+ session.parentID = visibleParentSession.id;
741
+ }
742
+ }
743
+ }
744
+
745
+ if (!parentSession) {
746
+ parentSession = inferClaudeParentSession(session, sessions, absorbedChildIds);
747
+ if (parentSession) {
748
+ session.parentID = parentSession.id;
749
+ }
750
+ }
751
+
752
+ if (!parentSession) {
753
+ continue;
754
+ }
755
+
756
+ if (session.children.length > 0) {
757
+ continue;
758
+ }
759
+
760
+ const childEntry = toAggregateClaudeChildEntry(session);
761
+ const existingChildIndex = parentSession.children.findIndex((child) => child.id === childEntry.id);
762
+ if (existingChildIndex >= 0) {
763
+ parentSession.children[existingChildIndex] = {
764
+ ...parentSession.children[existingChildIndex],
765
+ ...childEntry,
766
+ };
767
+ } else {
768
+ parentSession.children.push(childEntry);
769
+ }
770
+
771
+ sortChildEntries(parentSession.children);
772
+ absorbedChildIds.add(session.id);
773
+ }
774
+
775
+ return sessions.filter((session) => !absorbedChildIds.has(session.id));
776
+ }
777
+
881
778
  function sortChildEntries(children: ChildEntry[]): void {
882
779
  children.sort((a, b) => {
883
780
  const aActive = a.realTimeStatus === 'busy' || a.realTimeStatus === 'retry';
@@ -1014,531 +911,9 @@ async function getRemoteNodeSessionsResult(
1014
911
  }
1015
912
  }
1016
913
 
1017
- async function getLocalSessionsResult(stickyBusyDelayMs: number): Promise<SessionsRouteResult> {
1018
-
1019
- const { processes: rawProcessHints, timedOut: processDiscoveryTimedOut } =
1020
- discoverOpencodeProcessCwdsWithoutPortWithMeta();
1021
- const processHintsByDirectory = new Map<string, ProcessHint>();
1022
- for (const process of rawProcessHints) {
1023
- if (!process.cwd || process.cwd.startsWith('/private/tmp/opencode')) {
1024
- continue;
1025
- }
1026
- if (processHintsByDirectory.has(process.cwd)) {
1027
- continue;
1028
- }
1029
- processHintsByDirectory.set(process.cwd, {
1030
- pid: process.pid,
1031
- directory: process.cwd,
1032
- projectName: getProjectName(process.cwd),
1033
- reason: 'process_without_api_port',
1034
- });
1035
- }
1036
-
1037
- const { ports, timedOut: portDiscoveryTimedOut } = discoverOpencodePortsWithMeta();
1038
-
1039
- if (!ports.length) {
1040
- const processHints = Array.from(processHintsByDirectory.values());
1041
-
1042
- if (portDiscoveryTimedOut || processDiscoveryTimedOut) {
1043
- return {
1044
- payload: {
1045
- error: 'OpenCode discovery timed out',
1046
- hint: 'Host process discovery exceeded timeout. Retry shortly, or increase OPENCODE_DISCOVERY_TIMEOUT_MS.',
1047
- ...(processHints.length > 0 ? { processHints } : {}),
1048
- },
1049
- status: 503,
1050
- sourceMeta: {
1051
- online: false,
1052
- degraded: true,
1053
- reason: 'OpenCode discovery timed out',
1054
- },
1055
- };
1056
- }
1057
-
1058
- if (processHints.length > 0) {
1059
- return {
1060
- payload: { sessions: [], processHints },
1061
- sourceMeta: {
1062
- online: false,
1063
- reason: 'OpenCode server not found',
1064
- },
1065
- };
1066
- }
1067
-
1068
- return {
1069
- payload: {
1070
- error: 'OpenCode server not found',
1071
- hint: 'Make sure OpenCode is running with an exposed API port. Example: opencode --port <PORT> (VibePulse auto-detects active ports).'
1072
- },
1073
- status: 503,
1074
- sourceMeta: {
1075
- online: false,
1076
- reason: 'OpenCode server not found',
1077
- },
1078
- };
1079
- }
1080
-
1081
- try {
1082
- const results = await Promise.allSettled(ports.map(async (port) => {
1083
- const client = createOpencodeClient({ baseUrl: `http://localhost:${port}` });
1084
- const sessionsResult = await withTimeout(
1085
- (signal) => client.session.list({ signal }),
1086
- sessionListTimeoutMs,
1087
- `session.list(${port})`
1088
- );
1089
- const statusResult = await withTimeout(
1090
- (signal) => client.session.status({ signal }),
1091
- sessionStatusTimeoutMs,
1092
- `session.status(${port})`
1093
- ).catch(() => ({ data: {} }));
1094
- return { port, client, sessions: sessionsResult.data || [], status: statusResult.data || {} };
1095
- }));
1096
-
1097
- const allSessions: SessionLike[] = [];
1098
- const statusMap: Record<string, { type: 'idle' | 'busy' | 'retry' }> = {};
1099
- const clientByPort: Record<number, ReturnType<typeof createOpencodeClient>> = {};
1100
- const sessionPortMap: Record<string, number> = {};
1101
- const failedPorts: Array<{ port: number; reason: string }> = [];
1102
-
1103
- for (let i = 0; i < results.length; i++) {
1104
- const r = results[i];
1105
- const port = ports[i];
1106
- if (r.status !== 'fulfilled') {
1107
- failedPorts.push({
1108
- port,
1109
- reason: r.reason instanceof Error ? r.reason.message : String(r.reason),
1110
- });
1111
- continue;
1112
- }
1113
- allSessions.push(...r.value.sessions);
1114
- Object.assign(statusMap, r.value.status);
1115
- clientByPort[r.value.port] = r.value.client;
1116
- for (const session of r.value.sessions as SessionLike[]) {
1117
- if (!(session.id in sessionPortMap)) {
1118
- sessionPortMap[session.id] = r.value.port;
1119
- }
1120
- }
1121
- }
1122
-
1123
- // Deduplicate by session.id
1124
- const seen = new Set<string>();
1125
- const sessions = allSessions.filter((session) => {
1126
- if (seen.has(session.id)) return false;
1127
- seen.add(session.id);
1128
- return true;
1129
- });
1130
-
1131
- const parentSessions = sessions.filter((s) => !s.parentID);
1132
- const childSessions = sessions.filter((s) => !!s.parentID);
1133
-
1134
- const lifecycleNow = Date.now();
1135
- pruneSessionForceUnarchived(lifecycleNow);
1136
- pruneSessionStickyStatusBlocked(lifecycleNow);
1137
-
1138
- for (const session of parentSessions) {
1139
- if (session.time?.archived !== undefined && shouldForceSessionUnarchived(session.id, lifecycleNow)) {
1140
- session.time = {
1141
- ...session.time,
1142
- archived: undefined,
1143
- };
1144
- }
1145
- }
1146
-
1147
- for (const child of childSessions) {
1148
- if (child.time?.archived !== undefined && shouldForceSessionUnarchived(child.id, lifecycleNow)) {
1149
- child.time = {
1150
- ...child.time,
1151
- archived: undefined,
1152
- };
1153
- }
1154
- }
1155
-
1156
- if (results.length > 0 && failedPorts.length === results.length) {
1157
- pruneStickyState(Date.now(), new Set<string>());
1158
- return {
1159
- payload: {
1160
- error: 'Failed to fetch sessions from OpenCode ports',
1161
- hint: 'All discovered OpenCode API ports timed out or failed. Retry shortly or increase OPENCODE_SESSIONS_LIST_TIMEOUT_MS.',
1162
- failedPorts,
1163
- },
1164
- status: 503,
1165
- sourceMeta: {
1166
- online: false,
1167
- degraded: true,
1168
- reason: 'Failed to fetch sessions from OpenCode ports',
1169
- },
1170
- };
1171
- }
1172
-
1173
- if (failedPorts.length > 0 && parentSessions.length === 0 && childSessions.length === 0) {
1174
- pruneStickyState(Date.now(), new Set<string>());
1175
- const processHints = Array.from(processHintsByDirectory.values());
1176
- return {
1177
- payload: {
1178
- sessions: [],
1179
- processHints,
1180
- failedPorts,
1181
- degraded: true,
1182
- },
1183
- sourceMeta: {
1184
- online: true,
1185
- degraded: true,
1186
- },
1187
- };
1188
- }
1189
-
1190
- // Enrich parent sessions
1191
- const enrichedSessions: EnrichedSession[] = parentSessions.map((session) => {
1192
- const projectName = getProjectName(session.directory);
1193
- const branch = getGitBranch(session.directory);
1194
- return {
1195
- ...session,
1196
- projectName,
1197
- branch,
1198
- realTimeStatus: statusMap[session.id]?.type || 'idle',
1199
- waitingForUser: false,
1200
- children: [],
1201
- };
1202
- });
1203
-
1204
- const parentById = new Map(enrichedSessions.map((session) => [session.id, session]));
1205
-
1206
- const now = Date.now();
1207
- const unresolvedChildren: Array<{ parentId: string; child: SessionLike; childUpdatedAt: number }> = [];
1208
-
1209
- // Enrich and nest child sessions under parents
1210
- for (const child of childSessions) {
1211
- // Find parent by parentID
1212
- let parent = child.parentID
1213
- ? enrichedSessions.find((session) => session.id === child.parentID)
1214
- : null;
1215
-
1216
- if (!parent) {
1217
- const candidates = enrichedSessions
1218
- .filter((session) => session.directory === child.directory)
1219
- .sort((a, b) => getUpdatedAt(b) - getUpdatedAt(a));
1220
-
1221
- parent =
1222
- candidates.find((session) => session.realTimeStatus === 'busy' || session.realTimeStatus === 'retry') ||
1223
- candidates[0];
1224
- }
1225
-
1226
- if (!parent) {
1227
- continue;
1228
- }
1229
-
1230
- const statusFromMap = statusMap[child.id]?.type;
1231
- const childUpdatedAt = getUpdatedAt(child);
1232
- const isRecent = childUpdatedAt > 0 && now - childUpdatedAt <= CHILD_ACTIVE_WINDOW_MS;
1233
- const shouldSkipArchivedChild = !!child.time?.archived && !statusFromMap && !isRecent;
1234
-
1235
- if (shouldSkipArchivedChild) {
1236
- continue;
1237
- }
1238
-
1239
- if (statusFromMap && statusFromMap !== 'idle') {
1240
- parent.children.push(toChildEntry(child, statusFromMap));
1241
- } else if (isRecent) {
1242
- if (unresolvedChildren.length < CHILD_STATUS_MESSAGE_CHECK_LIMIT) {
1243
- unresolvedChildren.push({ parentId: parent.id, child, childUpdatedAt });
1244
- }
1245
- } else {
1246
- continue;
1247
- }
1248
- }
1249
-
1250
- if (unresolvedChildren.length > 0) {
1251
- const unresolvedChecks = await Promise.allSettled(
1252
- unresolvedChildren.map(async ({ parentId, child, childUpdatedAt }) => {
1253
- const port = sessionPortMap[child.id] ?? sessionPortMap[parentId];
1254
- const client = port ? clientByPort[port] : undefined;
1255
- const assumeBusyForUnknown =
1256
- childUpdatedAt > 0 && now - childUpdatedAt <= CHILD_UNKNOWN_STATE_BUSY_WINDOW_MS;
1257
- if (!client) {
1258
- return {
1259
- parentId,
1260
- child,
1261
- childStatus: assumeBusyForUnknown ? 'busy' as const : 'idle' as const,
1262
- };
1263
- }
1264
-
1265
- try {
1266
- const partStatuses = await fetchPartStatuses(client, child.id, sessionMessagesTimeoutMs);
1267
- const hasRunningState = partStatuses.some((status) => status === 'running');
1268
- const hasWaitingState = !hasRunningState && partStatuses.some(isWaitingPartStatus);
1269
- const hasActiveState = hasWaitingState || hasRunningState;
1270
- const recentlyActive = childUpdatedAt > 0 && now - childUpdatedAt <= 5 * 60 * 1000;
1271
-
1272
- return {
1273
- parentId,
1274
- child,
1275
- childWaitingForUser: hasWaitingState,
1276
- childStatus: hasActiveState
1277
- ? 'busy' as const
1278
- : recentlyActive || assumeBusyForUnknown
1279
- ? 'busy' as const
1280
- : 'idle' as const,
1281
- };
1282
- } catch {
1283
- return {
1284
- parentId,
1285
- child,
1286
- childWaitingForUser: false,
1287
- childStatus: assumeBusyForUnknown ? 'busy' as const : 'idle' as const,
1288
- };
1289
- }
1290
- })
1291
- );
1292
-
1293
- for (const check of unresolvedChecks) {
1294
- if (check.status !== 'fulfilled') continue;
1295
- if (check.value.childStatus === 'idle') continue;
1296
- const parent = parentById.get(check.value.parentId);
1297
- if (!parent) continue;
1298
- parent.children.push(toChildEntry(check.value.child, check.value.childStatus, check.value.childWaitingForUser));
1299
- }
1300
- }
1301
-
1302
- const parentStatusFallbackCandidates = enrichedSessions
1303
- .filter((session) => {
1304
- if (session.realTimeStatus !== 'idle') return false;
1305
- const updatedAt = getUpdatedAt(session);
1306
- if (updatedAt > 0 && now - updatedAt <= CHILD_ACTIVE_WINDOW_MS) return true;
1307
- return !!session.time?.archived;
1308
- })
1309
- .sort((a, b) => getUpdatedAt(b) - getUpdatedAt(a))
1310
- .slice(0, CHILD_STATUS_MESSAGE_CHECK_LIMIT);
1311
-
1312
- if (parentStatusFallbackCandidates.length > 0) {
1313
- const parentFallbackChecks = await Promise.allSettled(
1314
- parentStatusFallbackCandidates.map(async (session) => {
1315
- const updatedAt = getUpdatedAt(session);
1316
- const assumeBusyForUnknown =
1317
- updatedAt > 0 && now - updatedAt <= CHILD_UNKNOWN_STATE_BUSY_WINDOW_MS;
1318
- const port = sessionPortMap[session.id];
1319
- const client = port ? clientByPort[port] : undefined;
1320
-
1321
- if (!client) {
1322
- return {
1323
- sessionId: session.id,
1324
- status: assumeBusyForUnknown ? 'busy' as const : 'idle' as const,
1325
- waitingForUser: false,
1326
- };
1327
- }
1328
-
1329
- try {
1330
- const partStatuses = await fetchPartStatuses(client, session.id, sessionMessagesTimeoutMs);
1331
- const hasRunningState = partStatuses.some((status) => status === 'running');
1332
- const hasWaitingState = !hasRunningState && partStatuses.some(isWaitingPartStatus);
1333
- const hasCompletedState =
1334
- partStatuses.length > 0 && partStatuses.every((status) => status === 'completed');
1335
- const recentlyActive = hasRecentActivity(session, now);
1336
-
1337
- return {
1338
- sessionId: session.id,
1339
- status: hasRunningState || hasWaitingState
1340
- ? 'busy' as const
1341
- : hasCompletedState && !recentlyActive
1342
- ? 'idle' as const
1343
- : assumeBusyForUnknown || recentlyActive
1344
- ? 'busy' as const
1345
- : 'idle' as const,
1346
- waitingForUser: hasWaitingState,
1347
- };
1348
- } catch {
1349
- return {
1350
- sessionId: session.id,
1351
- status: assumeBusyForUnknown ? 'busy' as const : 'idle' as const,
1352
- waitingForUser: false,
1353
- };
1354
- }
1355
- })
1356
- );
1357
-
1358
- for (const check of parentFallbackChecks) {
1359
- if (check.status !== 'fulfilled') continue;
1360
- if (check.value.status === 'idle') continue;
1361
- const session = parentById.get(check.value.sessionId);
1362
- if (!session) continue;
1363
- session.realTimeStatus = check.value.status;
1364
- if (check.value.waitingForUser) {
1365
- session.waitingForUser = true;
1366
- }
1367
- }
1368
- }
1369
-
1370
- // Sort children for each parent: active first, then by updated time
1371
- for (const session of enrichedSessions) {
1372
- if (session.children.length > 0) {
1373
- sortChildEntries(session.children);
1374
- }
1375
- }
1376
-
1377
- const sessionsForInteractionChecks = enrichedSessions.filter(
1378
- (session) =>
1379
- session.realTimeStatus === 'busy' ||
1380
- !!session.time?.archived ||
1381
- session.children.some((child) => child.realTimeStatus === 'busy' || child.realTimeStatus === 'retry')
1382
- );
1383
- if (sessionsForInteractionChecks.length > 0) {
1384
- const pendingChecks = await Promise.allSettled(
1385
- sessionsForInteractionChecks.map(async (session) => {
1386
- const port = sessionPortMap[session.id];
1387
- const client = port ? clientByPort[port] : undefined;
1388
- if (!client) {
1389
- return {
1390
- sessionId: session.id,
1391
- parentWaiting: false,
1392
- waiting: false,
1393
- running: false,
1394
- waitingChildIds: new Set<string>(),
1395
- };
1396
- }
1397
-
1398
- try {
1399
- const partStatuses = await fetchPartStatuses(client, session.id, sessionMessagesTimeoutMs);
1400
- const hasRunning = partStatuses.some((status) => status === 'running');
1401
- const hasInteractionWait = !hasRunning && partStatuses.some(isWaitingPartStatus);
1402
-
1403
- const childStateChecks = await Promise.allSettled(
1404
- session.children
1405
- .filter((child) => child.realTimeStatus === 'busy' || child.realTimeStatus === 'retry')
1406
- .map(async (child) => {
1407
- const childPort = sessionPortMap[child.id] ?? sessionPortMap[session.id];
1408
- const childClient = childPort ? clientByPort[childPort] : undefined;
1409
- if (!childClient) {
1410
- return { childId: child.id, waiting: false };
1411
- }
1412
- try {
1413
- const childStatuses = await fetchPartStatuses(childClient, child.id, sessionMessagesTimeoutMs);
1414
- const childHasRunning = childStatuses.some((status) => status === 'running');
1415
- return {
1416
- childId: child.id,
1417
- waiting: !childHasRunning && childStatuses.some(isWaitingPartStatus),
1418
- };
1419
- } catch {
1420
- return { childId: child.id, waiting: false };
1421
- }
1422
- })
1423
- );
1424
-
1425
- const waitingChildIds = new Set(
1426
- childStateChecks
1427
- .filter((result): result is PromiseFulfilledResult<{ childId: string; waiting: boolean }> => result.status === 'fulfilled')
1428
- .filter((result) => result.value.waiting)
1429
- .map((result) => result.value.childId)
1430
- );
1431
-
1432
- const hasWaitingChildren =
1433
- waitingChildIds.size > 0 ||
1434
- session.children.some((child) => child.waitingForUser || child.realTimeStatus === 'retry');
1435
-
1436
- return {
1437
- sessionId: session.id,
1438
- parentWaiting: hasInteractionWait,
1439
- waiting: hasInteractionWait || hasWaitingChildren,
1440
- running: hasRunning,
1441
- waitingChildIds,
1442
- };
1443
- } catch {
1444
- return {
1445
- sessionId: session.id,
1446
- parentWaiting: false,
1447
- waiting: false,
1448
- running: false,
1449
- waitingChildIds: new Set<string>(),
1450
- };
1451
- }
1452
- })
1453
- );
1454
-
1455
- for (const result of pendingChecks) {
1456
- if (result.status === 'fulfilled') {
1457
- const session = enrichedSessions.find((candidate) => candidate.id === result.value.sessionId);
1458
- if (!session) continue;
1459
- for (const child of session.children) {
1460
- if (result.value.waitingChildIds.has(child.id)) {
1461
- child.waitingForUser = true;
1462
- }
1463
- }
1464
- if (result.value.running) {
1465
- session.realTimeStatus = 'busy';
1466
- }
1467
- if (result.value.parentWaiting) {
1468
- session.waitingForUser = true;
1469
- }
1470
- }
1471
- }
1472
- }
1473
-
1474
- const stickyNow = Date.now();
1475
- const activeStickyIds = new Set<string>();
1476
-
1477
- for (const session of enrichedSessions) {
1478
- activeStickyIds.add(session.id);
1479
- for (const child of session.children) {
1480
- activeStickyIds.add(`child:${child.id}`);
1481
- }
1482
- }
1483
-
1484
- for (const session of enrichedSessions) {
1485
- if (shouldSkipSessionStatusStabilization(session, stickyNow)) {
1486
- continue;
1487
- }
1488
-
1489
- applyStickyStatusStabilization(session, stickyNow, stickyBusyDelayMs);
1490
- }
1491
- pruneStickyState(stickyNow, activeStickyIds);
1492
-
1493
- const knownDirectories = new Set<string>();
1494
- for (const session of sessions) {
1495
- if (session.directory) {
1496
- knownDirectories.add(session.directory);
1497
- }
1498
- }
1499
-
1500
- const processHints = Array.from(processHintsByDirectory.values()).filter(
1501
- (hint) => !knownDirectories.has(hint.directory)
1502
- );
1503
-
1504
- const payload: SessionsSuccessPayload = {
1505
- sessions: enrichedSessions,
1506
- processHints,
1507
- };
1508
-
1509
- if (failedPorts.length > 0) {
1510
- payload.failedPorts = failedPorts;
1511
- payload.degraded = true;
1512
- }
1513
-
1514
- return {
1515
- payload,
1516
- sourceMeta: {
1517
- online: true,
1518
- ...(failedPorts.length > 0 ? { degraded: true } : {}),
1519
- },
1520
- };
1521
- } catch (error) {
1522
- console.error('Error fetching sessions:', error);
1523
- return {
1524
- payload: {
1525
- error: 'Failed to fetch sessions',
1526
- details: error instanceof Error ? error.message : String(error),
1527
- hint: 'Make sure OpenCode is running with an exposed API port. Example: opencode --port <PORT> (VibePulse auto-detects active ports).'
1528
- },
1529
- status: 500,
1530
- sourceMeta: {
1531
- online: false,
1532
- degraded: true,
1533
- reason: 'Failed to fetch sessions',
1534
- },
1535
- };
1536
- }
1537
- }
1538
-
1539
914
  async function handleGet() {
1540
915
  const stickyBusyDelayMs = await readStickyBusyDelayMs();
1541
- return toRouteResponse(await getLocalSessionsResult(stickyBusyDelayMs));
916
+ return toRouteResponse(await getLocalSessionsResult({ stickyBusyDelayMs, providers: [...LOCAL_POLLING_PROVIDERS] }));
1542
917
  }
1543
918
 
1544
919
  async function handlePost(request: Request) {
@@ -1568,7 +943,7 @@ async function handlePost(request: Request) {
1568
943
 
1569
944
  if (enabledSources.length === 1 && enabledSources[0].hostKind === 'local') {
1570
945
  const stickyBusyDelayMs = await readStickyBusyDelayMs();
1571
- const localResult = await getLocalSessionsResult(stickyBusyDelayMs);
946
+ const localResult = await getLocalSessionsResult({ stickyBusyDelayMs, providers: [...LOCAL_POLLING_PROVIDERS] });
1572
947
  const rawLocalMeta = localResult.sourceMeta ?? {
1573
948
  online: !localResult.status,
1574
949
  ...(localResult.status ? { degraded: true } : {}),
@@ -1634,7 +1009,7 @@ async function handlePost(request: Request) {
1634
1009
  resolvedSources.map(async (source) => ({
1635
1010
  source,
1636
1011
  result: source.hostKind === 'local'
1637
- ? await getLocalSessionsResult(stickyBusyDelayMs)
1012
+ ? await getLocalSessionsResult({ stickyBusyDelayMs, providers: [...LOCAL_POLLING_PROVIDERS] })
1638
1013
  : await getRemoteNodeSessionsResult(source, nodeRecordsById.get(source.hostId)),
1639
1014
  }))
1640
1015
  );
@@ -1686,7 +1061,7 @@ async function handlePost(request: Request) {
1686
1061
  }
1687
1062
 
1688
1063
  return Response.json({
1689
- sessions: aggregateSessions,
1064
+ sessions: rebuildAggregateClaudeTopology(aggregateSessions),
1690
1065
  processHints: aggregateProcessHints,
1691
1066
  hosts: hostStatuses,
1692
1067
  hostStatuses,