conductor-oss 0.19.2 → 0.20.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/package.json +5 -5
- package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/packages/web/.next/build-manifest.json +4 -4
- package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/app-update/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/auth/session/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/boards/comments/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/context-files/open/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/executor/health/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/pick-directory/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/github/webhook/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/remote-access/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/repositories/[id]/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/interrupt/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/dom/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/preview/screenshot/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/snapshot/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/token/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/ttyd/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/terminal/ttyd/ws/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/react-loadable-manifest.json +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/_2c837d66._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__cb0aeb63._.js → [root-of-the-server]__1329224f._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__ce4b4158._.js → [root-of-the-server]__1cb74434._.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__53bcdbc9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__c405048c._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__c79fcfd3._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_3aee3cff._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_532f707d._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_903c3fe3._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_9990bc82._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_a990354c._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b9330c69._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_152e81cd._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_a09380f4._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_3eb7b454._.js → node_modules_@clerk_nextjs_dist_esm_app-router_e767206a._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@radix-ui_19d0f177._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_b8d99fb4._.js → node_modules_@radix-ui_ef419f6b._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_core_dist_types_ba19386c.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_board_WorkspaceKanban_tsx_735b7999._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_components_sessions_SessionPreview_tsx_ec32db81._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_features_dashboard_DashboardClient_tsx_81ae42b0._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_features_dashboard_components_DashboardDialogs_tsx_32d3d858._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_hooks_5cb69d9a._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_hooks_f05b02cd._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/middleware-build-manifest.js +2 -2
- package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
- package/web/.next/standalone/packages/web/.next/static/chunks/{d934d0e74594a818.js → 2d65cd3bc1137b3d.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/{96b41c96a34195c2.js → 437b46a75e1c92a7.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/450e4de887dcd142.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{81e3cea43881b17d.js → 4b3ac491753f4910.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/4f912a60e3753a1f.css +4 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/50475743a65c6ae5.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/581440483618ce47.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/5c60a749d318d388.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/5cb35e89898ff9e1.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{378daaa8dbf5309c.js → 741a641e5fe294ff.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/9b1065a7ccd69a3e.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{a70ab593d305d020.js → a0af599485430d5f.js} +1 -1
- package/web/.next/{static/chunks/355bedd41c2e9b0c.js → standalone/packages/web/.next/static/chunks/ae6e408453a5d1dc.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/b1c03c2c194b81b8.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/b9c1238cc9302baa.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/c3443f28deeae499.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{4f9d3a3c14fb43da.js → c8e8ff2836531532.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/cb1a507b11d64bfb.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/ce2f01a7e421d6fe.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/dd6469b26407178b.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/e59b8003fba5213d.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{459f77e3c65b47d6.js → fc68108d32aea368.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/{turbopack-9809b4096e86149a.js → turbopack-c76ead44073de602.js} +1 -1
- package/web/.next/standalone/packages/web/src/components/board/WorkspaceKanban.tsx +2 -1
- package/web/.next/standalone/packages/web/src/components/sessions/SessionDiff.tsx +3 -2
- package/web/.next/standalone/packages/web/src/components/sessions/SessionPreview.tsx +3 -2
- package/web/.next/standalone/packages/web/src/components/sessions/SessionTerminal.tsx +1 -1
- package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +10 -11
- package/web/.next/standalone/packages/web/src/features/dashboard/components/DashboardDialogs.tsx +4 -142
- package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +20 -4
- package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +10 -0
- package/web/.next/standalone/packages/web/src/hooks/useAgents.ts +1 -1
- package/web/.next/standalone/packages/web/src/hooks/useNotificationAlerts.ts +291 -0
- package/web/.next/standalone/packages/web/src/lib/notificationSounds.ts +207 -0
- package/web/.next/standalone/packages/web/src/lib/sessionState.ts +11 -6
- package/web/.next/static/chunks/{d934d0e74594a818.js → 2d65cd3bc1137b3d.js} +1 -1
- package/web/.next/static/chunks/{96b41c96a34195c2.js → 437b46a75e1c92a7.js} +1 -1
- package/web/.next/static/chunks/450e4de887dcd142.js +1 -0
- package/web/.next/static/chunks/{81e3cea43881b17d.js → 4b3ac491753f4910.js} +1 -1
- package/web/.next/static/chunks/4f912a60e3753a1f.css +4 -0
- package/web/.next/static/chunks/50475743a65c6ae5.js +1 -0
- package/web/.next/static/chunks/581440483618ce47.js +1 -0
- package/web/.next/static/chunks/5c60a749d318d388.js +1 -0
- package/web/.next/static/chunks/5cb35e89898ff9e1.js +1 -0
- package/web/.next/static/chunks/{378daaa8dbf5309c.js → 741a641e5fe294ff.js} +1 -1
- package/web/.next/static/chunks/9b1065a7ccd69a3e.js +1 -0
- package/web/.next/static/chunks/{a70ab593d305d020.js → a0af599485430d5f.js} +1 -1
- package/web/.next/{standalone/packages/web/.next/static/chunks/355bedd41c2e9b0c.js → static/chunks/ae6e408453a5d1dc.js} +1 -1
- package/web/.next/static/chunks/b1c03c2c194b81b8.js +1 -0
- package/web/.next/static/chunks/b9c1238cc9302baa.js +1 -0
- package/web/.next/static/chunks/c3443f28deeae499.js +1 -0
- package/web/.next/static/chunks/{4f9d3a3c14fb43da.js → c8e8ff2836531532.js} +1 -1
- package/web/.next/static/chunks/cb1a507b11d64bfb.js +1 -0
- package/web/.next/static/chunks/ce2f01a7e421d6fe.js +1 -0
- package/web/.next/static/chunks/dd6469b26407178b.js +1 -0
- package/web/.next/static/chunks/e59b8003fba5213d.js +1 -0
- package/web/.next/static/chunks/{459f77e3c65b47d6.js → fc68108d32aea368.js} +1 -1
- package/web/.next/static/chunks/{turbopack-9809b4096e86149a.js → turbopack-c76ead44073de602.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__337468fd._.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_98876927._.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_883c65c7._.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_284bf164._.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_fc0422b6._.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_lib_cn_ts_d08d265f._.js +0 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/26a7e7bfa0c7c333.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/2760ffd80cc5974a.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/27e63de286c66b73.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/3066ce838f62bf36.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/34500baf24f4b1ba.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/5d8c72d5215f5485.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/6e98107b81bc5b12.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/8216f40bafea0ec2.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/997ac5c24d1d89f2.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/a0656cf00243d2a4.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/c2f9fe243cc8bd24.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/c3a61db21bd52eaf.css +0 -4
- package/web/.next/standalone/packages/web/.next/static/chunks/da9681f9820fc6bd.js +0 -1
- package/web/.next/static/chunks/26a7e7bfa0c7c333.js +0 -1
- package/web/.next/static/chunks/2760ffd80cc5974a.js +0 -1
- package/web/.next/static/chunks/27e63de286c66b73.js +0 -1
- package/web/.next/static/chunks/3066ce838f62bf36.js +0 -1
- package/web/.next/static/chunks/34500baf24f4b1ba.js +0 -1
- package/web/.next/static/chunks/5d8c72d5215f5485.js +0 -1
- package/web/.next/static/chunks/6e98107b81bc5b12.js +0 -1
- package/web/.next/static/chunks/8216f40bafea0ec2.js +0 -1
- package/web/.next/static/chunks/997ac5c24d1d89f2.js +0 -1
- package/web/.next/static/chunks/a0656cf00243d2a4.js +0 -1
- package/web/.next/static/chunks/c2f9fe243cc8bd24.js +0 -1
- package/web/.next/static/chunks/c3a61db21bd52eaf.css +0 -4
- package/web/.next/static/chunks/da9681f9820fc6bd.js +0 -1
- /package/web/.next/standalone/packages/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_buildManifest.js +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_ssgManifest.js +0 -0
- /package/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_buildManifest.js +0 -0
- /package/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{AThcyGsbhfLjpe2EymN_C → E909GLj6potSWpP-i6F73}/_ssgManifest.js +0 -0
|
@@ -100,7 +100,7 @@ function SessionTerminalView(props: SessionTerminalProps) {
|
|
|
100
100
|
setPromptSending(false);
|
|
101
101
|
setPromptError(null);
|
|
102
102
|
setQueuedInsertError(null);
|
|
103
|
-
}, [sessionId]);
|
|
103
|
+
}, [expectsLiveTerminal, sessionId]);
|
|
104
104
|
|
|
105
105
|
useEffect(() => {
|
|
106
106
|
if (!expectsLiveTerminal) {
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
import { useSession } from "@/hooks/useSession";
|
|
57
57
|
import { useSessions } from "@/hooks/useSessions";
|
|
58
58
|
import { useConfig, type ConfigProject } from "@/hooks/useConfig";
|
|
59
|
+
import { useNotificationAlerts } from "@/hooks/useNotificationAlerts";
|
|
59
60
|
import { useAgents } from "@/hooks/useAgents";
|
|
60
61
|
import { useResponsiveSidebarStateWithOptions } from "@/hooks/useResponsiveSidebarState";
|
|
61
62
|
import { AppShell } from "@/components/layout/AppShell";
|
|
@@ -242,9 +243,10 @@ function formatRepoUpdatedLabel(value: string | null | undefined): string | null
|
|
|
242
243
|
if (!value) return null;
|
|
243
244
|
const timestamp = Date.parse(value);
|
|
244
245
|
if (Number.isNaN(timestamp)) return null;
|
|
245
|
-
return `Updated ${new Intl.DateTimeFormat(
|
|
246
|
+
return `Updated ${new Intl.DateTimeFormat("en-US", {
|
|
246
247
|
month: "short",
|
|
247
248
|
day: "numeric",
|
|
249
|
+
timeZone: "UTC",
|
|
248
250
|
}).format(new Date(timestamp))}`;
|
|
249
251
|
}
|
|
250
252
|
|
|
@@ -495,16 +497,6 @@ function resolveIdeOption(editorId: string): { id: string; label: string } {
|
|
|
495
497
|
return IDE_OPTIONS.find((option) => option.id === editorId) ?? { id: editorId, label: editorId };
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
const NOTIFICATION_SOUND_OPTIONS = [
|
|
499
|
-
{ id: "abstract-sound-1", label: "Abstract Sound 1" },
|
|
500
|
-
{ id: "abstract-sound-2", label: "Abstract Sound 2" },
|
|
501
|
-
{ id: "abstract-sound-3", label: "Abstract Sound 3" },
|
|
502
|
-
{ id: "abstract-sound-4", label: "Abstract Sound 4" },
|
|
503
|
-
{ id: "cow-mooing", label: "Cow Mooing" },
|
|
504
|
-
{ id: "phone-vibration", label: "Phone Vibration" },
|
|
505
|
-
{ id: "rooster", label: "Rooster" },
|
|
506
|
-
];
|
|
507
|
-
|
|
508
500
|
function toObject(value: unknown): Record<string, unknown> {
|
|
509
501
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
510
502
|
return { ...(value as Record<string, unknown>) };
|
|
@@ -1791,6 +1783,13 @@ export default function DashboardClient() {
|
|
|
1791
1783
|
const onboardingRequired = !preferencesLoading && !!preferences && !preferences.onboardingAcknowledged;
|
|
1792
1784
|
const resolvedPreferences = preferences ?? normalizePreferences(null, selectedAgent || DEFAULT_AGENT);
|
|
1793
1785
|
const resolvedCodingAgent = selectedAgent || resolvedPreferences.codingAgent || DEFAULT_AGENT;
|
|
1786
|
+
const notificationProjectId = selectedProjectId ?? selectedSessionRecord?.projectId ?? null;
|
|
1787
|
+
|
|
1788
|
+
useNotificationAlerts({
|
|
1789
|
+
enabled: !preferencesLoading,
|
|
1790
|
+
projectId: notificationProjectId,
|
|
1791
|
+
preferences: resolvedPreferences.notifications,
|
|
1792
|
+
});
|
|
1794
1793
|
|
|
1795
1794
|
const handleSelectProject = useCallback((projectId: string | null) => {
|
|
1796
1795
|
navigateDashboard(
|
package/web/.next/standalone/packages/web/src/features/dashboard/components/DashboardDialogs.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
4
3
|
import { GitBranchIcon, LockIcon, MarkGithubIcon, RepoIcon } from "@primer/octicons-react";
|
|
5
4
|
import {
|
|
6
5
|
getAvailableAgentModels,
|
|
@@ -25,7 +24,6 @@ import {
|
|
|
25
24
|
Building2,
|
|
26
25
|
Check,
|
|
27
26
|
ChevronDown,
|
|
28
|
-
ChevronsRight,
|
|
29
27
|
Copy,
|
|
30
28
|
FolderGit2,
|
|
31
29
|
FolderKanban,
|
|
@@ -46,6 +44,7 @@ import {
|
|
|
46
44
|
import { normalizeAgentName } from "@/lib/agentUtils";
|
|
47
45
|
import { getKnownAgent, KNOWN_AGENT_ORDER } from "@/lib/knownAgents";
|
|
48
46
|
import { AgentTileIcon } from "@/components/AgentTileIcon";
|
|
47
|
+
import { playNotificationSound } from "@/lib/notificationSounds";
|
|
49
48
|
import { normalizeModelAccessPreferences } from "@/lib/modelAccess";
|
|
50
49
|
import {
|
|
51
50
|
getRuntimeCatalogDefaultModelForAccess,
|
|
@@ -161,9 +160,10 @@ function formatRepoUpdatedLabel(value: string | null | undefined): string | null
|
|
|
161
160
|
if (!value) return null;
|
|
162
161
|
const timestamp = Date.parse(value);
|
|
163
162
|
if (Number.isNaN(timestamp)) return null;
|
|
164
|
-
return `Updated ${new Intl.DateTimeFormat(
|
|
163
|
+
return `Updated ${new Intl.DateTimeFormat("en-US", {
|
|
165
164
|
month: "short",
|
|
166
165
|
day: "numeric",
|
|
166
|
+
timeZone: "UTC",
|
|
167
167
|
}).format(new Date(timestamp))}`;
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -387,18 +387,6 @@ const ONBOARDING_TABS: SettingsTab[] = [
|
|
|
387
387
|
{ id: "repositories", label: "Repository", icon: FolderGit2, implemented: true },
|
|
388
388
|
];
|
|
389
389
|
|
|
390
|
-
const IDE_OPTIONS = [
|
|
391
|
-
{ id: "vscode", label: "VS Code" },
|
|
392
|
-
{ id: "vscode-insiders", label: "VS Code Insiders" },
|
|
393
|
-
{ id: "cursor", label: "Cursor" },
|
|
394
|
-
{ id: "windsurf", label: "Windsurf" },
|
|
395
|
-
{ id: "intellij-idea", label: "IntelliJ IDEA" },
|
|
396
|
-
{ id: "zed", label: "Zed" },
|
|
397
|
-
{ id: "xcode", label: "Xcode" },
|
|
398
|
-
{ id: "antigravity", label: "Antigravity" },
|
|
399
|
-
{ id: "custom", label: "Custom" },
|
|
400
|
-
];
|
|
401
|
-
|
|
402
390
|
const MARKDOWN_EDITOR_OPTIONS = [
|
|
403
391
|
{ id: "obsidian", label: "Obsidian" },
|
|
404
392
|
{ id: "vscode", label: "VS Code" },
|
|
@@ -408,12 +396,6 @@ const MARKDOWN_EDITOR_OPTIONS = [
|
|
|
408
396
|
{ id: "custom", label: "Custom" },
|
|
409
397
|
];
|
|
410
398
|
|
|
411
|
-
const IDE_SUBMENU_OPTIONS = IDE_OPTIONS.filter((option) => option.id !== "custom");
|
|
412
|
-
|
|
413
|
-
function resolveIdeOption(editorId: string): { id: string; label: string } {
|
|
414
|
-
return IDE_OPTIONS.find((option) => option.id === editorId) ?? { id: editorId, label: editorId };
|
|
415
|
-
}
|
|
416
|
-
|
|
417
399
|
const NOTIFICATION_SOUND_OPTIONS = [
|
|
418
400
|
{ id: "abstract-sound-1", label: "Abstract Sound 1" },
|
|
419
401
|
{ id: "abstract-sound-2", label: "Abstract Sound 2" },
|
|
@@ -822,35 +804,6 @@ function getAgentModelAccessLabel(agent: string, modelAccess: ModelAccessPrefere
|
|
|
822
804
|
}
|
|
823
805
|
|
|
824
806
|
const MARKDOWN_EDITOR_ICON_CLASS = "block h-4 w-4 shrink-0";
|
|
825
|
-
const CODE_EDITOR_ICON_CLASS = "block h-4 w-4 shrink-0 object-contain";
|
|
826
|
-
|
|
827
|
-
type CodeEditorIconSpec =
|
|
828
|
-
| { kind: "icon"; icon: IconType; className: string }
|
|
829
|
-
| { kind: "image"; imageSrc: string; className: string };
|
|
830
|
-
|
|
831
|
-
const CODE_EDITOR_ICON_MAP: Record<string, CodeEditorIconSpec> = {
|
|
832
|
-
vscode: { kind: "image", imageSrc: "/icons/ide/vscode-dark.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
833
|
-
"vscode-insiders": { kind: "image", imageSrc: "/icons/ide/vscode-insiders.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
834
|
-
cursor: { kind: "image", imageSrc: "/icons/ide/cursor-dark.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
835
|
-
windsurf: { kind: "image", imageSrc: "/icons/ide/windsurf-dark.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
836
|
-
"intellij-idea": { kind: "image", imageSrc: "/icons/ide/intellij.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
837
|
-
zed: { kind: "image", imageSrc: "/icons/ide/zed-dark.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
838
|
-
xcode: { kind: "image", imageSrc: "/icons/ide/xcode.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
839
|
-
antigravity: { kind: "image", imageSrc: "/icons/ide/antigravity-dark.svg", className: CODE_EDITOR_ICON_CLASS },
|
|
840
|
-
custom: { kind: "icon", icon: Settings2, className: `${CODE_EDITOR_ICON_CLASS} text-[var(--vk-text-muted)]` },
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
function CodeEditorIcon({ editorId, label }: { editorId: string; label: string }) {
|
|
844
|
-
const iconSpec = CODE_EDITOR_ICON_MAP[editorId];
|
|
845
|
-
if (!iconSpec) {
|
|
846
|
-
return <Settings2 className={`${CODE_EDITOR_ICON_CLASS} text-[var(--vk-text-muted)]`} />;
|
|
847
|
-
}
|
|
848
|
-
if (iconSpec.kind === "image") {
|
|
849
|
-
return <img src={iconSpec.imageSrc} alt={`${label} logo`} className={iconSpec.className} />;
|
|
850
|
-
}
|
|
851
|
-
const Icon = iconSpec.icon;
|
|
852
|
-
return <Icon className={iconSpec.className} />;
|
|
853
|
-
}
|
|
854
807
|
|
|
855
808
|
function shellQuote(value: string): string {
|
|
856
809
|
return JSON.stringify(value);
|
|
@@ -2577,7 +2530,6 @@ function hydrateRepositoryDraft(value: RepositorySettingsPayload): RepositorySet
|
|
|
2577
2530
|
const managedRemoteProvider = remoteAccessSettings.provider ?? remoteAccessSettings.recommendedProvider;
|
|
2578
2531
|
const usingPrivateNetworkFlow = managedRemoteProvider === "tailscale";
|
|
2579
2532
|
const showManagedTunnelControls = managedRemoteProvider !== null || remoteAccessSettings.managed;
|
|
2580
|
-
const selectedIdeOption = resolveIdeOption(ide);
|
|
2581
2533
|
const settingsMenuClass = "z-50 min-w-[240px] rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] p-2 shadow-[0_18px_50px_rgba(0,0,0,0.35)]";
|
|
2582
2534
|
const settingsSubMenuClass = `${settingsMenuClass} min-w-[280px]`;
|
|
2583
2535
|
const settingsMenuItemClass = "flex min-h-[40px] cursor-default items-center gap-2 rounded-[4px] px-3 py-2 text-[14px] leading-[21px] text-[var(--vk-text-normal)] outline-none hover:bg-[var(--vk-bg-hover)] focus:bg-[var(--vk-bg-hover)]";
|
|
@@ -2955,97 +2907,6 @@ function hydrateRepositoryDraft(value: RepositorySettingsPayload): RepositorySet
|
|
|
2955
2907
|
|
|
2956
2908
|
{(isPreferencesTab || isGeneralTab) && (
|
|
2957
2909
|
<>
|
|
2958
|
-
<section className="space-y-3">
|
|
2959
|
-
<div className="space-y-1">
|
|
2960
|
-
<h4 className="text-[15px] font-medium text-[var(--vk-text-strong)]">Choose Your Code Editor</h4>
|
|
2961
|
-
<p className="text-[12px] text-[var(--vk-text-muted)]">
|
|
2962
|
-
This editor will be used when opening attempts and files.
|
|
2963
|
-
</p>
|
|
2964
|
-
</div>
|
|
2965
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
2966
|
-
<DropdownMenu.Root>
|
|
2967
|
-
<DropdownMenu.Trigger asChild>
|
|
2968
|
-
<button
|
|
2969
|
-
type="button"
|
|
2970
|
-
className="inline-flex h-11 items-center gap-2 rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-3 text-[14px] text-[var(--vk-text-normal)] outline-none transition hover:bg-[var(--vk-bg-hover)] data-[state=open]:bg-[var(--vk-bg-hover)]"
|
|
2971
|
-
aria-label="Choose code editor"
|
|
2972
|
-
>
|
|
2973
|
-
<span className="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-muted)]">
|
|
2974
|
-
<FolderOpen className="h-3.5 w-3.5" />
|
|
2975
|
-
</span>
|
|
2976
|
-
<span className="font-medium text-[var(--vk-text-strong)]">Open</span>
|
|
2977
|
-
<ChevronDown className="h-3.5 w-3.5 text-[var(--vk-text-muted)]" />
|
|
2978
|
-
</button>
|
|
2979
|
-
</DropdownMenu.Trigger>
|
|
2980
|
-
<DropdownMenu.Portal>
|
|
2981
|
-
<DropdownMenu.Content align="start" sideOffset={6} className={settingsMenuClass}>
|
|
2982
|
-
<p className="px-3 pb-1 text-[12px] font-medium uppercase tracking-[0.08em] text-[var(--vk-text-muted)]">
|
|
2983
|
-
Open With
|
|
2984
|
-
</p>
|
|
2985
|
-
<DropdownMenu.Sub>
|
|
2986
|
-
<DropdownMenu.SubTrigger className={`${settingsMenuItemClass} min-w-[220px] justify-between`}>
|
|
2987
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
2988
|
-
<CodeEditorIcon editorId={selectedIdeOption.id} label={selectedIdeOption.label} />
|
|
2989
|
-
<span>IDE</span>
|
|
2990
|
-
</div>
|
|
2991
|
-
<div className="ml-4 flex min-w-0 items-center gap-2">
|
|
2992
|
-
<span className="truncate text-[12px] text-[var(--vk-text-muted)]">
|
|
2993
|
-
{selectedIdeOption.label}
|
|
2994
|
-
</span>
|
|
2995
|
-
<ChevronsRight className="h-3.5 w-3.5 shrink-0 text-[var(--vk-text-muted)]" />
|
|
2996
|
-
</div>
|
|
2997
|
-
</DropdownMenu.SubTrigger>
|
|
2998
|
-
<DropdownMenu.Portal>
|
|
2999
|
-
<DropdownMenu.SubContent
|
|
3000
|
-
sideOffset={8}
|
|
3001
|
-
alignOffset={-4}
|
|
3002
|
-
className={settingsSubMenuClass}
|
|
3003
|
-
>
|
|
3004
|
-
<p className="px-3 pb-1 text-[12px] font-medium uppercase tracking-[0.08em] text-[var(--vk-text-muted)]">
|
|
3005
|
-
Editors
|
|
3006
|
-
</p>
|
|
3007
|
-
{IDE_SUBMENU_OPTIONS.map((option) => (
|
|
3008
|
-
<DropdownMenu.Item
|
|
3009
|
-
key={option.id}
|
|
3010
|
-
onSelect={() => setIde(option.id)}
|
|
3011
|
-
className={settingsMenuItemClass}
|
|
3012
|
-
>
|
|
3013
|
-
<CodeEditorIcon editorId={option.id} label={option.label} />
|
|
3014
|
-
<span className="flex-1">{option.label}</span>
|
|
3015
|
-
<span className="ml-auto inline-flex h-4 w-4 items-center justify-center text-[var(--vk-text-strong)]">
|
|
3016
|
-
{ide === option.id ? <Check className="h-4 w-4 text-[var(--vk-orange)]" /> : null}
|
|
3017
|
-
</span>
|
|
3018
|
-
</DropdownMenu.Item>
|
|
3019
|
-
))}
|
|
3020
|
-
</DropdownMenu.SubContent>
|
|
3021
|
-
</DropdownMenu.Portal>
|
|
3022
|
-
</DropdownMenu.Sub>
|
|
3023
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[var(--vk-border)]" />
|
|
3024
|
-
<DropdownMenu.Item onSelect={() => setIde("custom")} className={settingsMenuItemClass}>
|
|
3025
|
-
<CodeEditorIcon editorId="custom" label="Custom" />
|
|
3026
|
-
<span className="flex-1">Custom</span>
|
|
3027
|
-
<span className="ml-auto inline-flex h-4 w-4 items-center justify-center text-[var(--vk-text-strong)]">
|
|
3028
|
-
{ide === "custom" ? <Check className="h-4 w-4 text-[var(--vk-orange)]" /> : null}
|
|
3029
|
-
</span>
|
|
3030
|
-
</DropdownMenu.Item>
|
|
3031
|
-
</DropdownMenu.Content>
|
|
3032
|
-
</DropdownMenu.Portal>
|
|
3033
|
-
</DropdownMenu.Root>
|
|
3034
|
-
|
|
3035
|
-
<div className="inline-flex min-h-11 max-w-full items-center gap-2 rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-3 py-2">
|
|
3036
|
-
<span className="inline-flex h-7 w-7 items-center justify-center rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)]">
|
|
3037
|
-
<CodeEditorIcon editorId={selectedIdeOption.id} label={selectedIdeOption.label} />
|
|
3038
|
-
</span>
|
|
3039
|
-
<div className="min-w-0">
|
|
3040
|
-
<div className="truncate text-[14px] font-medium text-[var(--vk-text-strong)]">
|
|
3041
|
-
{selectedIdeOption.label}
|
|
3042
|
-
</div>
|
|
3043
|
-
<div className="truncate text-[11px] text-[var(--vk-text-muted)]">Current editor</div>
|
|
3044
|
-
</div>
|
|
3045
|
-
</div>
|
|
3046
|
-
</div>
|
|
3047
|
-
</section>
|
|
3048
|
-
|
|
3049
2910
|
<section className="space-y-2">
|
|
3050
2911
|
<h4 className="text-[15px] font-medium text-[var(--vk-text-strong)]">Markdown Editor</h4>
|
|
3051
2912
|
<p className="text-[12px] text-[var(--vk-text-muted)]">
|
|
@@ -3127,6 +2988,7 @@ function hydrateRepositoryDraft(value: RepositorySettingsPayload): RepositorySet
|
|
|
3127
2988
|
onClick={() => {
|
|
3128
2989
|
setSoundEnabled(true);
|
|
3129
2990
|
setSoundFile(option.id);
|
|
2991
|
+
void playNotificationSound(option.id);
|
|
3130
2992
|
}}
|
|
3131
2993
|
className={`flex items-center gap-2 rounded-[4px] border px-3 py-2 text-left ${
|
|
3132
2994
|
selected
|
package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo } from "react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import {
|
|
5
5
|
ArrowRight,
|
|
6
6
|
FolderGit2,
|
|
@@ -22,8 +22,11 @@ interface WorkspaceOverviewProps {
|
|
|
22
22
|
onSelectSession: (sessionId: string) => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const RELATIVE_TIME_TICK_MS = 60_000;
|
|
26
|
+
|
|
27
|
+
function formatRelativeTime(isoDate: string, now: number | null): string {
|
|
28
|
+
if (now === null) return "Updated recently";
|
|
29
|
+
const diffMs = now - new Date(isoDate).getTime();
|
|
27
30
|
if (!Number.isFinite(diffMs) || diffMs < 60_000) return "Updated now";
|
|
28
31
|
const minutes = Math.floor(diffMs / 60_000);
|
|
29
32
|
if (minutes < 60) return `Updated ${minutes}m ago`;
|
|
@@ -92,6 +95,17 @@ export function WorkspaceOverview({
|
|
|
92
95
|
onCreateWorkspace,
|
|
93
96
|
onSelectSession,
|
|
94
97
|
}: WorkspaceOverviewProps) {
|
|
98
|
+
const [relativeNow, setRelativeNow] = useState<number | null>(null);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
setRelativeNow(Date.now());
|
|
102
|
+
const interval = window.setInterval(() => {
|
|
103
|
+
setRelativeNow(Date.now());
|
|
104
|
+
}, RELATIVE_TIME_TICK_MS);
|
|
105
|
+
|
|
106
|
+
return () => window.clearInterval(interval);
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
95
109
|
const visibleSessions = useMemo(
|
|
96
110
|
() => sessions.filter((session) => session.status !== "archived"),
|
|
97
111
|
[sessions],
|
|
@@ -252,7 +266,9 @@ export function WorkspaceOverview({
|
|
|
252
266
|
</p>
|
|
253
267
|
</div>
|
|
254
268
|
<div className="shrink-0 text-right">
|
|
255
|
-
<p className="text-[11px] text-[var(--vk-text-muted)]">
|
|
269
|
+
<p className="text-[11px] text-[var(--vk-text-muted)]">
|
|
270
|
+
{formatRelativeTime(session.lastActivityAt, relativeNow)}
|
|
271
|
+
</p>
|
|
256
272
|
<ArrowRight className="ml-auto mt-2 h-4 w-4 text-[var(--vk-text-muted)]" />
|
|
257
273
|
</div>
|
|
258
274
|
</button>
|
|
@@ -9,6 +9,8 @@ import { WorkspaceSidebarPanel } from "@/components/layout/WorkspaceSidebarPanel
|
|
|
9
9
|
import { SessionDetail } from "@/components/sessions/SessionDetail";
|
|
10
10
|
import { shouldUseCompactTerminalChrome } from "@/components/sessions/sessionTerminalUtils";
|
|
11
11
|
import { useConfig } from "@/hooks/useConfig";
|
|
12
|
+
import { useNotificationAlerts } from "@/hooks/useNotificationAlerts";
|
|
13
|
+
import { usePreferences } from "@/hooks/usePreferences";
|
|
12
14
|
import { useSession } from "@/hooks/useSession";
|
|
13
15
|
import { useSessions } from "@/hooks/useSessions";
|
|
14
16
|
import type { DashboardSession } from "@/lib/types";
|
|
@@ -19,6 +21,7 @@ export default function SessionPageClient() {
|
|
|
19
21
|
const searchParams = useSearchParams();
|
|
20
22
|
const { projects } = useConfig();
|
|
21
23
|
const { session: currentSession } = useSession(params.id);
|
|
24
|
+
const { preferences, loading: preferencesLoading } = usePreferences();
|
|
22
25
|
const {
|
|
23
26
|
mobileSidebarOpen,
|
|
24
27
|
desktopSidebarOpen,
|
|
@@ -35,6 +38,13 @@ export default function SessionPageClient() {
|
|
|
35
38
|
return tab !== "overview" && tab !== "preview" && tab !== "diff";
|
|
36
39
|
}, [searchParams]);
|
|
37
40
|
const immersiveTerminalMode = terminalTabActive && compactTerminalChrome;
|
|
41
|
+
const notificationProjectId = currentSession?.projectId ?? null;
|
|
42
|
+
|
|
43
|
+
useNotificationAlerts({
|
|
44
|
+
enabled: !preferencesLoading && notificationProjectId !== null,
|
|
45
|
+
projectId: notificationProjectId,
|
|
46
|
+
preferences: preferences?.notifications ?? null,
|
|
47
|
+
});
|
|
38
48
|
|
|
39
49
|
const topBarTitle = useMemo(() => {
|
|
40
50
|
if (currentSession) {
|
|
@@ -126,7 +126,7 @@ async function refreshAgents(force = false): Promise<void> {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export function useAgents(): UseAgentsReturn {
|
|
129
|
-
const [snapshot, setSnapshot] = useState<AgentsStoreSnapshot>(
|
|
129
|
+
const [snapshot, setSnapshot] = useState<AgentsStoreSnapshot>({ agents: [], loading: true });
|
|
130
130
|
|
|
131
131
|
useEffect(() => {
|
|
132
132
|
const applySnapshot = () => setSnapshot(currentSnapshot());
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type MutableRefObject, useEffect, useRef } from "react";
|
|
4
|
+
import {
|
|
5
|
+
primeNotificationAudio,
|
|
6
|
+
playNotificationSound,
|
|
7
|
+
resolveNotificationSoundId,
|
|
8
|
+
type NotificationSoundId,
|
|
9
|
+
} from "@/lib/notificationSounds";
|
|
10
|
+
|
|
11
|
+
type NotificationPriority = "high" | "medium" | "low";
|
|
12
|
+
|
|
13
|
+
type NotificationRecord = {
|
|
14
|
+
id: string;
|
|
15
|
+
priority: NotificationPriority;
|
|
16
|
+
message: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
projectId: string;
|
|
20
|
+
type: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type NotificationResponse = {
|
|
24
|
+
notifications?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
interface NotificationPreferences {
|
|
28
|
+
soundEnabled: boolean;
|
|
29
|
+
soundFile: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface UseNotificationAlertsOptions {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
projectId: string | null;
|
|
35
|
+
preferences: NotificationPreferences | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const POLL_INTERVAL_MS = 10_000;
|
|
39
|
+
|
|
40
|
+
function isVisiblePage(): boolean {
|
|
41
|
+
return typeof document === "undefined" || document.visibilityState === "visible";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isNotificationPriority(value: unknown): value is NotificationPriority {
|
|
45
|
+
return value === "high" || value === "medium" || value === "low";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toObject(value: unknown): Record<string, unknown> {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
50
|
+
return { ...(value as Record<string, unknown>) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeNotification(value: unknown): NotificationRecord | null {
|
|
54
|
+
const payload = toObject(value);
|
|
55
|
+
const id = typeof payload.id === "string" ? payload.id.trim() : "";
|
|
56
|
+
const message = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
57
|
+
const timestamp = typeof payload.timestamp === "string" ? payload.timestamp.trim() : "";
|
|
58
|
+
const sessionId = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
59
|
+
const projectId = typeof payload.projectId === "string" ? payload.projectId.trim() : "";
|
|
60
|
+
const type = typeof payload.type === "string" ? payload.type.trim() : "";
|
|
61
|
+
const priority = isNotificationPriority(payload.priority) ? payload.priority : "low";
|
|
62
|
+
|
|
63
|
+
if (!id || !message || !timestamp || !sessionId || !projectId || !type) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
priority,
|
|
70
|
+
message,
|
|
71
|
+
timestamp,
|
|
72
|
+
sessionId,
|
|
73
|
+
projectId,
|
|
74
|
+
type,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeNotificationResponse(value: unknown): NotificationRecord[] {
|
|
79
|
+
const payload = toObject(value);
|
|
80
|
+
const notifications = Array.isArray(payload.notifications)
|
|
81
|
+
? payload.notifications
|
|
82
|
+
: [];
|
|
83
|
+
return notifications
|
|
84
|
+
.map((notification) => normalizeNotification(notification))
|
|
85
|
+
.filter((notification): notification is NotificationRecord => notification !== null)
|
|
86
|
+
.sort((left, right) => {
|
|
87
|
+
const timestampOrder = Date.parse(right.timestamp) - Date.parse(left.timestamp);
|
|
88
|
+
if (timestampOrder !== 0) return timestampOrder;
|
|
89
|
+
return right.id.localeCompare(left.id);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function latestTimestamp(notifications: NotificationRecord[]): string | null {
|
|
94
|
+
return notifications[0]?.timestamp ?? null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function shouldAlert(notification: NotificationRecord): boolean {
|
|
98
|
+
return notification.priority !== "low";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function pickAlertNotification(notifications: NotificationRecord[]): NotificationRecord | null {
|
|
102
|
+
return notifications.find((notification) => notification.priority === "high")
|
|
103
|
+
?? notifications.find((notification) => notification.priority === "medium")
|
|
104
|
+
?? notifications[0]
|
|
105
|
+
?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function notificationKey(notification: NotificationRecord): string {
|
|
109
|
+
return `${notification.id}::${notification.timestamp}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function clearTimer(timerRef: MutableRefObject<number | null>) {
|
|
113
|
+
if (timerRef.current !== null) {
|
|
114
|
+
window.clearTimeout(timerRef.current);
|
|
115
|
+
timerRef.current = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function useNotificationAlerts({
|
|
120
|
+
enabled,
|
|
121
|
+
projectId,
|
|
122
|
+
preferences,
|
|
123
|
+
}: UseNotificationAlertsOptions): void {
|
|
124
|
+
const soundEnabledRef = useRef(preferences?.soundEnabled !== false);
|
|
125
|
+
const soundFileRef = useRef<NotificationSoundId>(resolveNotificationSoundId(preferences?.soundFile));
|
|
126
|
+
const seenNotificationKeysRef = useRef(new Set<string>());
|
|
127
|
+
const latestTimestampRef = useRef<string | null>(null);
|
|
128
|
+
const initializedRef = useRef(false);
|
|
129
|
+
const activeProjectIdRef = useRef<string | null>(null);
|
|
130
|
+
const inFlightRef = useRef<Promise<void> | null>(null);
|
|
131
|
+
const pollTimerRef = useRef<number | null>(null);
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
soundEnabledRef.current = preferences?.soundEnabled !== false;
|
|
135
|
+
soundFileRef.current = resolveNotificationSoundId(preferences?.soundFile);
|
|
136
|
+
}, [preferences?.soundEnabled, preferences?.soundFile]);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!enabled || typeof window === "undefined") {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const normalizedProjectId = projectId?.trim() || null;
|
|
144
|
+
if (activeProjectIdRef.current !== normalizedProjectId) {
|
|
145
|
+
activeProjectIdRef.current = normalizedProjectId;
|
|
146
|
+
seenNotificationKeysRef.current = new Set();
|
|
147
|
+
latestTimestampRef.current = null;
|
|
148
|
+
initializedRef.current = false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let cancelled = false;
|
|
152
|
+
|
|
153
|
+
const loadNotifications = async (initial: boolean): Promise<void> => {
|
|
154
|
+
if (cancelled || !isVisiblePage()) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (inFlightRef.current) {
|
|
159
|
+
await inFlightRef.current;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const load = (async () => {
|
|
164
|
+
const params = new URLSearchParams({
|
|
165
|
+
limit: "20",
|
|
166
|
+
});
|
|
167
|
+
if (normalizedProjectId) {
|
|
168
|
+
params.set("project", normalizedProjectId);
|
|
169
|
+
}
|
|
170
|
+
if (!initial && latestTimestampRef.current) {
|
|
171
|
+
params.set("since", latestTimestampRef.current);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(`/api/notifications?${params.toString()}`, {
|
|
176
|
+
cache: "no-store",
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const payload = (await response.json().catch(() => null)) as NotificationResponse | null;
|
|
183
|
+
const notifications = normalizeNotificationResponse(payload);
|
|
184
|
+
if (notifications.length === 0) {
|
|
185
|
+
initializedRef.current = true;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
latestTimestampRef.current = latestTimestamp(notifications);
|
|
190
|
+
|
|
191
|
+
if (initial || !initializedRef.current) {
|
|
192
|
+
for (const notification of notifications) {
|
|
193
|
+
seenNotificationKeysRef.current.add(notificationKey(notification));
|
|
194
|
+
}
|
|
195
|
+
initializedRef.current = true;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const unseenNotifications = notifications.filter(
|
|
200
|
+
(notification) => !seenNotificationKeysRef.current.has(notificationKey(notification)),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
for (const notification of notifications) {
|
|
204
|
+
seenNotificationKeysRef.current.add(notificationKey(notification));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (unseenNotifications.length === 0) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const alertNotification = pickAlertNotification(unseenNotifications);
|
|
212
|
+
if (!alertNotification || !shouldAlert(alertNotification) || !soundEnabledRef.current) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await playNotificationSound(soundFileRef.current);
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignore transient notification polling failures.
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
|
|
222
|
+
inFlightRef.current = load;
|
|
223
|
+
try {
|
|
224
|
+
await load;
|
|
225
|
+
} finally {
|
|
226
|
+
if (inFlightRef.current === load) {
|
|
227
|
+
inFlightRef.current = null;
|
|
228
|
+
}
|
|
229
|
+
initializedRef.current = true;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const scheduleNextPoll = () => {
|
|
234
|
+
clearTimer(pollTimerRef);
|
|
235
|
+
if (cancelled || !isVisiblePage()) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pollTimerRef.current = window.setTimeout(() => {
|
|
240
|
+
void loadNotifications(false).finally(() => {
|
|
241
|
+
scheduleNextPoll();
|
|
242
|
+
});
|
|
243
|
+
}, POLL_INTERVAL_MS);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const startPolling = () => {
|
|
247
|
+
if (cancelled || !isVisiblePage()) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
void loadNotifications(!initializedRef.current).finally(() => {
|
|
252
|
+
scheduleNextPoll();
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const handleVisibilityChange = () => {
|
|
257
|
+
if (!isVisiblePage()) {
|
|
258
|
+
clearTimer(pollTimerRef);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
startPolling();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const handleFocus = () => {
|
|
265
|
+
if (isVisiblePage()) {
|
|
266
|
+
startPolling();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const handleAudioPrime = () => {
|
|
271
|
+
void primeNotificationAudio();
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
startPolling();
|
|
275
|
+
window.addEventListener("focus", handleFocus);
|
|
276
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
277
|
+
window.addEventListener("pointerdown", handleAudioPrime, true);
|
|
278
|
+
window.addEventListener("touchstart", handleAudioPrime, true);
|
|
279
|
+
window.addEventListener("keydown", handleAudioPrime, true);
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
cancelled = true;
|
|
283
|
+
clearTimer(pollTimerRef);
|
|
284
|
+
window.removeEventListener("focus", handleFocus);
|
|
285
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
286
|
+
window.removeEventListener("pointerdown", handleAudioPrime, true);
|
|
287
|
+
window.removeEventListener("touchstart", handleAudioPrime, true);
|
|
288
|
+
window.removeEventListener("keydown", handleAudioPrime, true);
|
|
289
|
+
};
|
|
290
|
+
}, [enabled, projectId]);
|
|
291
|
+
}
|