fleetlens 0.15.7 → 0.16.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/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +9 -0
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +62 -0
- package/app/apps/web/.next/server/app/_global-error/page.js +1 -1
- package/app/apps/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/agent/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/agent/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/agent/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/agent/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/agent/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/agent/page.js +18 -0
- package/app/apps/web/.next/server/app/agent/page.js.map +5 -0
- package/app/apps/web/.next/server/app/agent/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/agent/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route.js +7 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/events/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route.js +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/messages/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route.js +7 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route.js +7 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/chats/[id]/stop/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route.js +7 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/chats/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/index/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/index/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/index/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/index/route.js +9 -0
- package/app/apps/web/.next/server/app/api/agent/index/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/index/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/index/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route.js +10 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/mcp/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route.js +12 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/agent/suggestions/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/ask/route.js +3 -4
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +3 -3
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/projects/open-folder/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/credentials/route.js +5 -5
- package/app/apps/web/.next/server/app/api/settings/credentials/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js +3 -3
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/autostart/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js +2 -2
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js +2 -2
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js +2 -2
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page.js +2 -2
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js +2 -2
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js +2 -2
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page.js +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/page.js +1 -1
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +9 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_agent_chats_[id]_events_route_actions_0fkgl_r.js +3 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_agent_chats_[id]_stop_route_actions_036fon_.js +3 -0
- package/app/apps/web/.next/server/chunks/09og__next-internal_server_app_api_agent_chats_[id]_messages_route_actions_0ubwh4q.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__01~cgp8._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__02faosk._.js +30 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__02puo0u._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0367vbv._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__04g-b-m._.js +9 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__06xto5z._.js +9 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__096_llw._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__09cgmam._.js +28 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0_5tf-2._.js +21 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0gvw6dk._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mx_9mb._.js +5 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0pirsh0._.js +9 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0sbhe0s._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0tk8p3d._.js +28 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0u6pamj._.js +28 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__123tfcb._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__12jomao._.js +28 -0
- package/app/apps/web/.next/server/chunks/_132c.4p._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_agent_chats_[id]_route_actions_0b95y8h.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_agent_chats_route_actions_07b4w1x.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_agent_index_route_actions_03~ylvr.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_agent_mcp_route_actions_0-b30h..js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_agent_suggestions_route_actions_0r_5s~k.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0sr4wr_.js +13 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0z_xi9k.js +16 -0
- package/app/apps/web/.next/server/chunks/{packages_0o6.gpk._.js → packages_entries_dist_0-0s5ui._.js} +2 -2
- package/app/apps/web/.next/server/chunks/packages_entries_dist_0~0d7ku._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_entries_dist_node_0bckr7-.js +8 -8
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0e.gxk3._.js → [root-of-the-server]__0.wm54e._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__04il6m2._.js → [root-of-the-server]__04qodwp._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0k1x-c9._.js → [root-of-the-server]__07~kk2~._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__03uoeiw._.js → [root-of-the-server]__0_78i1t._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0amklf0._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0nr2~y1._.js → [root-of-the-server]__0d5y90m._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__03svi~4._.js → [root-of-the-server]__0iv286q._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0j683am._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0rvzdnx._.js → [root-of-the-server]__0kml6jt._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__08l86e-._.js → [root-of-the-server]__0lmftab._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ns6ydi._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0vo.r~s._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~8.fgs._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_0k8yioi._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{_13jdm1g._.js → _0~r5ica._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_agent_page_actions_0tuled1.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_app_sessions_[id]_session-view_tsx_0-9t5~.._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0x014hn._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_agent-chat_tsx_0xbtx2n._.js +5 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/{node_modules_0yrkyef._.js → node_modules_next_dist_0t-hj0x._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0~g07z5.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +8 -0
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/middleware-manifest.json +5 -5
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/04feoajklmrl3.js +3 -0
- package/app/apps/web/.next/static/chunks/07bzlfv97o1ig.js +10 -0
- package/app/apps/web/.next/static/chunks/0d~7p_ye7v7_5.css +1 -0
- package/app/apps/web/.next/static/chunks/0iiqpl6d_3i5~.js +1 -0
- package/app/apps/web/.next/static/chunks/0mry~475v0fa3.js +14 -0
- package/app/apps/web/.next/static/chunks/0qwgm81o9o~um.js +1 -0
- package/app/apps/web/.next/static/chunks/{0y3448zrint0k.js → 0~rycwo36m~c3.js} +1 -1
- package/app/apps/web/.next/static/chunks/12zmbd3mlcbn_.js +1 -0
- package/app/apps/web/.next/static/chunks/16bapkvp5.x37.js +1 -0
- package/app/apps/web/app/agent/page.tsx +12 -0
- package/app/apps/web/app/api/agent/chats/[id]/events/route.ts +86 -0
- package/app/apps/web/app/api/agent/chats/[id]/messages/route.ts +47 -0
- package/app/apps/web/app/api/agent/chats/[id]/route.ts +19 -0
- package/app/apps/web/app/api/agent/chats/[id]/stop/route.ts +10 -0
- package/app/apps/web/app/api/agent/chats/route.ts +26 -0
- package/app/apps/web/app/api/agent/index/route.ts +56 -0
- package/app/apps/web/app/api/agent/mcp/route.ts +31 -0
- package/app/apps/web/app/api/agent/suggestions/route.ts +150 -0
- package/app/apps/web/components/agent-chat.tsx +940 -0
- package/app/apps/web/components/sidebar.tsx +8 -21
- package/app/apps/web/lib/agent/chat-model.ts +125 -0
- package/app/apps/web/lib/agent/chat-store.test.ts +125 -0
- package/app/apps/web/lib/agent/chat-store.ts +59 -0
- package/app/apps/web/lib/agent/extract.test.ts +141 -0
- package/app/apps/web/lib/agent/extract.ts +75 -0
- package/app/apps/web/lib/agent/index-store.ts +206 -0
- package/app/apps/web/lib/agent/links.test.ts +27 -0
- package/app/apps/web/lib/agent/links.ts +10 -0
- package/app/apps/web/lib/agent/mcp.test.ts +83 -0
- package/app/apps/web/lib/agent/mcp.ts +85 -0
- package/app/apps/web/lib/agent/prompt.test.ts +38 -0
- package/app/apps/web/lib/agent/prompt.ts +44 -0
- package/app/apps/web/lib/agent/run.ts +426 -0
- package/app/apps/web/lib/agent/search.test.ts +111 -0
- package/app/apps/web/lib/agent/search.ts +144 -0
- package/app/apps/web/lib/agent/suggestions.test.ts +156 -0
- package/app/apps/web/lib/agent/suggestions.ts +167 -0
- package/app/apps/web/lib/agent/tools.test.ts +98 -0
- package/app/apps/web/lib/agent/tools.ts +176 -0
- package/app/apps/web/lib/agent/types.ts +50 -0
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/app/apps/web/vitest.config.ts +4 -0
- package/dist/daemon-worker.js +9 -4
- package/dist/index.js +19 -12
- package/menubar/FleetlensMenubar.app/Contents/Info.plist +2 -2
- package/menubar/FleetlensMenubar.app/Contents/MacOS/FleetlensMenubar +0 -0
- package/package.json +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__03ewxxu._.js +0 -9
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__03rmbz6._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__05z0pcw._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0acikq.._.js +0 -9
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b5r~-1._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0jbc7tz._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0ow1kn1._.js +0 -9
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0t7i-hy._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~xt99s._.js +0 -3
- package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0ck.78s.js +0 -21
- package/app/apps/web/.next/server/chunks/packages_09wmnv.._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0g-tomj._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +0 -12
- package/app/apps/web/.next/static/chunks/02xxpexzk3rf-.js +0 -1
- package/app/apps/web/.next/static/chunks/044mtrzh7d7va.js +0 -10
- package/app/apps/web/.next/static/chunks/05-k9oryi9wz1.js +0 -14
- package/app/apps/web/.next/static/chunks/05b0dn.r00hq1.js +0 -1
- package/app/apps/web/.next/static/chunks/0t2on1dhydyb2.css +0 -1
- package/app/apps/web/.next/static/chunks/0tliv91mhmrzn.js +0 -1
- /package/app/apps/web/.next/static/{5IC6kxw0JoPeCIPdnTpEp → bTww6lPxII4wde65VQDrF}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{5IC6kxw0JoPeCIPdnTpEp → bTww6lPxII4wde65VQDrF}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{5IC6kxw0JoPeCIPdnTpEp → bTww6lPxII4wde65VQDrF}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import type { IndexDoc } from "./types";
|
|
3
|
+
import {
|
|
4
|
+
FALLBACK_SUGGESTIONS,
|
|
5
|
+
buildSuggestionContext,
|
|
6
|
+
deterministicSuggestions,
|
|
7
|
+
parseSuggestions,
|
|
8
|
+
suggestionInputKey,
|
|
9
|
+
} from "./suggestions";
|
|
10
|
+
|
|
11
|
+
function doc(partial: Partial<IndexDoc> & { sessionId: string }): IndexDoc {
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
agent: "claude-code",
|
|
15
|
+
project: "/Users/me/Repo/foo",
|
|
16
|
+
day: "2026-07-08",
|
|
17
|
+
startIso: "2026-07-08T10:00:00.000Z",
|
|
18
|
+
title: "untitled",
|
|
19
|
+
chunks: [],
|
|
20
|
+
mtimeMs: 0,
|
|
21
|
+
sizeBytes: 0,
|
|
22
|
+
...partial,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const NOW = Date.parse("2026-07-10T12:00:00.000Z");
|
|
27
|
+
|
|
28
|
+
describe("deterministicSuggestions", () => {
|
|
29
|
+
test("steers labels toward the most recently active project", () => {
|
|
30
|
+
const docs = [
|
|
31
|
+
doc({ sessionId: "a", project: "/Users/me/Repo/workhub", day: "2026-07-09", startIso: "2026-07-09T10:00:00.000Z" }),
|
|
32
|
+
doc({ sessionId: "b", project: "/Users/me/Repo/workhub", day: "2026-07-08", startIso: "2026-07-08T10:00:00.000Z" }),
|
|
33
|
+
doc({ sessionId: "c", project: "/Users/me/Repo/old", day: "2026-06-01", startIso: "2026-06-01T10:00:00.000Z" }),
|
|
34
|
+
];
|
|
35
|
+
const suggestions = deterministicSuggestions(docs, NOW);
|
|
36
|
+
expect(suggestions).toHaveLength(4);
|
|
37
|
+
expect(suggestions.map((s) => s.category).sort()).toEqual(["find", "handoff", "recap", "synthesize"]);
|
|
38
|
+
// the active project's short name appears in at least two labels
|
|
39
|
+
const mentioning = suggestions.filter((s) => s.label.includes("workhub"));
|
|
40
|
+
expect(mentioning.length).toBeGreaterThanOrEqual(2);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("falls back to the static set when there are no docs", () => {
|
|
44
|
+
expect(deterministicSuggestions([], NOW)).toEqual(FALLBACK_SUGGESTIONS);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("prefers the git repo name over the folder basename", () => {
|
|
48
|
+
const docs = [
|
|
49
|
+
doc({
|
|
50
|
+
sessionId: "a",
|
|
51
|
+
project: "/Users/me/Repo/claude-lens",
|
|
52
|
+
repoName: "fleetlens",
|
|
53
|
+
day: "2026-07-09",
|
|
54
|
+
startIso: "2026-07-09T10:00:00.000Z",
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
const labels = deterministicSuggestions(docs, NOW).map((s) => s.label).join(" | ");
|
|
58
|
+
expect(labels).toContain("fleetlens");
|
|
59
|
+
expect(labels).not.toContain("claude-lens");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("spreads across projects: find uses the second most active project", () => {
|
|
63
|
+
const docs = [
|
|
64
|
+
doc({ sessionId: "a", project: "/Users/me/Repo/workhub", day: "2026-07-09", startIso: "2026-07-09T10:00:00.000Z" }),
|
|
65
|
+
doc({ sessionId: "b", project: "/Users/me/Repo/workhub", day: "2026-07-09", startIso: "2026-07-09T11:00:00.000Z" }),
|
|
66
|
+
doc({ sessionId: "c", project: "/Users/me/Repo/sidegig", day: "2026-07-08", startIso: "2026-07-08T10:00:00.000Z" }),
|
|
67
|
+
];
|
|
68
|
+
const suggestions = deterministicSuggestions(docs, NOW);
|
|
69
|
+
const find = suggestions.find((s) => s.category === "find")!;
|
|
70
|
+
expect(find.label).toContain("sidegig");
|
|
71
|
+
// the top project shows up at most twice so the set doesn't read as a broken record
|
|
72
|
+
const workhubMentions = suggestions.filter((s) => s.label.includes("workhub"));
|
|
73
|
+
expect(workhubMentions.length).toBeLessThanOrEqual(2);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("suggestionInputKey", () => {
|
|
78
|
+
test("changes when the newest session changes, stable otherwise", () => {
|
|
79
|
+
const docs = [doc({ sessionId: "a", startIso: "2026-07-09T10:00:00.000Z" })];
|
|
80
|
+
const k1 = suggestionInputKey(docs);
|
|
81
|
+
expect(suggestionInputKey([...docs])).toBe(k1);
|
|
82
|
+
const k2 = suggestionInputKey([...docs, doc({ sessionId: "b", startIso: "2026-07-10T09:00:00.000Z" })]);
|
|
83
|
+
expect(k2).not.toBe(k1);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("buildSuggestionContext", () => {
|
|
88
|
+
test("includes active projects, recent titles, and summary chunks", () => {
|
|
89
|
+
const docs = [
|
|
90
|
+
doc({
|
|
91
|
+
sessionId: "a",
|
|
92
|
+
project: "/Users/me/Repo/workhub",
|
|
93
|
+
day: "2026-07-09",
|
|
94
|
+
startIso: "2026-07-09T10:00:00.000Z",
|
|
95
|
+
title: "wire the billing webhook",
|
|
96
|
+
chunks: [{ role: "summary", text: "Shipped the webhook retry queue." }],
|
|
97
|
+
}),
|
|
98
|
+
doc({ sessionId: "c", project: "/Users/me/Repo/old", day: "2026-06-01", startIso: "2026-06-01T10:00:00.000Z", title: "legacy cleanup" }),
|
|
99
|
+
];
|
|
100
|
+
const ctx = buildSuggestionContext(docs, NOW);
|
|
101
|
+
expect(ctx).toContain("workhub");
|
|
102
|
+
expect(ctx).toContain("wire the billing webhook");
|
|
103
|
+
expect(ctx).toContain("Shipped the webhook retry queue.");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("parseSuggestions", () => {
|
|
108
|
+
const good = JSON.stringify([
|
|
109
|
+
{ label: "What did I ship in workhub yesterday?", category: "recap" },
|
|
110
|
+
{ label: "Find the webhook retry sessions", category: "find" },
|
|
111
|
+
{ label: "Summarize this week's workhub arc", category: "synthesize" },
|
|
112
|
+
{ label: "Draft a prompt to continue the billing work", category: "handoff" },
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
test("accepts a valid four-item array, with or without a code fence", () => {
|
|
116
|
+
expect(parseSuggestions(good)).toHaveLength(4);
|
|
117
|
+
expect(parseSuggestions("```json\n" + good + "\n```")).toHaveLength(4);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("extracts the array from surrounding prose (haiku loves preambles)", () => {
|
|
121
|
+
const wrapped = `Here are the four suggestion chips based on the digest:\n\n${good}\n\nLet me know if you'd like different ones!`;
|
|
122
|
+
expect(parseSuggestions(wrapped)).toHaveLength(4);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("rejects wrong categories, wrong counts, and non-JSON", () => {
|
|
126
|
+
expect(parseSuggestions(JSON.stringify([{ label: "x", category: "nope" }]))).toBeNull();
|
|
127
|
+
expect(parseSuggestions(JSON.stringify([]))).toBeNull();
|
|
128
|
+
expect(parseSuggestions("not json at all")).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("survives trailing prose containing a stray closing bracket", () => {
|
|
132
|
+
expect(parseSuggestions(`${good}\n\nThese cover [recap] and more!`)).toHaveLength(4);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("rejects duplicate categories", () => {
|
|
136
|
+
const dup = JSON.stringify([
|
|
137
|
+
{ label: "a", category: "find" },
|
|
138
|
+
{ label: "b", category: "find" },
|
|
139
|
+
{ label: "c", category: "synthesize" },
|
|
140
|
+
{ label: "d", category: "handoff" },
|
|
141
|
+
]);
|
|
142
|
+
expect(parseSuggestions(dup)).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("clamps overlong labels", () => {
|
|
146
|
+
const long = JSON.stringify([
|
|
147
|
+
{ label: "x".repeat(300), category: "recap" },
|
|
148
|
+
{ label: "b", category: "find" },
|
|
149
|
+
{ label: "c", category: "synthesize" },
|
|
150
|
+
{ label: "d", category: "handoff" },
|
|
151
|
+
]);
|
|
152
|
+
const parsed = parseSuggestions(long);
|
|
153
|
+
expect(parsed).not.toBeNull();
|
|
154
|
+
expect(parsed![0]!.label.length).toBeLessThanOrEqual(120);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { IndexDoc } from "./types";
|
|
3
|
+
|
|
4
|
+
/** One clickable empty-state chip. The four categories are the capability
|
|
5
|
+
* menu (recap / find / synthesize / handoff); personalization only changes
|
|
6
|
+
* the wording, never the shape. */
|
|
7
|
+
export type Suggestion = {
|
|
8
|
+
label: string;
|
|
9
|
+
category: "recap" | "find" | "synthesize" | "handoff";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const FALLBACK_SUGGESTIONS: Suggestion[] = [
|
|
13
|
+
{ label: "What did I work on yesterday?", category: "recap" },
|
|
14
|
+
{ label: "Find the sessions behind my most recent fix", category: "find" },
|
|
15
|
+
{ label: "Which projects did I touch this week, and what happened in each?", category: "synthesize" },
|
|
16
|
+
{ label: "Draft a prompt to continue my most recent feature work, with full context", category: "handoff" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const LABEL_MAX = 120;
|
|
20
|
+
const RECENT_WINDOW_MS = 7 * 86_400_000;
|
|
21
|
+
|
|
22
|
+
function shortName(project: string): string {
|
|
23
|
+
const parts = project.split("/").filter(Boolean);
|
|
24
|
+
return parts[parts.length - 1] ?? project;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ActiveProject = { project: string; display: string; sessions: number; lastDay: string };
|
|
28
|
+
|
|
29
|
+
function byRecentActivity(docs: IndexDoc[], nowMs: number): ActiveProject[] {
|
|
30
|
+
const cutoff = nowMs - RECENT_WINDOW_MS;
|
|
31
|
+
const agg = new Map<string, { display: string; sessions: number; lastDay: string }>();
|
|
32
|
+
for (const d of docs) {
|
|
33
|
+
const ms = d.startIso ? Date.parse(d.startIso) : NaN;
|
|
34
|
+
if (Number.isNaN(ms) || ms < cutoff) continue;
|
|
35
|
+
const cur = agg.get(d.project) ?? { display: shortName(d.project), sessions: 0, lastDay: "" };
|
|
36
|
+
cur.sessions++;
|
|
37
|
+
if (d.day > cur.lastDay) cur.lastDay = d.day;
|
|
38
|
+
// The git remote's repo name is what the rest of the UI shows.
|
|
39
|
+
if (d.repoName) cur.display = d.repoName;
|
|
40
|
+
agg.set(d.project, cur);
|
|
41
|
+
}
|
|
42
|
+
return Array.from(agg.entries())
|
|
43
|
+
.map(([project, v]) => ({ project, ...v }))
|
|
44
|
+
.sort((a, b) => b.lastDay.localeCompare(a.lastDay) || b.sessions - a.sessions);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** LLM-free personalization tier: template-fill the four categories from
|
|
48
|
+
* recent activity. Used when AI features are off, the LLM call fails, or
|
|
49
|
+
* nothing is cached yet. The top project appears at most twice; the find
|
|
50
|
+
* chip goes to the runner-up project so the set doesn't read as a loop. */
|
|
51
|
+
export function deterministicSuggestions(docs: IndexDoc[], nowMs: number): Suggestion[] {
|
|
52
|
+
const active = byRecentActivity(docs, nowMs);
|
|
53
|
+
const top = active[0];
|
|
54
|
+
if (!top) return FALLBACK_SUGGESTIONS;
|
|
55
|
+
const second = active[1];
|
|
56
|
+
return [
|
|
57
|
+
{ label: "What did I work on yesterday?", category: "recap" },
|
|
58
|
+
second
|
|
59
|
+
? { label: `Find the sessions where I worked on ${second.display}`, category: "find" }
|
|
60
|
+
: { label: "Find the sessions behind my most recent fix", category: "find" },
|
|
61
|
+
{ label: `Summarize this week's work on ${top.display}`, category: "synthesize" },
|
|
62
|
+
{ label: `Draft a prompt to continue my latest ${top.display} work, with full context`, category: "handoff" },
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Staleness key for the cached LLM suggestions: any new session (or a
|
|
67
|
+
* re-indexed newest session) invalidates. */
|
|
68
|
+
export function suggestionInputKey(docs: IndexDoc[]): string {
|
|
69
|
+
let newestIso = "";
|
|
70
|
+
let newestId = "";
|
|
71
|
+
for (const d of docs) {
|
|
72
|
+
const iso = d.startIso ?? "";
|
|
73
|
+
if (iso > newestIso) {
|
|
74
|
+
newestIso = iso;
|
|
75
|
+
newestId = d.sessionId;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return `${newestId}|${newestIso}|${docs.length}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CONTEXT_PROJECTS = 3;
|
|
82
|
+
const CONTEXT_TITLES = 8;
|
|
83
|
+
const CONTEXT_BRIEFS = 6;
|
|
84
|
+
|
|
85
|
+
/** Compact activity digest fed to the suggestion model. */
|
|
86
|
+
export function buildSuggestionContext(docs: IndexDoc[], nowMs: number): string {
|
|
87
|
+
const today = new Date(nowMs);
|
|
88
|
+
const lines: string[] = [];
|
|
89
|
+
lines.push(`Today: ${today.toLocaleDateString("en-CA")} (${today.toLocaleDateString("en-US", { weekday: "long" })})`);
|
|
90
|
+
|
|
91
|
+
const active = byRecentActivity(docs, nowMs).slice(0, CONTEXT_PROJECTS);
|
|
92
|
+
lines.push("", "Most active projects (last 7 days):");
|
|
93
|
+
for (const p of active) {
|
|
94
|
+
lines.push(`- ${p.display} — ${p.sessions} sessions, last active ${p.lastDay}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const displayFor = (d: IndexDoc) => d.repoName ?? shortName(d.project);
|
|
98
|
+
const recent = [...docs]
|
|
99
|
+
.sort((a, b) => (b.startIso ?? "").localeCompare(a.startIso ?? ""))
|
|
100
|
+
.slice(0, CONTEXT_TITLES);
|
|
101
|
+
lines.push("", "Most recent sessions:");
|
|
102
|
+
for (const d of recent) {
|
|
103
|
+
lines.push(`- [${d.day}] ${displayFor(d)}: ${d.title}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const briefs: string[] = [];
|
|
107
|
+
for (const d of recent) {
|
|
108
|
+
for (const c of d.chunks) {
|
|
109
|
+
if (c.role !== "summary") continue;
|
|
110
|
+
briefs.push(`- ${displayFor(d)}: ${c.text}`);
|
|
111
|
+
if (briefs.length >= CONTEXT_BRIEFS) break;
|
|
112
|
+
}
|
|
113
|
+
if (briefs.length >= CONTEXT_BRIEFS) break;
|
|
114
|
+
}
|
|
115
|
+
if (briefs.length > 0) {
|
|
116
|
+
lines.push("", "AI summaries of recent sessions:");
|
|
117
|
+
lines.push(...briefs);
|
|
118
|
+
}
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const SUGGESTION_SYSTEM_PROMPT = `You write the four suggestion chips shown on the empty state of an agent that searches the user's local coding-agent session history (their own past Claude Code / Codex / Gemini conversations).
|
|
123
|
+
|
|
124
|
+
You will receive a digest of their recent activity. Produce EXACTLY four chips, one per category:
|
|
125
|
+
- "recap" — a time-scoped "what happened" question (yesterday / this week)
|
|
126
|
+
- "find" — locate specific past sessions about a concrete thing they worked on
|
|
127
|
+
- "synthesize" — a cross-session summary or comparison
|
|
128
|
+
- "handoff" — draft a context-pack prompt to continue their current work in a fresh agent session
|
|
129
|
+
|
|
130
|
+
Rules:
|
|
131
|
+
- Write in the user's first-person voice, as something they would click ("What did I…", "Find…", "Draft a prompt…").
|
|
132
|
+
- Anchor chips in CONCRETE topics from the session titles and summaries — the feature, bug, or artifact itself ("the usage daemon fix", "the onboarding wizard"), not just repository names. A project name may appear in at most two of the four labels; a label with a topic and no project name is often better.
|
|
133
|
+
- Use project display names exactly as given in the digest.
|
|
134
|
+
- Specific and tempting beats generic. ≤ 90 characters per label.
|
|
135
|
+
- Output ONLY a JSON array: [{"label": "...", "category": "recap|find|synthesize|handoff"}, ...] — no prose, no markdown fence.`;
|
|
136
|
+
|
|
137
|
+
const suggestionSchema = z
|
|
138
|
+
.array(
|
|
139
|
+
z.object({
|
|
140
|
+
label: z.string().min(1),
|
|
141
|
+
category: z.enum(["recap", "find", "synthesize", "handoff"]),
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
.length(4)
|
|
145
|
+
// One chip per capability — the UI keys rows by category, and a model
|
|
146
|
+
// that doubles up (two "find"s) would collide.
|
|
147
|
+
.refine((arr) => new Set(arr.map((s) => s.category)).size === 4);
|
|
148
|
+
|
|
149
|
+
/** Pull the first JSON array out of the response — smaller models wrap it
|
|
150
|
+
* in preambles and fences no matter what the prompt says — then validate.
|
|
151
|
+
* Returns null on any mismatch; callers fall back to deterministic. */
|
|
152
|
+
export function parseSuggestions(content: string): Suggestion[] | null {
|
|
153
|
+
const start = content.indexOf("[");
|
|
154
|
+
if (start === -1) return null;
|
|
155
|
+
// Try every closing bracket from the end inward: trailing prose that
|
|
156
|
+
// happens to contain "]" must not poison an otherwise valid array.
|
|
157
|
+
for (let end = content.lastIndexOf("]"); end > start; end = content.lastIndexOf("]", end - 1)) {
|
|
158
|
+
try {
|
|
159
|
+
const result = suggestionSchema.safeParse(JSON.parse(content.slice(start, end + 1)));
|
|
160
|
+
if (!result.success) return null;
|
|
161
|
+
return result.data.map((s) => ({ ...s, label: s.label.slice(0, LABEL_MAX) }));
|
|
162
|
+
} catch {
|
|
163
|
+
/* unbalanced slice — try the next candidate bracket */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import type { IndexDoc } from "./types";
|
|
3
|
+
|
|
4
|
+
const doc = (over: Partial<IndexDoc>): IndexDoc => ({
|
|
5
|
+
version: 2,
|
|
6
|
+
sessionId: "s1",
|
|
7
|
+
agent: "claude-code",
|
|
8
|
+
project: "/repo/a",
|
|
9
|
+
day: "2026-07-01",
|
|
10
|
+
startIso: "2026-07-01T10:00:00Z",
|
|
11
|
+
title: "t",
|
|
12
|
+
chunks: [],
|
|
13
|
+
mtimeMs: 0,
|
|
14
|
+
sizeBytes: 0,
|
|
15
|
+
...over,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const DOCS: IndexDoc[] = [
|
|
19
|
+
doc({
|
|
20
|
+
sessionId: "aaa",
|
|
21
|
+
project: "/repo/a",
|
|
22
|
+
day: "2026-07-01",
|
|
23
|
+
startIso: "2026-07-01T10:00:00Z",
|
|
24
|
+
title: "fix daemon",
|
|
25
|
+
chunks: [
|
|
26
|
+
{ role: "summary", text: "ai brief" },
|
|
27
|
+
{ role: "user", text: "hello" },
|
|
28
|
+
{ role: "agent", text: "world" },
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
doc({ sessionId: "bbb", project: "/repo/a", day: "2026-07-03", startIso: "2026-07-03T09:00:00Z", title: "ship PR" }),
|
|
32
|
+
doc({ sessionId: "ccc", project: "/repo/b", day: "2026-07-02", startIso: "2026-07-02T08:00:00Z", title: "other repo" }),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
vi.mock("./index-store", () => ({
|
|
36
|
+
ensureIndex: async () => DOCS,
|
|
37
|
+
indexStats: () => ({ sessions: DOCS.length, building: false }),
|
|
38
|
+
}));
|
|
39
|
+
vi.mock("@claude-lens/parser/fs", () => ({
|
|
40
|
+
getAnySession: async (id: string) => (id === "aaa" ? { meta: { sessionId: id } } : null),
|
|
41
|
+
}));
|
|
42
|
+
vi.mock("@/lib/ai/session-summary", () => ({
|
|
43
|
+
summarizeSessionForAI: () => "STRUCTURED_DIGEST",
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
import { agentTools } from "./tools";
|
|
47
|
+
|
|
48
|
+
describe("list_projects", () => {
|
|
49
|
+
test("aggregates session counts and last-activity day, newest project first", async () => {
|
|
50
|
+
const out = JSON.parse(await agentTools.call("list_projects", {}));
|
|
51
|
+
expect(out.projects).toEqual([
|
|
52
|
+
{ project: "/repo/a", sessions: 2, lastDay: "2026-07-03" },
|
|
53
|
+
{ project: "/repo/b", sessions: 1, lastDay: "2026-07-02" },
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("get_session", () => {
|
|
59
|
+
test("full_text returns chunk text with roles, skipping nothing", async () => {
|
|
60
|
+
const out = await agentTools.call("get_session", { session_id: "aaa", mode: "full_text" });
|
|
61
|
+
expect(out).toContain("[user] hello");
|
|
62
|
+
expect(out).toContain("[agent] world");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("full_text on an unindexed session errors", async () => {
|
|
66
|
+
await expect(agentTools.call("get_session", { session_id: "zzz", mode: "full_text" })).rejects.toThrow(
|
|
67
|
+
/not found in index/,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("summary mode prepends indexed AI summary chunks to the digest", async () => {
|
|
72
|
+
const out = await agentTools.call("get_session", { session_id: "aaa" });
|
|
73
|
+
expect(out).toContain("## AI summary\nai brief");
|
|
74
|
+
expect(out).toContain("STRUCTURED_DIGEST");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("summary mode errors when the transcript is gone", async () => {
|
|
78
|
+
await expect(agentTools.call("get_session", { session_id: "bbb" })).rejects.toThrow(/not found/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("list_sessions", () => {
|
|
83
|
+
test("filters by project and date bounds, newest first", async () => {
|
|
84
|
+
const out = JSON.parse(await agentTools.call("list_sessions", { project: "/repo/a", after: "2026-07-02" }));
|
|
85
|
+
expect(out.total).toBe(1);
|
|
86
|
+
expect(out.sessions[0]).toMatchObject({ session_id: "bbb", url: "/sessions/bbb" });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("unfiltered list is sorted by startIso descending and respects limit", async () => {
|
|
90
|
+
const out = JSON.parse(await agentTools.call("list_sessions", { limit: 2 }));
|
|
91
|
+
expect(out.total).toBe(3);
|
|
92
|
+
expect(out.sessions.map((s: { session_id: string }) => s.session_id)).toEqual(["bbb", "ccc"]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("unknown tool throws", async () => {
|
|
97
|
+
await expect(agentTools.call("nope", {})).rejects.toThrow(/unknown tool/);
|
|
98
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { getAnySession } from "@claude-lens/parser/fs";
|
|
2
|
+
import { summarizeSessionForAI } from "@/lib/ai/session-summary";
|
|
3
|
+
import { ensureIndex, indexStats } from "./index-store";
|
|
4
|
+
import { searchDocs } from "./search";
|
|
5
|
+
import type { McpToolDef, McpTools } from "./mcp";
|
|
6
|
+
import type { SearchFilters } from "./types";
|
|
7
|
+
|
|
8
|
+
const FULL_TEXT_MAX = 48_000;
|
|
9
|
+
|
|
10
|
+
const DEFS: McpToolDef[] = [
|
|
11
|
+
{
|
|
12
|
+
name: "search_sessions",
|
|
13
|
+
description:
|
|
14
|
+
"Full-text search across every locally recorded coding-agent session (Claude Code, Codex, Gemini, …). " +
|
|
15
|
+
"Searches what the user and the agent actually said, plus AI-written session summaries. " +
|
|
16
|
+
"Multi-word queries are ranked-OR (sessions matching all words rank first); wrap a phrase in double quotes for exact match. " +
|
|
17
|
+
"Returns ranked hits with snippets and a dashboard url per session. " +
|
|
18
|
+
"Run several differently-worded searches when the first one isn't conclusive.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
query: { type: "string", description: "Search terms. Quote phrases for exact match." },
|
|
23
|
+
project: { type: "string", description: "Exact canonical project path filter, e.g. /Users/me/Repo/foo" },
|
|
24
|
+
agent: { type: "string", description: "Agent source filter: claude-code | codex | gemini-cli | …" },
|
|
25
|
+
after: { type: "string", description: "Only sessions starting on/after this local day (YYYY-MM-DD)" },
|
|
26
|
+
before: { type: "string", description: "Only sessions starting on/before this local day (YYYY-MM-DD)" },
|
|
27
|
+
limit: { type: "number", description: "Max hits to return (default 10, max 25)" },
|
|
28
|
+
},
|
|
29
|
+
required: ["query"],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "get_session",
|
|
34
|
+
description:
|
|
35
|
+
"Fetch one session by id. mode=summary (default) returns a compact structured digest: metadata, first user message, " +
|
|
36
|
+
"turn-by-turn timeline, PRs shipped, errors, final message. mode=full_text returns the raw conversational text " +
|
|
37
|
+
"(user + agent messages, capped). Use summary first; full_text only when you need exact wording.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
session_id: { type: "string" },
|
|
42
|
+
mode: { type: "string", enum: ["summary", "full_text"] },
|
|
43
|
+
},
|
|
44
|
+
required: ["session_id"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "list_sessions",
|
|
49
|
+
description:
|
|
50
|
+
"List recent sessions (newest first) with title, project, day, and agent. " +
|
|
51
|
+
"Use for time- or project-scoped questions like 'what did I work on last Tuesday' — filter with after/before/project.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
project: { type: "string", description: "Exact canonical project path filter" },
|
|
56
|
+
agent: { type: "string" },
|
|
57
|
+
after: { type: "string", description: "YYYY-MM-DD inclusive" },
|
|
58
|
+
before: { type: "string", description: "YYYY-MM-DD inclusive" },
|
|
59
|
+
limit: { type: "number", description: "Default 20, max 100" },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "list_projects",
|
|
65
|
+
description: "List every project that has recorded sessions, with session counts and the most recent activity day.",
|
|
66
|
+
inputSchema: { type: "object", properties: {} },
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
type SearchArgs = SearchFilters & { query: string; limit?: number };
|
|
71
|
+
type GetArgs = { session_id: string; mode?: "summary" | "full_text" };
|
|
72
|
+
type ListArgs = SearchFilters & { limit?: number };
|
|
73
|
+
|
|
74
|
+
async function searchSessions(args: SearchArgs): Promise<string> {
|
|
75
|
+
const docs = await ensureIndex();
|
|
76
|
+
const limit = Math.min(Math.max(1, args.limit ?? 10), 25);
|
|
77
|
+
const { hits, total } = searchDocs(docs, args.query, {
|
|
78
|
+
limit,
|
|
79
|
+
filters: { project: args.project, agent: args.agent, after: args.after, before: args.before },
|
|
80
|
+
});
|
|
81
|
+
return JSON.stringify({
|
|
82
|
+
indexed_sessions: indexStats().sessions,
|
|
83
|
+
total_matches: total,
|
|
84
|
+
hits: hits.map((h) => ({
|
|
85
|
+
session_id: h.sessionId,
|
|
86
|
+
url: `/sessions/${h.sessionId}`,
|
|
87
|
+
agent: h.agent,
|
|
88
|
+
project: h.project,
|
|
89
|
+
day: h.day,
|
|
90
|
+
title: h.title,
|
|
91
|
+
matched_terms: h.matchedTerms,
|
|
92
|
+
snippets: h.snippets,
|
|
93
|
+
})),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function getSessionTool(args: GetArgs): Promise<string> {
|
|
98
|
+
const docs = await ensureIndex();
|
|
99
|
+
const doc = docs.find((d) => d.sessionId === args.session_id);
|
|
100
|
+
if (args.mode === "full_text") {
|
|
101
|
+
if (!doc) throw new Error(`session not found in index: ${args.session_id}`);
|
|
102
|
+
let out = `# Session ${doc.sessionId}\nproject: ${doc.project}\nday: ${doc.day}\nagent: ${doc.agent}\n\n`;
|
|
103
|
+
for (const c of doc.chunks) {
|
|
104
|
+
const line = `[${c.role}] ${c.text}\n\n`;
|
|
105
|
+
if (out.length + line.length > FULL_TEXT_MAX) {
|
|
106
|
+
out += "…(truncated)";
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
out += line;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
const detail = await getAnySession(args.session_id);
|
|
114
|
+
if (!detail) throw new Error(`session not found: ${args.session_id}`);
|
|
115
|
+
const summaries = doc?.chunks.filter((c) => c.role === "summary").map((c) => c.text) ?? [];
|
|
116
|
+
const header = summaries.length ? `## AI summary\n${summaries.join("\n")}\n\n` : "";
|
|
117
|
+
return header + summarizeSessionForAI(detail);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function listSessions(args: ListArgs): Promise<string> {
|
|
121
|
+
const docs = await ensureIndex();
|
|
122
|
+
const limit = Math.min(Math.max(1, args.limit ?? 20), 100);
|
|
123
|
+
const filtered = docs
|
|
124
|
+
.filter(
|
|
125
|
+
(d) =>
|
|
126
|
+
(!args.project || d.project === args.project) &&
|
|
127
|
+
(!args.agent || d.agent === args.agent) &&
|
|
128
|
+
(!args.after || d.day >= args.after) &&
|
|
129
|
+
(!args.before || d.day <= args.before),
|
|
130
|
+
)
|
|
131
|
+
.sort((a, b) => (b.startIso ?? "").localeCompare(a.startIso ?? ""));
|
|
132
|
+
return JSON.stringify({
|
|
133
|
+
total: filtered.length,
|
|
134
|
+
sessions: filtered.slice(0, limit).map((d) => ({
|
|
135
|
+
session_id: d.sessionId,
|
|
136
|
+
url: `/sessions/${d.sessionId}`,
|
|
137
|
+
agent: d.agent,
|
|
138
|
+
project: d.project,
|
|
139
|
+
day: d.day,
|
|
140
|
+
title: d.title,
|
|
141
|
+
})),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function listProjects(): Promise<string> {
|
|
146
|
+
const docs = await ensureIndex();
|
|
147
|
+
const byProject = new Map<string, { sessions: number; lastDay: string }>();
|
|
148
|
+
for (const d of docs) {
|
|
149
|
+
const cur = byProject.get(d.project) ?? { sessions: 0, lastDay: "" };
|
|
150
|
+
cur.sessions++;
|
|
151
|
+
if (d.day > cur.lastDay) cur.lastDay = d.day;
|
|
152
|
+
byProject.set(d.project, cur);
|
|
153
|
+
}
|
|
154
|
+
const projects = Array.from(byProject.entries())
|
|
155
|
+
.map(([project, v]) => ({ project, ...v }))
|
|
156
|
+
.sort((a, b) => b.lastDay.localeCompare(a.lastDay));
|
|
157
|
+
return JSON.stringify({ projects });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const agentTools: McpTools = {
|
|
161
|
+
defs: DEFS,
|
|
162
|
+
call: async (name, args) => {
|
|
163
|
+
switch (name) {
|
|
164
|
+
case "search_sessions":
|
|
165
|
+
return searchSessions(args as SearchArgs);
|
|
166
|
+
case "get_session":
|
|
167
|
+
return getSessionTool(args as GetArgs);
|
|
168
|
+
case "list_sessions":
|
|
169
|
+
return listSessions(args as ListArgs);
|
|
170
|
+
case "list_projects":
|
|
171
|
+
return listProjects();
|
|
172
|
+
default:
|
|
173
|
+
throw new Error(`unknown tool: ${name}`);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const INDEX_VERSION = 2;
|
|
2
|
+
|
|
3
|
+
export type IndexChunk = {
|
|
4
|
+
role: "user" | "agent" | "summary";
|
|
5
|
+
text: string;
|
|
6
|
+
/** event index in the source transcript, when applicable */
|
|
7
|
+
idx?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** One searchable session — extracted conversational text plus metadata. */
|
|
11
|
+
export type IndexDoc = {
|
|
12
|
+
version: number;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
agent: string;
|
|
15
|
+
/** canonical project name (worktrees rolled up) */
|
|
16
|
+
project: string;
|
|
17
|
+
/** GitHub repo name from the origin remote — the display name the rest of
|
|
18
|
+
* the UI uses when the folder is named something else. */
|
|
19
|
+
repoName?: string;
|
|
20
|
+
/** local day of session start, YYYY-MM-DD */
|
|
21
|
+
day: string;
|
|
22
|
+
startIso?: string;
|
|
23
|
+
endIso?: string;
|
|
24
|
+
title: string;
|
|
25
|
+
chunks: IndexChunk[];
|
|
26
|
+
truncated?: boolean;
|
|
27
|
+
mtimeMs: number;
|
|
28
|
+
sizeBytes: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SearchFilters = {
|
|
32
|
+
project?: string;
|
|
33
|
+
agent?: string;
|
|
34
|
+
/** inclusive YYYY-MM-DD bounds on the session's start day */
|
|
35
|
+
after?: string;
|
|
36
|
+
before?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SearchSnippet = { role: string; text: string };
|
|
40
|
+
|
|
41
|
+
export type SearchHit = {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
agent: string;
|
|
44
|
+
project: string;
|
|
45
|
+
day: string;
|
|
46
|
+
title: string;
|
|
47
|
+
score: number;
|
|
48
|
+
matchedTerms: string[];
|
|
49
|
+
snippets: SearchSnippet[];
|
|
50
|
+
};
|