@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.
- package/apis/json-schema/NotificationEvent.yaml +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +4 -0
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +19 -6
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +5 -3
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +21 -13
- package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/ui.command.js +2 -1
- package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts +13 -0
- package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts.map +1 -0
- package/dist/src/presentation/web/app/api/agent-events/health/route.js +60 -0
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts +8 -4
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/agent-events/route.js +227 -55
- package/dist/src/presentation/web/app/layout.d.ts.map +1 -1
- package/dist/src/presentation/web/app/layout.js +14 -0
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +5 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +29 -1
- package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
- package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/index.js +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts +2 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.js +9 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts +8 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.js +17 -0
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +14 -2
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +1 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +151 -19
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +2 -1
- package/dist/src/presentation/web/dev-server.js +14 -0
- package/dist/src/presentation/web/hooks/use-agent-events.d.ts +7 -0
- package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-agent-events.js +135 -47
- package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.js +56 -16
- package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -9
- package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts +6 -0
- package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound-enabled.js +27 -0
- package/dist/src/presentation/web/hooks/use-sound.d.ts +15 -0
- package/dist/src/presentation/web/hooks/use-sound.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound.js +83 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.d.ts +8 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.js +67 -0
- package/dist/src/presentation/web/public/agent-events-sw.d.ts +35 -0
- package/dist/src/presentation/web/public/agent-events-sw.d.ts.map +1 -0
- package/dist/src/presentation/web/public/agent-events-sw.js +140 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
- package/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
- package/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
- package/web/.next/server/app/api/agent-events/health/route.js +6 -0
- package/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
- package/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
- package/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
- package/web/.next/server/app/api/agent-events/route.js +2 -1
- package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page.js +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- 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
- package/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
- package/web/.next/server/chunks/ssr/_1632a21f._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/routes-manifest.json +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js +2 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
- 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
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- 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
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/app/api/agent-events/health/route.ts +69 -0
- package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +258 -54
- package/web/.next/standalone/src/presentation/web/app/layout.tsx +14 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +39 -2
- package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/index.ts +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.tsx +27 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.tsx +21 -0
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +15 -1
- package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +194 -31
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +3 -1
- package/web/.next/standalone/src/presentation/web/dev-server.ts +21 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +161 -52
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +39 -10
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +68 -15
- package/web/.next/standalone/src/presentation/web/hooks/use-sound-enabled.ts +38 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-sound.stories.tsx +109 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-sound.ts +101 -0
- package/web/.next/standalone/src/presentation/web/public/agent-events-sw.js +164 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/button.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/caution.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/celebration.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/disabled.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/notification.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/progress_loop.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/ringtone_loop.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/select.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/toggle_off.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/toggle_on.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/transition_down.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/transition_up.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/1f13bc20e938335c.css +2 -0
- package/web/.next/static/chunks/702384d364482e4b.js +10 -0
- package/web/.next/static/chunks/{68fb91b6eca14536.js → 8f4461aee9a05741.js} +2 -2
- package/web/.next/static/chunks/9a82d8c76f175235.js +1 -0
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/types/link.d.ts +1 -0
- package/web/.next/types/routes.d.ts +2 -1
- package/web/.next/types/validator.ts +9 -0
- package/web/public/agent-events-sw.js +164 -0
- package/web/public/sounds/button.wav +0 -0
- package/web/public/sounds/caution.wav +0 -0
- package/web/public/sounds/celebration.wav +0 -0
- package/web/public/sounds/disabled.wav +0 -0
- package/web/public/sounds/notification.wav +0 -0
- package/web/public/sounds/progress_loop.wav +0 -0
- package/web/public/sounds/ringtone_loop.wav +0 -0
- package/web/public/sounds/select.wav +0 -0
- package/web/public/sounds/swipe.wav +0 -0
- package/web/public/sounds/swipe_01.wav +0 -0
- package/web/public/sounds/swipe_02.wav +0 -0
- package/web/public/sounds/swipe_03.wav +0 -0
- package/web/public/sounds/swipe_04.wav +0 -0
- package/web/public/sounds/swipe_05.wav +0 -0
- package/web/public/sounds/tap_01.wav +0 -0
- package/web/public/sounds/tap_02.wav +0 -0
- package/web/public/sounds/tap_03.wav +0 -0
- package/web/public/sounds/tap_04.wav +0 -0
- package/web/public/sounds/tap_05.wav +0 -0
- package/web/public/sounds/toggle_off.wav +0 -0
- package/web/public/sounds/toggle_on.wav +0 -0
- package/web/public/sounds/transition_down.wav +0 -0
- package/web/public/sounds/transition_up.wav +0 -0
- package/web/public/sounds/type_01.wav +0 -0
- package/web/public/sounds/type_02.wav +0 -0
- package/web/public/sounds/type_03.wav +0 -0
- package/web/public/sounds/type_04.wav +0 -0
- package/web/public/sounds/type_05.wav +0 -0
- package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
- package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +0 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
- package/web/.next/static/chunks/13d5334b17480a16.js +0 -10
- package/web/.next/static/chunks/16dec8021e251230.css +0 -2
- package/web/.next/static/chunks/22f30ce91a3c79c1.js +0 -1
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js.map → [root-of-the-server]__6e8b5181._.js.map} +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_buildManifest.js +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_ssgManifest.js +0 -0
|
@@ -2,80 +2,284 @@
|
|
|
2
2
|
* SSE API Route: GET /api/agent-events
|
|
3
3
|
*
|
|
4
4
|
* Streams agent lifecycle notification events to connected web UI clients
|
|
5
|
-
* via Server-Sent Events (SSE).
|
|
5
|
+
* via Server-Sent Events (SSE).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
7
|
+
* Uses DB polling with a per-connection cache so only deltas are sent.
|
|
8
|
+
* This avoids cross-module singleton issues with an in-process event bus.
|
|
9
|
+
*
|
|
10
|
+
* - Polls features + agent runs every 500ms
|
|
11
|
+
* - Compares against cached state and emits only changes
|
|
9
12
|
* - Sends heartbeat comments every 30 seconds to keep connection alive
|
|
10
13
|
* - Supports optional ?runId query parameter to filter events
|
|
11
|
-
* - Cleans up
|
|
14
|
+
* - Cleans up intervals on client disconnect
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
|
-
import {
|
|
17
|
+
import { resolve } from '@/lib/server-container';
|
|
18
|
+
import type { IAgentRunRepository } from '@shepai/core/application/ports/output/agents/agent-run-repository.interface';
|
|
19
|
+
import type { IPhaseTimingRepository } from '@shepai/core/application/ports/output/agents/phase-timing-repository.interface';
|
|
20
|
+
import type { Feature, AgentRun } from '@shepai/core/domain/generated/output';
|
|
21
|
+
import {
|
|
22
|
+
AgentRunStatus,
|
|
23
|
+
SdlcLifecycle,
|
|
24
|
+
NotificationEventType,
|
|
25
|
+
NotificationSeverity,
|
|
26
|
+
} from '@shepai/core/domain/generated/output';
|
|
15
27
|
import type { NotificationEvent } from '@shepai/core/domain/generated/output';
|
|
28
|
+
import type { ListFeaturesUseCase } from '@shepai/core/application/use-cases/features/list-features.use-case';
|
|
29
|
+
|
|
30
|
+
// Force dynamic — SSE streams must never be statically optimized or cached
|
|
31
|
+
export const dynamic = 'force-dynamic';
|
|
16
32
|
|
|
33
|
+
const POLL_INTERVAL_MS = 500;
|
|
17
34
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
interface CachedFeatureState {
|
|
37
|
+
status: AgentRunStatus | null;
|
|
38
|
+
lifecycle: string;
|
|
39
|
+
completedPhases: Set<string>;
|
|
40
|
+
featureName: string;
|
|
21
41
|
}
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Maps SdlcLifecycle values to agent graph node names so the client
|
|
45
|
+
* can derive the correct FeatureLifecyclePhase via mapPhaseNameToLifecycle().
|
|
46
|
+
*/
|
|
47
|
+
const LIFECYCLE_TO_NODE: Partial<Record<SdlcLifecycle, string>> = {
|
|
48
|
+
[SdlcLifecycle.Analyze]: 'analyze',
|
|
49
|
+
[SdlcLifecycle.Requirements]: 'requirements',
|
|
50
|
+
[SdlcLifecycle.Research]: 'research',
|
|
51
|
+
[SdlcLifecycle.Planning]: 'plan',
|
|
52
|
+
[SdlcLifecycle.Implementation]: 'implement',
|
|
53
|
+
[SdlcLifecycle.Review]: 'merge',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const STATUS_TO_EVENT: Partial<
|
|
57
|
+
Record<AgentRunStatus, { eventType: NotificationEventType; severity: NotificationSeverity }>
|
|
58
|
+
> = {
|
|
59
|
+
[AgentRunStatus.running]: {
|
|
60
|
+
eventType: NotificationEventType.AgentStarted,
|
|
61
|
+
severity: NotificationSeverity.Info,
|
|
62
|
+
},
|
|
63
|
+
[AgentRunStatus.waitingApproval]: {
|
|
64
|
+
eventType: NotificationEventType.WaitingApproval,
|
|
65
|
+
severity: NotificationSeverity.Warning,
|
|
66
|
+
},
|
|
67
|
+
[AgentRunStatus.completed]: {
|
|
68
|
+
eventType: NotificationEventType.AgentCompleted,
|
|
69
|
+
severity: NotificationSeverity.Success,
|
|
70
|
+
},
|
|
71
|
+
[AgentRunStatus.failed]: {
|
|
72
|
+
eventType: NotificationEventType.AgentFailed,
|
|
73
|
+
severity: NotificationSeverity.Error,
|
|
74
|
+
},
|
|
75
|
+
[AgentRunStatus.interrupted]: {
|
|
76
|
+
eventType: NotificationEventType.AgentFailed,
|
|
77
|
+
severity: NotificationSeverity.Warning,
|
|
78
|
+
},
|
|
79
|
+
[AgentRunStatus.cancelled]: {
|
|
80
|
+
eventType: NotificationEventType.AgentFailed,
|
|
81
|
+
severity: NotificationSeverity.Warning,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Map agent graph node name from AgentRun.result to a phase name. */
|
|
86
|
+
function resultToPhase(result: string | undefined): string | undefined {
|
|
87
|
+
if (!result?.startsWith('node:')) return undefined;
|
|
88
|
+
return result.slice(5); // "node:analyze" → "analyze"
|
|
25
89
|
}
|
|
26
90
|
|
|
27
91
|
export function GET(request: Request): Response {
|
|
28
|
-
|
|
29
|
-
|
|
92
|
+
try {
|
|
93
|
+
const url = new URL(request.url);
|
|
94
|
+
const runIdFilter = url.searchParams.get('runId');
|
|
30
95
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const bus = getNotificationBus();
|
|
96
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
97
|
+
start(controller) {
|
|
98
|
+
const encoder = new TextEncoder();
|
|
35
99
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
100
|
+
// Per-connection cache: featureId → last-seen state
|
|
101
|
+
const cache = new Map<string, CachedFeatureState>();
|
|
102
|
+
let stopped = false;
|
|
103
|
+
|
|
104
|
+
function enqueue(text: string) {
|
|
105
|
+
if (stopped) return;
|
|
106
|
+
try {
|
|
107
|
+
controller.enqueue(encoder.encode(text));
|
|
108
|
+
} catch {
|
|
109
|
+
// Stream may be closed
|
|
110
|
+
}
|
|
39
111
|
}
|
|
40
112
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
113
|
+
function emitEvent(event: NotificationEvent) {
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.log(
|
|
116
|
+
`[SSE] emit: ${event.eventType} for "${event.featureName}"${event.phaseName ? ` (${event.phaseName})` : ''}`
|
|
117
|
+
);
|
|
118
|
+
enqueue(`event: notification\ndata: ${JSON.stringify(event)}\n\n`);
|
|
45
119
|
}
|
|
46
|
-
};
|
|
47
120
|
|
|
48
|
-
|
|
121
|
+
let pollErrorCount = 0;
|
|
49
122
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
123
|
+
async function poll() {
|
|
124
|
+
if (stopped) return;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const listFeatures = resolve<ListFeaturesUseCase>('ListFeaturesUseCase');
|
|
128
|
+
const agentRunRepo = resolve<IAgentRunRepository>('IAgentRunRepository');
|
|
129
|
+
const phaseTimingRepo = resolve<IPhaseTimingRepository>('IPhaseTimingRepository');
|
|
130
|
+
|
|
131
|
+
const features = await listFeatures.execute();
|
|
132
|
+
|
|
133
|
+
// Build current state for features with agent runs
|
|
134
|
+
const entries: { feature: Feature; run: AgentRun | null }[] = await Promise.all(
|
|
135
|
+
features.map(async (feature) => {
|
|
136
|
+
const run = feature.agentRunId
|
|
137
|
+
? await agentRunRepo.findById(feature.agentRunId)
|
|
138
|
+
: null;
|
|
139
|
+
return { feature, run };
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
for (const { feature, run } of entries) {
|
|
144
|
+
if (!run) continue;
|
|
145
|
+
|
|
146
|
+
// Apply runId filter if present
|
|
147
|
+
if (runIdFilter && run.id !== runIdFilter) continue;
|
|
148
|
+
|
|
149
|
+
const prev = cache.get(feature.id);
|
|
150
|
+
|
|
151
|
+
if (!prev) {
|
|
152
|
+
// First time seeing this feature — seed cache, don't emit
|
|
153
|
+
const completedPhases = new Set<string>();
|
|
154
|
+
try {
|
|
155
|
+
const timings = await phaseTimingRepo.findByRunId(run.id);
|
|
156
|
+
for (const t of timings) {
|
|
157
|
+
if (t.completedAt) completedPhases.add(t.phase);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Ignore timing errors
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
cache.set(feature.id, {
|
|
164
|
+
status: run.status,
|
|
165
|
+
lifecycle: feature.lifecycle,
|
|
166
|
+
completedPhases,
|
|
167
|
+
featureName: feature.name,
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check for status change
|
|
173
|
+
if (prev.status !== run.status) {
|
|
174
|
+
prev.status = run.status;
|
|
175
|
+
const mapping = STATUS_TO_EVENT[run.status];
|
|
176
|
+
if (mapping) {
|
|
177
|
+
const phase = resultToPhase(run.result);
|
|
178
|
+
emitEvent({
|
|
179
|
+
eventType: mapping.eventType,
|
|
180
|
+
agentRunId: run.id,
|
|
181
|
+
featureId: feature.id,
|
|
182
|
+
featureName: feature.name,
|
|
183
|
+
...(phase && { phaseName: phase }),
|
|
184
|
+
message: `Agent status: ${run.status}`,
|
|
185
|
+
severity: mapping.severity,
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check for lifecycle change (agent stays "running" but moves through phases)
|
|
192
|
+
if (prev.lifecycle !== feature.lifecycle) {
|
|
193
|
+
prev.lifecycle = feature.lifecycle;
|
|
194
|
+
const nodeName = LIFECYCLE_TO_NODE[feature.lifecycle as SdlcLifecycle];
|
|
195
|
+
if (nodeName) {
|
|
196
|
+
emitEvent({
|
|
197
|
+
eventType: NotificationEventType.PhaseCompleted,
|
|
198
|
+
agentRunId: run.id,
|
|
199
|
+
featureId: feature.id,
|
|
200
|
+
featureName: feature.name,
|
|
201
|
+
phaseName: nodeName,
|
|
202
|
+
message: `Entered ${nodeName} phase`,
|
|
203
|
+
severity: NotificationSeverity.Info,
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check for new phase completions
|
|
210
|
+
try {
|
|
211
|
+
const timings = await phaseTimingRepo.findByRunId(run.id);
|
|
212
|
+
for (const t of timings) {
|
|
213
|
+
if (t.completedAt && !prev.completedPhases.has(t.phase)) {
|
|
214
|
+
prev.completedPhases.add(t.phase);
|
|
215
|
+
emitEvent({
|
|
216
|
+
eventType: NotificationEventType.PhaseCompleted,
|
|
217
|
+
agentRunId: run.id,
|
|
218
|
+
featureId: feature.id,
|
|
219
|
+
featureName: feature.name,
|
|
220
|
+
phaseName: t.phase,
|
|
221
|
+
message: `Completed ${t.phase} phase`,
|
|
222
|
+
severity: NotificationSeverity.Info,
|
|
223
|
+
timestamp: new Date().toISOString(),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// Ignore timing errors
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
pollErrorCount = 0; // Reset on success
|
|
232
|
+
} catch (error) {
|
|
233
|
+
pollErrorCount++;
|
|
234
|
+
// Log first few errors, then throttle to avoid spamming
|
|
235
|
+
if (pollErrorCount <= 3 || pollErrorCount % 60 === 0) {
|
|
236
|
+
// eslint-disable-next-line no-console
|
|
237
|
+
console.error(
|
|
238
|
+
`[SSE /api/agent-events] poll error #${pollErrorCount}:`,
|
|
239
|
+
error instanceof Error ? error.message : error
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
67
243
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
244
|
+
|
|
245
|
+
// First poll immediately, then every POLL_INTERVAL_MS
|
|
246
|
+
void poll();
|
|
247
|
+
const pollInterval = setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
248
|
+
|
|
249
|
+
// Heartbeat to keep connection alive
|
|
250
|
+
const heartbeatInterval = setInterval(() => {
|
|
251
|
+
enqueue(': heartbeat\n\n');
|
|
252
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
253
|
+
|
|
254
|
+
// Cleanup on client disconnect
|
|
255
|
+
const cleanup = () => {
|
|
256
|
+
stopped = true;
|
|
257
|
+
clearInterval(pollInterval);
|
|
258
|
+
clearInterval(heartbeatInterval);
|
|
259
|
+
try {
|
|
260
|
+
controller.close();
|
|
261
|
+
} catch {
|
|
262
|
+
// Stream may already be closed
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
request.signal.addEventListener('abort', cleanup, { once: true });
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return new Response(stream, {
|
|
271
|
+
headers: {
|
|
272
|
+
'Content-Type': 'text/event-stream',
|
|
273
|
+
'Cache-Control': 'no-cache',
|
|
274
|
+
Connection: 'keep-alive',
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
} catch (error) {
|
|
278
|
+
// eslint-disable-next-line no-console
|
|
279
|
+
console.error('[SSE route] GET handler error:', error);
|
|
280
|
+
return new Response(JSON.stringify({ error: String(error) }), {
|
|
281
|
+
status: 500,
|
|
282
|
+
headers: { 'Content-Type': 'application/json' },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
81
285
|
}
|
|
@@ -11,6 +11,20 @@ export const metadata: Metadata = {
|
|
|
11
11
|
title: 'Shep AI',
|
|
12
12
|
description:
|
|
13
13
|
'Autonomous AI Native SDLC Platform - Automate the development cycle from idea to deploy',
|
|
14
|
+
icons: [
|
|
15
|
+
{
|
|
16
|
+
rel: 'icon',
|
|
17
|
+
url: '/favicon-light.svg',
|
|
18
|
+
type: 'image/svg+xml',
|
|
19
|
+
media: '(prefers-color-scheme: light)',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
rel: 'icon',
|
|
23
|
+
url: '/favicon-dark.svg',
|
|
24
|
+
type: 'image/svg+xml',
|
|
25
|
+
media: '(prefers-color-scheme: dark)',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
14
28
|
};
|
|
15
29
|
|
|
16
30
|
export default function RootLayout({
|
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
* and its associated AgentRun status (mirrors CLI feat ls logic).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
SdlcLifecycle,
|
|
10
|
+
AgentRunStatus,
|
|
11
|
+
TaskState,
|
|
12
|
+
NotificationEventType,
|
|
13
|
+
} from '@shepai/core/domain/generated';
|
|
9
14
|
import type { Feature, AgentRun } from '@shepai/core/domain/generated';
|
|
10
|
-
import type { FeatureNodeState } from './feature-node-state-config';
|
|
15
|
+
import type { FeatureNodeState, FeatureLifecyclePhase } from './feature-node-state-config';
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Derives the visual node state from a Feature and its optional AgentRun.
|
|
@@ -85,3 +90,35 @@ export function deriveProgress(feature: Feature): number {
|
|
|
85
90
|
const doneCount = tasks.filter((t) => t.state === TaskState.Done).length;
|
|
86
91
|
return Math.round((doneCount / tasks.length) * 100);
|
|
87
92
|
}
|
|
93
|
+
|
|
94
|
+
/** Maps a NotificationEventType to the corresponding FeatureNodeState for optimistic UI updates. */
|
|
95
|
+
export function mapEventTypeToState(eventType: NotificationEventType): FeatureNodeState {
|
|
96
|
+
switch (eventType) {
|
|
97
|
+
case NotificationEventType.AgentStarted:
|
|
98
|
+
case NotificationEventType.PhaseCompleted:
|
|
99
|
+
return 'running';
|
|
100
|
+
case NotificationEventType.WaitingApproval:
|
|
101
|
+
return 'action-required';
|
|
102
|
+
case NotificationEventType.AgentCompleted:
|
|
103
|
+
return 'done';
|
|
104
|
+
case NotificationEventType.AgentFailed:
|
|
105
|
+
return 'error';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Maps an SSE event phaseName to a FeatureLifecyclePhase. Mirrors page.tsx nodeToLifecyclePhase. */
|
|
110
|
+
const phaseNameToLifecycle: Record<string, FeatureLifecyclePhase> = {
|
|
111
|
+
analyze: 'requirements',
|
|
112
|
+
requirements: 'requirements',
|
|
113
|
+
research: 'research',
|
|
114
|
+
plan: 'implementation',
|
|
115
|
+
implement: 'implementation',
|
|
116
|
+
merge: 'review',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function mapPhaseNameToLifecycle(
|
|
120
|
+
phaseName: string | undefined
|
|
121
|
+
): FeatureLifecyclePhase | undefined {
|
|
122
|
+
if (!phaseName) return undefined;
|
|
123
|
+
return phaseNameToLifecycle[phaseName];
|
|
124
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SoundToggle } from './sound-toggle';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { SoundToggle } from './sound-toggle';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof SoundToggle> = {
|
|
5
|
+
title: 'Composed/SoundToggle',
|
|
6
|
+
component: SoundToggle,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
},
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => <SoundToggle />,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const InContext: Story = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<div className="flex items-center gap-4 rounded-lg border p-4">
|
|
23
|
+
<span className="text-muted-foreground text-sm">Toggle sound:</span>
|
|
24
|
+
<SoundToggle />
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
};
|
package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Volume2, VolumeOff } from 'lucide-react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { useSoundEnabled } from '@/hooks/use-sound-enabled';
|
|
6
|
+
|
|
7
|
+
export function SoundToggle() {
|
|
8
|
+
const { enabled, toggle } = useSoundEnabled();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Button
|
|
12
|
+
variant="ghost"
|
|
13
|
+
size="icon"
|
|
14
|
+
onClick={toggle}
|
|
15
|
+
aria-label={enabled ? 'Mute sounds' : 'Unmute sounds'}
|
|
16
|
+
>
|
|
17
|
+
{enabled ? <Volume2 className="h-5 w-5" /> : <VolumeOff className="h-5 w-5" />}
|
|
18
|
+
<span className="sr-only">Toggle sound</span>
|
|
19
|
+
</Button>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -25,6 +25,7 @@ import type { PrdQuestionnaireData } from '@/components/common/prd-questionnaire
|
|
|
25
25
|
import { TechDecisionsDrawer } from '@/components/common/tech-decisions-review';
|
|
26
26
|
import { MergeReviewDrawer } from '@/components/common/merge-review';
|
|
27
27
|
import { NotificationPermissionBanner } from '@/components/common/notification-permission-banner';
|
|
28
|
+
import { useSound } from '@/hooks/use-sound';
|
|
28
29
|
import { ControlCenterEmptyState } from './control-center-empty-state';
|
|
29
30
|
import { useControlCenterState } from './use-control-center-state';
|
|
30
31
|
|
|
@@ -53,6 +54,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
53
54
|
handleDeleteFeature,
|
|
54
55
|
handleDeleteRepository,
|
|
55
56
|
closeCreateDrawer,
|
|
57
|
+
selectFeatureById,
|
|
56
58
|
pendingParentFeatureId,
|
|
57
59
|
} = useControlCenterState(initialNodes, initialEdges);
|
|
58
60
|
|
|
@@ -86,6 +88,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
86
88
|
|
|
87
89
|
// Reject state (shared by both drawers)
|
|
88
90
|
const [isRejecting, setIsRejecting] = useState(false);
|
|
91
|
+
const rejectSound = useSound('caution', { volume: 0.5 });
|
|
89
92
|
|
|
90
93
|
// Tech decisions drawer state
|
|
91
94
|
const [techDecisionsData, setTechDecisionsData] = useState<TechDecisionsReviewData | null>(null);
|
|
@@ -158,6 +161,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
158
161
|
return;
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
rejectSound.play();
|
|
161
165
|
toast.success(`${label} rejected — agent re-iterating (iteration ${result.iteration})`);
|
|
162
166
|
if (result.iterationWarning) {
|
|
163
167
|
toast.warning(
|
|
@@ -170,7 +174,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
170
174
|
setIsRejecting(false);
|
|
171
175
|
}
|
|
172
176
|
},
|
|
173
|
-
[selectedNode?.featureId, clearSelection]
|
|
177
|
+
[selectedNode?.featureId, clearSelection, rejectSound]
|
|
174
178
|
);
|
|
175
179
|
|
|
176
180
|
const handlePrdReject = useCallback(
|
|
@@ -325,6 +329,16 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
325
329
|
return () => window.removeEventListener('shep:open-create-drawer', handler);
|
|
326
330
|
}, [handleAddFeature]);
|
|
327
331
|
|
|
332
|
+
// Listen for notification "Review" clicks to open the relevant drawer
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const handler = (e: Event) => {
|
|
335
|
+
const featureId = (e as CustomEvent<{ featureId: string }>).detail.featureId;
|
|
336
|
+
selectFeatureById(featureId);
|
|
337
|
+
};
|
|
338
|
+
window.addEventListener('shep:select-feature', handler);
|
|
339
|
+
return () => window.removeEventListener('shep:select-feature', handler);
|
|
340
|
+
}, [selectFeatureById]);
|
|
341
|
+
|
|
328
342
|
if (!hasRepositories) {
|
|
329
343
|
return (
|
|
330
344
|
<>
|