vibepulse 0.2.1 → 0.3.0-beta.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 (294) 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/sessions/[id]/open-editor/route.js.nft.json +1 -1
  32. package/.next/server/app/api/node/sessions/route.js +5 -3
  33. package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  34. package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  35. package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  36. package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  37. package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  38. package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  39. package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  40. package/.next/server/app/api/profiles/route.js.nft.json +1 -1
  41. package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  42. package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  43. package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  44. package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  45. package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  46. package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  47. package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  48. package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  49. package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  50. package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  51. package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  52. package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  53. package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  54. package/.next/server/app/api/sessions/route.js +5 -3
  55. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  56. package/.next/server/app/index.html +1 -1
  57. package/.next/server/app/index.rsc +3 -3
  58. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app-paths-manifest.json +1 -0
  65. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  66. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
  67. package/.next/server/chunks/[root-of-the-server]__56690af0._.js +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__56690af0._.js.map +1 -1
  69. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
  71. package/.next/server/chunks/[root-of-the-server]__59175de4._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__59175de4._.js.map +1 -1
  73. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  74. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
  75. package/.next/server/chunks/[root-of-the-server]__64fffc02._.js +1 -1
  76. package/.next/server/chunks/[root-of-the-server]__64fffc02._.js.map +1 -1
  77. package/.next/server/chunks/[root-of-the-server]__89c5eeab._.js +1 -1
  78. package/.next/server/chunks/[root-of-the-server]__89c5eeab._.js.map +1 -1
  79. package/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js +1 -1
  80. package/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js.map +1 -1
  81. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  82. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
  83. package/.next/server/chunks/[root-of-the-server]__b796d06c._.js +1 -1
  84. package/.next/server/chunks/[root-of-the-server]__b796d06c._.js.map +1 -1
  85. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  86. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
  87. package/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js +1 -1
  88. package/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js.map +1 -1
  89. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  90. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
  91. package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  92. package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
  93. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  94. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
  95. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  96. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
  97. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  98. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
  99. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  100. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
  101. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  102. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
  103. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  104. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
  105. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  106. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
  107. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  108. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
  109. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +4 -4
  110. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
  111. package/.next/server/pages/404.html +1 -1
  112. package/.next/server/pages/500.html +2 -2
  113. package/.next/server/server-reference-manifest.js +1 -1
  114. package/.next/server/server-reference-manifest.json +1 -1
  115. package/.next/standalone/.next/BUILD_ID +1 -1
  116. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  117. package/.next/standalone/.next/build-manifest.json +2 -2
  118. package/.next/standalone/.next/prerender-manifest.json +3 -3
  119. package/.next/standalone/.next/routes-manifest.json +8 -0
  120. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  121. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  123. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  124. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  125. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  126. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  127. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  128. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  129. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  130. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  131. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  132. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  133. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  134. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  135. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  137. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  138. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  139. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  140. package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
  142. package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  145. package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  147. package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  149. package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
  150. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  151. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  152. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  153. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  154. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  155. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  156. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  157. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  158. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  159. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  160. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  161. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  162. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  163. package/.next/standalone/.next/server/app/api/sessions/route.js +5 -3
  164. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  165. package/.next/standalone/.next/server/app/index.html +1 -1
  166. package/.next/standalone/.next/server/app/index.rsc +3 -3
  167. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  168. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  169. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  170. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  171. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  172. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  173. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  174. package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  175. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56690af0._.js +1 -1
  176. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  177. package/.next/standalone/.next/server/chunks/[root-of-the-server]__59175de4._.js +1 -1
  178. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/[root-of-the-server]__64fffc02._.js +1 -1
  180. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c5eeab._.js +1 -1
  181. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8da6c5a8._.js +1 -1
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  183. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b796d06c._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c2ce5c0f._.js +1 -1
  186. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  187. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  188. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  189. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  190. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  191. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  192. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  193. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  194. package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  196. package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +4 -4
  197. package/.next/standalone/.next/server/pages/404.html +1 -1
  198. package/.next/standalone/.next/server/pages/500.html +2 -2
  199. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  200. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  201. package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
  202. package/.next/standalone/.next/static/chunks/c1294e057d8d4681.js +13 -0
  203. package/.next/standalone/AGENTS.md +2 -2
  204. package/.next/standalone/README.md +30 -7
  205. package/.next/standalone/docs/session-status-detection.md +36 -0
  206. package/.next/standalone/docs/superpowers/plans/2026-04-05-oh-my-openagent-migration.md +57 -0
  207. package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
  208. package/.next/standalone/package-lock.json +2 -2
  209. package/.next/standalone/package.json +1 -1
  210. package/.next/standalone/src/app/api/AGENTS.md +2 -2
  211. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
  212. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
  213. package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
  214. package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
  215. package/.next/standalone/src/app/api/opencode-config/status/route.ts +6 -7
  216. package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
  217. package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
  218. package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
  219. package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
  220. package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
  221. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
  222. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
  223. package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
  224. package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
  225. package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
  226. package/.next/standalone/src/app/api/sessions/route.ts +365 -981
  227. package/.next/standalone/src/components/AGENTS.md +2 -2
  228. package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
  229. package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
  230. package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
  231. package/.next/standalone/src/components/ProjectCard.tsx +238 -86
  232. package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
  233. package/.next/standalone/src/components/SessionCard.tsx +182 -76
  234. package/.next/standalone/src/components/opencode-config/AgentConfigForm.test.tsx +28 -0
  235. package/.next/standalone/src/components/opencode-config/AgentConfigForm.tsx +9 -9
  236. package/.next/standalone/src/components/opencode-config/ConfigButton.tsx +4 -4
  237. package/.next/standalone/src/components/opencode-config/categories/CategoriesList.tsx +10 -8
  238. package/.next/standalone/src/components/opencode-config/categories/CategoriesManager.test.tsx +29 -1
  239. package/.next/standalone/src/components/opencode-config/categories/CategoryConfigForm.test.tsx +49 -0
  240. package/.next/standalone/src/components/opencode-config/categories/CategoryConfigForm.tsx +6 -6
  241. package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
  242. package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
  243. package/.next/standalone/src/index.ts +1 -1
  244. package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
  245. package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
  246. package/.next/standalone/src/lib/opencodeConfig.test.ts +53 -4
  247. package/.next/standalone/src/lib/opencodeConfig.ts +24 -12
  248. package/.next/standalone/src/lib/profiles/storage.test.ts +38 -1
  249. package/.next/standalone/src/lib/profiles/storage.ts +17 -17
  250. package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
  251. package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
  252. package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
  253. package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
  254. package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
  255. package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
  256. package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
  257. package/.next/standalone/src/lib/session-providers/types.ts +131 -0
  258. package/.next/standalone/src/lib/transform.test.ts +253 -0
  259. package/.next/standalone/src/lib/transform.ts +96 -37
  260. package/.next/standalone/src/types/index.ts +23 -17
  261. package/.next/standalone/src/types/opencodeConfig.ts +1 -9
  262. package/.next/static/chunks/b3bc362202331708.css +3 -0
  263. package/.next/static/chunks/c1294e057d8d4681.js +13 -0
  264. package/.next/trace +1 -1
  265. package/.next/trace-build +1 -1
  266. package/.next/types/routes.d.ts +2 -1
  267. package/.next/types/validator.ts +9 -0
  268. package/README.md +30 -7
  269. package/package.json +1 -1
  270. package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  271. package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
  272. package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  273. package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
  274. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  275. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
  276. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  277. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
  278. package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  279. package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  285. package/.next/standalone/.next/static/chunks/7ac19aaef01f4a03.js +0 -13
  286. package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
  287. package/.next/static/chunks/7ac19aaef01f4a03.js +0 -13
  288. package/.next/static/chunks/f42202943f6742e5.css +0 -3
  289. /package/.next/standalone/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  290. /package/.next/standalone/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  291. /package/.next/standalone/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
  292. /package/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  293. /package/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  294. /package/.next/static/{Fw2R3y-fHX4B2SWxNy_4X → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
@@ -13,13 +13,19 @@ vi.mock('@/lib/sessionArchiveOverrides', () => ({
13
13
  markSessionStickyStatusBlocked: vi.fn(),
14
14
  }));
15
15
 
16
+ vi.mock('@/lib/claudeSessionOverrides', () => ({
17
+ markClaudeSessionArchived: vi.fn(),
18
+ }));
19
+
16
20
  import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
17
21
  import { listNodeRecords } from '@/lib/nodeRegistry';
22
+ import { markClaudeSessionArchived } from '@/lib/claudeSessionOverrides';
18
23
 
19
24
  import { POST } from './route';
20
25
 
21
26
  const mockDiscoverPortsWithMeta: any = discoverOpencodePortsWithMeta;
22
27
  const mockListNodeRecords: any = listNodeRecords;
28
+ const mockMarkClaudeSessionArchived: any = markClaudeSessionArchived;
23
29
 
24
30
  describe('/api/sessions/[id]/archive', () => {
25
31
  beforeEach(() => {
@@ -40,6 +46,23 @@ describe('/api/sessions/[id]/archive', () => {
40
46
  expect(mockFetch).toHaveBeenCalledWith('http://localhost:7777/session/abc', expect.objectContaining({ method: 'PATCH' }));
41
47
  });
42
48
 
49
+ it('treats UUID-like local ids without claude namespace as opencode sessions', async () => {
50
+ const opencodeUuid = '550e8400-e29b-41d4-a716-446655440000';
51
+ const mockFetch = vi.fn(async () => new Response(JSON.stringify({ error: 'missing' }), { status: 404 }));
52
+ vi.stubGlobal('fetch', mockFetch);
53
+
54
+ const response = await POST(new Request(`http://localhost/api/sessions/local:${opencodeUuid}/archive`, { method: 'POST' }), {
55
+ params: Promise.resolve({ id: `local:${opencodeUuid}` }),
56
+ });
57
+ const data = await response.json();
58
+
59
+ expect(response.status).toBe(404);
60
+ expect(data).toEqual({ error: 'Session not found', reason: 'session_not_found' });
61
+ expect(mockMarkClaudeSessionArchived).not.toHaveBeenCalled();
62
+ expect(mockDiscoverPortsWithMeta).toHaveBeenCalled();
63
+ expect(mockFetch).toHaveBeenCalledWith(`http://localhost:7777/session/${opencodeUuid}`, expect.objectContaining({ method: 'PATCH' }));
64
+ });
65
+
43
66
  it('forwards remote archive ids to the matching node endpoint', async () => {
44
67
  mockListNodeRecords.mockResolvedValue([
45
68
  {
@@ -98,6 +121,84 @@ describe('/api/sessions/[id]/archive', () => {
98
121
  expect(data).toEqual({ error: 'Session not found', reason: 'session_not_found' });
99
122
  });
100
123
 
124
+ it('archives Claude sessions through local override storage before any OpenCode execution', async () => {
125
+ const mockFetch = vi.fn();
126
+ vi.stubGlobal('fetch', mockFetch);
127
+
128
+ const response = await POST(new Request('http://localhost/api/sessions/local:claude~550e8400-e29b-41d4-a716-446655440000/archive', { method: 'POST' }), {
129
+ params: Promise.resolve({ id: 'local:claude~550e8400-e29b-41d4-a716-446655440000' }),
130
+ });
131
+ const data = await response.json();
132
+
133
+ expect(response.status).toBe(200);
134
+ expect(data).toEqual({ success: true });
135
+ expect(mockMarkClaudeSessionArchived).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
136
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
137
+ expect(mockFetch).not.toHaveBeenCalled();
138
+ });
139
+
140
+ it('archives scoped Claude sidechain sessions through local override storage', async () => {
141
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
142
+ const mockFetch = vi.fn();
143
+ vi.stubGlobal('fetch', mockFetch);
144
+
145
+ const response = await POST(new Request(`http://localhost/api/sessions/local:${scopedSessionId}/archive`, { method: 'POST' }), {
146
+ params: Promise.resolve({ id: `local:${scopedSessionId}` }),
147
+ });
148
+ const data = await response.json();
149
+
150
+ expect(response.status).toBe(200);
151
+ expect(data).toEqual({ success: true });
152
+ expect(mockMarkClaudeSessionArchived).toHaveBeenCalledWith(scopedSessionId);
153
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
154
+ expect(mockFetch).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it('rejects remote Claude archive requests before local override or node execution', async () => {
158
+ const mockFetch = vi.fn();
159
+ vi.stubGlobal('fetch', mockFetch);
160
+
161
+ const response = await POST(new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/archive', { method: 'POST' }), {
162
+ params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }),
163
+ });
164
+ const data = await response.json();
165
+
166
+ expect(response.status).toBe(403);
167
+ expect(data).toEqual({
168
+ error: 'Session action not supported by provider',
169
+ reason: 'provider_capability_unsupported',
170
+ provider: 'claude-code',
171
+ capability: 'archive',
172
+ });
173
+ expect(mockMarkClaudeSessionArchived).not.toHaveBeenCalled();
174
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
175
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
176
+ expect(mockFetch).not.toHaveBeenCalled();
177
+ });
178
+
179
+ it('rejects remote scoped Claude sidechain archive requests before node execution', async () => {
180
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
181
+ const mockFetch = vi.fn();
182
+ vi.stubGlobal('fetch', mockFetch);
183
+
184
+ const response = await POST(new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/archive`, { method: 'POST' }), {
185
+ params: Promise.resolve({ id: `node-1:${scopedSessionId}` }),
186
+ });
187
+ const data = await response.json();
188
+
189
+ expect(response.status).toBe(403);
190
+ expect(data).toEqual({
191
+ error: 'Session action not supported by provider',
192
+ reason: 'provider_capability_unsupported',
193
+ provider: 'claude-code',
194
+ capability: 'archive',
195
+ });
196
+ expect(mockMarkClaudeSessionArchived).not.toHaveBeenCalled();
197
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
198
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
199
+ expect(mockFetch).not.toHaveBeenCalled();
200
+ });
201
+
101
202
  it('does not misclassify non-404 local archive failures as session_not_found', async () => {
102
203
  const mockFetch = vi.fn(async () => new Response(JSON.stringify({ error: 'boom' }), { status: 500 }));
103
204
  vi.stubGlobal('fetch', mockFetch);
@@ -1,11 +1,13 @@
1
1
  import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
2
- import { parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
2
+ import { ActionSessionReference, parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
3
3
  import { listNodeRecords } from '@/lib/nodeRegistry';
4
4
  import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
5
+ import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
5
6
  import {
6
- clearSessionForceUnarchived,
7
- markSessionStickyStatusBlocked,
7
+ clearSessionForceUnarchived,
8
+ markSessionStickyStatusBlocked,
8
9
  } from '@/lib/sessionArchiveOverrides';
10
+ import { markClaudeSessionArchived } from '@/lib/claudeSessionOverrides';
9
11
 
10
12
  const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
11
13
 
@@ -23,6 +25,20 @@ function createSessionNotFoundResponse() {
23
25
  );
24
26
  }
25
27
 
28
+ function createUnsupportedCapabilityResponse(capability: 'archive', sessionId: string) {
29
+ const provider = detectProviderFromRawId(sessionId);
30
+
31
+ return Response.json(
32
+ {
33
+ error: 'Session action not supported by provider',
34
+ reason: 'provider_capability_unsupported',
35
+ provider,
36
+ capability,
37
+ },
38
+ { status: 403 }
39
+ );
40
+ }
41
+
26
42
  function createArchiveFailureResponse(
27
43
  status: number,
28
44
  message?: string,
@@ -87,17 +103,36 @@ async function forwardRemoteArchive(hostId: string, sessionId: string): Promise<
87
103
 
88
104
  export async function POST(_: Request, { params }: { params: Promise<{ id: string }> }) {
89
105
  const { id } = await params;
90
- const sessionId = resolveLocalActionSessionId(id);
106
+ let actionTarget: ActionSessionReference;
91
107
 
92
- if (!sessionId) {
93
- try {
94
- const actionTarget = parseActionSessionReference(id);
95
- if (actionTarget.isRemote) {
96
- return forwardRemoteArchive(actionTarget.hostId, actionTarget.sessionId);
97
- }
98
- } catch {
99
- return createInvalidActionSessionIdResponse();
108
+ try {
109
+ actionTarget = parseActionSessionReference(id);
110
+ } catch {
111
+ return createInvalidActionSessionIdResponse();
112
+ }
113
+
114
+ const provider = detectProviderFromRawId(actionTarget.sessionId);
115
+ if (!getDefaultProviderContext(provider).capabilities.archive) {
116
+ return createUnsupportedCapabilityResponse('archive', actionTarget.sessionId);
117
+ }
118
+
119
+ if (provider === 'claude-code' && actionTarget.isRemote) {
120
+ return createUnsupportedCapabilityResponse('archive', actionTarget.sessionId);
121
+ }
122
+
123
+ if (provider === 'claude-code') {
124
+ await markClaudeSessionArchived(extractProviderRawId(actionTarget.sessionId));
125
+ const localSessionId = resolveLocalActionSessionId(id);
126
+ if (localSessionId) {
127
+ clearSessionForceUnarchived(localSessionId);
128
+ markSessionStickyStatusBlocked(localSessionId);
100
129
  }
130
+ return Response.json({ success: true });
131
+ }
132
+
133
+ const sessionId = resolveLocalActionSessionId(id);
134
+ if (!sessionId && actionTarget.isRemote) {
135
+ return forwardRemoteArchive(actionTarget.hostId, actionTarget.sessionId);
101
136
  }
102
137
 
103
138
  if (!sessionId) {
@@ -17,15 +17,21 @@ vi.mock('@/lib/sessionArchiveOverrides', () => ({
17
17
  clearSessionStickyStatusBlocked: vi.fn(),
18
18
  }));
19
19
 
20
+ vi.mock('@/lib/claudeSessionOverrides', () => ({
21
+ markClaudeSessionDeleted: vi.fn(),
22
+ }));
23
+
20
24
  import { createOpencodeClient } from '@opencode-ai/sdk';
21
25
  import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
22
26
  import { listNodeRecords } from '@/lib/nodeRegistry';
27
+ import { markClaudeSessionDeleted } from '@/lib/claudeSessionOverrides';
23
28
 
24
29
  import { POST } from './route';
25
30
 
26
31
  const mockCreateOpencodeClient: any = createOpencodeClient;
27
32
  const mockDiscoverPortsWithMeta: any = discoverOpencodePortsWithMeta;
28
33
  const mockListNodeRecords: any = listNodeRecords;
34
+ const mockMarkClaudeSessionDeleted: any = markClaudeSessionDeleted;
29
35
  const mockSessionDelete = vi.fn();
30
36
 
31
37
  describe('/api/sessions/[id]/delete', () => {
@@ -110,4 +116,90 @@ describe('/api/sessions/[id]/delete', () => {
110
116
  message: '404 not found',
111
117
  });
112
118
  });
119
+
120
+ it('deletes Claude sessions through local override storage before any OpenCode execution', async () => {
121
+ const mockFetch = vi.fn();
122
+ vi.stubGlobal('fetch', mockFetch);
123
+
124
+ const response = await POST(new Request('http://localhost/api/sessions/local:claude~550e8400-e29b-41d4-a716-446655440000/delete', { method: 'POST' }), {
125
+ params: Promise.resolve({ id: 'local:claude~550e8400-e29b-41d4-a716-446655440000' }),
126
+ });
127
+ const data = await response.json();
128
+
129
+ expect(response.status).toBe(200);
130
+ expect(data).toEqual({ success: true });
131
+ expect(mockMarkClaudeSessionDeleted).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
132
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
133
+ expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
134
+ expect(mockSessionDelete).not.toHaveBeenCalled();
135
+ expect(mockFetch).not.toHaveBeenCalled();
136
+ });
137
+
138
+ it('deletes scoped Claude sidechain sessions through local override storage', async () => {
139
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
140
+ const mockFetch = vi.fn();
141
+ vi.stubGlobal('fetch', mockFetch);
142
+
143
+ const response = await POST(new Request(`http://localhost/api/sessions/local:${scopedSessionId}/delete`, { method: 'POST' }), {
144
+ params: Promise.resolve({ id: `local:${scopedSessionId}` }),
145
+ });
146
+ const data = await response.json();
147
+
148
+ expect(response.status).toBe(200);
149
+ expect(data).toEqual({ success: true });
150
+ expect(mockMarkClaudeSessionDeleted).toHaveBeenCalledWith(scopedSessionId);
151
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
152
+ expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
153
+ expect(mockSessionDelete).not.toHaveBeenCalled();
154
+ expect(mockFetch).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it('rejects remote Claude delete requests before local override or node execution', async () => {
158
+ const mockFetch = vi.fn();
159
+ vi.stubGlobal('fetch', mockFetch);
160
+
161
+ const response = await POST(new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/delete', { method: 'POST' }), {
162
+ params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }),
163
+ });
164
+ const data = await response.json();
165
+
166
+ expect(response.status).toBe(403);
167
+ expect(data).toEqual({
168
+ error: 'Session action not supported by provider',
169
+ reason: 'provider_capability_unsupported',
170
+ provider: 'claude-code',
171
+ capability: 'delete',
172
+ });
173
+ expect(mockMarkClaudeSessionDeleted).not.toHaveBeenCalled();
174
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
175
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
176
+ expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
177
+ expect(mockSessionDelete).not.toHaveBeenCalled();
178
+ expect(mockFetch).not.toHaveBeenCalled();
179
+ });
180
+
181
+ it('rejects remote scoped Claude sidechain deletes before node execution', async () => {
182
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
183
+ const mockFetch = vi.fn();
184
+ vi.stubGlobal('fetch', mockFetch);
185
+
186
+ const response = await POST(new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/delete`, { method: 'POST' }), {
187
+ params: Promise.resolve({ id: `node-1:${scopedSessionId}` }),
188
+ });
189
+ const data = await response.json();
190
+
191
+ expect(response.status).toBe(403);
192
+ expect(data).toEqual({
193
+ error: 'Session action not supported by provider',
194
+ reason: 'provider_capability_unsupported',
195
+ provider: 'claude-code',
196
+ capability: 'delete',
197
+ });
198
+ expect(mockMarkClaudeSessionDeleted).not.toHaveBeenCalled();
199
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
200
+ expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
201
+ expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
202
+ expect(mockSessionDelete).not.toHaveBeenCalled();
203
+ expect(mockFetch).not.toHaveBeenCalled();
204
+ });
113
205
  });
@@ -1,12 +1,14 @@
1
1
  import { createOpencodeClient } from '@opencode-ai/sdk';
2
2
  import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
3
- import { parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
3
+ import { ActionSessionReference, parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
4
4
  import { listNodeRecords } from '@/lib/nodeRegistry';
5
5
  import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
6
+ import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
6
7
  import {
7
8
  clearSessionForceUnarchived,
8
9
  clearSessionStickyStatusBlocked,
9
10
  } from '@/lib/sessionArchiveOverrides';
11
+ import { markClaudeSessionDeleted } from '@/lib/claudeSessionOverrides';
10
12
 
11
13
  const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
12
14
 
@@ -28,6 +30,20 @@ function createSessionNotFoundResponse(message?: string) {
28
30
  );
29
31
  }
30
32
 
33
+ function createUnsupportedCapabilityResponse(capability: 'delete', sessionId: string) {
34
+ const provider = detectProviderFromRawId(sessionId);
35
+
36
+ return Response.json(
37
+ {
38
+ error: 'Session action not supported by provider',
39
+ reason: 'provider_capability_unsupported',
40
+ provider,
41
+ capability,
42
+ },
43
+ { status: 403 }
44
+ );
45
+ }
46
+
31
47
  async function forwardRemoteDelete(hostId: string, sessionId: string): Promise<Response> {
32
48
  const nodeRecords = await listNodeRecords();
33
49
  const nodeRecord = nodeRecords.find((node) => node.nodeId === hostId);
@@ -77,17 +93,36 @@ async function forwardRemoteDelete(hostId: string, sessionId: string): Promise<R
77
93
 
78
94
  export async function POST(_: Request, { params }: { params: Promise<{ id: string }> }) {
79
95
  const { id } = await params;
80
- const sessionId = resolveLocalActionSessionId(id);
96
+ let actionTarget: ActionSessionReference;
81
97
 
82
- if (!sessionId) {
83
- try {
84
- const actionTarget = parseActionSessionReference(id);
85
- if (actionTarget.isRemote) {
86
- return forwardRemoteDelete(actionTarget.hostId, actionTarget.sessionId);
87
- }
88
- } catch {
89
- return createInvalidActionSessionIdResponse();
98
+ try {
99
+ actionTarget = parseActionSessionReference(id);
100
+ } catch {
101
+ return createInvalidActionSessionIdResponse();
102
+ }
103
+
104
+ const provider = detectProviderFromRawId(actionTarget.sessionId);
105
+ if (!getDefaultProviderContext(provider).capabilities.delete) {
106
+ return createUnsupportedCapabilityResponse('delete', actionTarget.sessionId);
107
+ }
108
+
109
+ if (provider === 'claude-code' && actionTarget.isRemote) {
110
+ return createUnsupportedCapabilityResponse('delete', actionTarget.sessionId);
111
+ }
112
+
113
+ if (provider === 'claude-code') {
114
+ await markClaudeSessionDeleted(extractProviderRawId(actionTarget.sessionId));
115
+ const localSessionId = resolveLocalActionSessionId(id);
116
+ if (localSessionId) {
117
+ clearSessionForceUnarchived(localSessionId);
118
+ clearSessionStickyStatusBlocked(localSessionId);
90
119
  }
120
+ return Response.json({ success: true });
121
+ }
122
+
123
+ const sessionId = resolveLocalActionSessionId(id);
124
+ if (!sessionId && actionTarget.isRemote) {
125
+ return forwardRemoteDelete(actionTarget.hostId, actionTarget.sessionId);
91
126
  }
92
127
 
93
128
  if (!sessionId) {
@@ -215,4 +215,78 @@ describe('/api/sessions/[id]/open-editor', () => {
215
215
  expect(response.status).toBe(400);
216
216
  expect(data).toEqual({ error: 'Invalid action session id', reason: 'invalid_action_session_id' });
217
217
  });
218
+
219
+ it('rejects unsupported Claude open-editor requests before remote node execution', async () => {
220
+ const mockFetch = vi.fn();
221
+ vi.stubGlobal('fetch', mockFetch);
222
+
223
+ const response = await POST(
224
+ new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/open-editor', {
225
+ method: 'POST',
226
+ headers: { 'Content-Type': 'application/json' },
227
+ body: JSON.stringify({ tool: 'vscode' }),
228
+ }),
229
+ { params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }) }
230
+ );
231
+ const data = await response.json();
232
+
233
+ expect(response.status).toBe(403);
234
+ expect(data).toEqual({
235
+ error: 'Session action not supported by provider',
236
+ reason: 'provider_capability_unsupported',
237
+ provider: 'claude-code',
238
+ capability: 'openEditor',
239
+ });
240
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
241
+ expect(mockFetch).not.toHaveBeenCalled();
242
+ });
243
+
244
+ it('rejects unsupported scoped Claude sidechain open-editor requests before remote node execution', async () => {
245
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
246
+ const mockFetch = vi.fn();
247
+ vi.stubGlobal('fetch', mockFetch);
248
+
249
+ const response = await POST(
250
+ new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/open-editor`, {
251
+ method: 'POST',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: JSON.stringify({ tool: 'vscode' }),
254
+ }),
255
+ { params: Promise.resolve({ id: `node-1:${scopedSessionId}` }) }
256
+ );
257
+ const data = await response.json();
258
+
259
+ expect(response.status).toBe(403);
260
+ expect(data).toEqual({
261
+ error: 'Session action not supported by provider',
262
+ reason: 'provider_capability_unsupported',
263
+ provider: 'claude-code',
264
+ capability: 'openEditor',
265
+ });
266
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
267
+ expect(mockFetch).not.toHaveBeenCalled();
268
+ });
269
+
270
+ it('rejects local scoped Claude sidechain open-editor requests as unsupported capability', async () => {
271
+ const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
272
+
273
+ const response = await POST(
274
+ new Request(`http://localhost/api/sessions/local:${scopedSessionId}/open-editor`, {
275
+ method: 'POST',
276
+ headers: { 'Content-Type': 'application/json' },
277
+ body: JSON.stringify({ tool: 'vscode' }),
278
+ }),
279
+ { params: Promise.resolve({ id: `local:${scopedSessionId}` }) }
280
+ );
281
+ const data = await response.json();
282
+
283
+ expect(response.status).toBe(403);
284
+ expect(data).toEqual({
285
+ error: 'Session action not supported by provider',
286
+ reason: 'provider_capability_unsupported',
287
+ provider: 'claude-code',
288
+ capability: 'openEditor',
289
+ });
290
+ expect(mockListNodeRecords).not.toHaveBeenCalled();
291
+ });
218
292
  });
@@ -1,6 +1,7 @@
1
- import { parseActionSessionReference } from '@/lib/hostIdentity';
1
+ import { ActionSessionReference, parseActionSessionReference } from '@/lib/hostIdentity';
2
2
  import { listNodeRecords } from '@/lib/nodeRegistry';
3
3
  import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
4
+ import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
4
5
 
5
6
  const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
6
7
 
@@ -18,16 +19,35 @@ function createSessionNotFoundResponse() {
18
19
  );
19
20
  }
20
21
 
22
+ function createUnsupportedCapabilityResponse(capability: 'openEditor', sessionId: string) {
23
+ const provider = detectProviderFromRawId(sessionId);
24
+
25
+ return Response.json(
26
+ {
27
+ error: 'Session action not supported by provider',
28
+ reason: 'provider_capability_unsupported',
29
+ provider,
30
+ capability,
31
+ },
32
+ { status: 403 }
33
+ );
34
+ }
35
+
21
36
  export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
22
37
  const { id } = await params;
23
38
 
24
- let actionTarget;
39
+ let actionTarget: ActionSessionReference;
25
40
  try {
26
41
  actionTarget = parseActionSessionReference(id);
27
42
  } catch {
28
43
  return createInvalidActionSessionIdResponse();
29
44
  }
30
45
 
46
+ const provider = detectProviderFromRawId(actionTarget.sessionId);
47
+ if (!getDefaultProviderContext(provider).capabilities.openEditor) {
48
+ return createUnsupportedCapabilityResponse('openEditor', actionTarget.sessionId);
49
+ }
50
+
31
51
  if (!actionTarget.isRemote) {
32
52
  return createInvalidActionSessionIdResponse();
33
53
  }