@shepai/cli 1.53.2 → 1.54.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 (262) hide show
  1. package/apis/json-schema/NotificationEvent.yaml +4 -0
  2. package/dist/packages/core/src/domain/generated/output.d.ts +4 -0
  3. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  4. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +4 -0
  5. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -1
  6. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +19 -6
  7. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +5 -3
  8. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
  9. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +21 -13
  10. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  11. package/dist/src/presentation/cli/commands/ui.command.js +2 -1
  12. package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts +13 -0
  13. package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts.map +1 -0
  14. package/dist/src/presentation/web/app/api/agent-events/health/route.js +60 -0
  15. package/dist/src/presentation/web/app/api/agent-events/route.d.ts +8 -4
  16. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  17. package/dist/src/presentation/web/app/api/agent-events/route.js +227 -55
  18. package/dist/src/presentation/web/app/layout.d.ts.map +1 -1
  19. package/dist/src/presentation/web/app/layout.js +14 -0
  20. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +5 -1
  21. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  22. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +29 -1
  23. package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
  25. package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts +2 -0
  26. package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts.map +1 -0
  27. package/dist/src/presentation/web/components/common/sound-toggle/index.js +1 -0
  28. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts +2 -0
  29. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts.map +1 -0
  30. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.js +9 -0
  31. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts +8 -0
  32. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts.map +1 -0
  33. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.js +17 -0
  34. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  35. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +14 -2
  36. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +1 -0
  37. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +151 -19
  39. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
  40. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +2 -1
  41. package/dist/src/presentation/web/dev-server.js +14 -0
  42. package/dist/src/presentation/web/hooks/use-agent-events.d.ts +7 -0
  43. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
  44. package/dist/src/presentation/web/hooks/use-agent-events.js +135 -47
  45. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  46. package/dist/src/presentation/web/hooks/use-notifications.js +56 -16
  47. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
  48. package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -9
  49. package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts +6 -0
  50. package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts.map +1 -0
  51. package/dist/src/presentation/web/hooks/use-sound-enabled.js +27 -0
  52. package/dist/src/presentation/web/hooks/use-sound.d.ts +15 -0
  53. package/dist/src/presentation/web/hooks/use-sound.d.ts.map +1 -0
  54. package/dist/src/presentation/web/hooks/use-sound.js +83 -0
  55. package/dist/src/presentation/web/hooks/use-sound.stories.d.ts +8 -0
  56. package/dist/src/presentation/web/hooks/use-sound.stories.d.ts.map +1 -0
  57. package/dist/src/presentation/web/hooks/use-sound.stories.js +67 -0
  58. package/dist/src/presentation/web/public/agent-events-sw.d.ts +35 -0
  59. package/dist/src/presentation/web/public/agent-events-sw.d.ts.map +1 -0
  60. package/dist/src/presentation/web/public/agent-events-sw.js +140 -0
  61. package/dist/tsconfig.build.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/web/.next/BUILD_ID +1 -1
  64. package/web/.next/app-path-routes-manifest.json +1 -0
  65. package/web/.next/build-manifest.json +2 -2
  66. package/web/.next/cache/.previewinfo +1 -1
  67. package/web/.next/cache/.rscinfo +1 -1
  68. package/web/.next/cache/.tsbuildinfo +1 -1
  69. package/web/.next/cache/config.json +3 -3
  70. package/web/.next/fallback-build-manifest.json +2 -2
  71. package/web/.next/prerender-manifest.json +3 -3
  72. package/web/.next/required-server-files.js +1 -1
  73. package/web/.next/required-server-files.json +1 -1
  74. package/web/.next/routes-manifest.json +6 -0
  75. package/web/.next/server/app/_global-error.html +2 -2
  76. package/web/.next/server/app/_global-error.rsc +1 -1
  77. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  78. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  83. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  84. package/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
  85. package/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
  86. package/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
  87. package/web/.next/server/app/api/agent-events/health/route.js +6 -0
  88. package/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
  89. package/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
  90. package/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
  91. package/web/.next/server/app/api/agent-events/route.js +2 -1
  92. package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
  93. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  94. package/web/.next/server/app/page.js.nft.json +1 -1
  95. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  96. package/web/.next/server/app/version/page.js +1 -1
  97. package/web/.next/server/app/version/page.js.nft.json +1 -1
  98. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  99. package/web/.next/server/app-paths-manifest.json +1 -0
  100. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
  101. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -0
  102. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
  103. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js.map +1 -0
  104. package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
  105. package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js.map +1 -0
  106. package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
  107. package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js.map +1 -0
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  111. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js → server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
  112. package/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
  113. package/web/.next/server/chunks/ssr/_1632a21f._.js.map +1 -0
  114. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  115. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  117. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  119. package/web/.next/server/pages/500.html +2 -2
  120. package/web/.next/server/server-reference-manifest.js +1 -1
  121. package/web/.next/server/server-reference-manifest.json +15 -15
  122. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  123. package/web/.next/standalone/src/presentation/web/.next/app-path-routes-manifest.json +1 -0
  124. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  125. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  126. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  127. package/web/.next/standalone/src/presentation/web/.next/routes-manifest.json +6 -0
  128. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  129. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  130. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  131. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  132. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  133. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  134. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  135. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  136. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  137. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
  138. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
  139. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
  140. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js +6 -0
  141. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
  142. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
  143. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
  144. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js +2 -1
  145. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
  146. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  147. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  148. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  149. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +1 -1
  150. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  151. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/app-paths-manifest.json +1 -0
  153. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
  154. package/web/.next/standalone/src/presentation/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
  155. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
  156. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
  157. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  158. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  159. package/web/.next/{server/chunks/ssr/[root-of-the-server]__249c74f6._.js → standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
  160. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
  161. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  162. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  163. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  164. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  165. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  166. package/web/.next/standalone/src/presentation/web/app/api/agent-events/health/route.ts +69 -0
  167. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +258 -54
  168. package/web/.next/standalone/src/presentation/web/app/layout.tsx +14 -0
  169. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +39 -2
  170. package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +1 -0
  171. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/index.ts +1 -0
  172. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.tsx +27 -0
  173. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.tsx +21 -0
  174. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +15 -1
  175. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +194 -31
  176. package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +3 -1
  177. package/web/.next/standalone/src/presentation/web/dev-server.ts +21 -0
  178. package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +161 -52
  179. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +39 -10
  180. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +68 -15
  181. package/web/.next/standalone/src/presentation/web/hooks/use-sound-enabled.ts +38 -0
  182. package/web/.next/standalone/src/presentation/web/hooks/use-sound.stories.tsx +109 -0
  183. package/web/.next/standalone/src/presentation/web/hooks/use-sound.ts +101 -0
  184. package/web/.next/standalone/src/presentation/web/public/agent-events-sw.js +164 -0
  185. package/web/.next/standalone/src/presentation/web/public/sounds/button.wav +0 -0
  186. package/web/.next/standalone/src/presentation/web/public/sounds/caution.wav +0 -0
  187. package/web/.next/standalone/src/presentation/web/public/sounds/celebration.wav +0 -0
  188. package/web/.next/standalone/src/presentation/web/public/sounds/disabled.wav +0 -0
  189. package/web/.next/standalone/src/presentation/web/public/sounds/notification.wav +0 -0
  190. package/web/.next/standalone/src/presentation/web/public/sounds/progress_loop.wav +0 -0
  191. package/web/.next/standalone/src/presentation/web/public/sounds/ringtone_loop.wav +0 -0
  192. package/web/.next/standalone/src/presentation/web/public/sounds/select.wav +0 -0
  193. package/web/.next/standalone/src/presentation/web/public/sounds/swipe.wav +0 -0
  194. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_01.wav +0 -0
  195. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_02.wav +0 -0
  196. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_03.wav +0 -0
  197. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_04.wav +0 -0
  198. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_05.wav +0 -0
  199. package/web/.next/standalone/src/presentation/web/public/sounds/tap_01.wav +0 -0
  200. package/web/.next/standalone/src/presentation/web/public/sounds/tap_02.wav +0 -0
  201. package/web/.next/standalone/src/presentation/web/public/sounds/tap_03.wav +0 -0
  202. package/web/.next/standalone/src/presentation/web/public/sounds/tap_04.wav +0 -0
  203. package/web/.next/standalone/src/presentation/web/public/sounds/tap_05.wav +0 -0
  204. package/web/.next/standalone/src/presentation/web/public/sounds/toggle_off.wav +0 -0
  205. package/web/.next/standalone/src/presentation/web/public/sounds/toggle_on.wav +0 -0
  206. package/web/.next/standalone/src/presentation/web/public/sounds/transition_down.wav +0 -0
  207. package/web/.next/standalone/src/presentation/web/public/sounds/transition_up.wav +0 -0
  208. package/web/.next/standalone/src/presentation/web/public/sounds/type_01.wav +0 -0
  209. package/web/.next/standalone/src/presentation/web/public/sounds/type_02.wav +0 -0
  210. package/web/.next/standalone/src/presentation/web/public/sounds/type_03.wav +0 -0
  211. package/web/.next/standalone/src/presentation/web/public/sounds/type_04.wav +0 -0
  212. package/web/.next/standalone/src/presentation/web/public/sounds/type_05.wav +0 -0
  213. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  214. package/web/.next/static/chunks/1f13bc20e938335c.css +2 -0
  215. package/web/.next/static/chunks/702384d364482e4b.js +10 -0
  216. package/web/.next/static/chunks/{68fb91b6eca14536.js → 8f4461aee9a05741.js} +2 -2
  217. package/web/.next/static/chunks/9a82d8c76f175235.js +1 -0
  218. package/web/.next/trace +1 -1
  219. package/web/.next/trace-build +1 -1
  220. package/web/.next/types/link.d.ts +1 -0
  221. package/web/.next/types/routes.d.ts +2 -1
  222. package/web/.next/types/validator.ts +9 -0
  223. package/web/public/agent-events-sw.js +164 -0
  224. package/web/public/sounds/button.wav +0 -0
  225. package/web/public/sounds/caution.wav +0 -0
  226. package/web/public/sounds/celebration.wav +0 -0
  227. package/web/public/sounds/disabled.wav +0 -0
  228. package/web/public/sounds/notification.wav +0 -0
  229. package/web/public/sounds/progress_loop.wav +0 -0
  230. package/web/public/sounds/ringtone_loop.wav +0 -0
  231. package/web/public/sounds/select.wav +0 -0
  232. package/web/public/sounds/swipe.wav +0 -0
  233. package/web/public/sounds/swipe_01.wav +0 -0
  234. package/web/public/sounds/swipe_02.wav +0 -0
  235. package/web/public/sounds/swipe_03.wav +0 -0
  236. package/web/public/sounds/swipe_04.wav +0 -0
  237. package/web/public/sounds/swipe_05.wav +0 -0
  238. package/web/public/sounds/tap_01.wav +0 -0
  239. package/web/public/sounds/tap_02.wav +0 -0
  240. package/web/public/sounds/tap_03.wav +0 -0
  241. package/web/public/sounds/tap_04.wav +0 -0
  242. package/web/public/sounds/tap_05.wav +0 -0
  243. package/web/public/sounds/toggle_off.wav +0 -0
  244. package/web/public/sounds/toggle_on.wav +0 -0
  245. package/web/public/sounds/transition_down.wav +0 -0
  246. package/web/public/sounds/transition_up.wav +0 -0
  247. package/web/public/sounds/type_01.wav +0 -0
  248. package/web/public/sounds/type_02.wav +0 -0
  249. package/web/public/sounds/type_03.wav +0 -0
  250. package/web/public/sounds/type_04.wav +0 -0
  251. package/web/public/sounds/type_05.wav +0 -0
  252. package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
  253. package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js.map +0 -1
  254. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +0 -1
  255. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
  256. package/web/.next/static/chunks/13d5334b17480a16.js +0 -10
  257. package/web/.next/static/chunks/16dec8021e251230.css +0 -2
  258. package/web/.next/static/chunks/22f30ce91a3c79c1.js +0 -1
  259. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js.map → [root-of-the-server]__6e8b5181._.js.map} +0 -0
  260. /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_buildManifest.js +0 -0
  261. /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_clientMiddlewareManifest.json +0 -0
  262. /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_ssgManifest.js +0 -0
@@ -9,6 +9,8 @@ import { deleteFeature } from '../../../app/actions/delete-feature.js';
9
9
  import { addRepository } from '../../../app/actions/add-repository.js';
10
10
  import { deleteRepository } from '../../../app/actions/delete-repository.js';
11
11
  import { useAgentEventsContext } from '../../../hooks/agent-events-provider.js';
12
+ import { useSound } from '../../../hooks/use-sound.js';
13
+ import { mapEventTypeToState, mapPhaseNameToLifecycle, } from '../../common/feature-node/derive-feature-state.js';
12
14
  let nextFeatureId = 0;
13
15
  export function useControlCenterState(initialNodes, initialEdges) {
14
16
  const router = useRouter();
@@ -28,6 +30,9 @@ export function useControlCenterState(initialNodes, initialEdges) {
28
30
  const [selectedNode, setSelectedNode] = useState(null);
29
31
  const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false);
30
32
  const [isDeleting, setIsDeleting] = useState(false);
33
+ const deleteSound = useSound('transition_down', { volume: 0.5 });
34
+ const createSound = useSound('transition_up', { volume: 0.5 });
35
+ const clickSound = useSound('tap_01', { volume: 0.3 });
31
36
  const [pendingRepoNodeId, setPendingRepoNodeId] = useState(null);
32
37
  // Sync server props into local state when router.refresh() delivers new data
33
38
  const initialNodeKey = initialNodes
@@ -38,6 +43,17 @@ export function useControlCenterState(initialNodes, initialEdges) {
38
43
  .map((e) => e.id)
39
44
  .sort()
40
45
  .join(',');
46
+ // Track previous feature states so we can detect transitions on server refresh
47
+ const prevFeatureStatesRef = useRef(new Map());
48
+ // Stable key that changes when feature DATA changes (state/lifecycle), not just IDs.
49
+ const initialDataKey = initialNodes
50
+ .filter((n) => n.type === 'featureNode')
51
+ .map((n) => {
52
+ const d = n.data;
53
+ return `${n.id}:${d.state}:${d.lifecycle}`;
54
+ })
55
+ .sort()
56
+ .join(',');
41
57
  // Sync server props into local state — keyed by derived strings (initialNodeKey/initialEdgeKey)
42
58
  // to avoid infinite re-renders from unstable array references.
43
59
  useEffect(() => {
@@ -69,33 +85,128 @@ export function useControlCenterState(initialNodes, initialEdges) {
69
85
  useEffect(() => {
70
86
  setEdges(initialEdges);
71
87
  }, [initialEdgeKey, initialEdges, setEdges]);
72
- // Refresh server data when SSE agent events arrive (status changes)
73
- const { lastEvent } = useAgentEventsContext();
74
- const processedEventRef = useRef(null);
88
+ // Fallback notifications from server-refresh state transitions.
89
+ // Fires only when SSE didn't already deliver the matching event for a feature,
90
+ // e.g. when the SSE connection was down or the event was seeded into the cache.
91
+ const { events } = useAgentEventsContext();
92
+ useEffect(() => {
93
+ const prevStates = prevFeatureStatesRef.current;
94
+ for (const node of initialNodes) {
95
+ if (node.type !== 'featureNode')
96
+ continue;
97
+ const data = node.data;
98
+ const prev = prevStates.get(node.id);
99
+ if (prev !== undefined && prev !== data.state) {
100
+ // Check if SSE already delivered a matching event for this feature
101
+ const sseAlreadyCovered = events.some((e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state);
102
+ if (!sseAlreadyCovered) {
103
+ if (data.state === 'done') {
104
+ toast.success(data.name, { description: 'Feature completed!' });
105
+ }
106
+ else if (data.state === 'action-required') {
107
+ toast.warning(data.name, {
108
+ description: 'Waiting for your approval',
109
+ action: {
110
+ label: 'Review',
111
+ onClick: () => {
112
+ window.dispatchEvent(new CustomEvent('shep:select-feature', {
113
+ detail: { featureId: data.featureId },
114
+ }));
115
+ },
116
+ },
117
+ });
118
+ }
119
+ else if (data.state === 'error') {
120
+ toast.error(data.name, { description: data.errorMessage ?? 'Agent failed' });
121
+ }
122
+ }
123
+ }
124
+ prevStates.set(node.id, data.state);
125
+ }
126
+ }, [initialDataKey, initialNodes, events]);
127
+ // Targeted optimistic updates from SSE agent events + debounced reconciliation.
128
+ // Uses `events` array (not `lastEvent`) so that React batching cannot silently
129
+ // drop events when multiple SSE messages arrive in the same tick.
130
+ const processedEventCountRef = useRef(0);
131
+ const reconcileTimerRef = useRef(undefined);
75
132
  useEffect(() => {
76
- if (!lastEvent)
133
+ return () => clearTimeout(reconcileTimerRef.current);
134
+ }, []);
135
+ useEffect(() => {
136
+ if (events.length <= processedEventCountRef.current)
77
137
  return;
78
- const key = `${lastEvent.agentRunId}-${lastEvent.eventType}-${lastEvent.timestamp}`;
79
- if (processedEventRef.current === key)
138
+ const newEvents = events.slice(processedEventCountRef.current);
139
+ processedEventCountRef.current = events.length;
140
+ for (const event of newEvents) {
141
+ const newState = mapEventTypeToState(event.eventType);
142
+ const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
143
+ // Targeted node update — only clone the matched node
144
+ setNodes((prev) => prev.map((node) => {
145
+ if (node.type !== 'featureNode')
146
+ return node;
147
+ const data = node.data;
148
+ if (data.featureId !== event.featureId)
149
+ return node;
150
+ return {
151
+ ...node,
152
+ data: {
153
+ ...data,
154
+ state: newState,
155
+ ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
156
+ },
157
+ };
158
+ }));
159
+ // Update drawer if it's showing the matched feature
160
+ setSelectedNode((prev) => {
161
+ if (prev?.featureId !== event.featureId)
162
+ return prev;
163
+ return {
164
+ ...prev,
165
+ state: newState,
166
+ ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
167
+ };
168
+ });
169
+ }
170
+ // Debounced background reconciliation (3s after last SSE event)
171
+ clearTimeout(reconcileTimerRef.current);
172
+ reconcileTimerRef.current = setTimeout(() => router.refresh(), 3000);
173
+ }, [events, router]);
174
+ // Periodic polling fallback: refresh server data every 5s when any feature
175
+ // is in an active state (running/action-required). This ensures the UI stays
176
+ // current even if the SSE connection drops — belt-and-suspenders alongside SSE.
177
+ useEffect(() => {
178
+ const hasActiveFeature = nodes.some((n) => {
179
+ if (n.type !== 'featureNode')
180
+ return false;
181
+ const data = n.data;
182
+ return (data.state === 'running' || data.state === 'action-required' || data.state === 'creating');
183
+ });
184
+ if (!hasActiveFeature)
80
185
  return;
81
- processedEventRef.current = key;
82
- router.refresh();
83
- }, [lastEvent, router]);
186
+ const interval = setInterval(() => router.refresh(), 5_000);
187
+ return () => clearInterval(interval);
188
+ }, [nodes, router]);
84
189
  const onNodesChange = useCallback((changes) => {
85
190
  setNodes((ns) => applyNodeChanges(changes, ns));
86
191
  }, []);
192
+ const closeSound = useSound('transition_down', { volume: 0.01 });
87
193
  const clearSelection = useCallback(() => {
88
- setSelectedNode(null);
89
- }, []);
194
+ setSelectedNode((prev) => {
195
+ if (prev)
196
+ closeSound.play();
197
+ return null;
198
+ });
199
+ }, [closeSound]);
90
200
  const handleNodeClick = useCallback((_event, node) => {
91
201
  if (node.type === 'featureNode') {
92
202
  const data = node.data;
93
203
  if (data.state === 'creating')
94
204
  return;
205
+ clickSound.play();
95
206
  setIsCreateDrawerOpen(false);
96
207
  setSelectedNode(data);
97
208
  }
98
- }, []);
209
+ }, [clickSound]);
99
210
  // Keyboard shortcut: Escape to deselect
100
211
  useEffect(() => {
101
212
  const handleKeyDown = (e) => {
@@ -242,9 +353,10 @@ export function useControlCenterState(initialNodes, initialEdges) {
242
353
  return id;
243
354
  }, [setEdges]);
244
355
  const handleAddFeature = useCallback(() => {
356
+ clickSound.play();
245
357
  setSelectedNode(null);
246
358
  setIsCreateDrawerOpen(true);
247
- }, []);
359
+ }, [clickSound]);
248
360
  const [pendingParentFeatureId, setPendingParentFeatureId] = useState();
249
361
  const handleCreateFeatureSubmit = useCallback((data) => {
250
362
  // 1. Insert optimistic node instantly
@@ -272,6 +384,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
272
384
  toast.error(result.error);
273
385
  return;
274
386
  }
387
+ createSound.play();
275
388
  router.refresh();
276
389
  })
277
390
  .catch(() => {
@@ -280,11 +393,12 @@ export function useControlCenterState(initialNodes, initialEdges) {
280
393
  setEdges((prev) => prev.filter((e) => e.target !== tempId));
281
394
  toast.error('Failed to create feature');
282
395
  });
283
- }, [router, createFeatureNode, pendingRepoNodeId, pendingParentFeatureId, setEdges]);
396
+ }, [router, createFeatureNode, pendingRepoNodeId, createSound, pendingParentFeatureId, setEdges]);
284
397
  const closeCreateDrawer = useCallback(() => {
398
+ closeSound.play();
285
399
  setIsCreateDrawerOpen(false);
286
400
  setPendingParentFeatureId(undefined);
287
- }, []);
401
+ }, [closeSound]);
288
402
  const handleDeleteFeature = useCallback(async (featureId) => {
289
403
  setIsDeleting(true);
290
404
  try {
@@ -305,6 +419,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
305
419
  setEdges(result.edges);
306
420
  return result.nodes;
307
421
  });
422
+ deleteSound.play();
308
423
  toast.success('Feature deleted successfully');
309
424
  router.refresh();
310
425
  }
@@ -314,7 +429,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
314
429
  finally {
315
430
  setIsDeleting(false);
316
431
  }
317
- }, [router, edges, setEdges]);
432
+ }, [router, edges, deleteSound, setEdges]);
318
433
  const handleDeleteRepository = useCallback(async (repositoryId) => {
319
434
  const repoNodeId = `repo-${repositoryId}`;
320
435
  // Optimistic: remove node and its edges immediately
@@ -327,6 +442,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
327
442
  router.refresh();
328
443
  return;
329
444
  }
445
+ deleteSound.play();
330
446
  toast.success('Repository removed');
331
447
  router.refresh();
332
448
  }
@@ -334,12 +450,13 @@ export function useControlCenterState(initialNodes, initialEdges) {
334
450
  toast.error('Failed to remove repository');
335
451
  router.refresh();
336
452
  }
337
- }, [router, setEdges]);
453
+ }, [router, deleteSound, setEdges]);
338
454
  const handleAddFeatureToRepo = useCallback((repoNodeId) => {
455
+ clickSound.play();
339
456
  setSelectedNode(null);
340
457
  setPendingRepoNodeId(repoNodeId);
341
458
  setIsCreateDrawerOpen(true);
342
- }, []);
459
+ }, [clickSound]);
343
460
  const handleAddFeatureToFeature = useCallback((featureNodeId) => {
344
461
  // Extract feature ID from node ID (format: "feat-<uuid>")
345
462
  const featureId = featureNodeId.startsWith('feat-') ? featureNodeId.slice(5) : featureNodeId;
@@ -428,6 +545,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
428
545
  target: e.target === tempId ? realId : e.target,
429
546
  id: e.id.replace(tempId, realId),
430
547
  })));
548
+ createSound.play();
431
549
  router.refresh();
432
550
  })
433
551
  .catch(() => {
@@ -439,7 +557,20 @@ export function useControlCenterState(initialNodes, initialEdges) {
439
557
  : n));
440
558
  toast.error('Failed to add repository');
441
559
  });
442
- }, [router, setEdges]);
560
+ }, [router, createSound, setEdges]);
561
+ // Ref keeps latest nodes so selectFeatureById has a stable identity
562
+ const nodesRef = useRef(nodes);
563
+ nodesRef.current = nodes;
564
+ const selectFeatureById = useCallback((featureId) => {
565
+ const node = nodesRef.current.find((n) => n.type === 'featureNode' && n.data.featureId === featureId);
566
+ if (node) {
567
+ const data = node.data;
568
+ if (data.state !== 'creating') {
569
+ setIsCreateDrawerOpen(false);
570
+ setSelectedNode(data);
571
+ }
572
+ }
573
+ }, []);
443
574
  const pendingNode = pendingRepoNodeId ? nodes.find((n) => n.id === pendingRepoNodeId) : null;
444
575
  const pendingRepositoryPath = pendingNode?.data?.repositoryPath ?? '';
445
576
  return {
@@ -464,5 +595,6 @@ export function useControlCenterState(initialNodes, initialEdges) {
464
595
  handleDeleteRepository,
465
596
  isDeleting,
466
597
  createFeatureNode,
598
+ selectFeatureById,
467
599
  };
468
600
  }
@@ -1 +1 @@
1
- {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/layouts/app-shell/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpD,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AA0BD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,2CAMnD"}
1
+ {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/layouts/app-shell/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQpD,UAAU,aAAa;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AA2BD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,2CAMnD"}
@@ -4,6 +4,7 @@ import { useCallback } from 'react';
4
4
  import { SidebarProvider, SidebarInset } from '../../ui/sidebar.js';
5
5
  import { AppSidebar } from '../../layouts/app-sidebar/index.js';
6
6
  import { ThemeToggle } from '../../common/theme-toggle/index.js';
7
+ import { SoundToggle } from '../../common/sound-toggle/index.js';
7
8
  import { AgentEventsProvider } from '../../../hooks/agent-events-provider.js';
8
9
  import { useNotifications } from '../../../hooks/use-notifications.js';
9
10
  /** Inner shell that consumes the agent-events context for notifications. */
@@ -13,7 +14,7 @@ function AppShellInner({ children }) {
13
14
  const handleNewFeature = useCallback(() => {
14
15
  window.dispatchEvent(new CustomEvent('shep:open-create-drawer'));
15
16
  }, []);
16
- return (_jsxs(SidebarProvider, { children: [_jsx(AppSidebar, { features: [], onNewFeature: handleNewFeature }), _jsx(SidebarInset, { children: _jsxs("div", { className: "relative h-full", children: [_jsx("div", { className: "absolute top-3 right-3 z-50", children: _jsx(ThemeToggle, {}) }), _jsx("main", { className: "h-full", children: children })] }) })] }));
17
+ return (_jsxs(SidebarProvider, { children: [_jsx(AppSidebar, { features: [], onNewFeature: handleNewFeature }), _jsx(SidebarInset, { children: _jsxs("div", { className: "relative h-full", children: [_jsxs("div", { className: "absolute top-3 right-3 z-50 flex gap-1", children: [_jsx(SoundToggle, {}), _jsx(ThemeToggle, {})] }), _jsx("main", { className: "h-full", children: children })] }) })] }));
17
18
  }
18
19
  export function AppShell({ children }) {
19
20
  return (_jsx(AgentEventsProvider, { children: _jsx(AppShellInner, { children: children }) }));
@@ -17,6 +17,7 @@ import path from 'node:path';
17
17
  import { initializeContainer, container } from '../../../packages/core/src/infrastructure/di/container.js';
18
18
  import { InitializeSettingsUseCase } from '../../../packages/core/src/application/use-cases/settings/initialize-settings.use-case.js';
19
19
  import { initializeSettings } from '../../../packages/core/src/infrastructure/services/settings.service.js';
20
+ import { initializeNotificationWatcher, getNotificationWatcher, } from '../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.js';
20
21
  const DEFAULT_PORT = 3000;
21
22
  async function isPortAvailable(port) {
22
23
  return new Promise((resolve) => {
@@ -53,6 +54,13 @@ async function main() {
53
54
  const initSettingsUseCase = container.resolve(InitializeSettingsUseCase);
54
55
  const settings = await initSettingsUseCase.execute();
55
56
  initializeSettings(settings);
57
+ // Start notification watcher for real-time SSE events (same as shep ui)
58
+ const runRepo = container.resolve('IAgentRunRepository');
59
+ const phaseTimingRepo = container.resolve('IPhaseTimingRepository');
60
+ const featureRepo = container.resolve('IFeatureRepository');
61
+ const notificationService = container.resolve('INotificationService');
62
+ initializeNotificationWatcher(runRepo, phaseTimingRepo, featureRepo, notificationService);
63
+ getNotificationWatcher().start();
56
64
  }
57
65
  catch (error) {
58
66
  console.warn('[dev-server] DI initialization failed — features will be empty:', error);
@@ -92,6 +100,12 @@ async function main() {
92
100
  console.log('\n[dev-server] Shutting down...');
93
101
  const forceExit = setTimeout(() => process.exit(0), 2000);
94
102
  try {
103
+ try {
104
+ getNotificationWatcher().stop();
105
+ }
106
+ catch {
107
+ /* not initialized */
108
+ }
95
109
  server.closeAllConnections();
96
110
  await Promise.all([
97
111
  new Promise((resolve) => server.close(() => resolve())),
@@ -8,5 +8,12 @@ export interface UseAgentEventsResult {
8
8
  lastEvent: NotificationEvent | null;
9
9
  connectionStatus: ConnectionStatus;
10
10
  }
11
+ /**
12
+ * Hook that receives real-time agent notification events via a Service Worker.
13
+ *
14
+ * The SW maintains a single SSE connection to /api/agent-events and
15
+ * broadcasts events to all open tabs. Falls back to direct EventSource
16
+ * when Service Workers are unavailable.
17
+ */
11
18
  export declare function useAgentEvents(options?: UseAgentEventsOptions): UseAgentEventsResult;
12
19
  //# sourceMappingURL=use-agent-events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-events.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-agent-events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;AAE3E,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAOD,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,oBAAoB,CAsFpF"}
1
+ {"version":3,"file":"use-agent-events.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-agent-events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;AAE3E,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,oBAAoB,CA2GpF"}
@@ -1,53 +1,143 @@
1
1
  'use client';
2
2
  import { useCallback, useEffect, useRef, useState } from 'react';
3
- const BASE_BACKOFF_MS = 1000;
4
- const MAX_BACKOFF_MS = 30_000;
5
- /** Only reset backoff after connection is stable for this long */
6
- const STABLE_CONNECTION_MS = 5_000;
3
+ const SW_PATH = '/agent-events-sw.js';
4
+ /**
5
+ * Hook that receives real-time agent notification events via a Service Worker.
6
+ *
7
+ * The SW maintains a single SSE connection to /api/agent-events and
8
+ * broadcasts events to all open tabs. Falls back to direct EventSource
9
+ * when Service Workers are unavailable.
10
+ */
7
11
  export function useAgentEvents(options) {
8
12
  const [events, setEvents] = useState([]);
9
13
  const [lastEvent, setLastEvent] = useState(null);
10
14
  const [connectionStatus, setConnectionStatus] = useState('disconnected');
11
- const backoffRef = useRef(BASE_BACKOFF_MS);
12
- const reconnectTimerRef = useRef(null);
13
- const stableTimerRef = useRef(null);
14
- const esRef = useRef(null);
15
15
  const runId = options?.runId;
16
- const connect = useCallback(() => {
17
- if (typeof window === 'undefined' || typeof EventSource === 'undefined') {
16
+ const swRef = useRef(null);
17
+ const onMessage = useCallback((event) => {
18
+ const msg = event.data;
19
+ if (!msg || typeof msg !== 'object')
18
20
  return;
21
+ if (msg.type === 'notification') {
22
+ const parsed = msg.data;
23
+ setEvents((prev) => [...prev, parsed]);
24
+ setLastEvent(parsed);
19
25
  }
20
- // Prevent duplicate connections
21
- if (esRef.current) {
26
+ else if (msg.type === 'status') {
27
+ setConnectionStatus(msg.status);
28
+ }
29
+ }, []);
30
+ useEffect(() => {
31
+ if (typeof window === 'undefined')
22
32
  return;
33
+ // ?sse=direct bypasses the Service Worker for debugging
34
+ const bypassSW = typeof location !== 'undefined' && new URLSearchParams(location.search).has('sse');
35
+ // Fallback: direct EventSource when SW not supported or bypassed
36
+ if (!navigator.serviceWorker || bypassSW) {
37
+ return connectDirectEventSource(runId, setEvents, setLastEvent, setConnectionStatus);
23
38
  }
39
+ let cancelled = false;
40
+ const fallbackCleanupRef = { current: undefined };
41
+ function subscribeToWorker(worker) {
42
+ if (cancelled)
43
+ return;
44
+ swRef.current = worker;
45
+ worker.postMessage({ type: 'subscribe', runId });
46
+ setConnectionStatus('connecting');
47
+ }
48
+ // Listen for messages from whatever SW controls this page
49
+ navigator.serviceWorker.addEventListener('message', onMessage);
50
+ // Re-subscribe when SW controller changes (e.g., after SW update via skipWaiting)
51
+ function handleControllerChange() {
52
+ if (cancelled)
53
+ return;
54
+ const newController = navigator.serviceWorker.controller;
55
+ if (newController) {
56
+ swRef.current = newController;
57
+ newController.postMessage({ type: 'subscribe', runId });
58
+ setConnectionStatus('connecting');
59
+ }
60
+ }
61
+ navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
62
+ navigator.serviceWorker
63
+ .register(SW_PATH, { scope: '/' })
64
+ .then((registration) => {
65
+ if (cancelled)
66
+ return;
67
+ // Use the active worker, or wait for it to activate
68
+ const sw = registration.active ?? registration.installing ?? registration.waiting;
69
+ if (!sw) {
70
+ // No worker at all — fall back to direct EventSource
71
+ fallbackCleanupRef.current = connectDirectEventSource(runId, setEvents, setLastEvent, setConnectionStatus);
72
+ return;
73
+ }
74
+ if (sw.state === 'activated') {
75
+ subscribeToWorker(sw);
76
+ }
77
+ else {
78
+ sw.addEventListener('statechange', () => {
79
+ if (sw.state === 'activated')
80
+ subscribeToWorker(sw);
81
+ });
82
+ }
83
+ })
84
+ .catch(() => {
85
+ // SW registration failed — fall back to direct EventSource
86
+ if (!cancelled) {
87
+ fallbackCleanupRef.current = connectDirectEventSource(runId, setEvents, setLastEvent, setConnectionStatus);
88
+ }
89
+ });
90
+ return () => {
91
+ cancelled = true;
92
+ navigator.serviceWorker.removeEventListener('message', onMessage);
93
+ navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
94
+ swRef.current?.postMessage({ type: 'unsubscribe' });
95
+ swRef.current = null;
96
+ fallbackCleanupRef.current?.();
97
+ };
98
+ }, [runId, onMessage]);
99
+ return { events, lastEvent, connectionStatus };
100
+ }
101
+ // --- EventSource fallback (identical to the previous implementation) ---
102
+ const BASE_BACKOFF_MS = 1000;
103
+ const MAX_BACKOFF_MS = 30_000;
104
+ const STABLE_CONNECTION_MS = 5_000;
105
+ function connectDirectEventSource(runId, setEvents, setLastEvent, setConnectionStatus) {
106
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
107
+ if (typeof EventSource === 'undefined')
108
+ return () => { };
109
+ let es = null;
110
+ let stopped = false;
111
+ let backoff = BASE_BACKOFF_MS;
112
+ let reconnectTimer = null;
113
+ let stableTimer = null;
114
+ function connect() {
115
+ if (stopped)
116
+ return;
24
117
  const url = runId
25
118
  ? `/api/agent-events?runId=${encodeURIComponent(runId)}`
26
119
  : '/api/agent-events';
27
- const es = new EventSource(url);
28
- esRef.current = es;
120
+ es = new EventSource(url);
29
121
  setConnectionStatus('connecting');
30
122
  es.onopen = () => {
31
123
  setConnectionStatus('connected');
32
- // Only reset backoff after connection stays open for a while
33
- stableTimerRef.current = setTimeout(() => {
34
- stableTimerRef.current = null;
35
- backoffRef.current = BASE_BACKOFF_MS;
124
+ stableTimer = setTimeout(() => {
125
+ stableTimer = null;
126
+ backoff = BASE_BACKOFF_MS;
36
127
  }, STABLE_CONNECTION_MS);
37
128
  };
38
129
  es.onerror = () => {
39
- es.close();
40
- esRef.current = null;
130
+ es?.close();
131
+ es = null;
41
132
  setConnectionStatus('disconnected');
42
- // Cancel stable timer — connection wasn't stable
43
- if (stableTimerRef.current !== null) {
44
- clearTimeout(stableTimerRef.current);
45
- stableTimerRef.current = null;
133
+ if (stableTimer !== null) {
134
+ clearTimeout(stableTimer);
135
+ stableTimer = null;
46
136
  }
47
- const delay = backoffRef.current;
48
- backoffRef.current = Math.min(delay * 2, MAX_BACKOFF_MS);
49
- reconnectTimerRef.current = setTimeout(() => {
50
- reconnectTimerRef.current = null;
137
+ const delay = backoff;
138
+ backoff = Math.min(delay * 2, MAX_BACKOFF_MS);
139
+ reconnectTimer = setTimeout(() => {
140
+ reconnectTimer = null;
51
141
  connect();
52
142
  }, delay);
53
143
  };
@@ -56,23 +146,21 @@ export function useAgentEvents(options) {
56
146
  setEvents((prev) => [...prev, parsed]);
57
147
  setLastEvent(parsed);
58
148
  }));
59
- }, [runId]);
60
- useEffect(() => {
61
- connect();
62
- return () => {
63
- if (reconnectTimerRef.current !== null) {
64
- clearTimeout(reconnectTimerRef.current);
65
- reconnectTimerRef.current = null;
66
- }
67
- if (stableTimerRef.current !== null) {
68
- clearTimeout(stableTimerRef.current);
69
- stableTimerRef.current = null;
70
- }
71
- if (esRef.current) {
72
- esRef.current.close();
73
- esRef.current = null;
74
- }
75
- };
76
- }, [connect]);
77
- return { events, lastEvent, connectionStatus };
149
+ }
150
+ connect();
151
+ return () => {
152
+ stopped = true;
153
+ if (reconnectTimer !== null) {
154
+ clearTimeout(reconnectTimer);
155
+ reconnectTimer = null;
156
+ }
157
+ if (stableTimer !== null) {
158
+ clearTimeout(stableTimer);
159
+ stableTimer = null;
160
+ }
161
+ if (es) {
162
+ es.close();
163
+ es = null;
164
+ }
165
+ };
78
166
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-notifications.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,wBAAwB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,sBAAsB,EAAE,sBAAsB,CAAC;CAChD;AAqBD,wBAAgB,gBAAgB,IAAI,sBAAsB,CAmCzD"}
1
+ {"version":3,"file":"use-notifications.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,sBAAsB;IACrC,wBAAwB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,sBAAsB,EAAE,sBAAsB,CAAC;CAChD;AA4CD,wBAAgB,gBAAgB,IAAI,sBAAsB,CAgEzD"}