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,206 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { agentSources, cclensPath, invalidateFile } from "@claude-lens/parser/fs";
|
|
4
|
+
import type { SessionMeta } from "@claude-lens/parser";
|
|
5
|
+
import { parseEntryKey } from "@claude-lens/entries";
|
|
6
|
+
import { listEntryKeys, readEntry } from "@claude-lens/entries/fs";
|
|
7
|
+
import { extractIndexDoc } from "./extract";
|
|
8
|
+
import { INDEX_VERSION, type IndexDoc } from "./types";
|
|
9
|
+
|
|
10
|
+
export type IndexProgress = { built: number; reused: number; total: number };
|
|
11
|
+
export type IndexStats = { sessions: number; lastRefreshMs: number | null; building: boolean };
|
|
12
|
+
|
|
13
|
+
function indexDir(): string {
|
|
14
|
+
return cclensPath("agent-index");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function docPath(sessionId: string): string {
|
|
18
|
+
return join(indexDir(), `${sessionId}.json`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Next compiles each route into its own module graph, so plain module
|
|
22
|
+
* state would give every API route a private copy of the index (and its
|
|
23
|
+
* own inflight latch → concurrent full rebuilds). globalThis is the one
|
|
24
|
+
* store they all share — same trick as the canonical dev-safe singleton. */
|
|
25
|
+
type Store = {
|
|
26
|
+
memory: Map<string, IndexDoc>;
|
|
27
|
+
lastRefreshMs: number | null;
|
|
28
|
+
inflight: Promise<IndexDoc[]> | null;
|
|
29
|
+
progressListeners: Set<(p: IndexProgress) => void>;
|
|
30
|
+
};
|
|
31
|
+
const store: Store = ((globalThis as Record<string, unknown>).__fleetlensAgentIndex ??= {
|
|
32
|
+
memory: new Map<string, IndexDoc>(),
|
|
33
|
+
lastRefreshMs: null,
|
|
34
|
+
inflight: null,
|
|
35
|
+
progressListeners: new Set(),
|
|
36
|
+
}) as Store;
|
|
37
|
+
|
|
38
|
+
function readDiskDoc(sessionId: string): IndexDoc | null {
|
|
39
|
+
try {
|
|
40
|
+
const raw = readFileSync(docPath(sessionId), "utf8");
|
|
41
|
+
const doc = JSON.parse(raw) as IndexDoc;
|
|
42
|
+
return doc.version === INDEX_VERSION ? doc : null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeDiskDoc(doc: IndexDoc): void {
|
|
49
|
+
mkdirSync(indexDir(), { recursive: true });
|
|
50
|
+
const final = docPath(doc.sessionId);
|
|
51
|
+
const tmp = `${final}.tmp.${process.pid}`;
|
|
52
|
+
writeFileSync(tmp, JSON.stringify(doc), "utf8");
|
|
53
|
+
renameSync(tmp, final);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** brief_summary chunks from enriched entries — a free semantic layer on top
|
|
57
|
+
* of the raw transcript text. Read only for the sessions being (re)built. */
|
|
58
|
+
function entrySummaries(sessionIds: Set<string>): Map<string, string[]> {
|
|
59
|
+
const out = new Map<string, string[]>();
|
|
60
|
+
if (sessionIds.size === 0) return out;
|
|
61
|
+
for (const key of listEntryKeys()) {
|
|
62
|
+
const parsed = parseEntryKey(key);
|
|
63
|
+
if (!parsed || !sessionIds.has(parsed.session_id)) continue;
|
|
64
|
+
const entry = readEntry(parsed.session_id, parsed.local_day);
|
|
65
|
+
const brief = entry?.enrichment?.brief_summary;
|
|
66
|
+
if (!brief) continue;
|
|
67
|
+
const list = out.get(parsed.session_id) ?? [];
|
|
68
|
+
list.push(brief);
|
|
69
|
+
out.set(parsed.session_id, list);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function refresh(onProgress?: (p: IndexProgress) => void): Promise<IndexDoc[]> {
|
|
75
|
+
const metaLists = await Promise.all(agentSources.map((s) => s.listSessions().catch(() => [] as SessionMeta[])));
|
|
76
|
+
type Pending = { meta: SessionMeta; source: (typeof agentSources)[number]; mtimeMs: number; sizeBytes: number };
|
|
77
|
+
const alive = new Set<string>();
|
|
78
|
+
const pending: Pending[] = [];
|
|
79
|
+
let reused = 0;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < agentSources.length; i++) {
|
|
82
|
+
for (const meta of metaLists[i]!) {
|
|
83
|
+
alive.add(meta.id);
|
|
84
|
+
let st;
|
|
85
|
+
try {
|
|
86
|
+
st = statSync(meta.filePath);
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// Version check matters for memory hits too: the globalThis store
|
|
91
|
+
// outlives hot reloads, so a bumped INDEX_VERSION must evict them.
|
|
92
|
+
const cached = store.memory.get(meta.id) ?? readDiskDoc(meta.id);
|
|
93
|
+
if (cached && cached.version === INDEX_VERSION && cached.mtimeMs === st.mtimeMs && cached.sizeBytes === st.size) {
|
|
94
|
+
store.memory.set(meta.id, cached);
|
|
95
|
+
reused++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
pending.push({ meta, source: agentSources[i]!, mtimeMs: st.mtimeMs, sizeBytes: st.size });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Drop sessions whose transcripts were pruned so search can't surface
|
|
103
|
+
// ghosts — from memory AND disk (a stale disk doc would re-inflate the
|
|
104
|
+
// indexStats readdir count on the next cold boot).
|
|
105
|
+
for (const id of store.memory.keys()) {
|
|
106
|
+
if (!alive.has(id)) store.memory.delete(id);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
for (const f of readdirSync(indexDir())) {
|
|
110
|
+
if (!f.endsWith(".json")) continue;
|
|
111
|
+
const id = f.slice(0, -".json".length);
|
|
112
|
+
if (!alive.has(id)) rmSync(join(indexDir(), f), { force: true });
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
/* index dir may not exist yet */
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const total = pending.length + reused;
|
|
119
|
+
const summaries = entrySummaries(new Set(pending.map((p) => p.meta.id)));
|
|
120
|
+
let built = 0;
|
|
121
|
+
onProgress?.({ built, reused, total });
|
|
122
|
+
|
|
123
|
+
for (const { meta, source, mtimeMs, sizeBytes } of pending) {
|
|
124
|
+
try {
|
|
125
|
+
const detail = await source.getSession(meta.id);
|
|
126
|
+
if (!detail) continue;
|
|
127
|
+
const doc = extractIndexDoc(detail, { mtimeMs, sizeBytes });
|
|
128
|
+
for (const brief of summaries.get(meta.id) ?? []) {
|
|
129
|
+
doc.chunks.unshift({ role: "summary", text: brief });
|
|
130
|
+
}
|
|
131
|
+
writeDiskDoc(doc);
|
|
132
|
+
store.memory.set(meta.id, doc);
|
|
133
|
+
// Keep the parser's unbounded detail cache from ballooning during a
|
|
134
|
+
// full first build over hundreds of transcripts.
|
|
135
|
+
invalidateFile(meta.filePath);
|
|
136
|
+
} catch {
|
|
137
|
+
/* one broken transcript must not sink the whole index */
|
|
138
|
+
}
|
|
139
|
+
built++;
|
|
140
|
+
if (built % 20 === 0 || built === pending.length) onProgress?.({ built, reused, total });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
store.lastRefreshMs = Date.now();
|
|
144
|
+
return Array.from(store.memory.values());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A refresh is stat-cheap but still lists + stats every transcript; one
|
|
148
|
+
* agent turn calls several tools back-to-back, so results this fresh are
|
|
149
|
+
* served from memory. */
|
|
150
|
+
const FRESH_MS = 15_000;
|
|
151
|
+
|
|
152
|
+
/** Incrementally refresh and return every IndexDoc. Fast when nothing
|
|
153
|
+
* changed (one stat per transcript); first-ever call parses everything. */
|
|
154
|
+
export function ensureIndex(
|
|
155
|
+
onProgress?: (p: IndexProgress) => void,
|
|
156
|
+
opts?: { force?: boolean },
|
|
157
|
+
): Promise<IndexDoc[]> {
|
|
158
|
+
// Join the inflight refresh rather than returning its bare promise — a
|
|
159
|
+
// joiner's onProgress must still fire or its progress stream sits silent.
|
|
160
|
+
if (onProgress) store.progressListeners.add(onProgress);
|
|
161
|
+
if (store.inflight) return store.inflight;
|
|
162
|
+
if (
|
|
163
|
+
!opts?.force &&
|
|
164
|
+
store.memory.size > 0 &&
|
|
165
|
+
store.lastRefreshMs !== null &&
|
|
166
|
+
Date.now() - store.lastRefreshMs < FRESH_MS
|
|
167
|
+
) {
|
|
168
|
+
if (onProgress) store.progressListeners.delete(onProgress);
|
|
169
|
+
return Promise.resolve(Array.from(store.memory.values()));
|
|
170
|
+
}
|
|
171
|
+
store.inflight = refresh((p) => {
|
|
172
|
+
for (const fn of store.progressListeners) {
|
|
173
|
+
try {
|
|
174
|
+
fn(p);
|
|
175
|
+
} catch {
|
|
176
|
+
/* one broken listener must not stall the build */
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}).finally(() => {
|
|
180
|
+
store.inflight = null;
|
|
181
|
+
store.progressListeners.clear();
|
|
182
|
+
});
|
|
183
|
+
return store.inflight;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Whatever is in memory right now — no refresh, no blocking. Empty until
|
|
187
|
+
* the first ensureIndex() of this server process completes. */
|
|
188
|
+
export function peekDocs(): IndexDoc[] {
|
|
189
|
+
return Array.from(store.memory.values());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function indexStats(): IndexStats {
|
|
193
|
+
let sessions = store.memory.size;
|
|
194
|
+
if (sessions === 0) {
|
|
195
|
+
try {
|
|
196
|
+
sessions = readdirSync(indexDir()).filter((f) => f.endsWith(".json")).length;
|
|
197
|
+
} catch {
|
|
198
|
+
sessions = 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { sessions, lastRefreshMs: store.lastRefreshMs, building: store.inflight !== null };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function indexIsEmpty(): boolean {
|
|
205
|
+
return store.memory.size === 0 && !existsSync(indexDir());
|
|
206
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { classifyHref } from "./links";
|
|
3
|
+
|
|
4
|
+
describe("classifyHref", () => {
|
|
5
|
+
test("session links render as internal links", () => {
|
|
6
|
+
expect(classifyHref("/sessions/abc-123")).toBe("session");
|
|
7
|
+
expect(classifyHref("/sessions/abc-123?day=2026-07-01")).toBe("session");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("absolute http(s) links render as external links", () => {
|
|
11
|
+
expect(classifyHref("https://github.com/cowcow02/fleetlens/pull/88")).toBe("external");
|
|
12
|
+
expect(classifyHref("http://example.com")).toBe("external");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("relative and unknown hrefs are unwrapped to plain text", () => {
|
|
16
|
+
expect(classifyHref("project_team_onboarding_wizard.md")).toBe("text");
|
|
17
|
+
expect(classifyHref("docs/some-notes.md")).toBe("text");
|
|
18
|
+
expect(classifyHref("/projects/foo")).toBe("text");
|
|
19
|
+
expect(classifyHref("")).toBe("text");
|
|
20
|
+
expect(classifyHref(undefined)).toBe("text");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("javascript and data urls are never rendered as links", () => {
|
|
24
|
+
expect(classifyHref("javascript:alert(1)")).toBe("text");
|
|
25
|
+
expect(classifyHref("data:text/html,hi")).toBe("text");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The model sometimes cites things that aren't linkable — memory-file
|
|
2
|
+
* names, repo paths — and a relative href silently resolves against the
|
|
3
|
+
* page origin into a dead localhost URL. Only two destinations are real:
|
|
4
|
+
* session pages and absolute web URLs; everything else renders as text. */
|
|
5
|
+
export function classifyHref(href: string | undefined): "session" | "external" | "text" {
|
|
6
|
+
if (!href) return "text";
|
|
7
|
+
if (href.startsWith("/sessions/")) return "session";
|
|
8
|
+
if (/^https?:\/\//.test(href)) return "external";
|
|
9
|
+
return "text";
|
|
10
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { handleMcpMessage, type McpTools } from "./mcp";
|
|
3
|
+
|
|
4
|
+
const TOOLS: McpTools = {
|
|
5
|
+
defs: [
|
|
6
|
+
{
|
|
7
|
+
name: "search_sessions",
|
|
8
|
+
description: "Search sessions",
|
|
9
|
+
inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
call: async (name, args) => {
|
|
13
|
+
if (name === "search_sessions") return JSON.stringify({ echo: (args as { query: string }).query });
|
|
14
|
+
throw new Error(`unknown tool: ${name}`);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function rpc(method: string, params?: unknown, id: number | string | null = 1) {
|
|
19
|
+
return { jsonrpc: "2.0", id, method, ...(params !== undefined ? { params } : {}) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("handleMcpMessage", () => {
|
|
23
|
+
test("initialize returns protocol version, capabilities, and server info", async () => {
|
|
24
|
+
const res = (await handleMcpMessage(
|
|
25
|
+
rpc("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "claude", version: "1" } }),
|
|
26
|
+
TOOLS,
|
|
27
|
+
)) as { jsonrpc: string; id: number; result: { protocolVersion: string; capabilities: { tools: object }; serverInfo: { name: string } } };
|
|
28
|
+
expect(res.jsonrpc).toBe("2.0");
|
|
29
|
+
expect(res.id).toBe(1);
|
|
30
|
+
expect(res.result.protocolVersion).toBe("2025-06-18");
|
|
31
|
+
expect(res.result.capabilities.tools).toBeDefined();
|
|
32
|
+
expect(res.result.serverInfo.name).toBe("fleetlens");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("notifications get no response", async () => {
|
|
36
|
+
const res = await handleMcpMessage({ jsonrpc: "2.0", method: "notifications/initialized" }, TOOLS);
|
|
37
|
+
expect(res).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("tools/list returns the tool definitions", async () => {
|
|
41
|
+
const res = (await handleMcpMessage(rpc("tools/list"), TOOLS)) as {
|
|
42
|
+
result: { tools: Array<{ name: string; inputSchema: object }> };
|
|
43
|
+
};
|
|
44
|
+
expect(res.result.tools).toHaveLength(1);
|
|
45
|
+
expect(res.result.tools[0]!.name).toBe("search_sessions");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("tools/call dispatches and wraps the result as text content", async () => {
|
|
49
|
+
const res = (await handleMcpMessage(
|
|
50
|
+
rpc("tools/call", { name: "search_sessions", arguments: { query: "daemon" } }),
|
|
51
|
+
TOOLS,
|
|
52
|
+
)) as { result: { content: Array<{ type: string; text: string }>; isError?: boolean } };
|
|
53
|
+
expect(res.result.content[0]!.type).toBe("text");
|
|
54
|
+
expect(JSON.parse(res.result.content[0]!.text)).toEqual({ echo: "daemon" });
|
|
55
|
+
expect(res.result.isError).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("tools/call surfaces tool failures as isError results, not protocol errors", async () => {
|
|
59
|
+
const res = (await handleMcpMessage(
|
|
60
|
+
rpc("tools/call", { name: "does_not_exist", arguments: {} }),
|
|
61
|
+
TOOLS,
|
|
62
|
+
)) as { result: { content: Array<{ type: string; text: string }>; isError: boolean } };
|
|
63
|
+
expect(res.result.isError).toBe(true);
|
|
64
|
+
expect(res.result.content[0]!.text).toContain("unknown tool");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("unknown methods return JSON-RPC method-not-found", async () => {
|
|
68
|
+
const res = (await handleMcpMessage(rpc("resources/list"), TOOLS)) as {
|
|
69
|
+
error: { code: number; message: string };
|
|
70
|
+
};
|
|
71
|
+
expect(res.error.code).toBe(-32601);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("malformed bodies return JSON-RPC invalid-request", async () => {
|
|
75
|
+
const res = (await handleMcpMessage("not an object", TOOLS)) as { error: { code: number } };
|
|
76
|
+
expect(res.error.code).toBe(-32600);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("ping returns an empty result", async () => {
|
|
80
|
+
const res = (await handleMcpMessage(rpc("ping"), TOOLS)) as { result: object };
|
|
81
|
+
expect(res.result).toEqual({});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) message handler: initialize / tools/list /
|
|
3
|
+
* tools/call over plain JSON-RPC (streamable-HTTP transport). No SDK
|
|
4
|
+
* dependency — the claude CLI is the only client this will ever serve.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
8
|
+
|
|
9
|
+
export type McpToolDef = {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type McpTools = {
|
|
16
|
+
defs: McpToolDef[];
|
|
17
|
+
/** Returns the tool's text payload; throw to signal a tool error. */
|
|
18
|
+
call: (name: string, args: unknown) => Promise<string>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PROTOCOL_VERSION = "2025-06-18";
|
|
22
|
+
|
|
23
|
+
type RpcRequest = {
|
|
24
|
+
jsonrpc?: string;
|
|
25
|
+
id?: number | string | null;
|
|
26
|
+
method?: string;
|
|
27
|
+
params?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function result(id: number | string | null, payload: unknown) {
|
|
31
|
+
return { jsonrpc: "2.0", id, result: payload };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function error(id: number | string | null, code: number, message: string) {
|
|
35
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Handle one JSON-RPC message. Returns the response object, or null for
|
|
39
|
+
* notifications (no id → nothing to send back). */
|
|
40
|
+
export async function handleMcpMessage(body: unknown, tools: McpTools): Promise<unknown | null> {
|
|
41
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
42
|
+
return error(null, -32600, "invalid request");
|
|
43
|
+
}
|
|
44
|
+
const msg = body as RpcRequest;
|
|
45
|
+
if (typeof msg.method !== "string") {
|
|
46
|
+
return error(msg.id ?? null, -32600, "invalid request");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isNotification = msg.id === undefined;
|
|
50
|
+
if (isNotification) return null;
|
|
51
|
+
const id = msg.id ?? null;
|
|
52
|
+
|
|
53
|
+
switch (msg.method) {
|
|
54
|
+
case "initialize": {
|
|
55
|
+
const requested = msg.params?.protocolVersion;
|
|
56
|
+
return result(id, {
|
|
57
|
+
protocolVersion: typeof requested === "string" ? requested : PROTOCOL_VERSION,
|
|
58
|
+
capabilities: { tools: {} },
|
|
59
|
+
serverInfo: { name: "fleetlens", version: pkg.version },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
case "ping":
|
|
63
|
+
return result(id, {});
|
|
64
|
+
case "tools/list":
|
|
65
|
+
return result(id, { tools: tools.defs });
|
|
66
|
+
case "tools/call": {
|
|
67
|
+
const name = msg.params?.name;
|
|
68
|
+
const args = msg.params?.arguments ?? {};
|
|
69
|
+
if (typeof name !== "string") {
|
|
70
|
+
return error(id, -32602, "tools/call requires params.name");
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const text = await tools.call(name, args);
|
|
74
|
+
return result(id, { content: [{ type: "text", text }] });
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return result(id, {
|
|
77
|
+
content: [{ type: "text", text: (err as Error).message }],
|
|
78
|
+
isError: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return error(id, -32601, `method not found: ${msg.method}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { buildUserPrompt, type ChatMessage } from "./prompt";
|
|
3
|
+
|
|
4
|
+
const user = (text: string): ChatMessage => ({ role: "user", text });
|
|
5
|
+
const assistant = (text: string): ChatMessage => ({ role: "assistant", text });
|
|
6
|
+
|
|
7
|
+
describe("buildUserPrompt", () => {
|
|
8
|
+
test("a single message passes through verbatim with no history framing", () => {
|
|
9
|
+
expect(buildUserPrompt([user("find my daemon work")])).toBe("find my daemon work");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("prior turns are framed as conversation history before the new message", () => {
|
|
13
|
+
const out = buildUserPrompt([user("first question"), assistant("first answer"), user("follow-up")]);
|
|
14
|
+
expect(out).toContain("Conversation so far:");
|
|
15
|
+
expect(out).toContain("User: first question");
|
|
16
|
+
expect(out).toContain("You (assistant): first answer");
|
|
17
|
+
expect(out.endsWith("User's new message: follow-up")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("history is trimmed to the most recent 12 messages", () => {
|
|
21
|
+
const messages: ChatMessage[] = [];
|
|
22
|
+
for (let i = 0; i < 20; i++) messages.push(user(`message ${i}`));
|
|
23
|
+
const out = buildUserPrompt(messages);
|
|
24
|
+
expect(out).not.toContain("message 7");
|
|
25
|
+
expect(out).toContain("message 8");
|
|
26
|
+
expect(out.endsWith("User's new message: message 19")).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("overlong messages are truncated with an ellipsis", () => {
|
|
30
|
+
const out = buildUserPrompt([user("x".repeat(5000)), user("short")]);
|
|
31
|
+
expect(out).toContain("x".repeat(4000) + "…");
|
|
32
|
+
expect(out).not.toContain("x".repeat(4001));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("empty input yields an empty prompt", () => {
|
|
36
|
+
expect(buildUserPrompt([])).toBe("");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type ChatMessage = { role: "user" | "assistant"; text: string };
|
|
2
|
+
|
|
3
|
+
const HISTORY_MAX_MESSAGES = 12;
|
|
4
|
+
const HISTORY_MSG_MAX_CHARS = 4000;
|
|
5
|
+
|
|
6
|
+
export function agentSystemPrompt(): string {
|
|
7
|
+
const today = new Date();
|
|
8
|
+
const day = today.toLocaleDateString("en-CA"); // YYYY-MM-DD in local tz
|
|
9
|
+
const weekday = today.toLocaleDateString("en-US", { weekday: "long" });
|
|
10
|
+
return `You are the Fleetlens Agent. Fleetlens is a local, privacy-first dashboard over the user's coding-agent history: every Claude Code, Codex, Gemini (and other) session recorded on this machine. You have tools that search and read those sessions.
|
|
11
|
+
|
|
12
|
+
Today is ${weekday}, ${day}. All session data is local; nothing you do leaves this machine.
|
|
13
|
+
|
|
14
|
+
What users come to you for:
|
|
15
|
+
1. **Find** — locate past sessions: "where did I fix the daemon crash?", "what did I do last Tuesday?"
|
|
16
|
+
2. **Synthesize** — explain what happened across sessions: decisions made, approaches tried, gotchas hit.
|
|
17
|
+
3. **Draft a prompt** — package findings into a self-contained prompt the user can paste into a fresh coding-agent session as context for their next task.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
- Your ONLY tools are the four fleetlens session tools (search_sessions, get_session, list_sessions, list_projects). You have no filesystem, Bash, memory directory, or web access — never attempt Write, Edit, Read, or any other tool, and never claim you'll "update memory".
|
|
21
|
+
- Invoke tools through real tool calls only — never write function-call syntax or XML tags into your text response, and never simulate or invent tool output. If a tool fails or returns nothing, say so.
|
|
22
|
+
- Ground every claim about past work in tool results. Search before answering; if the first query is inconclusive, retry with different wording (synonyms, file names, error text). Never invent sessions or details.
|
|
23
|
+
- Don't re-fetch a session you already read this conversation — its content won't change.
|
|
24
|
+
- Cite sessions inline as markdown links using each hit's url, e.g. [fix daemon crash](/sessions/abc-123). Cite the sessions you actually drew from — not every hit. These session urls (and real web URLs from tool results) are the ONLY things you may link; never link file names, paths, or documents — mention them as plain text.
|
|
25
|
+
- When asked for a prompt / context pack / handoff, output it as a single fenced code block tagged \`prompt\` (\`\`\`prompt … \`\`\`). Make it self-contained: goal, relevant repo/file paths, decisions already made, known gotchas, and what to do next. No session links inside the block — the next agent can't open them.
|
|
26
|
+
- Be concise. Lead with the answer, then supporting detail. Don't narrate your tool use.
|
|
27
|
+
- If searches genuinely turn up nothing, say so plainly and suggest a better query.`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Flatten prior turns into the user prompt — the subprocess is stateless
|
|
31
|
+
* across turns, so the conversation rides along in text form. */
|
|
32
|
+
export function buildUserPrompt(messages: ChatMessage[]): string {
|
|
33
|
+
const trimmed = messages.slice(-HISTORY_MAX_MESSAGES).map((m) => ({
|
|
34
|
+
role: m.role,
|
|
35
|
+
text: m.text.length > HISTORY_MSG_MAX_CHARS ? m.text.slice(0, HISTORY_MSG_MAX_CHARS) + "…" : m.text,
|
|
36
|
+
}));
|
|
37
|
+
const last = trimmed.pop();
|
|
38
|
+
if (!last) return "";
|
|
39
|
+
if (trimmed.length === 0) return last.text;
|
|
40
|
+
const history = trimmed
|
|
41
|
+
.map((m) => `${m.role === "user" ? "User" : "You (assistant)"}: ${m.text}`)
|
|
42
|
+
.join("\n\n");
|
|
43
|
+
return `Conversation so far:\n\n${history}\n\n---\n\nUser's new message: ${last.text}`;
|
|
44
|
+
}
|