fleetlens 0.2.9 → 0.4.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 +6 -6
- 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/build-manifest.json +3 -3
- 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/build-manifest.json +3 -3
- 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/api/ask/route.js +3 -2
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/events/route.js +1 -0
- package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/insights/months-index/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js +7 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/months-index/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/pdf/[key]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/route.js +8 -0
- package/app/apps/web/.next/server/app/api/insights/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/saved/[key]/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js +6 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/saved/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js +7 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/insights/weeks-index/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/[key]/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js +16 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js.map +5 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/insights/print/[key]/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/page.js +3 -3
- 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/build-manifest.json +3 -3
- 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/projects/[slug]/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -3
- 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/build-manifest.json +3 -3
- 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/sessions/[id]/page/build-manifest.json +3 -3
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +2 -2
- 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/build-manifest.json +3 -3
- 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/usage/page/build-manifest.json +3 -3
- 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_insights_months-index_route_actions_0~h~zci.js +3 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_saved_[key]_route_actions_0a5j9yw.js +3 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_insights_weeks-index_route_actions_0hh-1c7.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__0rv9ct5._.js → [externals]__0_xztrf._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0.hmj0z._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__01h_g-p._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0nh-z10._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0op9af~._.js +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0ri7q~r._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0rr1~~x._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0s0gv.0._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_pdf_[key]_route_actions_0lu-lnx.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_route_actions_0h8l5lz.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_insights_saved_route_actions_13rlblw.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_00t2xj6.js +68 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0sgyieh._.js → node_modules_next_dist_esm_build_templates_app-route_0ck.78s.js} +5 -5
- package/app/apps/web/.next/server/chunks/packages_parser_dist_fs_0bc7ptm.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__00z7zlm._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08.d5ia._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b323~k._.js +8 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0euu8sa._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gio_ck._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0gx-4qa._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0w52fbc._.js +3 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0z45d0g._.js +49 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__13itnvx._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_09n75_5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/_0r~__0v._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/_0xies90._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_[key]_page_actions_0_z2~e_.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_page_actions_0zjdm~n.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_insights_print_[key]_page_actions_0a853fw.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_06j3c.0._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insight-report_tsx_13q2a-c._.js +12 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_insights-view_tsx_11730ta._.js +6 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0-lmm4p._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0nqz8dw._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_0t-hj0x._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0vz7_px.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10cdrh2.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_12n1lw_.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0-jvt~a._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_analytics_0k4dgst.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +7 -1
- package/app/apps/web/.next/server/middleware-build-manifest.js +6 -6
- 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/03.mv4y4.w-vb.js +6 -0
- package/app/apps/web/.next/static/chunks/03_7xtpi9sv4g.js +4 -0
- package/app/apps/web/.next/static/chunks/03g5dw5eskotd.js +6 -0
- package/app/apps/web/.next/static/chunks/063oimt30jeo9.js +10 -0
- package/app/apps/web/.next/static/chunks/08c1pcrocqq5s.js +6 -0
- package/app/apps/web/.next/static/chunks/0e-wrhqh4h3r5.js +2 -0
- package/app/apps/web/.next/static/chunks/0fc6dk2qtokwh.js +1 -0
- package/app/apps/web/.next/static/chunks/0i7er7_bfab36.js +10 -0
- package/app/apps/web/.next/static/chunks/0ipytyi01h0ta.js +1 -0
- package/app/apps/web/.next/static/chunks/137uw2-qbro07.js +31 -0
- package/app/apps/web/.next/static/chunks/{turbopack-17n9y8y0h4q9f.js → turbopack-169w-1._23ra_.js} +1 -1
- package/app/apps/web/app/api/insights/months-index/route.ts +80 -0
- package/app/apps/web/app/api/insights/pdf/[key]/route.ts +45 -0
- package/app/apps/web/app/api/insights/route.ts +508 -0
- package/app/apps/web/app/api/insights/saved/[key]/route.ts +11 -0
- package/app/apps/web/app/api/insights/saved/route.ts +9 -0
- package/app/apps/web/app/api/insights/weeks-index/route.ts +95 -0
- package/app/apps/web/app/insights/[key]/page.tsx +59 -0
- package/app/apps/web/app/insights/page.tsx +7 -0
- package/app/apps/web/app/insights/print/[key]/page.tsx +73 -0
- package/app/apps/web/app/layout.tsx +2 -0
- package/app/apps/web/app/projects/[slug]/page.tsx +2 -0
- package/app/apps/web/app/sessions/[id]/page.tsx +27 -1
- package/app/apps/web/app/sessions/[id]/session-view.tsx +372 -793
- package/app/apps/web/app/sessions/[id]/team-tab/adapter.ts +633 -0
- package/app/apps/web/app/sessions/[id]/team-tab/format.ts +20 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-minimap.tsx +682 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-tab-client.tsx +78 -0
- package/app/apps/web/app/sessions/[id]/team-tab/team-table.tsx +796 -0
- package/app/apps/web/app/sessions/[id]/team-tab/turn-drawer.tsx +805 -0
- package/app/apps/web/app/sessions/[id]/tool-cards.tsx +461 -0
- package/app/apps/web/app/sessions/[id]/turn-steps.tsx +306 -0
- package/app/apps/web/app/sessions/sessions-grid.tsx +36 -5
- package/app/apps/web/components/insight-report.tsx +1031 -0
- package/app/apps/web/components/insights-view.tsx +647 -0
- package/app/apps/web/components/live-refresher.tsx +11 -2
- package/app/apps/web/components/live-sessions-widget.tsx +20 -6
- package/app/apps/web/components/sidebar.tsx +8 -0
- package/app/apps/web/components/team-badge.tsx +90 -0
- package/app/apps/web/eslint.config.mjs +2 -0
- package/app/apps/web/lib/ai/chrome-pdf.ts +64 -0
- package/app/apps/web/lib/ai/insights-prompt.ts +71 -0
- package/app/apps/web/lib/ai/saved-reports.ts +83 -0
- package/app/apps/web/lib/date-range.ts +11 -2
- package/app/apps/web/lib/use-live-events.ts +7 -2
- package/app/apps/web/package.json +3 -2
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +2765 -11
- package/dist/index.js +3047 -253
- package/package.json +5 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__08cr2u9._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0b..h_8._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0cdd~8n._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ms7h0~._.js +0 -8
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~a~v00._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/_0zv~jv-._.js +0 -12
- package/app/apps/web/.next/server/chunks/ssr/_12dtryh._.js +0 -12
- package/app/apps/web/.next/server/chunks/ssr/node_modules_0i2xw~e._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0.knjn.._.js +0 -3
- package/app/apps/web/.next/static/chunks/0257pdz1-imal.js +0 -2
- package/app/apps/web/.next/static/chunks/03fw7f2obcvq~.js +0 -6
- package/app/apps/web/.next/static/chunks/07oyxajlp3dvb.js +0 -6
- package/app/apps/web/.next/static/chunks/0a24elrqoqqep.js +0 -10
- package/app/apps/web/.next/static/chunks/0i4zodv86~~~i.js +0 -1
- package/app/apps/web/.next/static/chunks/0qmh881w772ef.js +0 -31
- package/app/apps/web/.next/static/chunks/0rbm60-qyshdm.js +0 -6
- package/app/apps/web/.next/static/chunks/0wdga7zbwvg70.js +0 -1
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{2rEsiJssG30QsuNT0Lp9S → G_4ofKI9v9ARC_lb9JILZ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { formatGap } from "@/lib/format";
|
|
2
|
+
import type {
|
|
3
|
+
TeamView,
|
|
4
|
+
SessionDetail,
|
|
5
|
+
PresentationRow,
|
|
6
|
+
SubagentRun,
|
|
7
|
+
TurnMegaRow,
|
|
8
|
+
} from "@claude-lens/parser";
|
|
9
|
+
import { buildPresentation, buildMegaRows } from "@claude-lens/parser";
|
|
10
|
+
|
|
11
|
+
export type TeamTrack = {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
color: string;
|
|
15
|
+
isLead: boolean;
|
|
16
|
+
presentationRows: PresentationRow[];
|
|
17
|
+
subagents: SubagentRun[];
|
|
18
|
+
sessionStartMs: number;
|
|
19
|
+
turns: TeamTurn[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type TeamTurn = {
|
|
23
|
+
id: string;
|
|
24
|
+
trackId: string;
|
|
25
|
+
startMs: number;
|
|
26
|
+
endMs: number;
|
|
27
|
+
megaRow: TurnMegaRow;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
userPrompt?: PresentationRow;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type IdleBand = {
|
|
33
|
+
kind: "idle";
|
|
34
|
+
startMs: number;
|
|
35
|
+
endMs: number;
|
|
36
|
+
durationMs: number;
|
|
37
|
+
yPx: number;
|
|
38
|
+
heightPx: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type YAnchor = {
|
|
42
|
+
tsMs: number;
|
|
43
|
+
yPx: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type XAnchor = {
|
|
47
|
+
tsMs: number;
|
|
48
|
+
xFrac: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Idle band expressed as an x-range for the minimap. Unlike the body's
|
|
52
|
+
* IdleBand (yPx), this is expressed as normalized fractions [0, 1] so
|
|
53
|
+
* the minimap's horizontal scale stays independent of pixel width. */
|
|
54
|
+
export type MinimapIdleBand = {
|
|
55
|
+
startMs: number;
|
|
56
|
+
endMs: number;
|
|
57
|
+
durationMs: number;
|
|
58
|
+
xFracStart: number;
|
|
59
|
+
xFracEnd: number;
|
|
60
|
+
label: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** When the total team span exceeds DAY_PAGE_HOURS we switch the minimap
|
|
64
|
+
* from a single compressed strip into day pages, each rendered on its own
|
|
65
|
+
* x-scale so a single day has the whole minimap width to itself. Page
|
|
66
|
+
* nav arrows move between pages. */
|
|
67
|
+
export type DayPage = {
|
|
68
|
+
startMs: number;
|
|
69
|
+
endMs: number;
|
|
70
|
+
label: string;
|
|
71
|
+
xAnchors: XAnchor[];
|
|
72
|
+
minimapIdleBands: MinimapIdleBand[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type TimelineData = {
|
|
76
|
+
tracks: TeamTrack[];
|
|
77
|
+
yAnchors: YAnchor[];
|
|
78
|
+
idleBands: IdleBand[];
|
|
79
|
+
totalHeightPx: number;
|
|
80
|
+
firstEventMs: number;
|
|
81
|
+
lastEventMs: number;
|
|
82
|
+
timeTicks: { tsMs: number; yPx: number; label: string }[];
|
|
83
|
+
/** Event-anchored x-scale for the minimap. Same spirit as yAnchors:
|
|
84
|
+
* active intervals get proportional width, all-idle stretches collapse.
|
|
85
|
+
* When dayPages is set, minimap consumers should prefer those; this one
|
|
86
|
+
* still covers the full span for consumers that don't paginate. */
|
|
87
|
+
xAnchors: XAnchor[];
|
|
88
|
+
minimapIdleBands: MinimapIdleBand[];
|
|
89
|
+
/** True when the team spans more than one local day — consumers can
|
|
90
|
+
* show full dates in hover cards / labels instead of just times. */
|
|
91
|
+
multiDay: boolean;
|
|
92
|
+
/** Present when the team span exceeds DAY_PAGE_HOURS. The minimap
|
|
93
|
+
* splits itself across these pages instead of cramming everything
|
|
94
|
+
* into one compressed strip. */
|
|
95
|
+
dayPages?: DayPage[];
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Code-level knob for the day-page width. 24h is the default that fits
|
|
99
|
+
* most multi-day team traces; 12h is available for fleets that run in
|
|
100
|
+
* shorter bursts. Not exposed to users. */
|
|
101
|
+
const DAY_PAGE_HOURS = 24;
|
|
102
|
+
const DAY_PAGE_MS = DAY_PAGE_HOURS * 60 * 60 * 1000;
|
|
103
|
+
|
|
104
|
+
const LEAD_COLOR = "#f0b429";
|
|
105
|
+
const MEMBER_COLORS = [
|
|
106
|
+
"#58a6ff",
|
|
107
|
+
"#b58cf0",
|
|
108
|
+
"#3fb950",
|
|
109
|
+
"#f85149",
|
|
110
|
+
"#db6d28",
|
|
111
|
+
"#ff7b72",
|
|
112
|
+
"#56d364",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const TURN_MIN_HEIGHT = 140;
|
|
116
|
+
function turnPreferredHeight(durationMs: number): number {
|
|
117
|
+
const minutes = durationMs / 60_000;
|
|
118
|
+
// Gentle log growth so a 10-hour turn doesn't blow up the page.
|
|
119
|
+
const bonus = Math.min(220, 80 * Math.log10(minutes + 1));
|
|
120
|
+
return TURN_MIN_HEIGHT + bonus;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function idleBandHeight(gapMs: number): number {
|
|
124
|
+
if (gapMs < 10_000) return 6;
|
|
125
|
+
if (gapMs < 60_000) return 18;
|
|
126
|
+
if (gapMs < 10 * 60_000) return 24;
|
|
127
|
+
if (gapMs < 60 * 60_000) return 32;
|
|
128
|
+
return 44;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Gaps smaller than this between turn activity are NOT collapsed — they stay
|
|
132
|
+
// proportional to keep short pauses readable.
|
|
133
|
+
const ALL_IDLE_THRESHOLD_MS = 30_000;
|
|
134
|
+
|
|
135
|
+
export function teamViewToTimelineData(
|
|
136
|
+
view: TeamView,
|
|
137
|
+
details: Map<string, SessionDetail>,
|
|
138
|
+
): TimelineData {
|
|
139
|
+
const tracks: TeamTrack[] = [];
|
|
140
|
+
|
|
141
|
+
const lead = details.get(view.leadSessionId);
|
|
142
|
+
if (lead) tracks.push(buildTrack(lead, "LEAD", LEAD_COLOR, true));
|
|
143
|
+
|
|
144
|
+
view.memberSessionIds.forEach((id, i) => {
|
|
145
|
+
const d = details.get(id);
|
|
146
|
+
if (!d) return;
|
|
147
|
+
const label = view.agentNameBySessionId.get(id) ?? id.slice(0, 8);
|
|
148
|
+
tracks.push(
|
|
149
|
+
buildTrack(d, label, MEMBER_COLORS[i % MEMBER_COLORS.length]!, false),
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const allTurns = tracks.flatMap((t) => t.turns);
|
|
154
|
+
allTurns.sort((a, b) => a.startMs - b.startMs);
|
|
155
|
+
|
|
156
|
+
const { yAnchors, idleBands, totalHeightPx, timeTicks } = buildYFunction(
|
|
157
|
+
allTurns,
|
|
158
|
+
view.firstEventMs,
|
|
159
|
+
view.lastEventMs,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const multiDay = isMultiDay(view.firstEventMs, view.lastEventMs);
|
|
163
|
+
const { xAnchors, minimapIdleBands } = buildXFunction(
|
|
164
|
+
allTurns,
|
|
165
|
+
view.firstEventMs,
|
|
166
|
+
view.lastEventMs,
|
|
167
|
+
multiDay,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const spanMs = view.lastEventMs - view.firstEventMs;
|
|
171
|
+
const dayPages =
|
|
172
|
+
spanMs > DAY_PAGE_MS
|
|
173
|
+
? buildDayPages(allTurns, view.firstEventMs, view.lastEventMs, multiDay)
|
|
174
|
+
: undefined;
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
tracks,
|
|
178
|
+
yAnchors,
|
|
179
|
+
idleBands,
|
|
180
|
+
totalHeightPx,
|
|
181
|
+
firstEventMs: view.firstEventMs,
|
|
182
|
+
lastEventMs: view.lastEventMs,
|
|
183
|
+
timeTicks,
|
|
184
|
+
xAnchors,
|
|
185
|
+
minimapIdleBands,
|
|
186
|
+
multiDay,
|
|
187
|
+
dayPages,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Walk the full span in DAY_PAGE_HOURS slices starting at the local-day
|
|
192
|
+
* boundary that contains firstEventMs. Each page runs buildXFunction
|
|
193
|
+
* over just the turns that overlap its window so the compressed scale
|
|
194
|
+
* is tight to that day's actual activity. */
|
|
195
|
+
function buildDayPages(
|
|
196
|
+
allTurns: TeamTurn[],
|
|
197
|
+
firstEventMs: number,
|
|
198
|
+
lastEventMs: number,
|
|
199
|
+
multiDay: boolean,
|
|
200
|
+
): DayPage[] {
|
|
201
|
+
// Anchor page 0 at local midnight of firstEventMs so successive pages
|
|
202
|
+
// line up with real calendar days (feels natural when labels show
|
|
203
|
+
// "Mar 27", "Mar 28", etc.).
|
|
204
|
+
const firstDate = new Date(firstEventMs);
|
|
205
|
+
const pageStart = new Date(
|
|
206
|
+
firstDate.getFullYear(),
|
|
207
|
+
firstDate.getMonth(),
|
|
208
|
+
firstDate.getDate(),
|
|
209
|
+
).getTime();
|
|
210
|
+
|
|
211
|
+
const pages: DayPage[] = [];
|
|
212
|
+
let cursor = pageStart;
|
|
213
|
+
while (cursor < lastEventMs) {
|
|
214
|
+
const startMs = cursor;
|
|
215
|
+
const endMs = Math.min(lastEventMs, cursor + DAY_PAGE_MS);
|
|
216
|
+
const clampedStart = Math.max(startMs, firstEventMs);
|
|
217
|
+
// Keep any turn that touches this window.
|
|
218
|
+
const pageTurns = allTurns.filter(
|
|
219
|
+
(t) => t.endMs >= clampedStart && t.startMs <= endMs,
|
|
220
|
+
);
|
|
221
|
+
// Skip empty days. A long team span can have multi-day gaps where
|
|
222
|
+
// nothing actually happened — showing blank pages for those just
|
|
223
|
+
// forces the user to click through noise. The minimap's job is to
|
|
224
|
+
// visualize activity, so days without any are dropped entirely.
|
|
225
|
+
if (pageTurns.length === 0) {
|
|
226
|
+
cursor += DAY_PAGE_MS;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const windowStart = clampedStart;
|
|
230
|
+
const windowEnd = endMs;
|
|
231
|
+
const { xAnchors, minimapIdleBands } = buildXFunction(
|
|
232
|
+
pageTurns,
|
|
233
|
+
windowStart,
|
|
234
|
+
windowEnd,
|
|
235
|
+
multiDay,
|
|
236
|
+
);
|
|
237
|
+
pages.push({
|
|
238
|
+
startMs: windowStart,
|
|
239
|
+
endMs: windowEnd,
|
|
240
|
+
label: formatDayLabel(startMs),
|
|
241
|
+
xAnchors,
|
|
242
|
+
minimapIdleBands,
|
|
243
|
+
});
|
|
244
|
+
cursor += DAY_PAGE_MS;
|
|
245
|
+
}
|
|
246
|
+
return pages;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function formatDayLabel(ms: number): string {
|
|
250
|
+
return new Date(ms).toLocaleDateString([], {
|
|
251
|
+
month: "short",
|
|
252
|
+
day: "numeric",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type ActiveRegion = { startMs: number; endMs: number };
|
|
257
|
+
|
|
258
|
+
function mergeActiveRegions(turns: TeamTurn[]): ActiveRegion[] {
|
|
259
|
+
const sorted = [...turns].sort((a, b) => a.startMs - b.startMs);
|
|
260
|
+
const active: ActiveRegion[] = [];
|
|
261
|
+
for (const t of sorted) {
|
|
262
|
+
const last = active[active.length - 1];
|
|
263
|
+
if (last && t.startMs <= last.endMs) {
|
|
264
|
+
if (t.endMs > last.endMs) last.endMs = t.endMs;
|
|
265
|
+
} else {
|
|
266
|
+
active.push({ startMs: t.startMs, endMs: t.endMs });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return active;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isMultiDay(firstMs: number, lastMs: number): boolean {
|
|
273
|
+
const a = new Date(firstMs);
|
|
274
|
+
const b = new Date(lastMs);
|
|
275
|
+
return (
|
|
276
|
+
a.getFullYear() !== b.getFullYear() ||
|
|
277
|
+
a.getMonth() !== b.getMonth() ||
|
|
278
|
+
a.getDate() !== b.getDate()
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function buildTrack(
|
|
283
|
+
d: SessionDetail,
|
|
284
|
+
label: string,
|
|
285
|
+
color: string,
|
|
286
|
+
isLead: boolean,
|
|
287
|
+
): TeamTrack {
|
|
288
|
+
const sessionStartMs = d.firstTimestamp ? Date.parse(d.firstTimestamp) : 0;
|
|
289
|
+
const presentationRows = buildPresentation(d.events);
|
|
290
|
+
const mega = buildMegaRows(presentationRows);
|
|
291
|
+
|
|
292
|
+
const turns: TeamTurn[] = [];
|
|
293
|
+
let turnIndex = 0;
|
|
294
|
+
let lastUserRow: PresentationRow | undefined;
|
|
295
|
+
for (const m of mega) {
|
|
296
|
+
if (m.kind === "user") {
|
|
297
|
+
lastUserRow = m;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (m.kind !== "turn") continue;
|
|
301
|
+
if (m.tOffsetMs === undefined || m.durationMs === undefined) continue;
|
|
302
|
+
const startMs = sessionStartMs + m.tOffsetMs;
|
|
303
|
+
// Never zero — always at least 1s so layout math never divides by 0.
|
|
304
|
+
const durationMs = Math.max(1000, m.durationMs);
|
|
305
|
+
turns.push({
|
|
306
|
+
id: `${d.sessionId}:${turnIndex++}`,
|
|
307
|
+
trackId: d.sessionId,
|
|
308
|
+
startMs,
|
|
309
|
+
endMs: startMs + durationMs,
|
|
310
|
+
megaRow: m,
|
|
311
|
+
durationMs,
|
|
312
|
+
userPrompt: lastUserRow,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
id: d.sessionId,
|
|
318
|
+
label,
|
|
319
|
+
color,
|
|
320
|
+
isLead,
|
|
321
|
+
presentationRows,
|
|
322
|
+
subagents: d.subagents ?? [],
|
|
323
|
+
sessionStartMs,
|
|
324
|
+
turns,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function buildYFunction(
|
|
329
|
+
allTurns: TeamTurn[],
|
|
330
|
+
firstEventMs: number,
|
|
331
|
+
lastEventMs: number,
|
|
332
|
+
): {
|
|
333
|
+
yAnchors: YAnchor[];
|
|
334
|
+
idleBands: IdleBand[];
|
|
335
|
+
totalHeightPx: number;
|
|
336
|
+
timeTicks: { tsMs: number; yPx: number; label: string }[];
|
|
337
|
+
} {
|
|
338
|
+
if (allTurns.length === 0) {
|
|
339
|
+
return {
|
|
340
|
+
yAnchors: [
|
|
341
|
+
{ tsMs: firstEventMs, yPx: 0 },
|
|
342
|
+
{ tsMs: lastEventMs, yPx: 0 },
|
|
343
|
+
],
|
|
344
|
+
idleBands: [],
|
|
345
|
+
totalHeightPx: 0,
|
|
346
|
+
timeTicks: [],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const active = mergeActiveRegions(allTurns);
|
|
351
|
+
|
|
352
|
+
const anchorSet = new Set<number>();
|
|
353
|
+
anchorSet.add(firstEventMs);
|
|
354
|
+
anchorSet.add(lastEventMs);
|
|
355
|
+
for (const a of active) {
|
|
356
|
+
anchorSet.add(a.startMs);
|
|
357
|
+
anchorSet.add(a.endMs);
|
|
358
|
+
}
|
|
359
|
+
for (const t of allTurns) {
|
|
360
|
+
anchorSet.add(t.startMs);
|
|
361
|
+
anchorSet.add(t.endMs);
|
|
362
|
+
}
|
|
363
|
+
const anchorTimes = [...anchorSet].sort((a, b) => a - b);
|
|
364
|
+
|
|
365
|
+
type Interval = {
|
|
366
|
+
startMs: number;
|
|
367
|
+
endMs: number;
|
|
368
|
+
isIdle: boolean;
|
|
369
|
+
heightPx: number;
|
|
370
|
+
};
|
|
371
|
+
const intervals: Interval[] = [];
|
|
372
|
+
for (let i = 0; i < anchorTimes.length - 1; i++) {
|
|
373
|
+
const startMs = anchorTimes[i]!;
|
|
374
|
+
const endMs = anchorTimes[i + 1]!;
|
|
375
|
+
const durationMs = endMs - startMs;
|
|
376
|
+
if (durationMs <= 0) continue;
|
|
377
|
+
|
|
378
|
+
const insideActive = active.some(
|
|
379
|
+
(a) => a.startMs <= startMs && a.endMs >= endMs,
|
|
380
|
+
);
|
|
381
|
+
if (!insideActive && durationMs >= ALL_IDLE_THRESHOLD_MS) {
|
|
382
|
+
intervals.push({
|
|
383
|
+
startMs,
|
|
384
|
+
endMs,
|
|
385
|
+
isIdle: true,
|
|
386
|
+
heightPx: idleBandHeight(durationMs),
|
|
387
|
+
});
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Baseline so short active intervals still have enough room to render.
|
|
392
|
+
let needed = 24;
|
|
393
|
+
for (const t of allTurns) {
|
|
394
|
+
const overlap = Math.min(t.endMs, endMs) - Math.max(t.startMs, startMs);
|
|
395
|
+
if (overlap <= 0) continue;
|
|
396
|
+
const fraction = overlap / t.durationMs;
|
|
397
|
+
const contribution = turnPreferredHeight(t.durationMs) * fraction;
|
|
398
|
+
if (contribution > needed) needed = contribution;
|
|
399
|
+
}
|
|
400
|
+
intervals.push({ startMs, endMs, isIdle: false, heightPx: needed });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const yAnchors: YAnchor[] = [{ tsMs: anchorTimes[0]!, yPx: 0 }];
|
|
404
|
+
let cursorY = 0;
|
|
405
|
+
for (let i = 0; i < intervals.length; i++) {
|
|
406
|
+
cursorY += intervals[i]!.heightPx;
|
|
407
|
+
yAnchors.push({ tsMs: intervals[i]!.endMs, yPx: cursorY });
|
|
408
|
+
}
|
|
409
|
+
const totalHeightPx = cursorY;
|
|
410
|
+
|
|
411
|
+
const idleBands: IdleBand[] = [];
|
|
412
|
+
let cursor2 = 0;
|
|
413
|
+
for (const i of intervals) {
|
|
414
|
+
if (i.isIdle) {
|
|
415
|
+
idleBands.push({
|
|
416
|
+
kind: "idle",
|
|
417
|
+
startMs: i.startMs,
|
|
418
|
+
endMs: i.endMs,
|
|
419
|
+
durationMs: i.endMs - i.startMs,
|
|
420
|
+
yPx: cursor2,
|
|
421
|
+
heightPx: i.heightPx,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
cursor2 += i.heightPx;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const timeTicks = yAnchors.map((a) => ({
|
|
428
|
+
tsMs: a.tsMs,
|
|
429
|
+
yPx: a.yPx,
|
|
430
|
+
label: formatTimeLabel(a.tsMs),
|
|
431
|
+
}));
|
|
432
|
+
|
|
433
|
+
return { yAnchors, idleBands, totalHeightPx, timeTicks };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function formatTimeLabel(ms: number): string {
|
|
437
|
+
return new Date(ms).toLocaleTimeString([], {
|
|
438
|
+
hour: "2-digit",
|
|
439
|
+
minute: "2-digit",
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* ----------- X function (minimap horizontal scale) ----------- */
|
|
444
|
+
|
|
445
|
+
/** Synthetic "ms weight" each active interval contributes. Used to keep
|
|
446
|
+
* short active bursts visually wide enough to click while still letting
|
|
447
|
+
* genuinely long activity dominate. */
|
|
448
|
+
const ACTIVE_MIN_WEIGHT_MS = 45_000;
|
|
449
|
+
|
|
450
|
+
/** Synthetic "ms weight" each idle interval collapses to. Doesn't scale
|
|
451
|
+
* with real duration — a 10-minute gap and a 10-hour overnight gap both
|
|
452
|
+
* take roughly the same horizontal space, with the actual duration and
|
|
453
|
+
* date carried in the hatched band's label. */
|
|
454
|
+
function idleWeightMs(gapMs: number): number {
|
|
455
|
+
if (gapMs < 60_000) return 20_000;
|
|
456
|
+
if (gapMs < 10 * 60_000) return 40_000;
|
|
457
|
+
if (gapMs < 60 * 60_000) return 60_000;
|
|
458
|
+
if (gapMs < 6 * 60 * 60_000) return 80_000;
|
|
459
|
+
return 100_000; // multi-hour, multi-day — effectively capped
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function buildXFunction(
|
|
463
|
+
allTurns: TeamTurn[],
|
|
464
|
+
firstEventMs: number,
|
|
465
|
+
lastEventMs: number,
|
|
466
|
+
multiDay: boolean,
|
|
467
|
+
): {
|
|
468
|
+
xAnchors: XAnchor[];
|
|
469
|
+
minimapIdleBands: MinimapIdleBand[];
|
|
470
|
+
} {
|
|
471
|
+
if (allTurns.length === 0 || lastEventMs <= firstEventMs) {
|
|
472
|
+
return {
|
|
473
|
+
xAnchors: [
|
|
474
|
+
{ tsMs: firstEventMs, xFrac: 0 },
|
|
475
|
+
{ tsMs: lastEventMs, xFrac: 1 },
|
|
476
|
+
],
|
|
477
|
+
minimapIdleBands: [],
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const active = mergeActiveRegions(allTurns);
|
|
482
|
+
|
|
483
|
+
// Build the interval list: alternating active / idle spans that cover
|
|
484
|
+
// [firstEventMs, lastEventMs] end to end.
|
|
485
|
+
type Interval = {
|
|
486
|
+
startMs: number;
|
|
487
|
+
endMs: number;
|
|
488
|
+
isIdle: boolean;
|
|
489
|
+
weight: number;
|
|
490
|
+
};
|
|
491
|
+
const intervals: Interval[] = [];
|
|
492
|
+
let cursor = firstEventMs;
|
|
493
|
+
for (const a of active) {
|
|
494
|
+
if (a.startMs > cursor) {
|
|
495
|
+
const gap = a.startMs - cursor;
|
|
496
|
+
intervals.push({
|
|
497
|
+
startMs: cursor,
|
|
498
|
+
endMs: a.startMs,
|
|
499
|
+
isIdle: gap >= ALL_IDLE_THRESHOLD_MS,
|
|
500
|
+
weight:
|
|
501
|
+
gap >= ALL_IDLE_THRESHOLD_MS
|
|
502
|
+
? idleWeightMs(gap)
|
|
503
|
+
: Math.max(ACTIVE_MIN_WEIGHT_MS, gap),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const dur = a.endMs - a.startMs;
|
|
507
|
+
intervals.push({
|
|
508
|
+
startMs: a.startMs,
|
|
509
|
+
endMs: a.endMs,
|
|
510
|
+
isIdle: false,
|
|
511
|
+
weight: Math.max(ACTIVE_MIN_WEIGHT_MS, dur),
|
|
512
|
+
});
|
|
513
|
+
cursor = a.endMs;
|
|
514
|
+
}
|
|
515
|
+
if (cursor < lastEventMs) {
|
|
516
|
+
const gap = lastEventMs - cursor;
|
|
517
|
+
intervals.push({
|
|
518
|
+
startMs: cursor,
|
|
519
|
+
endMs: lastEventMs,
|
|
520
|
+
isIdle: gap >= ALL_IDLE_THRESHOLD_MS,
|
|
521
|
+
weight:
|
|
522
|
+
gap >= ALL_IDLE_THRESHOLD_MS
|
|
523
|
+
? idleWeightMs(gap)
|
|
524
|
+
: Math.max(ACTIVE_MIN_WEIGHT_MS, gap),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const totalWeight = intervals.reduce((s, i) => s + i.weight, 0) || 1;
|
|
529
|
+
|
|
530
|
+
const xAnchors: XAnchor[] = [{ tsMs: firstEventMs, xFrac: 0 }];
|
|
531
|
+
let cumWeight = 0;
|
|
532
|
+
for (const i of intervals) {
|
|
533
|
+
cumWeight += i.weight;
|
|
534
|
+
xAnchors.push({ tsMs: i.endMs, xFrac: cumWeight / totalWeight });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const minimapIdleBands: MinimapIdleBand[] = [];
|
|
538
|
+
let walkWeight = 0;
|
|
539
|
+
for (const i of intervals) {
|
|
540
|
+
const start = walkWeight / totalWeight;
|
|
541
|
+
walkWeight += i.weight;
|
|
542
|
+
const end = walkWeight / totalWeight;
|
|
543
|
+
if (i.isIdle) {
|
|
544
|
+
minimapIdleBands.push({
|
|
545
|
+
startMs: i.startMs,
|
|
546
|
+
endMs: i.endMs,
|
|
547
|
+
durationMs: i.endMs - i.startMs,
|
|
548
|
+
xFracStart: start,
|
|
549
|
+
xFracEnd: end,
|
|
550
|
+
label: idleLabel(i.startMs, i.endMs, multiDay),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return { xAnchors, minimapIdleBands };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function idleLabel(startMs: number, endMs: number, multiDay: boolean): string {
|
|
559
|
+
const dur = `${formatGap(endMs - startMs)} idle`;
|
|
560
|
+
if (!multiDay) return dur;
|
|
561
|
+
const a = new Date(startMs);
|
|
562
|
+
const b = new Date(endMs);
|
|
563
|
+
const sameDay =
|
|
564
|
+
a.getFullYear() === b.getFullYear() &&
|
|
565
|
+
a.getMonth() === b.getMonth() &&
|
|
566
|
+
a.getDate() === b.getDate();
|
|
567
|
+
if (sameDay) return dur;
|
|
568
|
+
const toDate = b.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
569
|
+
return `${dur} → ${toDate}`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function xOfMs(anchors: XAnchor[], tsMs: number): number {
|
|
573
|
+
if (anchors.length === 0) return 0;
|
|
574
|
+
if (tsMs <= anchors[0]!.tsMs) return anchors[0]!.xFrac;
|
|
575
|
+
if (tsMs >= anchors[anchors.length - 1]!.tsMs)
|
|
576
|
+
return anchors[anchors.length - 1]!.xFrac;
|
|
577
|
+
|
|
578
|
+
let lo = 0;
|
|
579
|
+
let hi = anchors.length - 1;
|
|
580
|
+
while (hi - lo > 1) {
|
|
581
|
+
const mid = (lo + hi) >> 1;
|
|
582
|
+
if (anchors[mid]!.tsMs <= tsMs) lo = mid;
|
|
583
|
+
else hi = mid;
|
|
584
|
+
}
|
|
585
|
+
const a = anchors[lo]!;
|
|
586
|
+
const b = anchors[hi]!;
|
|
587
|
+
if (b.tsMs === a.tsMs) return a.xFrac;
|
|
588
|
+
const frac = (tsMs - a.tsMs) / (b.tsMs - a.tsMs);
|
|
589
|
+
return a.xFrac + frac * (b.xFrac - a.xFrac);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Inverse of xOfMs — given an xFrac in [0, 1], return the wall-clock ms
|
|
593
|
+
* that lives at that compressed position. Used by the minimap's click-to-
|
|
594
|
+
* seek. */
|
|
595
|
+
export function msOfXFrac(anchors: XAnchor[], xFrac: number): number {
|
|
596
|
+
if (anchors.length === 0) return 0;
|
|
597
|
+
if (xFrac <= anchors[0]!.xFrac) return anchors[0]!.tsMs;
|
|
598
|
+
if (xFrac >= anchors[anchors.length - 1]!.xFrac)
|
|
599
|
+
return anchors[anchors.length - 1]!.tsMs;
|
|
600
|
+
|
|
601
|
+
let lo = 0;
|
|
602
|
+
let hi = anchors.length - 1;
|
|
603
|
+
while (hi - lo > 1) {
|
|
604
|
+
const mid = (lo + hi) >> 1;
|
|
605
|
+
if (anchors[mid]!.xFrac <= xFrac) lo = mid;
|
|
606
|
+
else hi = mid;
|
|
607
|
+
}
|
|
608
|
+
const a = anchors[lo]!;
|
|
609
|
+
const b = anchors[hi]!;
|
|
610
|
+
if (b.xFrac === a.xFrac) return a.tsMs;
|
|
611
|
+
const frac = (xFrac - a.xFrac) / (b.xFrac - a.xFrac);
|
|
612
|
+
return a.tsMs + frac * (b.tsMs - a.tsMs);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function yOfMs(anchors: YAnchor[], tsMs: number): number {
|
|
616
|
+
if (anchors.length === 0) return 0;
|
|
617
|
+
if (tsMs <= anchors[0]!.tsMs) return anchors[0]!.yPx;
|
|
618
|
+
if (tsMs >= anchors[anchors.length - 1]!.tsMs)
|
|
619
|
+
return anchors[anchors.length - 1]!.yPx;
|
|
620
|
+
|
|
621
|
+
let lo = 0;
|
|
622
|
+
let hi = anchors.length - 1;
|
|
623
|
+
while (hi - lo > 1) {
|
|
624
|
+
const mid = (lo + hi) >> 1;
|
|
625
|
+
if (anchors[mid]!.tsMs <= tsMs) lo = mid;
|
|
626
|
+
else hi = mid;
|
|
627
|
+
}
|
|
628
|
+
const a = anchors[lo]!;
|
|
629
|
+
const b = anchors[hi]!;
|
|
630
|
+
if (b.tsMs === a.tsMs) return a.yPx;
|
|
631
|
+
const frac = (tsMs - a.tsMs) / (b.tsMs - a.tsMs);
|
|
632
|
+
return a.yPx + frac * (b.yPx - a.yPx);
|
|
633
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionEvent } from "@claude-lens/parser";
|
|
2
|
+
|
|
3
|
+
type TeammateMsg = NonNullable<SessionEvent["teammateMessage"]>;
|
|
4
|
+
|
|
5
|
+
export function formatTeammatePreview(tm: TeammateMsg): string {
|
|
6
|
+
switch (tm.kind) {
|
|
7
|
+
case "idle-notification":
|
|
8
|
+
return `${tm.teammateId} is idle / available`;
|
|
9
|
+
case "shutdown-request":
|
|
10
|
+
return `${tm.teammateId} requesting shutdown`;
|
|
11
|
+
case "shutdown-approved":
|
|
12
|
+
return `${tm.teammateId} shutdown approved`;
|
|
13
|
+
case "teammate-terminated":
|
|
14
|
+
return `${tm.teammateId} has shut down`;
|
|
15
|
+
case "task-assignment":
|
|
16
|
+
return `task assigned to ${tm.teammateId}`;
|
|
17
|
+
default:
|
|
18
|
+
return tm.body.length > 120 ? tm.body.slice(0, 120) + "…" : tm.body;
|
|
19
|
+
}
|
|
20
|
+
}
|