@shepai/cli 1.53.2 → 1.55.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/README.md +22 -8
- package/apis/json-schema/NotificationEvent.yaml +4 -0
- package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.d.ts +49 -0
- package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.d.ts.map +1 -0
- package/dist/packages/core/src/application/ports/output/services/daemon-service.interface.js +7 -0
- package/dist/packages/core/src/application/ports/output/services/index.d.ts +1 -0
- package/dist/packages/core/src/application/ports/output/services/index.d.ts.map +1 -1
- 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/di/container.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/di/container.js +2 -0
- package/dist/packages/core/src/infrastructure/services/browser-opener.service.d.ts +1 -0
- package/dist/packages/core/src/infrastructure/services/browser-opener.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/browser-opener.service.js +4 -0
- package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.d.ts +16 -0
- package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/daemon/daemon-pid.service.js +72 -0
- package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.d.ts +7 -0
- package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/filesystem/shep-directory.service.js +9 -0
- 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/_serve.command.d.ts +32 -0
- package/dist/src/presentation/cli/commands/_serve.command.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/_serve.command.js +91 -0
- package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts +27 -0
- package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/daemon/start-daemon.js +135 -0
- package/dist/src/presentation/cli/commands/start.command.d.ts +15 -0
- package/dist/src/presentation/cli/commands/start.command.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/start.command.js +33 -0
- package/dist/src/presentation/cli/commands/status.command.d.ts +20 -0
- package/dist/src/presentation/cli/commands/status.command.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/status.command.js +119 -0
- package/dist/src/presentation/cli/commands/stop.command.d.ts +22 -0
- package/dist/src/presentation/cli/commands/stop.command.d.ts.map +1 -0
- package/dist/src/presentation/cli/commands/stop.command.js +79 -0
- 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/cli/index.d.ts +5 -2
- package/dist/src/presentation/cli/index.d.ts.map +1 -1
- package/dist/src/presentation/cli/index.js +23 -4
- 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/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -1
- 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/3eaa6f2966e27b8f.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 → JmDtIt3S6EfLuRKwlzJrW}/_buildManifest.js +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → JmDtIt3S6EfLuRKwlzJrW}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → JmDtIt3S6EfLuRKwlzJrW}/_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
|
<>
|