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
package/dist/daemon-worker.js
CHANGED
|
@@ -1,7 +1,2590 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ../parser/dist/types.js
|
|
12
|
+
var init_types = __esm({
|
|
13
|
+
"../parser/dist/types.js"() {
|
|
14
|
+
"use strict";
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// ../parser/dist/parser.js
|
|
19
|
+
function cleanText(s) {
|
|
20
|
+
return s.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
21
|
+
}
|
|
22
|
+
function truncate(s, n = 200) {
|
|
23
|
+
const one = cleanText(s);
|
|
24
|
+
return one.length > n ? one.slice(0, n - 1) + "\u2026" : one;
|
|
25
|
+
}
|
|
26
|
+
function classifyTeammateMessage(text) {
|
|
27
|
+
const m = text.match(TEAMMATE_MSG_RE);
|
|
28
|
+
if (!m)
|
|
29
|
+
return void 0;
|
|
30
|
+
const teammateId = m[1];
|
|
31
|
+
const body = m[2].trim();
|
|
32
|
+
let kind = "message";
|
|
33
|
+
if (body.startsWith("{")) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(body);
|
|
36
|
+
if (parsed.type === "idle_notification")
|
|
37
|
+
kind = "idle-notification";
|
|
38
|
+
else if (parsed.type === "shutdown_request")
|
|
39
|
+
kind = "shutdown-request";
|
|
40
|
+
else if (parsed.type === "shutdown_approved")
|
|
41
|
+
kind = "shutdown-approved";
|
|
42
|
+
else if (parsed.type === "task_assignment")
|
|
43
|
+
kind = "task-assignment";
|
|
44
|
+
else if (parsed.type === "teammate_terminated")
|
|
45
|
+
kind = "teammate-terminated";
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { teammateId, body, kind };
|
|
50
|
+
}
|
|
51
|
+
function extractUsage(u) {
|
|
52
|
+
if (!u || typeof u !== "object")
|
|
53
|
+
return void 0;
|
|
54
|
+
const r = u;
|
|
55
|
+
const toNum = (v) => typeof v === "number" ? v : 0;
|
|
56
|
+
return {
|
|
57
|
+
input: toNum(r.input_tokens),
|
|
58
|
+
output: toNum(r.output_tokens),
|
|
59
|
+
cacheRead: toNum(r.cache_read_input_tokens),
|
|
60
|
+
cacheWrite: toNum(r.cache_creation_input_tokens)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function toEvent(raw, index) {
|
|
64
|
+
if (!raw || typeof raw !== "object")
|
|
65
|
+
return null;
|
|
66
|
+
const r = raw;
|
|
67
|
+
const rawType = String(r.type ?? "");
|
|
68
|
+
const uuid = r.uuid;
|
|
69
|
+
const parentUuid = r.parentUuid ?? null;
|
|
70
|
+
const timestamp = r.timestamp;
|
|
71
|
+
if (rawType === "assistant") {
|
|
72
|
+
const msg = r.message ?? {};
|
|
73
|
+
const content = msg.content ?? [];
|
|
74
|
+
const usage = extractUsage(msg.usage);
|
|
75
|
+
const model = msg.model;
|
|
76
|
+
const requestId = r.requestId;
|
|
77
|
+
const messageId = msg.id;
|
|
78
|
+
const stopReason = msg.stop_reason;
|
|
79
|
+
const first = content[0];
|
|
80
|
+
let role = "agent";
|
|
81
|
+
let preview;
|
|
82
|
+
let toolName;
|
|
83
|
+
let toolUseId;
|
|
84
|
+
if (first?.type === "thinking") {
|
|
85
|
+
role = "agent-thinking";
|
|
86
|
+
preview = "[thinking] " + truncate(first.thinking, 140);
|
|
87
|
+
} else if (first?.type === "tool_use") {
|
|
88
|
+
role = "tool-call";
|
|
89
|
+
toolName = first.name;
|
|
90
|
+
toolUseId = first.id;
|
|
91
|
+
preview = `${first.name} ` + truncate(JSON.stringify(first.input ?? {}), 100);
|
|
92
|
+
} else if (first?.type === "text") {
|
|
93
|
+
preview = truncate(first.text, 200);
|
|
94
|
+
} else {
|
|
95
|
+
preview = `<assistant:${first?.type ?? "empty"}>`;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
index,
|
|
99
|
+
uuid,
|
|
100
|
+
parentUuid,
|
|
101
|
+
timestamp,
|
|
102
|
+
role,
|
|
103
|
+
rawType,
|
|
104
|
+
preview,
|
|
105
|
+
blocks: content,
|
|
106
|
+
usage,
|
|
107
|
+
model,
|
|
108
|
+
requestId,
|
|
109
|
+
messageId,
|
|
110
|
+
stopReason,
|
|
111
|
+
toolName,
|
|
112
|
+
toolUseId,
|
|
113
|
+
raw
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (rawType === "user") {
|
|
117
|
+
const msg = r.message ?? {};
|
|
118
|
+
const c = msg.content;
|
|
119
|
+
if (typeof c === "string") {
|
|
120
|
+
const tm = classifyTeammateMessage(c);
|
|
121
|
+
return {
|
|
122
|
+
index,
|
|
123
|
+
uuid,
|
|
124
|
+
parentUuid,
|
|
125
|
+
timestamp,
|
|
126
|
+
role: "user",
|
|
127
|
+
rawType,
|
|
128
|
+
preview: truncate(c, 200),
|
|
129
|
+
blocks: [{ type: "text", text: c }],
|
|
130
|
+
raw,
|
|
131
|
+
teammateMessage: tm
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(c)) {
|
|
135
|
+
const first = c[0];
|
|
136
|
+
if (first?.type === "tool_result") {
|
|
137
|
+
const resultText = typeof first.content === "string" ? first.content : JSON.stringify(first.content);
|
|
138
|
+
return {
|
|
139
|
+
index,
|
|
140
|
+
uuid,
|
|
141
|
+
parentUuid,
|
|
142
|
+
timestamp,
|
|
143
|
+
role: "tool-result",
|
|
144
|
+
rawType,
|
|
145
|
+
preview: truncate(resultText, 200),
|
|
146
|
+
blocks: c,
|
|
147
|
+
toolUseId: first.tool_use_id,
|
|
148
|
+
toolResult: r.toolUseResult,
|
|
149
|
+
raw
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const textBlock = c.find((b) => b?.type === "text");
|
|
153
|
+
const tm = textBlock ? classifyTeammateMessage(textBlock.text) : void 0;
|
|
154
|
+
return {
|
|
155
|
+
index,
|
|
156
|
+
uuid,
|
|
157
|
+
parentUuid,
|
|
158
|
+
timestamp,
|
|
159
|
+
role: "user",
|
|
160
|
+
rawType,
|
|
161
|
+
preview: textBlock ? truncate(textBlock.text, 200) : truncate(JSON.stringify(c), 200),
|
|
162
|
+
blocks: c,
|
|
163
|
+
raw,
|
|
164
|
+
teammateMessage: tm
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (rawType === "attachment") {
|
|
169
|
+
const a = r.attachment ?? {};
|
|
170
|
+
const at = String(a.type ?? "attachment");
|
|
171
|
+
let preview = `<attachment: ${at}>`;
|
|
172
|
+
if (typeof a.content === "string" && a.content.length > 0) {
|
|
173
|
+
preview = `[${at}] ` + truncate(a.content, 140);
|
|
174
|
+
} else if (Array.isArray(a.addedNames)) {
|
|
175
|
+
preview = `[${at}] +${a.addedNames.length} names`;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
index,
|
|
179
|
+
uuid,
|
|
180
|
+
parentUuid,
|
|
181
|
+
timestamp,
|
|
182
|
+
role: "system",
|
|
183
|
+
rawType,
|
|
184
|
+
preview,
|
|
185
|
+
blocks: [{ type: "text", text: JSON.stringify(a, null, 2) }],
|
|
186
|
+
attachmentType: at,
|
|
187
|
+
raw
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
index,
|
|
192
|
+
uuid,
|
|
193
|
+
parentUuid,
|
|
194
|
+
timestamp,
|
|
195
|
+
role: "meta",
|
|
196
|
+
rawType: rawType || "unknown",
|
|
197
|
+
preview: `${rawType}: ${truncate(JSON.stringify(r), 140)}`,
|
|
198
|
+
blocks: [{ type: "text", text: JSON.stringify(r, null, 2) }],
|
|
199
|
+
raw
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function parseTranscript(rawLines) {
|
|
203
|
+
const events = [];
|
|
204
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
205
|
+
const ev = toEvent(rawLines[i], i);
|
|
206
|
+
if (ev)
|
|
207
|
+
events.push(ev);
|
|
208
|
+
}
|
|
209
|
+
const timestamped = events.map((e) => e.timestamp ? Date.parse(e.timestamp) : void 0).filter((n) => typeof n === "number" && !Number.isNaN(n));
|
|
210
|
+
const startMs = timestamped.length ? Math.min(...timestamped) : void 0;
|
|
211
|
+
const endMs = timestamped.length ? Math.max(...timestamped) : void 0;
|
|
212
|
+
const firstTs = startMs !== void 0 ? new Date(startMs).toISOString() : void 0;
|
|
213
|
+
const lastTs = endMs !== void 0 ? new Date(endMs).toISOString() : void 0;
|
|
214
|
+
for (const ev of events) {
|
|
215
|
+
if (!ev.timestamp)
|
|
216
|
+
continue;
|
|
217
|
+
const ms = Date.parse(ev.timestamp);
|
|
218
|
+
if (Number.isNaN(ms))
|
|
219
|
+
continue;
|
|
220
|
+
if (startMs !== void 0)
|
|
221
|
+
ev.tOffsetMs = Math.max(0, ms - startMs);
|
|
222
|
+
}
|
|
223
|
+
const isConversational = (r) => r === "user" || r === "agent" || r === "agent-thinking" || r === "tool-call" || r === "tool-result";
|
|
224
|
+
const chrono = events.filter((e) => e.timestamp && isConversational(e.role)).map((e) => ({ e, ms: Date.parse(e.timestamp) })).filter((x) => !Number.isNaN(x.ms)).sort((a, b) => a.ms - b.ms);
|
|
225
|
+
for (let i = 1; i < chrono.length; i++) {
|
|
226
|
+
chrono[i].e.gapMs = Math.max(0, chrono[i].ms - chrono[i - 1].ms);
|
|
227
|
+
}
|
|
228
|
+
const IDLE_THRESHOLD_MS = 3 * 60 * 1e3;
|
|
229
|
+
const allChrono = events.filter((e) => e.timestamp).map((e) => Date.parse(e.timestamp)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
|
|
230
|
+
const activeSegments = [];
|
|
231
|
+
let airTimeMs = 0;
|
|
232
|
+
if (allChrono.length > 0) {
|
|
233
|
+
let segStart = allChrono[0];
|
|
234
|
+
let segEnd = allChrono[0];
|
|
235
|
+
for (let i = 1; i < allChrono.length; i++) {
|
|
236
|
+
const t = allChrono[i];
|
|
237
|
+
const gap = t - segEnd;
|
|
238
|
+
if (gap > IDLE_THRESHOLD_MS) {
|
|
239
|
+
activeSegments.push({ startMs: segStart, endMs: segEnd });
|
|
240
|
+
segStart = t;
|
|
241
|
+
} else if (gap > 0) {
|
|
242
|
+
airTimeMs += gap;
|
|
243
|
+
}
|
|
244
|
+
segEnd = t;
|
|
245
|
+
}
|
|
246
|
+
activeSegments.push({ startMs: segStart, endMs: segEnd });
|
|
247
|
+
}
|
|
248
|
+
const totalUsage = { ...BLANK_USAGE };
|
|
249
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
250
|
+
let model;
|
|
251
|
+
let sessionId = "";
|
|
252
|
+
let cwd;
|
|
253
|
+
let gitBranch;
|
|
254
|
+
let teamName;
|
|
255
|
+
let agentName;
|
|
256
|
+
let hasTeamCreate = false;
|
|
257
|
+
let hasOutboundDispatch = false;
|
|
258
|
+
let toolCallCount = 0;
|
|
259
|
+
let turnCount = 0;
|
|
260
|
+
let firstUserPreview;
|
|
261
|
+
let lastAgentPreview;
|
|
262
|
+
let lastUserPreview;
|
|
263
|
+
let linesAdded = 0;
|
|
264
|
+
let linesRemoved = 0;
|
|
265
|
+
const filesEdited = /* @__PURE__ */ new Set();
|
|
266
|
+
for (const e of events) {
|
|
267
|
+
if (e.role === "tool-call") {
|
|
268
|
+
toolCallCount++;
|
|
269
|
+
const toolBlock = e.blocks.find((b) => b && b.type === "tool_use");
|
|
270
|
+
if (toolBlock) {
|
|
271
|
+
const input = toolBlock.input ?? {};
|
|
272
|
+
if (toolBlock.name === "TeamCreate")
|
|
273
|
+
hasTeamCreate = true;
|
|
274
|
+
if (toolBlock.name === "SendMessage") {
|
|
275
|
+
const to = typeof input.to === "string" ? input.to : "";
|
|
276
|
+
if (to && to !== "team-lead")
|
|
277
|
+
hasOutboundDispatch = true;
|
|
278
|
+
}
|
|
279
|
+
const fp = typeof input.file_path === "string" ? input.file_path : void 0;
|
|
280
|
+
if (toolBlock.name === "Edit" && fp) {
|
|
281
|
+
filesEdited.add(fp);
|
|
282
|
+
const oldStr = typeof input.old_string === "string" ? input.old_string : "";
|
|
283
|
+
const newStr = typeof input.new_string === "string" ? input.new_string : "";
|
|
284
|
+
const oldLines = oldStr ? oldStr.split("\n").length : 0;
|
|
285
|
+
const newLines = newStr ? newStr.split("\n").length : 0;
|
|
286
|
+
linesAdded += Math.max(0, newLines - oldLines);
|
|
287
|
+
linesRemoved += Math.max(0, oldLines - newLines);
|
|
288
|
+
} else if (toolBlock.name === "Write" && fp) {
|
|
289
|
+
filesEdited.add(fp);
|
|
290
|
+
const content = typeof input.content === "string" ? input.content : "";
|
|
291
|
+
linesAdded += content ? content.split("\n").length : 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (e.role === "user") {
|
|
296
|
+
const rawMsg = e.raw?.message;
|
|
297
|
+
const rawContent = typeof rawMsg?.content === "string" ? rawMsg.content : "";
|
|
298
|
+
const isHidden = rawContent.startsWith("<command-name>") || rawContent.startsWith("<local-command-caveat>") || rawContent.startsWith("Base directory for this skill:") || rawContent.startsWith("<task-notification>");
|
|
299
|
+
const isTeamNoise = e.teammateMessage !== void 0 && !agentName;
|
|
300
|
+
if (!isHidden && !isTeamNoise) {
|
|
301
|
+
if (!firstUserPreview) {
|
|
302
|
+
firstUserPreview = e.teammateMessage ? e.teammateMessage.body : e.preview;
|
|
303
|
+
}
|
|
304
|
+
turnCount++;
|
|
305
|
+
lastUserPreview = e.preview;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (e.role === "agent") {
|
|
309
|
+
lastAgentPreview = e.preview;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
for (const r of rawLines) {
|
|
313
|
+
if (!r || typeof r !== "object")
|
|
314
|
+
continue;
|
|
315
|
+
const o = r;
|
|
316
|
+
if (typeof o.sessionId === "string" && !sessionId)
|
|
317
|
+
sessionId = o.sessionId;
|
|
318
|
+
if (typeof o.cwd === "string" && !cwd)
|
|
319
|
+
cwd = o.cwd;
|
|
320
|
+
if (typeof o.gitBranch === "string" && !gitBranch)
|
|
321
|
+
gitBranch = o.gitBranch;
|
|
322
|
+
if (typeof o.teamName === "string" && !teamName)
|
|
323
|
+
teamName = o.teamName;
|
|
324
|
+
if (typeof o.agentName === "string" && !agentName)
|
|
325
|
+
agentName = o.agentName;
|
|
326
|
+
if (o.type === "assistant") {
|
|
327
|
+
const m = o.message;
|
|
328
|
+
if (m) {
|
|
329
|
+
if (typeof m.model === "string" && !model)
|
|
330
|
+
model = m.model;
|
|
331
|
+
const mid = typeof m.id === "string" ? m.id : void 0;
|
|
332
|
+
const rid = typeof o.requestId === "string" ? o.requestId : void 0;
|
|
333
|
+
const dedupKey = mid != null ? `${mid}:${rid ?? ""}` : void 0;
|
|
334
|
+
if (dedupKey && seenMessageIds.has(dedupKey))
|
|
335
|
+
continue;
|
|
336
|
+
if (dedupKey)
|
|
337
|
+
seenMessageIds.add(dedupKey);
|
|
338
|
+
const u = extractUsage(m.usage);
|
|
339
|
+
if (u) {
|
|
340
|
+
totalUsage.input += u.input;
|
|
341
|
+
totalUsage.output += u.output;
|
|
342
|
+
totalUsage.cacheRead += u.cacheRead;
|
|
343
|
+
totalUsage.cacheWrite += u.cacheWrite;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
events,
|
|
350
|
+
meta: {
|
|
351
|
+
sessionId,
|
|
352
|
+
firstTimestamp: firstTs,
|
|
353
|
+
lastTimestamp: lastTs,
|
|
354
|
+
durationMs: startMs !== void 0 && endMs !== void 0 ? endMs - startMs : void 0,
|
|
355
|
+
eventCount: events.length,
|
|
356
|
+
model,
|
|
357
|
+
cwd,
|
|
358
|
+
gitBranch,
|
|
359
|
+
totalUsage,
|
|
360
|
+
status: "idle",
|
|
361
|
+
firstUserPreview,
|
|
362
|
+
lastUserPreview,
|
|
363
|
+
lastAgentPreview,
|
|
364
|
+
toolCallCount,
|
|
365
|
+
turnCount,
|
|
366
|
+
airTimeMs,
|
|
367
|
+
activeSegments,
|
|
368
|
+
linesAdded,
|
|
369
|
+
linesRemoved,
|
|
370
|
+
filesEdited: filesEdited.size,
|
|
371
|
+
teamName,
|
|
372
|
+
agentName,
|
|
373
|
+
isTeamLead: teamName !== void 0 && agentName === void 0 && (hasTeamCreate || hasOutboundDispatch)
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
var BLANK_USAGE, TEAMMATE_MSG_RE;
|
|
378
|
+
var init_parser = __esm({
|
|
379
|
+
"../parser/dist/parser.js"() {
|
|
380
|
+
"use strict";
|
|
381
|
+
BLANK_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
382
|
+
TEAMMATE_MSG_RE = /^\s*<teammate-message\s+teammate_id="([^"]+)"[^>]*>([\s\S]*?)<\/teammate-message>/;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ../parser/dist/presentation.js
|
|
387
|
+
function rowPrimaryIndex(r) {
|
|
388
|
+
if (r.kind === "tool-group")
|
|
389
|
+
return r.events[0].index;
|
|
390
|
+
return r.event.index;
|
|
391
|
+
}
|
|
392
|
+
function buildMegaRows(rows) {
|
|
393
|
+
const out = [];
|
|
394
|
+
let buffer = [];
|
|
395
|
+
const flush = () => {
|
|
396
|
+
if (buffer.length === 0)
|
|
397
|
+
return;
|
|
398
|
+
let agentMessages = 0;
|
|
399
|
+
let toolCalls = 0;
|
|
400
|
+
let errors = 0;
|
|
401
|
+
let firstAgentIndex;
|
|
402
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
403
|
+
const seenMsgIds = /* @__PURE__ */ new Set();
|
|
404
|
+
const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
405
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
406
|
+
const r = buffer[i];
|
|
407
|
+
if (r.kind === "agent") {
|
|
408
|
+
agentMessages++;
|
|
409
|
+
if (firstAgentIndex === void 0)
|
|
410
|
+
firstAgentIndex = i;
|
|
411
|
+
const msgId = r.event.messageId;
|
|
412
|
+
const usage = r.event.usage;
|
|
413
|
+
if (msgId && !seenMsgIds.has(msgId) && usage) {
|
|
414
|
+
seenMsgIds.add(msgId);
|
|
415
|
+
tokens.input += usage.input;
|
|
416
|
+
tokens.output += usage.output;
|
|
417
|
+
tokens.cacheRead += usage.cacheRead;
|
|
418
|
+
tokens.cacheWrite += usage.cacheWrite;
|
|
419
|
+
}
|
|
420
|
+
} else if (r.kind === "tool-group") {
|
|
421
|
+
toolCalls += r.count;
|
|
422
|
+
for (const t of r.toolNames) {
|
|
423
|
+
toolMap.set(t.name, (toolMap.get(t.name) ?? 0) + t.count);
|
|
424
|
+
}
|
|
425
|
+
} else if (r.kind === "error") {
|
|
426
|
+
errors++;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
let finalAgentIndex;
|
|
430
|
+
for (let i = buffer.length - 1; i >= 0; i--) {
|
|
431
|
+
const r = buffer[i];
|
|
432
|
+
if (r.kind !== "agent")
|
|
433
|
+
continue;
|
|
434
|
+
const prev = buffer[i - 1];
|
|
435
|
+
if (prev?.kind === "task-notification")
|
|
436
|
+
continue;
|
|
437
|
+
finalAgentIndex = i;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
if (finalAgentIndex === void 0) {
|
|
441
|
+
for (let i = buffer.length - 1; i >= 0; i--) {
|
|
442
|
+
if (buffer[i].kind === "agent") {
|
|
443
|
+
finalAgentIndex = i;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const firstAgentPreview = firstAgentIndex !== void 0 && buffer[firstAgentIndex].kind === "agent" ? buffer[firstAgentIndex].event.preview : void 0;
|
|
449
|
+
const finalAgentPreview = finalAgentIndex !== void 0 && buffer[finalAgentIndex].kind === "agent" ? buffer[finalAgentIndex].event.preview : void 0;
|
|
450
|
+
const toolNames = Array.from(toolMap.entries()).sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
|
|
451
|
+
const first = buffer[0];
|
|
452
|
+
const last = buffer[buffer.length - 1];
|
|
453
|
+
const start = first.tOffsetMs;
|
|
454
|
+
const end = last.tOffsetMs;
|
|
455
|
+
const durationMs = start !== void 0 && end !== void 0 ? end - start : void 0;
|
|
456
|
+
out.push({
|
|
457
|
+
kind: "turn",
|
|
458
|
+
rows: buffer,
|
|
459
|
+
firstPrimaryIndex: rowPrimaryIndex(first),
|
|
460
|
+
tOffsetMs: start,
|
|
461
|
+
durationMs,
|
|
462
|
+
summary: {
|
|
463
|
+
agentMessages,
|
|
464
|
+
toolCalls,
|
|
465
|
+
errors,
|
|
466
|
+
firstAgentPreview,
|
|
467
|
+
finalAgentPreview,
|
|
468
|
+
firstAgentIndex,
|
|
469
|
+
finalAgentIndex,
|
|
470
|
+
toolNames,
|
|
471
|
+
totalTokens: tokens
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
buffer = [];
|
|
475
|
+
};
|
|
476
|
+
for (const r of rows) {
|
|
477
|
+
if (r.kind === "user" || r.kind === "interrupt") {
|
|
478
|
+
flush();
|
|
479
|
+
out.push(r);
|
|
480
|
+
} else {
|
|
481
|
+
buffer.push(r);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
flush();
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
function parseTaskNotification(text) {
|
|
488
|
+
if (!TASK_NOTIFICATION_RE.test(text))
|
|
489
|
+
return null;
|
|
490
|
+
const pick = (tag) => {
|
|
491
|
+
const m = text.match(new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`));
|
|
492
|
+
return m ? m[1].trim() : void 0;
|
|
493
|
+
};
|
|
494
|
+
const statusRaw = (pick("status") ?? "").toLowerCase();
|
|
495
|
+
let status = "unknown";
|
|
496
|
+
if (statusRaw === "success" || statusRaw === "ok" || statusRaw === "done" || statusRaw === "completed")
|
|
497
|
+
status = "success";
|
|
498
|
+
else if (statusRaw === "failed" || statusRaw === "error" || statusRaw === "fail")
|
|
499
|
+
status = "failed";
|
|
500
|
+
else if (statusRaw === "running" || statusRaw === "in_progress")
|
|
501
|
+
status = "running";
|
|
502
|
+
return {
|
|
503
|
+
status,
|
|
504
|
+
summary: pick("summary") ?? "Background task notification",
|
|
505
|
+
taskId: pick("task-id"),
|
|
506
|
+
toolUseId: pick("tool-use-id"),
|
|
507
|
+
outputFile: pick("output-file")
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function isAttachmentOrMeta(e) {
|
|
511
|
+
return e.role === "system" || e.role === "meta";
|
|
512
|
+
}
|
|
513
|
+
function firstTextOfBlocks(blocks) {
|
|
514
|
+
for (const b of blocks) {
|
|
515
|
+
if (b?.type === "text")
|
|
516
|
+
return b.text;
|
|
517
|
+
}
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
function hasZeroUsage(e) {
|
|
521
|
+
if (!e.usage)
|
|
522
|
+
return false;
|
|
523
|
+
const u = e.usage;
|
|
524
|
+
return u.input === 0 && u.output === 0 && u.cacheRead === 0 && u.cacheWrite === 0;
|
|
525
|
+
}
|
|
526
|
+
function buildPresentation(events) {
|
|
527
|
+
const byMessageId = /* @__PURE__ */ new Map();
|
|
528
|
+
for (const e of events) {
|
|
529
|
+
if (!e.messageId)
|
|
530
|
+
continue;
|
|
531
|
+
const list = byMessageId.get(e.messageId) ?? [];
|
|
532
|
+
list.push(e);
|
|
533
|
+
byMessageId.set(e.messageId, list);
|
|
534
|
+
}
|
|
535
|
+
const visible = events.filter((e) => {
|
|
536
|
+
if (isAttachmentOrMeta(e))
|
|
537
|
+
return false;
|
|
538
|
+
if (e.role === "tool-result")
|
|
539
|
+
return false;
|
|
540
|
+
if (e.role === "agent-thinking")
|
|
541
|
+
return false;
|
|
542
|
+
if (e.role === "user") {
|
|
543
|
+
const txt = firstTextOfBlocks(e.blocks) ?? "";
|
|
544
|
+
if (SKILL_CONTENT_RE.test(txt))
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
});
|
|
549
|
+
const rows = [];
|
|
550
|
+
for (let i = 0; i < visible.length; i++) {
|
|
551
|
+
const e = visible[i];
|
|
552
|
+
if (e.role === "tool-call") {
|
|
553
|
+
const start = i;
|
|
554
|
+
while (i + 1 < visible.length && visible[i + 1].role === "tool-call") {
|
|
555
|
+
i++;
|
|
556
|
+
}
|
|
557
|
+
const group = visible.slice(start, i + 1);
|
|
558
|
+
const order = [];
|
|
559
|
+
const counts = /* @__PURE__ */ new Map();
|
|
560
|
+
for (const ev of group) {
|
|
561
|
+
const name = ev.toolName ?? "tool";
|
|
562
|
+
if (!counts.has(name))
|
|
563
|
+
order.push(name);
|
|
564
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
565
|
+
}
|
|
566
|
+
const toolNames = order.map((name) => ({
|
|
567
|
+
name,
|
|
568
|
+
count: counts.get(name) ?? 1
|
|
569
|
+
}));
|
|
570
|
+
rows.push({
|
|
571
|
+
kind: "tool-group",
|
|
572
|
+
toolNames,
|
|
573
|
+
count: group.length,
|
|
574
|
+
events: group,
|
|
575
|
+
tOffsetMs: group[0].tOffsetMs,
|
|
576
|
+
gapMs: group[0].gapMs
|
|
577
|
+
});
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (e.role === "user") {
|
|
581
|
+
const txt = firstTextOfBlocks(e.blocks) ?? "";
|
|
582
|
+
const taskNotif = parseTaskNotification(txt);
|
|
583
|
+
if (taskNotif) {
|
|
584
|
+
rows.push({
|
|
585
|
+
kind: "task-notification",
|
|
586
|
+
event: e,
|
|
587
|
+
status: taskNotif.status,
|
|
588
|
+
summary: taskNotif.summary,
|
|
589
|
+
taskId: taskNotif.taskId,
|
|
590
|
+
toolUseId: taskNotif.toolUseId,
|
|
591
|
+
outputFile: taskNotif.outputFile,
|
|
592
|
+
tOffsetMs: e.tOffsetMs,
|
|
593
|
+
gapMs: e.gapMs
|
|
594
|
+
});
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (INTERRUPT_RE.test(txt)) {
|
|
598
|
+
rows.push({
|
|
599
|
+
kind: "interrupt",
|
|
600
|
+
event: e,
|
|
601
|
+
tOffsetMs: e.tOffsetMs,
|
|
602
|
+
gapMs: e.gapMs
|
|
603
|
+
});
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const slashMatch = txt.match(SLASH_COMMAND_RE);
|
|
607
|
+
if (slashMatch) {
|
|
608
|
+
const cmd = slashMatch[1];
|
|
609
|
+
const args = (slashMatch[2] ?? "").trim();
|
|
610
|
+
const pretty = args ? `${cmd} ${args}` : cmd;
|
|
611
|
+
rows.push({
|
|
612
|
+
kind: "user",
|
|
613
|
+
event: e,
|
|
614
|
+
displayPreview: pretty,
|
|
615
|
+
displayBlocks: [{ type: "text", text: pretty }],
|
|
616
|
+
tOffsetMs: e.tOffsetMs,
|
|
617
|
+
gapMs: e.gapMs
|
|
618
|
+
});
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
rows.push({
|
|
622
|
+
kind: "user",
|
|
623
|
+
event: e,
|
|
624
|
+
tOffsetMs: e.tOffsetMs,
|
|
625
|
+
gapMs: e.gapMs
|
|
626
|
+
});
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (e.role === "agent") {
|
|
630
|
+
const txt = firstTextOfBlocks(e.blocks) ?? "";
|
|
631
|
+
if (hasZeroUsage(e)) {
|
|
632
|
+
rows.push({
|
|
633
|
+
kind: "model",
|
|
634
|
+
event: e,
|
|
635
|
+
tOffsetMs: e.tOffsetMs,
|
|
636
|
+
gapMs: e.gapMs
|
|
637
|
+
});
|
|
638
|
+
if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
|
|
639
|
+
rows.push({
|
|
640
|
+
kind: "error",
|
|
641
|
+
event: e,
|
|
642
|
+
message: txt,
|
|
643
|
+
tOffsetMs: e.tOffsetMs,
|
|
644
|
+
gapMs: 0
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (RATE_LIMIT_RE.test(txt) && txt.length < 500) {
|
|
650
|
+
rows.push({
|
|
651
|
+
kind: "error",
|
|
652
|
+
event: e,
|
|
653
|
+
message: txt,
|
|
654
|
+
tOffsetMs: e.tOffsetMs,
|
|
655
|
+
gapMs: e.gapMs
|
|
656
|
+
});
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const grouped = e.messageId ? byMessageId.get(e.messageId) ?? [e] : [e];
|
|
660
|
+
rows.push({
|
|
661
|
+
kind: "agent",
|
|
662
|
+
event: e,
|
|
663
|
+
groupedEvents: grouped,
|
|
664
|
+
tOffsetMs: e.tOffsetMs,
|
|
665
|
+
gapMs: e.gapMs
|
|
666
|
+
});
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return rows;
|
|
671
|
+
}
|
|
672
|
+
var INTERRUPT_RE, RATE_LIMIT_RE, SKILL_CONTENT_RE, SLASH_COMMAND_RE, TASK_NOTIFICATION_RE;
|
|
673
|
+
var init_presentation = __esm({
|
|
674
|
+
"../parser/dist/presentation.js"() {
|
|
675
|
+
"use strict";
|
|
676
|
+
INTERRUPT_RE = /\[request interrupted|interrupted by user/i;
|
|
677
|
+
RATE_LIMIT_RE = /rate.?limit|overloaded_error|api error|Error sending message/i;
|
|
678
|
+
SKILL_CONTENT_RE = /^Base directory for this skill:/;
|
|
679
|
+
SLASH_COMMAND_RE = /<command-name>(\/[^<\s]+)<\/command-name>(?:[\s\S]*?<command-args>([\s\S]*?)<\/command-args>)?/;
|
|
680
|
+
TASK_NOTIFICATION_RE = /<task-notification>/;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// ../parser/dist/analytics.js
|
|
685
|
+
function toLocalDay(ms) {
|
|
686
|
+
const d = new Date(ms);
|
|
687
|
+
const y = d.getFullYear();
|
|
688
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
689
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
690
|
+
return `${y}-${m}-${day}`;
|
|
691
|
+
}
|
|
692
|
+
function sessionDay(meta) {
|
|
693
|
+
if (!meta.firstTimestamp)
|
|
694
|
+
return void 0;
|
|
695
|
+
const ms = Date.parse(meta.firstTimestamp);
|
|
696
|
+
if (Number.isNaN(ms))
|
|
697
|
+
return void 0;
|
|
698
|
+
return toLocalDay(ms);
|
|
699
|
+
}
|
|
700
|
+
function findWorktreeMarker(projectName) {
|
|
701
|
+
for (const marker of WORKTREE_MARKERS) {
|
|
702
|
+
const idx = projectName.lastIndexOf(marker);
|
|
703
|
+
if (idx >= 0)
|
|
704
|
+
return { idx, marker };
|
|
705
|
+
}
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
function canonicalProjectName(projectName) {
|
|
709
|
+
const hit = findWorktreeMarker(projectName);
|
|
710
|
+
if (hit)
|
|
711
|
+
return projectName.slice(0, hit.idx);
|
|
712
|
+
return projectName;
|
|
713
|
+
}
|
|
714
|
+
function worktreeName(projectName) {
|
|
715
|
+
const hit = findWorktreeMarker(projectName);
|
|
716
|
+
if (!hit)
|
|
717
|
+
return null;
|
|
718
|
+
const tail = projectName.slice(hit.idx + hit.marker.length);
|
|
719
|
+
const slash = tail.indexOf("/");
|
|
720
|
+
return slash >= 0 ? tail.slice(0, slash) : tail;
|
|
721
|
+
}
|
|
722
|
+
function makeDay(date) {
|
|
723
|
+
return {
|
|
724
|
+
date,
|
|
725
|
+
sessions: 0,
|
|
726
|
+
toolCalls: 0,
|
|
727
|
+
turns: 0,
|
|
728
|
+
tokens: { ...BLANK_USAGE2 },
|
|
729
|
+
durationMs: 0,
|
|
730
|
+
airTimeMs: 0,
|
|
731
|
+
peakParallelism: 0
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function daysBetween(startDay, endDay) {
|
|
735
|
+
const [ys, ms, ds] = startDay.split("-").map(Number);
|
|
736
|
+
const [ye, me, de] = endDay.split("-").map(Number);
|
|
737
|
+
const start = new Date(ys, ms - 1, ds);
|
|
738
|
+
const end = new Date(ye, me - 1, de);
|
|
739
|
+
const out = [];
|
|
740
|
+
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
|
741
|
+
out.push(toLocalDay(d.getTime()));
|
|
742
|
+
}
|
|
743
|
+
return out;
|
|
744
|
+
}
|
|
745
|
+
function dailyActivity(sessions) {
|
|
746
|
+
if (sessions.length === 0)
|
|
747
|
+
return [];
|
|
748
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
749
|
+
let minDay;
|
|
750
|
+
let maxDay;
|
|
751
|
+
const touchBucket = (day) => {
|
|
752
|
+
if (!minDay || day < minDay)
|
|
753
|
+
minDay = day;
|
|
754
|
+
if (!maxDay || day > maxDay)
|
|
755
|
+
maxDay = day;
|
|
756
|
+
const b = byDay.get(day) ?? makeDay(day);
|
|
757
|
+
byDay.set(day, b);
|
|
758
|
+
return b;
|
|
759
|
+
};
|
|
760
|
+
for (const s of sessions) {
|
|
761
|
+
const startDay = sessionDay(s);
|
|
762
|
+
if (startDay) {
|
|
763
|
+
const bucket = touchBucket(startDay);
|
|
764
|
+
bucket.toolCalls += s.toolCallCount ?? 0;
|
|
765
|
+
bucket.turns += s.turnCount ?? 0;
|
|
766
|
+
bucket.tokens.input += s.totalUsage.input;
|
|
767
|
+
bucket.tokens.output += s.totalUsage.output;
|
|
768
|
+
bucket.tokens.cacheRead += s.totalUsage.cacheRead;
|
|
769
|
+
bucket.tokens.cacheWrite += s.totalUsage.cacheWrite;
|
|
770
|
+
bucket.durationMs += s.durationMs ?? 0;
|
|
771
|
+
}
|
|
772
|
+
const segments = s.activeSegments;
|
|
773
|
+
if (segments && segments.length > 0) {
|
|
774
|
+
const touchedDays = /* @__PURE__ */ new Set();
|
|
775
|
+
for (const seg of segments) {
|
|
776
|
+
let cur = seg.startMs;
|
|
777
|
+
while (cur < seg.endMs) {
|
|
778
|
+
const day = toLocalDay(cur);
|
|
779
|
+
touchedDays.add(day);
|
|
780
|
+
const d = new Date(cur);
|
|
781
|
+
d.setHours(24, 0, 0, 0);
|
|
782
|
+
const dayEndMs = Math.min(d.getTime(), seg.endMs);
|
|
783
|
+
const bucket = touchBucket(day);
|
|
784
|
+
bucket.airTimeMs += dayEndMs - cur;
|
|
785
|
+
cur = dayEndMs;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
for (const day of touchedDays) {
|
|
789
|
+
const bucket = touchBucket(day);
|
|
790
|
+
bucket.sessions++;
|
|
791
|
+
}
|
|
792
|
+
} else if (startDay) {
|
|
793
|
+
const bucket = touchBucket(startDay);
|
|
794
|
+
bucket.sessions++;
|
|
795
|
+
bucket.airTimeMs += s.airTimeMs ?? 0;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const range = minDay && maxDay ? daysBetween(minDay, maxDay) : [];
|
|
799
|
+
const out = [];
|
|
800
|
+
for (const day of range) {
|
|
801
|
+
out.push(byDay.get(day) ?? makeDay(day));
|
|
802
|
+
}
|
|
803
|
+
const parallelism = computeParallelism(sessions);
|
|
804
|
+
const byDayPar = /* @__PURE__ */ new Map();
|
|
805
|
+
for (const p of parallelism) {
|
|
806
|
+
const day = toLocalDay(p.atMs);
|
|
807
|
+
const cur = byDayPar.get(day) ?? 0;
|
|
808
|
+
if (p.active > cur)
|
|
809
|
+
byDayPar.set(day, p.active);
|
|
810
|
+
}
|
|
811
|
+
for (const b of out) {
|
|
812
|
+
b.peakParallelism = byDayPar.get(b.date) ?? 0;
|
|
813
|
+
}
|
|
814
|
+
return out;
|
|
815
|
+
}
|
|
816
|
+
function computeParallelism(sessions) {
|
|
817
|
+
const events = [];
|
|
818
|
+
for (const s of sessions) {
|
|
819
|
+
const segs = s.activeSegments && s.activeSegments.length > 0 ? s.activeSegments : s.firstTimestamp && s.lastTimestamp ? (() => {
|
|
820
|
+
const start = Date.parse(s.firstTimestamp);
|
|
821
|
+
const end = Date.parse(s.lastTimestamp);
|
|
822
|
+
if (Number.isNaN(start) || Number.isNaN(end) || end < start)
|
|
823
|
+
return [];
|
|
824
|
+
return [{ startMs: start, endMs: end }];
|
|
825
|
+
})() : [];
|
|
826
|
+
for (const seg of segs) {
|
|
827
|
+
events.push({ ms: seg.startMs, delta: 1, sessionId: s.id });
|
|
828
|
+
events.push({ ms: seg.endMs, delta: -1, sessionId: s.id });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
events.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
|
|
832
|
+
const segCount = /* @__PURE__ */ new Map();
|
|
833
|
+
const points = [];
|
|
834
|
+
for (const e of events) {
|
|
835
|
+
if (e.delta === 1) {
|
|
836
|
+
segCount.set(e.sessionId, (segCount.get(e.sessionId) ?? 0) + 1);
|
|
837
|
+
} else {
|
|
838
|
+
const n = (segCount.get(e.sessionId) ?? 0) - 1;
|
|
839
|
+
if (n <= 0)
|
|
840
|
+
segCount.delete(e.sessionId);
|
|
841
|
+
else
|
|
842
|
+
segCount.set(e.sessionId, n);
|
|
843
|
+
}
|
|
844
|
+
points.push({
|
|
845
|
+
atMs: e.ms,
|
|
846
|
+
active: segCount.size,
|
|
847
|
+
sessions: Array.from(segCount.keys()).slice(0, 10)
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return points;
|
|
851
|
+
}
|
|
852
|
+
function detectParallelRuns(sessions, minActive = 2) {
|
|
853
|
+
const points = computeParallelism(sessions);
|
|
854
|
+
const runs = [];
|
|
855
|
+
let cur = null;
|
|
856
|
+
for (const p of points) {
|
|
857
|
+
if (p.active >= minActive) {
|
|
858
|
+
if (!cur) {
|
|
859
|
+
cur = { startMs: p.atMs, endMs: p.atMs, peak: p.active, sessions: [...p.sessions] };
|
|
860
|
+
} else {
|
|
861
|
+
cur.endMs = p.atMs;
|
|
862
|
+
if (p.active > cur.peak)
|
|
863
|
+
cur.peak = p.active;
|
|
864
|
+
for (const sid of p.sessions) {
|
|
865
|
+
if (!cur.sessions.includes(sid))
|
|
866
|
+
cur.sessions.push(sid);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
} else if (cur) {
|
|
870
|
+
cur.endMs = p.atMs;
|
|
871
|
+
runs.push(cur);
|
|
872
|
+
cur = null;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (cur)
|
|
876
|
+
runs.push(cur);
|
|
877
|
+
return runs;
|
|
878
|
+
}
|
|
879
|
+
function detectPrMarkers(session) {
|
|
880
|
+
const out = [];
|
|
881
|
+
const total = session.durationMs ?? 0;
|
|
882
|
+
for (const e of session.events) {
|
|
883
|
+
if (e.role !== "tool-call" || e.toolName !== "Bash")
|
|
884
|
+
continue;
|
|
885
|
+
const toolUseBlock = e.blocks.find((b) => b && "type" in b && b.type === "tool_use");
|
|
886
|
+
if (!toolUseBlock || toolUseBlock.type !== "tool_use")
|
|
887
|
+
continue;
|
|
888
|
+
const input = toolUseBlock.input;
|
|
889
|
+
const command = typeof input?.command === "string" ? input.command : "";
|
|
890
|
+
if (!PR_CREATE_RE.test(command))
|
|
891
|
+
continue;
|
|
892
|
+
const titleMatch = command.match(TITLE_FLAG_RE);
|
|
893
|
+
const numberMatch = command.match(PR_NUMBER_RE);
|
|
894
|
+
out.push({
|
|
895
|
+
sessionId: session.id,
|
|
896
|
+
timestamp: e.timestamp ?? session.firstTimestamp ?? "",
|
|
897
|
+
tOffsetMs: e.tOffsetMs,
|
|
898
|
+
positionInSession: total > 0 && e.tOffsetMs !== void 0 ? Math.min(1, Math.max(0, e.tOffsetMs / total)) : void 0,
|
|
899
|
+
command: command.slice(0, 300),
|
|
900
|
+
prNumber: numberMatch ? Number(numberMatch[1]) : void 0,
|
|
901
|
+
title: titleMatch?.[1]
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
return out;
|
|
905
|
+
}
|
|
906
|
+
function sessionAirTimeMs(events, idleThresholdMs = 3 * 60 * 1e3) {
|
|
907
|
+
let air = 0;
|
|
908
|
+
for (const e of events) {
|
|
909
|
+
if (e.gapMs === void 0)
|
|
910
|
+
continue;
|
|
911
|
+
if (e.gapMs <= idleThresholdMs)
|
|
912
|
+
air += e.gapMs;
|
|
913
|
+
}
|
|
914
|
+
return air;
|
|
915
|
+
}
|
|
916
|
+
function highLevelMetrics(sessions) {
|
|
917
|
+
const totals = {
|
|
918
|
+
sessionCount: sessions.length,
|
|
919
|
+
totalDurationMs: 0,
|
|
920
|
+
totalAirTimeMs: 0,
|
|
921
|
+
totalTokens: { ...BLANK_USAGE2 },
|
|
922
|
+
totalToolCalls: 0,
|
|
923
|
+
totalTurns: 0,
|
|
924
|
+
avgTurnsPerSession: 0,
|
|
925
|
+
avgDurationMs: 0,
|
|
926
|
+
totalLinesAdded: 0,
|
|
927
|
+
totalLinesRemoved: 0,
|
|
928
|
+
totalFilesEdited: 0
|
|
929
|
+
};
|
|
930
|
+
for (const s of sessions) {
|
|
931
|
+
totals.totalDurationMs += s.durationMs ?? 0;
|
|
932
|
+
totals.totalAirTimeMs += s.airTimeMs ?? s.durationMs ?? 0;
|
|
933
|
+
totals.totalTokens.input += s.totalUsage.input;
|
|
934
|
+
totals.totalTokens.output += s.totalUsage.output;
|
|
935
|
+
totals.totalTokens.cacheRead += s.totalUsage.cacheRead;
|
|
936
|
+
totals.totalTokens.cacheWrite += s.totalUsage.cacheWrite;
|
|
937
|
+
totals.totalToolCalls += s.toolCallCount ?? 0;
|
|
938
|
+
totals.totalTurns += s.turnCount ?? 0;
|
|
939
|
+
totals.totalLinesAdded += s.linesAdded ?? 0;
|
|
940
|
+
totals.totalLinesRemoved += s.linesRemoved ?? 0;
|
|
941
|
+
totals.totalFilesEdited += s.filesEdited ?? 0;
|
|
942
|
+
}
|
|
943
|
+
if (sessions.length > 0) {
|
|
944
|
+
totals.avgTurnsPerSession = totals.totalTurns / sessions.length;
|
|
945
|
+
totals.avgDurationMs = totals.totalDurationMs / sessions.length;
|
|
946
|
+
}
|
|
947
|
+
return totals;
|
|
948
|
+
}
|
|
949
|
+
function computeActiveSegments(events, idleThresholdMs = 3 * 60 * 1e3) {
|
|
950
|
+
const timed = events.filter((e) => e.timestamp).map((e) => Date.parse(e.timestamp)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
|
|
951
|
+
if (timed.length === 0)
|
|
952
|
+
return [];
|
|
953
|
+
const segments = [];
|
|
954
|
+
let segStart = timed[0];
|
|
955
|
+
let segEnd = timed[0];
|
|
956
|
+
for (let i = 1; i < timed.length; i++) {
|
|
957
|
+
const t = timed[i];
|
|
958
|
+
if (t - segEnd > idleThresholdMs) {
|
|
959
|
+
segments.push({ startMs: segStart, endMs: segEnd });
|
|
960
|
+
segStart = t;
|
|
961
|
+
}
|
|
962
|
+
segEnd = t;
|
|
963
|
+
}
|
|
964
|
+
segments.push({ startMs: segStart, endMs: segEnd });
|
|
965
|
+
return segments;
|
|
966
|
+
}
|
|
967
|
+
function buildGanttDay(sessions, date) {
|
|
968
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
969
|
+
const dayStartMs = new Date(y, m - 1, d).getTime();
|
|
970
|
+
const dayEndMs = new Date(y, m - 1, d + 1).getTime();
|
|
971
|
+
const ganttSessions = [];
|
|
972
|
+
for (const s of sessions) {
|
|
973
|
+
const allSegments = computeActiveSegments(s.events);
|
|
974
|
+
const daySegments = allSegments.filter((seg) => seg.endMs > dayStartMs && seg.startMs < dayEndMs).map((seg) => ({
|
|
975
|
+
startMs: Math.max(seg.startMs, dayStartMs),
|
|
976
|
+
endMs: Math.min(seg.endMs, dayEndMs)
|
|
977
|
+
}));
|
|
978
|
+
if (daySegments.length === 0)
|
|
979
|
+
continue;
|
|
980
|
+
const startMs = Math.min(...daySegments.map((seg) => seg.startMs));
|
|
981
|
+
const endMs = Math.max(...daySegments.map((seg) => seg.endMs));
|
|
982
|
+
const activeMs = daySegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
|
|
983
|
+
ganttSessions.push({
|
|
984
|
+
id: s.id,
|
|
985
|
+
projectName: s.projectName,
|
|
986
|
+
projectDir: s.projectDir,
|
|
987
|
+
firstUserPreview: s.firstUserPreview,
|
|
988
|
+
lastAgentPreview: s.lastAgentPreview,
|
|
989
|
+
model: s.model,
|
|
990
|
+
segments: daySegments,
|
|
991
|
+
startMs,
|
|
992
|
+
endMs,
|
|
993
|
+
activeMs,
|
|
994
|
+
totalUsage: s.totalUsage
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
ganttSessions.sort((a, b) => a.startMs - b.startMs);
|
|
998
|
+
const evts = [];
|
|
999
|
+
for (const s of ganttSessions) {
|
|
1000
|
+
for (const seg of s.segments) {
|
|
1001
|
+
evts.push({ ms: seg.startMs, delta: 1 });
|
|
1002
|
+
evts.push({ ms: seg.endMs, delta: -1 });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
evts.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
|
|
1006
|
+
let active = 0;
|
|
1007
|
+
let peak = 0;
|
|
1008
|
+
for (const e of evts) {
|
|
1009
|
+
active += e.delta;
|
|
1010
|
+
if (active > peak)
|
|
1011
|
+
peak = active;
|
|
1012
|
+
}
|
|
1013
|
+
return { date, sessions: ganttSessions, dayStartMs, dayEndMs, peakActiveParallelism: peak };
|
|
1014
|
+
}
|
|
1015
|
+
function detectBurstsCore(sessions, opts) {
|
|
1016
|
+
const minDurationMs = opts.minDurationMs ?? 6e4;
|
|
1017
|
+
const mergeGapMs = opts.mergeGapMs ?? 10 * 6e4;
|
|
1018
|
+
const minActive = opts.minActive ?? 2;
|
|
1019
|
+
const events = [];
|
|
1020
|
+
for (const s of sessions) {
|
|
1021
|
+
for (const seg of s.segments) {
|
|
1022
|
+
if (seg.endMs <= seg.startMs)
|
|
1023
|
+
continue;
|
|
1024
|
+
events.push({ ms: seg.startMs, delta: 1, sessionId: s.id });
|
|
1025
|
+
events.push({ ms: seg.endMs, delta: -1, sessionId: s.id });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
events.sort((a, b) => a.ms - b.ms || b.delta - a.delta);
|
|
1029
|
+
const segCount = /* @__PURE__ */ new Map();
|
|
1030
|
+
const rawRuns = [];
|
|
1031
|
+
let cur = null;
|
|
1032
|
+
for (const e of events) {
|
|
1033
|
+
if (e.delta === 1) {
|
|
1034
|
+
segCount.set(e.sessionId, (segCount.get(e.sessionId) ?? 0) + 1);
|
|
1035
|
+
} else {
|
|
1036
|
+
const n = (segCount.get(e.sessionId) ?? 0) - 1;
|
|
1037
|
+
if (n <= 0)
|
|
1038
|
+
segCount.delete(e.sessionId);
|
|
1039
|
+
else
|
|
1040
|
+
segCount.set(e.sessionId, n);
|
|
1041
|
+
}
|
|
1042
|
+
const active = segCount.size;
|
|
1043
|
+
if (active >= minActive) {
|
|
1044
|
+
if (!cur) {
|
|
1045
|
+
cur = {
|
|
1046
|
+
startMs: e.ms,
|
|
1047
|
+
endMs: e.ms,
|
|
1048
|
+
peak: active,
|
|
1049
|
+
sessionIds: new Set(segCount.keys())
|
|
1050
|
+
};
|
|
1051
|
+
} else {
|
|
1052
|
+
cur.endMs = e.ms;
|
|
1053
|
+
if (active > cur.peak)
|
|
1054
|
+
cur.peak = active;
|
|
1055
|
+
for (const sid of segCount.keys())
|
|
1056
|
+
cur.sessionIds.add(sid);
|
|
1057
|
+
}
|
|
1058
|
+
} else if (cur) {
|
|
1059
|
+
cur.endMs = e.ms;
|
|
1060
|
+
rawRuns.push(cur);
|
|
1061
|
+
cur = null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (cur)
|
|
1065
|
+
rawRuns.push(cur);
|
|
1066
|
+
const significant = rawRuns.filter((r) => r.endMs - r.startMs >= minDurationMs);
|
|
1067
|
+
const merged = [];
|
|
1068
|
+
for (const r of significant) {
|
|
1069
|
+
const last = merged[merged.length - 1];
|
|
1070
|
+
if (last && r.startMs - last.endMs <= mergeGapMs) {
|
|
1071
|
+
last.endMs = r.endMs;
|
|
1072
|
+
if (r.peak > last.peak)
|
|
1073
|
+
last.peak = r.peak;
|
|
1074
|
+
for (const sid of r.sessionIds)
|
|
1075
|
+
last.sessionIds.add(sid);
|
|
1076
|
+
} else {
|
|
1077
|
+
merged.push({
|
|
1078
|
+
startMs: r.startMs,
|
|
1079
|
+
endMs: r.endMs,
|
|
1080
|
+
peak: r.peak,
|
|
1081
|
+
sessionIds: new Set(r.sessionIds)
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
const projectBySession = /* @__PURE__ */ new Map();
|
|
1086
|
+
for (const s of sessions)
|
|
1087
|
+
projectBySession.set(s.id, s.projectDir);
|
|
1088
|
+
return merged.map((r) => {
|
|
1089
|
+
const projectDirs = Array.from(new Set(Array.from(r.sessionIds).map((sid) => projectBySession.get(sid)).filter((p) => !!p)));
|
|
1090
|
+
return {
|
|
1091
|
+
startMs: r.startMs,
|
|
1092
|
+
endMs: r.endMs,
|
|
1093
|
+
peak: r.peak,
|
|
1094
|
+
sessionIds: Array.from(r.sessionIds),
|
|
1095
|
+
projectDirs,
|
|
1096
|
+
crossProject: projectDirs.length > 1
|
|
1097
|
+
};
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function computeParallelismBursts(gantt, opts = {}) {
|
|
1101
|
+
return detectBurstsCore(gantt.sessions, opts);
|
|
1102
|
+
}
|
|
1103
|
+
function computeBurstsFromSessions(sessions, opts = {}) {
|
|
1104
|
+
const input = [];
|
|
1105
|
+
for (const s of sessions) {
|
|
1106
|
+
if (!s.activeSegments || s.activeSegments.length === 0)
|
|
1107
|
+
continue;
|
|
1108
|
+
input.push({
|
|
1109
|
+
id: s.id,
|
|
1110
|
+
projectDir: s.projectDir,
|
|
1111
|
+
segments: s.activeSegments
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
return detectBurstsCore(input, opts);
|
|
1115
|
+
}
|
|
1116
|
+
function summarizeBursts(bursts) {
|
|
1117
|
+
let peakConcurrent = 0;
|
|
1118
|
+
let totalParallelMs = 0;
|
|
1119
|
+
let crossProjectBurstCount = 0;
|
|
1120
|
+
const days = /* @__PURE__ */ new Set();
|
|
1121
|
+
for (const b of bursts) {
|
|
1122
|
+
if (b.peak > peakConcurrent)
|
|
1123
|
+
peakConcurrent = b.peak;
|
|
1124
|
+
totalParallelMs += b.endMs - b.startMs;
|
|
1125
|
+
if (b.crossProject)
|
|
1126
|
+
crossProjectBurstCount++;
|
|
1127
|
+
days.add(toLocalDay(b.startMs));
|
|
1128
|
+
}
|
|
1129
|
+
return {
|
|
1130
|
+
peakConcurrent,
|
|
1131
|
+
totalParallelMs,
|
|
1132
|
+
burstCount: bursts.length,
|
|
1133
|
+
crossProjectBurstCount,
|
|
1134
|
+
activeDayCount: days.size
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function groupByProject(sessions) {
|
|
1138
|
+
const map = /* @__PURE__ */ new Map();
|
|
1139
|
+
for (const s of sessions) {
|
|
1140
|
+
const canonical = canonicalProjectName(s.projectName);
|
|
1141
|
+
const key = canonical;
|
|
1142
|
+
let cur = map.get(key);
|
|
1143
|
+
if (!cur) {
|
|
1144
|
+
cur = {
|
|
1145
|
+
projectDir: canonical,
|
|
1146
|
+
projectName: canonical,
|
|
1147
|
+
sessions: [],
|
|
1148
|
+
rawProjectDirs: [],
|
|
1149
|
+
worktreeCount: 0,
|
|
1150
|
+
metrics: highLevelMetrics([]),
|
|
1151
|
+
lastActiveMs: void 0
|
|
1152
|
+
};
|
|
1153
|
+
map.set(key, cur);
|
|
1154
|
+
}
|
|
1155
|
+
cur.sessions.push(s);
|
|
1156
|
+
if (!cur.rawProjectDirs.includes(s.projectDir)) {
|
|
1157
|
+
cur.rawProjectDirs.push(s.projectDir);
|
|
1158
|
+
}
|
|
1159
|
+
const last = s.lastTimestamp ? Date.parse(s.lastTimestamp) : void 0;
|
|
1160
|
+
if (last && (!cur.lastActiveMs || last > cur.lastActiveMs)) {
|
|
1161
|
+
cur.lastActiveMs = last;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
for (const p of map.values()) {
|
|
1165
|
+
p.metrics = highLevelMetrics(p.sessions);
|
|
1166
|
+
const wtNames = /* @__PURE__ */ new Set();
|
|
1167
|
+
for (const s of p.sessions) {
|
|
1168
|
+
const wt = worktreeName(s.projectName);
|
|
1169
|
+
if (wt)
|
|
1170
|
+
wtNames.add(wt);
|
|
1171
|
+
}
|
|
1172
|
+
p.worktreeCount = wtNames.size;
|
|
1173
|
+
}
|
|
1174
|
+
return Array.from(map.values()).sort((a, b) => (b.lastActiveMs ?? 0) - (a.lastActiveMs ?? 0));
|
|
1175
|
+
}
|
|
1176
|
+
var BLANK_USAGE2, WORKTREE_MARKERS, PR_CREATE_RE, PR_NUMBER_RE, TITLE_FLAG_RE;
|
|
1177
|
+
var init_analytics = __esm({
|
|
1178
|
+
"../parser/dist/analytics.js"() {
|
|
1179
|
+
"use strict";
|
|
1180
|
+
BLANK_USAGE2 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
1181
|
+
WORKTREE_MARKERS = ["/.claude/worktrees/", "/.worktrees/"];
|
|
1182
|
+
PR_CREATE_RE = /gh\s+pr\s+create\b/i;
|
|
1183
|
+
PR_NUMBER_RE = /#(\d+)/;
|
|
1184
|
+
TITLE_FLAG_RE = /--title\s+["']([^"']+)["']/;
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
// ../parser/dist/capsule.js
|
|
1189
|
+
function trunc(s, n) {
|
|
1190
|
+
if (!s)
|
|
1191
|
+
return "";
|
|
1192
|
+
const one = s.replace(/\s+/g, " ").trim();
|
|
1193
|
+
return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
|
|
1194
|
+
}
|
|
1195
|
+
function minimizeImages(s) {
|
|
1196
|
+
if (!s)
|
|
1197
|
+
return s;
|
|
1198
|
+
let counter = 0;
|
|
1199
|
+
return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
|
|
1200
|
+
}
|
|
1201
|
+
function firstNSentences(s, n = 3, hardCap = 500) {
|
|
1202
|
+
if (!s)
|
|
1203
|
+
return "";
|
|
1204
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
1205
|
+
const out = flat.split(SENTENCE_SPLIT_RE).slice(0, n).join(" ").trim();
|
|
1206
|
+
return out.length > hardCap ? out.slice(0, hardCap - 1) + "\u2026" : out;
|
|
1207
|
+
}
|
|
1208
|
+
function median(xs) {
|
|
1209
|
+
if (xs.length === 0)
|
|
1210
|
+
return 0;
|
|
1211
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
1212
|
+
const n = s.length;
|
|
1213
|
+
return n % 2 ? s[n >> 1] : (s[(n >> 1) - 1] + s[n >> 1]) / 2;
|
|
1214
|
+
}
|
|
1215
|
+
function p90(xs) {
|
|
1216
|
+
if (xs.length === 0)
|
|
1217
|
+
return 0;
|
|
1218
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
1219
|
+
return s[Math.min(Math.floor(0.9 * s.length), s.length - 1)];
|
|
1220
|
+
}
|
|
1221
|
+
function bashVerb(cmd) {
|
|
1222
|
+
if (!cmd)
|
|
1223
|
+
return "?";
|
|
1224
|
+
let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
|
|
1225
|
+
if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
|
|
1226
|
+
const parts = cmd.split(/&&|;/, 3);
|
|
1227
|
+
if (parts.length >= 2)
|
|
1228
|
+
firstSeg = parts[1].trim();
|
|
1229
|
+
}
|
|
1230
|
+
const tokens = firstSeg.split(/\s+/).filter(Boolean);
|
|
1231
|
+
while (tokens.length && SHELL_NOISE.has(tokens[0]))
|
|
1232
|
+
tokens.shift();
|
|
1233
|
+
if (!tokens.length)
|
|
1234
|
+
return "?";
|
|
1235
|
+
let first = tokens[0];
|
|
1236
|
+
if (first.includes("=") && !first.startsWith("/")) {
|
|
1237
|
+
tokens.shift();
|
|
1238
|
+
if (!tokens.length)
|
|
1239
|
+
return "?";
|
|
1240
|
+
first = tokens[0];
|
|
1241
|
+
}
|
|
1242
|
+
if (first.includes("/") && !first.startsWith("./")) {
|
|
1243
|
+
first = first.replace(/\/+$/, "").split("/").pop();
|
|
1244
|
+
}
|
|
1245
|
+
return first.replace(/^["']|["']$/g, "") || "?";
|
|
1246
|
+
}
|
|
1247
|
+
function newTurn(startMs, userInput = "") {
|
|
1248
|
+
return {
|
|
1249
|
+
startMs,
|
|
1250
|
+
lastMs: startMs,
|
|
1251
|
+
eventTs: startMs !== void 0 ? [startMs] : [],
|
|
1252
|
+
userInput,
|
|
1253
|
+
firstToolsIfNoText: [],
|
|
1254
|
+
toolsTotal: 0,
|
|
1255
|
+
toolMix: /* @__PURE__ */ new Map(),
|
|
1256
|
+
bashVerbs: /* @__PURE__ */ new Map(),
|
|
1257
|
+
skills: /* @__PURE__ */ new Map(),
|
|
1258
|
+
subagents: [],
|
|
1259
|
+
taskOps: 0,
|
|
1260
|
+
toolErrors: 0,
|
|
1261
|
+
consecSameToolMax: 0,
|
|
1262
|
+
consec: 0,
|
|
1263
|
+
endedInInterrupt: false,
|
|
1264
|
+
models: /* @__PURE__ */ new Map(),
|
|
1265
|
+
tokensIn: 0,
|
|
1266
|
+
tokensOut: 0,
|
|
1267
|
+
tokensCacheR: 0,
|
|
1268
|
+
tokensCacheW: 0,
|
|
1269
|
+
seenMsgIds: /* @__PURE__ */ new Set()
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function closeTurn(t, closed) {
|
|
1273
|
+
const ts = [...t.eventTs].sort((a, b) => a - b);
|
|
1274
|
+
let active = 0;
|
|
1275
|
+
for (let i = 1; i < ts.length; i++) {
|
|
1276
|
+
const dt = ts[i] - ts[i - 1];
|
|
1277
|
+
if (dt < IDLE_GAP_MS)
|
|
1278
|
+
active += dt;
|
|
1279
|
+
}
|
|
1280
|
+
const wall = t.startMs !== void 0 && t.lastMs !== void 0 ? t.lastMs - t.startMs : 0;
|
|
1281
|
+
closed.push({
|
|
1282
|
+
startMs: t.startMs ?? 0,
|
|
1283
|
+
activeMs: active,
|
|
1284
|
+
wallMs: wall,
|
|
1285
|
+
userInput: t.userInput,
|
|
1286
|
+
firstAgentText: t.firstAgentText,
|
|
1287
|
+
lastAgentText: t.lastAgentText,
|
|
1288
|
+
firstToolsIfNoText: t.firstToolsIfNoText,
|
|
1289
|
+
toolsTotal: t.toolsTotal,
|
|
1290
|
+
toolMix: t.toolMix,
|
|
1291
|
+
bashVerbs: t.bashVerbs,
|
|
1292
|
+
skills: t.skills,
|
|
1293
|
+
subagents: t.subagents,
|
|
1294
|
+
taskOps: t.taskOps,
|
|
1295
|
+
toolErrors: t.toolErrors,
|
|
1296
|
+
consecSameToolMax: t.consecSameToolMax,
|
|
1297
|
+
endedInInterrupt: t.endedInInterrupt,
|
|
1298
|
+
models: t.models,
|
|
1299
|
+
totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function firstTextBlock(blocks) {
|
|
1303
|
+
if (!Array.isArray(blocks))
|
|
1304
|
+
return "";
|
|
1305
|
+
for (const b of blocks) {
|
|
1306
|
+
if (b && typeof b === "object" && b.type === "text") {
|
|
1307
|
+
return String(b.text ?? "");
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return "";
|
|
1311
|
+
}
|
|
1312
|
+
function parseMs(iso) {
|
|
1313
|
+
if (!iso)
|
|
1314
|
+
return void 0;
|
|
1315
|
+
const ms = Date.parse(iso);
|
|
1316
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
1317
|
+
}
|
|
1318
|
+
function buildCapsule(session, opts = {}) {
|
|
1319
|
+
const { compact = false } = opts;
|
|
1320
|
+
let firstUserSession;
|
|
1321
|
+
let finalAgentSession;
|
|
1322
|
+
const prTitles = [];
|
|
1323
|
+
let commits = 0;
|
|
1324
|
+
let pushes = 0;
|
|
1325
|
+
const modelTurnsGlobal = /* @__PURE__ */ new Map();
|
|
1326
|
+
let totalToolErrors = 0;
|
|
1327
|
+
let totalInterrupts = 0;
|
|
1328
|
+
let exitPlanCalls = 0;
|
|
1329
|
+
let taskOpsGlobal = 0;
|
|
1330
|
+
const closed = [];
|
|
1331
|
+
let cur;
|
|
1332
|
+
let sessionFirstTs;
|
|
1333
|
+
let sessionLastTs;
|
|
1334
|
+
const allEventTs = [];
|
|
1335
|
+
for (const ev of session.events) {
|
|
1336
|
+
const tsMs = parseMs(ev.timestamp);
|
|
1337
|
+
if (tsMs !== void 0) {
|
|
1338
|
+
if (sessionFirstTs === void 0 || tsMs < sessionFirstTs)
|
|
1339
|
+
sessionFirstTs = tsMs;
|
|
1340
|
+
if (sessionLastTs === void 0 || tsMs > sessionLastTs)
|
|
1341
|
+
sessionLastTs = tsMs;
|
|
1342
|
+
allEventTs.push(tsMs);
|
|
1343
|
+
}
|
|
1344
|
+
const rawType = ev.rawType;
|
|
1345
|
+
const raw = ev.raw;
|
|
1346
|
+
const msg = raw?.message ?? {};
|
|
1347
|
+
const content = msg.content;
|
|
1348
|
+
if (rawType === "user") {
|
|
1349
|
+
const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
|
|
1350
|
+
if (!isToolResultOnly) {
|
|
1351
|
+
const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
|
|
1352
|
+
if (cur) {
|
|
1353
|
+
if (text && INTERRUPT_RE2.test(text)) {
|
|
1354
|
+
cur.endedInInterrupt = true;
|
|
1355
|
+
totalInterrupts++;
|
|
1356
|
+
}
|
|
1357
|
+
closeTurn(cur, closed);
|
|
1358
|
+
}
|
|
1359
|
+
cur = newTurn(tsMs, text);
|
|
1360
|
+
if (!firstUserSession && text && !text.startsWith("<command-name>")) {
|
|
1361
|
+
firstUserSession = text;
|
|
1362
|
+
}
|
|
1363
|
+
} else {
|
|
1364
|
+
if (Array.isArray(content)) {
|
|
1365
|
+
for (const c of content) {
|
|
1366
|
+
if (c && typeof c === "object" && c.type === "tool_result" && c.is_error) {
|
|
1367
|
+
totalToolErrors++;
|
|
1368
|
+
if (cur)
|
|
1369
|
+
cur.toolErrors++;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (cur && tsMs !== void 0) {
|
|
1374
|
+
cur.eventTs.push(tsMs);
|
|
1375
|
+
cur.lastMs = tsMs;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
} else if (rawType === "assistant") {
|
|
1379
|
+
if (cur && tsMs !== void 0) {
|
|
1380
|
+
cur.eventTs.push(tsMs);
|
|
1381
|
+
cur.lastMs = tsMs;
|
|
1382
|
+
}
|
|
1383
|
+
const model = msg.model;
|
|
1384
|
+
if (model) {
|
|
1385
|
+
modelTurnsGlobal.set(model, (modelTurnsGlobal.get(model) ?? 0) + 1);
|
|
1386
|
+
if (cur)
|
|
1387
|
+
cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
|
|
1388
|
+
}
|
|
1389
|
+
const msgId = msg.id;
|
|
1390
|
+
const usage = msg.usage;
|
|
1391
|
+
if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage) {
|
|
1392
|
+
cur.seenMsgIds.add(msgId);
|
|
1393
|
+
cur.tokensIn += usage.input_tokens ?? 0;
|
|
1394
|
+
cur.tokensOut += usage.output_tokens ?? 0;
|
|
1395
|
+
cur.tokensCacheR += usage.cache_read_input_tokens ?? 0;
|
|
1396
|
+
cur.tokensCacheW += usage.cache_creation_input_tokens ?? 0;
|
|
1397
|
+
}
|
|
1398
|
+
if (Array.isArray(content)) {
|
|
1399
|
+
for (const c of content) {
|
|
1400
|
+
if (!c || typeof c !== "object")
|
|
1401
|
+
continue;
|
|
1402
|
+
const ct = c;
|
|
1403
|
+
if (ct.type === "text" && ct.text) {
|
|
1404
|
+
finalAgentSession = ct.text;
|
|
1405
|
+
if (cur) {
|
|
1406
|
+
if (cur.firstAgentText === void 0)
|
|
1407
|
+
cur.firstAgentText = ct.text;
|
|
1408
|
+
cur.lastAgentText = ct.text;
|
|
1409
|
+
}
|
|
1410
|
+
} else if (ct.type === "tool_use") {
|
|
1411
|
+
const name = ct.name ?? "unknown";
|
|
1412
|
+
if (cur) {
|
|
1413
|
+
cur.toolsTotal++;
|
|
1414
|
+
cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
|
|
1415
|
+
if (cur.firstAgentText === void 0 && cur.firstToolsIfNoText.length < 3) {
|
|
1416
|
+
cur.firstToolsIfNoText.push(name);
|
|
1417
|
+
}
|
|
1418
|
+
if (name === cur.lastTool)
|
|
1419
|
+
cur.consec++;
|
|
1420
|
+
else {
|
|
1421
|
+
cur.lastTool = name;
|
|
1422
|
+
cur.consec = 1;
|
|
1423
|
+
}
|
|
1424
|
+
if (cur.consec > cur.consecSameToolMax)
|
|
1425
|
+
cur.consecSameToolMax = cur.consec;
|
|
1426
|
+
if (name === "Skill") {
|
|
1427
|
+
const sk = String(ct.input?.skill ?? "unknown");
|
|
1428
|
+
cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
|
|
1429
|
+
} else if (name === "ToolSearch") {
|
|
1430
|
+
const q = String(ct.input?.query ?? "");
|
|
1431
|
+
const key = `(ToolSearch: ${trunc(q, 60)})`;
|
|
1432
|
+
cur.skills.set(key, (cur.skills.get(key) ?? 0) + 1);
|
|
1433
|
+
}
|
|
1434
|
+
if (name === "Agent") {
|
|
1435
|
+
const inp = ct.input ?? {};
|
|
1436
|
+
cur.subagents.push({
|
|
1437
|
+
type: String(inp.subagent_type ?? "general-purpose"),
|
|
1438
|
+
model: typeof inp.model === "string" ? inp.model : void 0,
|
|
1439
|
+
description: trunc(String(inp.description ?? ""), 80),
|
|
1440
|
+
prompt_preview: trunc(String(inp.prompt ?? ""), 240),
|
|
1441
|
+
background: Boolean(inp.run_in_background)
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
if (name === "ExitPlanMode")
|
|
1445
|
+
exitPlanCalls++;
|
|
1446
|
+
if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
|
|
1447
|
+
cur.taskOps++;
|
|
1448
|
+
taskOpsGlobal++;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (name === "Bash") {
|
|
1452
|
+
const cmd = String(ct.input?.command ?? "");
|
|
1453
|
+
if (cur)
|
|
1454
|
+
cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
|
|
1455
|
+
if (/\bgh\s+pr\s+create\b/.test(cmd)) {
|
|
1456
|
+
const m = cmd.match(PR_TITLE_RE);
|
|
1457
|
+
prTitles.push(m?.[1] ?? cmd.slice(0, 120));
|
|
1458
|
+
}
|
|
1459
|
+
if (/\bgit\s+commit\b/.test(cmd))
|
|
1460
|
+
commits++;
|
|
1461
|
+
if (/\bgit\s+push\b/.test(cmd))
|
|
1462
|
+
pushes++;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
if (cur)
|
|
1470
|
+
closeTurn(cur, closed);
|
|
1471
|
+
const tsSorted = [...allEventTs].sort((a, b) => a - b);
|
|
1472
|
+
let activeMsTotal = 0;
|
|
1473
|
+
for (let i = 1; i < tsSorted.length; i++) {
|
|
1474
|
+
const dt = tsSorted[i] - tsSorted[i - 1];
|
|
1475
|
+
if (dt < IDLE_GAP_MS)
|
|
1476
|
+
activeMsTotal += dt;
|
|
1477
|
+
}
|
|
1478
|
+
const turnActives = closed.map((t) => t.activeMs);
|
|
1479
|
+
const turnTools = closed.map((t) => t.toolsTotal);
|
|
1480
|
+
const turnTokens = closed.map((t) => t.totalTokens);
|
|
1481
|
+
const subagentTurns = closed.filter((t) => t.subagents.length > 0).length;
|
|
1482
|
+
const primaryModel = [...modelTurnsGlobal.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
1483
|
+
const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
|
|
1484
|
+
const longestTurnActive = Math.max(0, ...turnActives);
|
|
1485
|
+
const sessionSkills = /* @__PURE__ */ new Map();
|
|
1486
|
+
const sessionSubagents = [];
|
|
1487
|
+
for (const t of closed) {
|
|
1488
|
+
for (const [k, v] of t.skills)
|
|
1489
|
+
sessionSkills.set(k, (sessionSkills.get(k) ?? 0) + v);
|
|
1490
|
+
sessionSubagents.push(...t.subagents);
|
|
1491
|
+
}
|
|
1492
|
+
const skillCalls = [...sessionSkills.values()].reduce((a, b) => a + b, 0);
|
|
1493
|
+
const prs = prTitles.length;
|
|
1494
|
+
let outcome;
|
|
1495
|
+
if (prs > 0)
|
|
1496
|
+
outcome = "shipped";
|
|
1497
|
+
else if (commits > 0 && pushes > 0)
|
|
1498
|
+
outcome = "shipped-no-pr";
|
|
1499
|
+
else if (commits > 0)
|
|
1500
|
+
outcome = "partial";
|
|
1501
|
+
else if (activeMsTotal < 2 * 6e4 && closed.length < 3)
|
|
1502
|
+
outcome = "trivial";
|
|
1503
|
+
else
|
|
1504
|
+
outcome = "exploratory";
|
|
1505
|
+
const flags = [];
|
|
1506
|
+
if (totalInterrupts >= 3)
|
|
1507
|
+
flags.push("interrupt_heavy");
|
|
1508
|
+
if (totalToolErrors >= 20)
|
|
1509
|
+
flags.push("high_errors");
|
|
1510
|
+
if (consecMax >= 8)
|
|
1511
|
+
flags.push("loop_suspected");
|
|
1512
|
+
if (activeMsTotal < 5 * 6e4 && prs >= 1)
|
|
1513
|
+
flags.push("fast_ship");
|
|
1514
|
+
if (exitPlanCalls > 0)
|
|
1515
|
+
flags.push("plan_used");
|
|
1516
|
+
if (subagentTurns >= 3)
|
|
1517
|
+
flags.push("orchestrated");
|
|
1518
|
+
if (longestTurnActive >= 20 * 6e4 && totalInterrupts === 0)
|
|
1519
|
+
flags.push("long_autonomous");
|
|
1520
|
+
const maxTokens = Math.max(1, ...turnTokens);
|
|
1521
|
+
const maxTools = Math.max(1, ...turnTools);
|
|
1522
|
+
function significance(t) {
|
|
1523
|
+
return 0.5 * (t.totalTokens / maxTokens) + 0.5 * (t.toolsTotal / maxTools);
|
|
1524
|
+
}
|
|
1525
|
+
let substantive = closed.filter((t) => t.activeMs >= TURN_FLOOR_MS || t.toolsTotal >= 1);
|
|
1526
|
+
const droppedTrivial = closed.length - substantive.length;
|
|
1527
|
+
if (substantive.length > MAX_TURNS_IN_CAPSULE) {
|
|
1528
|
+
substantive = [...substantive].sort((a, b) => significance(b) - significance(a)).slice(0, MAX_TURNS_IN_CAPSULE);
|
|
1529
|
+
}
|
|
1530
|
+
substantive.sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
|
|
1531
|
+
function renderTurn(t) {
|
|
1532
|
+
const tOffsetS = t.startMs && sessionFirstTs ? (t.startMs - sessionFirstTs) / 1e3 : 0;
|
|
1533
|
+
const topToolsArr = [];
|
|
1534
|
+
const topTools = [...t.toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
1535
|
+
for (const [name, count] of topTools) {
|
|
1536
|
+
if (name === "Bash" && t.bashVerbs.size > 0) {
|
|
1537
|
+
const sub = [...t.bashVerbs.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
|
|
1538
|
+
topToolsArr.push(`Bash\xD7${count} (${sub})`);
|
|
1539
|
+
} else {
|
|
1540
|
+
topToolsArr.push(count > 1 ? `${name}\xD7${count}` : name);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const firstAgentRaw = t.firstAgentText ?? (t.firstToolsIfNoText.length > 0 ? `(tools only: ${t.firstToolsIfNoText.join(", ")})` : "");
|
|
1544
|
+
const firstTrunc = trunc(minimizeImages(firstAgentRaw), 260);
|
|
1545
|
+
const finalTrunc = firstNSentences(minimizeImages(t.lastAgentText ?? ""), 3, 500);
|
|
1546
|
+
const ended = t.endedInInterrupt ? "interrupt" : t.lastAgentText ? "text" : "tool-in-flight";
|
|
1547
|
+
const model = [...t.models.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
1548
|
+
const turn = {
|
|
1549
|
+
t_offset_min: Math.round(tOffsetS / 6) / 10,
|
|
1550
|
+
active_min: Math.round(t.activeMs / 6e3) / 10,
|
|
1551
|
+
wall_min: Math.round(t.wallMs / 6e3) / 10,
|
|
1552
|
+
user_input: trunc(minimizeImages(t.userInput), 300),
|
|
1553
|
+
tools_total: t.toolsTotal,
|
|
1554
|
+
top_tools: topToolsArr,
|
|
1555
|
+
tokens: t.totalTokens,
|
|
1556
|
+
errors: t.toolErrors,
|
|
1557
|
+
consec_same_tool_max: t.consecSameToolMax,
|
|
1558
|
+
ended,
|
|
1559
|
+
model
|
|
1560
|
+
};
|
|
1561
|
+
if (firstTrunc && finalTrunc && firstTrunc === finalTrunc) {
|
|
1562
|
+
turn.agent = firstTrunc;
|
|
1563
|
+
} else if (firstAgentRaw && t.lastAgentText && t.firstAgentText === t.lastAgentText) {
|
|
1564
|
+
turn.agent = finalTrunc;
|
|
1565
|
+
} else {
|
|
1566
|
+
turn.first_agent = firstTrunc;
|
|
1567
|
+
turn.final_agent = finalTrunc;
|
|
1568
|
+
}
|
|
1569
|
+
if (t.skills.size > 0)
|
|
1570
|
+
turn.skills = Object.fromEntries(t.skills);
|
|
1571
|
+
if (t.subagents.length > 0)
|
|
1572
|
+
turn.subagents = t.subagents;
|
|
1573
|
+
return turn;
|
|
1574
|
+
}
|
|
1575
|
+
const capsule = {
|
|
1576
|
+
session_id: session.id,
|
|
1577
|
+
project: session.projectName,
|
|
1578
|
+
start_iso: session.firstTimestamp,
|
|
1579
|
+
end_iso: session.lastTimestamp,
|
|
1580
|
+
outcome,
|
|
1581
|
+
flags,
|
|
1582
|
+
primary_model: primaryModel,
|
|
1583
|
+
model_mix: Object.fromEntries(modelTurnsGlobal),
|
|
1584
|
+
numbers: {
|
|
1585
|
+
active_min: Math.round(activeMsTotal / 6e3) / 10,
|
|
1586
|
+
turn_count: closed.length,
|
|
1587
|
+
longest_turn_min: Math.round(longestTurnActive / 6e3) / 10,
|
|
1588
|
+
median_turn_min: Math.round(median(turnActives) / 6e3) / 10,
|
|
1589
|
+
p90_turn_min: Math.round(p90(turnActives) / 6e3) / 10,
|
|
1590
|
+
tools_total: turnTools.reduce((a, b) => a + b, 0),
|
|
1591
|
+
tokens_total: turnTokens.reduce((a, b) => a + b, 0),
|
|
1592
|
+
subagent_turns: subagentTurns,
|
|
1593
|
+
subagent_calls: sessionSubagents.length,
|
|
1594
|
+
skill_calls: skillCalls,
|
|
1595
|
+
task_ops: taskOpsGlobal,
|
|
1596
|
+
interrupts: totalInterrupts,
|
|
1597
|
+
tool_errors: totalToolErrors,
|
|
1598
|
+
consec_same_tool_max: consecMax,
|
|
1599
|
+
exit_plan_calls: exitPlanCalls,
|
|
1600
|
+
prs,
|
|
1601
|
+
commits,
|
|
1602
|
+
pushes
|
|
1603
|
+
},
|
|
1604
|
+
pr_titles: prTitles,
|
|
1605
|
+
first_user: trunc(minimizeImages(firstUserSession ?? ""), 400),
|
|
1606
|
+
final_agent: trunc(minimizeImages(finalAgentSession ?? ""), 400)
|
|
1607
|
+
};
|
|
1608
|
+
if (sessionSkills.size > 0)
|
|
1609
|
+
capsule.skills = Object.fromEntries(sessionSkills);
|
|
1610
|
+
if (sessionSubagents.length > 0)
|
|
1611
|
+
capsule.subagents = sessionSubagents;
|
|
1612
|
+
if (!compact) {
|
|
1613
|
+
capsule.numbers.turns_in_capsule = substantive.length;
|
|
1614
|
+
capsule.numbers.trivial_turns_dropped = droppedTrivial;
|
|
1615
|
+
capsule.turns = substantive.map(renderTurn);
|
|
1616
|
+
}
|
|
1617
|
+
return capsule;
|
|
1618
|
+
}
|
|
1619
|
+
var IDLE_GAP_MS, MAX_TURNS_IN_CAPSULE, TURN_FLOOR_MS, INTERRUPT_RE2, SENTENCE_SPLIT_RE, IMAGE_BLOCK_RE, PR_TITLE_RE, BASH_CHAIN_SPLIT_RE, SHELL_NOISE;
|
|
1620
|
+
var init_capsule = __esm({
|
|
1621
|
+
"../parser/dist/capsule.js"() {
|
|
1622
|
+
"use strict";
|
|
1623
|
+
IDLE_GAP_MS = 3 * 60 * 1e3;
|
|
1624
|
+
MAX_TURNS_IN_CAPSULE = 25;
|
|
1625
|
+
TURN_FLOOR_MS = 15 * 1e3;
|
|
1626
|
+
INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
|
|
1627
|
+
SENTENCE_SPLIT_RE = /(?<=[.!?])\s+(?=[A-Z[`])/;
|
|
1628
|
+
IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
|
|
1629
|
+
PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
|
|
1630
|
+
BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
|
|
1631
|
+
SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
// ../parser/dist/aggregate.js
|
|
1636
|
+
function isoDay(d) {
|
|
1637
|
+
return toLocalDay(d.getTime());
|
|
1638
|
+
}
|
|
1639
|
+
function rangeDays(start, end) {
|
|
1640
|
+
const out = [];
|
|
1641
|
+
const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
|
|
1642
|
+
const stop = new Date(end.getFullYear(), end.getMonth(), end.getDate());
|
|
1643
|
+
while (cur.getTime() <= stop.getTime()) {
|
|
1644
|
+
out.push(new Date(cur));
|
|
1645
|
+
cur.setDate(cur.getDate() + 1);
|
|
1646
|
+
}
|
|
1647
|
+
return out;
|
|
1648
|
+
}
|
|
1649
|
+
function prettyProjectName(p) {
|
|
1650
|
+
const cleaned = p.replace(/^\/Users\/[^/]+\//, "");
|
|
1651
|
+
return cleaned || p;
|
|
1652
|
+
}
|
|
1653
|
+
function buildPeriodBundle(caps, opts) {
|
|
1654
|
+
const { period, trivial_dropped, sessions_total } = opts;
|
|
1655
|
+
const days = rangeDays(period.start, period.end);
|
|
1656
|
+
const dayKeys = new Map(days.map((d) => [isoDay(d), d]));
|
|
1657
|
+
const dayAgent = /* @__PURE__ */ new Map();
|
|
1658
|
+
const daySessions = /* @__PURE__ */ new Map();
|
|
1659
|
+
for (const k of dayKeys.keys()) {
|
|
1660
|
+
dayAgent.set(k, 0);
|
|
1661
|
+
daySessions.set(k, 0);
|
|
1662
|
+
}
|
|
1663
|
+
for (const c of caps) {
|
|
1664
|
+
if (!c.start_iso)
|
|
1665
|
+
continue;
|
|
1666
|
+
const d = isoDay(new Date(c.start_iso));
|
|
1667
|
+
if (!dayAgent.has(d))
|
|
1668
|
+
continue;
|
|
1669
|
+
dayAgent.set(d, (dayAgent.get(d) ?? 0) + c.numbers.active_min);
|
|
1670
|
+
daySessions.set(d, (daySessions.get(d) ?? 0) + 1);
|
|
1671
|
+
}
|
|
1672
|
+
const by_day = days.map((d) => {
|
|
1673
|
+
const key = isoDay(d);
|
|
1674
|
+
return {
|
|
1675
|
+
date: key,
|
|
1676
|
+
day_name: DAY_NAMES[d.getDay()],
|
|
1677
|
+
agent_min: Math.round((dayAgent.get(key) ?? 0) * 10) / 10,
|
|
1678
|
+
sessions: daySessions.get(key) ?? 0
|
|
1679
|
+
};
|
|
1680
|
+
});
|
|
1681
|
+
const projectAccum = /* @__PURE__ */ new Map();
|
|
1682
|
+
for (const c of caps) {
|
|
1683
|
+
const canon = canonicalProjectName(c.project ?? "unknown");
|
|
1684
|
+
const b = projectAccum.get(canon) ?? { agent_min: 0, prs: 0, commits: 0 };
|
|
1685
|
+
b.agent_min += c.numbers.active_min;
|
|
1686
|
+
b.prs += c.numbers.prs;
|
|
1687
|
+
b.commits += c.numbers.commits;
|
|
1688
|
+
projectAccum.set(canon, b);
|
|
1689
|
+
}
|
|
1690
|
+
const totalProjectMin = [...projectAccum.values()].reduce((a, b) => a + b.agent_min, 0);
|
|
1691
|
+
const project_shares = [...projectAccum.entries()].map(([name, b]) => ({
|
|
1692
|
+
name,
|
|
1693
|
+
display_name: prettyProjectName(name),
|
|
1694
|
+
agent_min: Math.round(b.agent_min * 10) / 10,
|
|
1695
|
+
share_pct: totalProjectMin === 0 ? 0 : Math.round(b.agent_min / totalProjectMin * 100),
|
|
1696
|
+
prs: b.prs,
|
|
1697
|
+
commits: b.commits
|
|
1698
|
+
})).sort((a, b) => b.agent_min - a.agent_min);
|
|
1699
|
+
const shipped_prs = [];
|
|
1700
|
+
for (const c of caps) {
|
|
1701
|
+
for (const title of c.pr_titles) {
|
|
1702
|
+
shipped_prs.push({
|
|
1703
|
+
title,
|
|
1704
|
+
project: prettyProjectName(canonicalProjectName(c.project ?? "")),
|
|
1705
|
+
session_id: c.session_id,
|
|
1706
|
+
start_iso: c.start_iso ?? "",
|
|
1707
|
+
active_min: c.numbers.active_min,
|
|
1708
|
+
commits: c.numbers.commits,
|
|
1709
|
+
subagents: c.numbers.subagent_calls,
|
|
1710
|
+
flags: c.flags
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
const flags_count = {};
|
|
1715
|
+
const skills_total = {};
|
|
1716
|
+
const totals = { agent_min: 0, commits: 0, pushes: 0, prs: 0, subagent_calls: 0, tool_errors: 0 };
|
|
1717
|
+
for (const c of caps) {
|
|
1718
|
+
for (const f of c.flags)
|
|
1719
|
+
flags_count[f] = (flags_count[f] ?? 0) + 1;
|
|
1720
|
+
if (c.skills) {
|
|
1721
|
+
for (const [k, v] of Object.entries(c.skills)) {
|
|
1722
|
+
skills_total[k] = (skills_total[k] ?? 0) + v;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
totals.agent_min += c.numbers.active_min;
|
|
1726
|
+
totals.commits += c.numbers.commits;
|
|
1727
|
+
totals.pushes += c.numbers.pushes;
|
|
1728
|
+
totals.prs += c.numbers.prs;
|
|
1729
|
+
totals.subagent_calls += c.numbers.subagent_calls;
|
|
1730
|
+
totals.tool_errors += c.numbers.tool_errors;
|
|
1731
|
+
}
|
|
1732
|
+
totals.agent_min = Math.round(totals.agent_min * 10) / 10;
|
|
1733
|
+
const longest = [...caps].sort((a, b) => b.numbers.active_min - a.numbers.active_min)[0];
|
|
1734
|
+
const shipped = caps.filter((c) => c.numbers.prs >= 1);
|
|
1735
|
+
const fastest = shipped.sort((a, b) => a.numbers.active_min - b.numbers.active_min)[0];
|
|
1736
|
+
const mostErr = [...caps].sort((a, b) => b.numbers.tool_errors - a.numbers.tool_errors)[0];
|
|
1737
|
+
const wandered = [...caps].filter((c) => c.outcome === "exploratory" && c.numbers.prs === 0 && c.numbers.commits === 0 && c.numbers.pushes === 0 && c.numbers.active_min >= 10).sort((a, b) => b.numbers.active_min - a.numbers.active_min)[0];
|
|
1738
|
+
const outliers = {};
|
|
1739
|
+
if (longest && longest.numbers.active_min > 0) {
|
|
1740
|
+
outliers.longest_run = {
|
|
1741
|
+
session_id: longest.session_id,
|
|
1742
|
+
project: prettyProjectName(canonicalProjectName(longest.project ?? "")),
|
|
1743
|
+
active_min: longest.numbers.active_min,
|
|
1744
|
+
flags: longest.flags
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
if (fastest) {
|
|
1748
|
+
outliers.fastest_ship = {
|
|
1749
|
+
session_id: fastest.session_id,
|
|
1750
|
+
project: prettyProjectName(canonicalProjectName(fastest.project ?? "")),
|
|
1751
|
+
active_min: fastest.numbers.active_min,
|
|
1752
|
+
pr_title: fastest.pr_titles[0] ?? ""
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
if (mostErr && mostErr.numbers.tool_errors > 0) {
|
|
1756
|
+
outliers.most_errors = {
|
|
1757
|
+
session_id: mostErr.session_id,
|
|
1758
|
+
project: prettyProjectName(canonicalProjectName(mostErr.project ?? "")),
|
|
1759
|
+
active_min: mostErr.numbers.active_min,
|
|
1760
|
+
tool_errors: mostErr.numbers.tool_errors
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
if (wandered) {
|
|
1764
|
+
outliers.wandered = {
|
|
1765
|
+
session_id: wandered.session_id,
|
|
1766
|
+
project: prettyProjectName(canonicalProjectName(wandered.project ?? "")),
|
|
1767
|
+
active_min: wandered.numbers.active_min
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
const label = period.range_type === "month" ? period.start.toLocaleDateString("en-US", { month: "long", year: "numeric" }) : `${period.start.toLocaleDateString("en-US", { month: "short", day: "numeric" })} \u2014 ${period.end.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
|
|
1771
|
+
return {
|
|
1772
|
+
period: {
|
|
1773
|
+
start: isoDay(period.start),
|
|
1774
|
+
end: isoDay(period.end),
|
|
1775
|
+
label,
|
|
1776
|
+
day_count: days.length,
|
|
1777
|
+
range_type: period.range_type ?? "custom"
|
|
1778
|
+
},
|
|
1779
|
+
counts: { sessions_total, substantive: caps.length, trivial_dropped },
|
|
1780
|
+
totals,
|
|
1781
|
+
by_day,
|
|
1782
|
+
project_shares,
|
|
1783
|
+
shipped_prs,
|
|
1784
|
+
flags_count,
|
|
1785
|
+
skills_total,
|
|
1786
|
+
outliers
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function calendarWeek(ref = /* @__PURE__ */ new Date()) {
|
|
1790
|
+
const d = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate());
|
|
1791
|
+
const dow = d.getDay();
|
|
1792
|
+
const daysSinceMon = (dow + 6) % 7;
|
|
1793
|
+
const monday = new Date(d);
|
|
1794
|
+
monday.setDate(d.getDate() - daysSinceMon);
|
|
1795
|
+
const sunday = new Date(monday);
|
|
1796
|
+
sunday.setDate(monday.getDate() + 6);
|
|
1797
|
+
return { start: monday, end: sunday };
|
|
1798
|
+
}
|
|
1799
|
+
function priorCalendarWeek(ref = /* @__PURE__ */ new Date()) {
|
|
1800
|
+
const cur = calendarWeek(ref);
|
|
1801
|
+
const prevEnd = new Date(cur.start);
|
|
1802
|
+
prevEnd.setDate(cur.start.getDate() - 1);
|
|
1803
|
+
const prevStart = new Date(prevEnd);
|
|
1804
|
+
prevStart.setDate(prevEnd.getDate() - 6);
|
|
1805
|
+
return { start: prevStart, end: prevEnd };
|
|
1806
|
+
}
|
|
1807
|
+
function last4CompletedWeeks(ref = /* @__PURE__ */ new Date()) {
|
|
1808
|
+
const { end } = priorCalendarWeek(ref);
|
|
1809
|
+
const start = new Date(end);
|
|
1810
|
+
start.setDate(end.getDate() - 27);
|
|
1811
|
+
return { start, end };
|
|
1812
|
+
}
|
|
1813
|
+
function calendarMonth(ref = /* @__PURE__ */ new Date()) {
|
|
1814
|
+
const start = new Date(ref.getFullYear(), ref.getMonth(), 1);
|
|
1815
|
+
const end = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
|
|
1816
|
+
return { start, end };
|
|
1817
|
+
}
|
|
1818
|
+
function priorCalendarMonth(ref = /* @__PURE__ */ new Date()) {
|
|
1819
|
+
const start = new Date(ref.getFullYear(), ref.getMonth() - 1, 1);
|
|
1820
|
+
const end = new Date(ref.getFullYear(), ref.getMonth(), 0);
|
|
1821
|
+
return { start, end };
|
|
1822
|
+
}
|
|
1823
|
+
function aggregateConcurrency(bursts, period) {
|
|
1824
|
+
const days = rangeDays(period.start, period.end);
|
|
1825
|
+
const byDayKey = /* @__PURE__ */ new Map();
|
|
1826
|
+
for (const d of days)
|
|
1827
|
+
byDayKey.set(isoDay(d), { peak: 0, has_cross_project: false });
|
|
1828
|
+
for (const b of bursts) {
|
|
1829
|
+
const dayKey = isoDay(new Date(b.startMs));
|
|
1830
|
+
const cur = byDayKey.get(dayKey);
|
|
1831
|
+
if (!cur)
|
|
1832
|
+
continue;
|
|
1833
|
+
if (b.peak > cur.peak)
|
|
1834
|
+
cur.peak = b.peak;
|
|
1835
|
+
if (b.crossProject)
|
|
1836
|
+
cur.has_cross_project = true;
|
|
1837
|
+
}
|
|
1838
|
+
const by_day = days.map((d) => {
|
|
1839
|
+
const k = isoDay(d);
|
|
1840
|
+
const v = byDayKey.get(k);
|
|
1841
|
+
return { date: k, peak: v.peak, has_cross_project: v.has_cross_project };
|
|
1842
|
+
});
|
|
1843
|
+
let peak = 0;
|
|
1844
|
+
let peak_day;
|
|
1845
|
+
let multi_agent_days = 0;
|
|
1846
|
+
let cross_project_days = 0;
|
|
1847
|
+
for (const d of by_day) {
|
|
1848
|
+
if (d.peak > peak) {
|
|
1849
|
+
peak = d.peak;
|
|
1850
|
+
peak_day = d.date;
|
|
1851
|
+
}
|
|
1852
|
+
if (d.peak >= 3)
|
|
1853
|
+
multi_agent_days++;
|
|
1854
|
+
if (d.has_cross_project)
|
|
1855
|
+
cross_project_days++;
|
|
1856
|
+
}
|
|
1857
|
+
return { by_day, peak, peak_day, multi_agent_days, cross_project_days };
|
|
1858
|
+
}
|
|
1859
|
+
var DAY_NAMES;
|
|
1860
|
+
var init_aggregate = __esm({
|
|
1861
|
+
"../parser/dist/aggregate.js"() {
|
|
1862
|
+
"use strict";
|
|
1863
|
+
init_analytics();
|
|
1864
|
+
DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
// ../parser/dist/team.js
|
|
1869
|
+
function groupByTeam(sessions, details) {
|
|
1870
|
+
const byTeam = /* @__PURE__ */ new Map();
|
|
1871
|
+
for (const s of sessions) {
|
|
1872
|
+
if (!s.teamName)
|
|
1873
|
+
continue;
|
|
1874
|
+
const arr = byTeam.get(s.teamName) ?? [];
|
|
1875
|
+
arr.push(s);
|
|
1876
|
+
byTeam.set(s.teamName, arr);
|
|
1877
|
+
}
|
|
1878
|
+
const views = [];
|
|
1879
|
+
for (const [teamName, group] of byTeam) {
|
|
1880
|
+
const view = buildTeamView(teamName, group, details);
|
|
1881
|
+
if (view)
|
|
1882
|
+
views.push(view);
|
|
1883
|
+
}
|
|
1884
|
+
return views;
|
|
1885
|
+
}
|
|
1886
|
+
function buildTeamView(teamName, group, details) {
|
|
1887
|
+
const candidates = group.filter((s) => s.isTeamLead);
|
|
1888
|
+
if (candidates.length === 0)
|
|
1889
|
+
return null;
|
|
1890
|
+
let lead;
|
|
1891
|
+
for (const c of candidates) {
|
|
1892
|
+
const d = details.get(c.sessionId);
|
|
1893
|
+
if (d && containsTeamCreate(d.events)) {
|
|
1894
|
+
lead = c;
|
|
1895
|
+
break;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (!lead) {
|
|
1899
|
+
const sorted = [...candidates].sort((a, b) => Date.parse(a.firstTimestamp ?? "") - Date.parse(b.firstTimestamp ?? ""));
|
|
1900
|
+
lead = sorted[0];
|
|
1901
|
+
}
|
|
1902
|
+
const agentNameBySessionId = /* @__PURE__ */ new Map();
|
|
1903
|
+
for (const s of group)
|
|
1904
|
+
agentNameBySessionId.set(s.sessionId, s.agentName);
|
|
1905
|
+
const members = group.filter((s) => s.sessionId !== lead.sessionId).sort((a, b) => Date.parse(a.firstTimestamp ?? "") - Date.parse(b.firstTimestamp ?? ""));
|
|
1906
|
+
const sessionIdByAgentName = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const m of members) {
|
|
1908
|
+
if (m.agentName)
|
|
1909
|
+
sessionIdByAgentName.set(m.agentName, m.sessionId);
|
|
1910
|
+
}
|
|
1911
|
+
const messages = [];
|
|
1912
|
+
for (const s of group) {
|
|
1913
|
+
const d = details.get(s.sessionId);
|
|
1914
|
+
if (!d)
|
|
1915
|
+
continue;
|
|
1916
|
+
const senderId = s.sessionId;
|
|
1917
|
+
const senderTeammateId = s.agentName ?? "team-lead";
|
|
1918
|
+
for (const ev of d.events) {
|
|
1919
|
+
const msg = extractSendMessage(ev);
|
|
1920
|
+
if (!msg)
|
|
1921
|
+
continue;
|
|
1922
|
+
const toTeammateId = msg.to;
|
|
1923
|
+
const toSessionId = toTeammateId === "team-lead" ? lead.sessionId : sessionIdByAgentName.get(toTeammateId) ?? "";
|
|
1924
|
+
messages.push({
|
|
1925
|
+
tsMs: ev.timestamp ? Date.parse(ev.timestamp) : 0,
|
|
1926
|
+
fromSessionId: senderId,
|
|
1927
|
+
fromTeammateId: senderTeammateId,
|
|
1928
|
+
toSessionId,
|
|
1929
|
+
toTeammateId,
|
|
1930
|
+
body: msg.body,
|
|
1931
|
+
kind: "message"
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
messages.sort((a, b) => a.tsMs - b.tsMs);
|
|
1936
|
+
const allTs = [];
|
|
1937
|
+
for (const s of group) {
|
|
1938
|
+
if (s.firstTimestamp)
|
|
1939
|
+
allTs.push(Date.parse(s.firstTimestamp));
|
|
1940
|
+
if (s.lastTimestamp)
|
|
1941
|
+
allTs.push(Date.parse(s.lastTimestamp));
|
|
1942
|
+
}
|
|
1943
|
+
const firstEventMs = allTs.length ? Math.min(...allTs) : 0;
|
|
1944
|
+
const lastEventMs = allTs.length ? Math.max(...allTs) : 0;
|
|
1945
|
+
return {
|
|
1946
|
+
teamName,
|
|
1947
|
+
leadSessionId: lead.sessionId,
|
|
1948
|
+
memberSessionIds: members.map((m) => m.sessionId),
|
|
1949
|
+
agentNameBySessionId,
|
|
1950
|
+
messages,
|
|
1951
|
+
firstEventMs,
|
|
1952
|
+
lastEventMs
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function containsTeamCreate(events) {
|
|
1956
|
+
for (const ev of events) {
|
|
1957
|
+
if (ev.role !== "tool-call")
|
|
1958
|
+
continue;
|
|
1959
|
+
if (ev.toolName === "TeamCreate")
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
return false;
|
|
1963
|
+
}
|
|
1964
|
+
function extractSendMessage(ev) {
|
|
1965
|
+
if (ev.role !== "tool-call" || ev.toolName !== "SendMessage")
|
|
1966
|
+
return null;
|
|
1967
|
+
for (const b of ev.blocks) {
|
|
1968
|
+
if (!b || typeof b !== "object")
|
|
1969
|
+
continue;
|
|
1970
|
+
if (b.type !== "tool_use")
|
|
1971
|
+
continue;
|
|
1972
|
+
const input = b.input ?? {};
|
|
1973
|
+
const to = typeof input.to === "string" ? input.to : "";
|
|
1974
|
+
const body = typeof input.message === "string" ? input.message : "";
|
|
1975
|
+
if (!to)
|
|
1976
|
+
return null;
|
|
1977
|
+
return { to, body };
|
|
1978
|
+
}
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
var init_team = __esm({
|
|
1982
|
+
"../parser/dist/team.js"() {
|
|
1983
|
+
"use strict";
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
// ../parser/dist/index.js
|
|
1988
|
+
var dist_exports = {};
|
|
1989
|
+
__export(dist_exports, {
|
|
1990
|
+
aggregateConcurrency: () => aggregateConcurrency,
|
|
1991
|
+
buildCapsule: () => buildCapsule,
|
|
1992
|
+
buildGanttDay: () => buildGanttDay,
|
|
1993
|
+
buildMegaRows: () => buildMegaRows,
|
|
1994
|
+
buildPeriodBundle: () => buildPeriodBundle,
|
|
1995
|
+
buildPresentation: () => buildPresentation,
|
|
1996
|
+
calendarMonth: () => calendarMonth,
|
|
1997
|
+
calendarWeek: () => calendarWeek,
|
|
1998
|
+
canonicalProjectName: () => canonicalProjectName,
|
|
1999
|
+
computeActiveSegments: () => computeActiveSegments,
|
|
2000
|
+
computeBurstsFromSessions: () => computeBurstsFromSessions,
|
|
2001
|
+
computeParallelism: () => computeParallelism,
|
|
2002
|
+
computeParallelismBursts: () => computeParallelismBursts,
|
|
2003
|
+
dailyActivity: () => dailyActivity,
|
|
2004
|
+
daysBetween: () => daysBetween,
|
|
2005
|
+
detectParallelRuns: () => detectParallelRuns,
|
|
2006
|
+
detectPrMarkers: () => detectPrMarkers,
|
|
2007
|
+
groupByProject: () => groupByProject,
|
|
2008
|
+
groupByTeam: () => groupByTeam,
|
|
2009
|
+
highLevelMetrics: () => highLevelMetrics,
|
|
2010
|
+
last4CompletedWeeks: () => last4CompletedWeeks,
|
|
2011
|
+
parseTranscript: () => parseTranscript,
|
|
2012
|
+
priorCalendarMonth: () => priorCalendarMonth,
|
|
2013
|
+
priorCalendarWeek: () => priorCalendarWeek,
|
|
2014
|
+
rowPrimaryIndex: () => rowPrimaryIndex,
|
|
2015
|
+
sessionAirTimeMs: () => sessionAirTimeMs,
|
|
2016
|
+
sessionDay: () => sessionDay,
|
|
2017
|
+
summarizeBursts: () => summarizeBursts,
|
|
2018
|
+
toLocalDay: () => toLocalDay,
|
|
2019
|
+
worktreeName: () => worktreeName
|
|
2020
|
+
});
|
|
2021
|
+
var init_dist = __esm({
|
|
2022
|
+
"../parser/dist/index.js"() {
|
|
2023
|
+
"use strict";
|
|
2024
|
+
init_types();
|
|
2025
|
+
init_parser();
|
|
2026
|
+
init_presentation();
|
|
2027
|
+
init_analytics();
|
|
2028
|
+
init_capsule();
|
|
2029
|
+
init_aggregate();
|
|
2030
|
+
init_team();
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
// ../parser/dist/fs.js
|
|
2035
|
+
var fs_exports = {};
|
|
2036
|
+
__export(fs_exports, {
|
|
2037
|
+
DEFAULT_ROOT: () => DEFAULT_ROOT,
|
|
2038
|
+
cacheStats: () => cacheStats,
|
|
2039
|
+
clearCaches: () => clearCaches,
|
|
2040
|
+
decodeProjectName: () => decodeProjectName,
|
|
2041
|
+
findTeamLead: () => findTeamLead,
|
|
2042
|
+
getSession: () => getSession,
|
|
2043
|
+
invalidateFile: () => invalidateFile,
|
|
2044
|
+
listProjects: () => listProjects,
|
|
2045
|
+
listSessions: () => listSessions,
|
|
2046
|
+
loadTeamForSession: () => loadTeamForSession,
|
|
2047
|
+
loadUsageByDay: () => loadUsageByDay,
|
|
2048
|
+
readJsonlFile: () => readJsonlFile,
|
|
2049
|
+
sessionIdFromFileName: () => sessionIdFromFileName,
|
|
2050
|
+
walkJsonlFiles: () => walkJsonlFiles
|
|
2051
|
+
});
|
|
2052
|
+
import { promises as fs } from "node:fs";
|
|
2053
|
+
import os from "node:os";
|
|
2054
|
+
import path from "node:path";
|
|
2055
|
+
function decodeProjectName(dir) {
|
|
2056
|
+
if (!dir.startsWith("-"))
|
|
2057
|
+
return dir;
|
|
2058
|
+
return "/" + dir.slice(1).replace(/-/g, "/");
|
|
2059
|
+
}
|
|
2060
|
+
async function readJsonlFile(filePath) {
|
|
2061
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
2062
|
+
const out = [];
|
|
2063
|
+
for (const line of raw.split("\n")) {
|
|
2064
|
+
const trimmed = line.trim();
|
|
2065
|
+
if (!trimmed)
|
|
2066
|
+
continue;
|
|
2067
|
+
try {
|
|
2068
|
+
out.push(JSON.parse(trimmed));
|
|
2069
|
+
} catch {
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
return out;
|
|
2073
|
+
}
|
|
2074
|
+
function cacheStats() {
|
|
2075
|
+
return { metaEntries: metaCache.size, detailEntries: detailCache.size };
|
|
2076
|
+
}
|
|
2077
|
+
function clearCaches() {
|
|
2078
|
+
metaCache.clear();
|
|
2079
|
+
detailCache.clear();
|
|
2080
|
+
fileListCache = null;
|
|
2081
|
+
}
|
|
2082
|
+
function invalidateFile(fullPath) {
|
|
2083
|
+
metaCache.delete(fullPath);
|
|
2084
|
+
detailCache.delete(fullPath);
|
|
2085
|
+
fileListCache = null;
|
|
2086
|
+
}
|
|
2087
|
+
async function walkJsonlFiles(root = DEFAULT_ROOT) {
|
|
2088
|
+
if (fileListCache && fileListCache.root === root && Date.now() - fileListCache.capturedAtMs < FILE_LIST_TTL_MS) {
|
|
2089
|
+
return fileListCache.files;
|
|
2090
|
+
}
|
|
2091
|
+
let topEntries;
|
|
2092
|
+
try {
|
|
2093
|
+
topEntries = await fs.readdir(root, { withFileTypes: true });
|
|
2094
|
+
} catch {
|
|
2095
|
+
return [];
|
|
2096
|
+
}
|
|
2097
|
+
const projectDirs = topEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2098
|
+
const perProject = await Promise.all(projectDirs.map(async (projectDir) => {
|
|
2099
|
+
const projectPath = path.join(root, projectDir);
|
|
2100
|
+
let files;
|
|
2101
|
+
try {
|
|
2102
|
+
files = await fs.readdir(projectPath);
|
|
2103
|
+
} catch {
|
|
2104
|
+
return [];
|
|
2105
|
+
}
|
|
2106
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2107
|
+
const refs = await Promise.all(jsonlFiles.map(async (f) => {
|
|
2108
|
+
const fullPath = path.join(projectPath, f);
|
|
2109
|
+
try {
|
|
2110
|
+
const stat = await fs.stat(fullPath);
|
|
2111
|
+
return {
|
|
2112
|
+
projectDir,
|
|
2113
|
+
fileName: f,
|
|
2114
|
+
fullPath,
|
|
2115
|
+
mtimeMs: stat.mtimeMs,
|
|
2116
|
+
sizeBytes: stat.size
|
|
2117
|
+
};
|
|
2118
|
+
} catch {
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
}));
|
|
2122
|
+
return refs.filter((r) => r !== null);
|
|
2123
|
+
}));
|
|
2124
|
+
const all = perProject.flat();
|
|
2125
|
+
fileListCache = { files: all, capturedAtMs: Date.now(), root };
|
|
2126
|
+
return all;
|
|
2127
|
+
}
|
|
2128
|
+
function sessionIdFromFileName(fileName) {
|
|
2129
|
+
return fileName.replace(/\.jsonl$/i, "");
|
|
2130
|
+
}
|
|
2131
|
+
function aggregateUsageFromLines(lines, seenKeys, usage) {
|
|
2132
|
+
for (const raw of lines) {
|
|
2133
|
+
if (!raw || typeof raw !== "object")
|
|
2134
|
+
continue;
|
|
2135
|
+
const r = raw;
|
|
2136
|
+
if (r.type !== "assistant")
|
|
2137
|
+
continue;
|
|
2138
|
+
const m = r.message;
|
|
2139
|
+
if (!m)
|
|
2140
|
+
continue;
|
|
2141
|
+
const mid = typeof m.id === "string" ? m.id : void 0;
|
|
2142
|
+
const rid = typeof r.requestId === "string" ? r.requestId : void 0;
|
|
2143
|
+
const key = mid != null && rid != null ? `${mid}:${rid}` : void 0;
|
|
2144
|
+
if (key && seenKeys.has(key))
|
|
2145
|
+
continue;
|
|
2146
|
+
if (key)
|
|
2147
|
+
seenKeys.add(key);
|
|
2148
|
+
const u = m.usage;
|
|
2149
|
+
if (u) {
|
|
2150
|
+
const toNum = (v) => typeof v === "number" ? v : 0;
|
|
2151
|
+
usage.input += toNum(u.input_tokens);
|
|
2152
|
+
usage.output += toNum(u.output_tokens);
|
|
2153
|
+
usage.cacheRead += toNum(u.cache_read_input_tokens);
|
|
2154
|
+
usage.cacheWrite += toNum(u.cache_creation_input_tokens);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
async function computeSessionUsageWithSubagents(parentLines, projectDirPath, sessionId) {
|
|
2159
|
+
const usage = { ...BLANK_USAGE3 };
|
|
2160
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2161
|
+
aggregateUsageFromLines(parentLines, seenKeys, usage);
|
|
2162
|
+
const subagentsDir = path.join(projectDirPath, sessionId, "subagents");
|
|
2163
|
+
let entries;
|
|
2164
|
+
try {
|
|
2165
|
+
entries = await fs.readdir(subagentsDir);
|
|
2166
|
+
} catch {
|
|
2167
|
+
return usage;
|
|
2168
|
+
}
|
|
2169
|
+
const jsonlFiles = entries.filter((f) => f.endsWith(".jsonl"));
|
|
2170
|
+
if (jsonlFiles.length === 0)
|
|
2171
|
+
return usage;
|
|
2172
|
+
await Promise.all(jsonlFiles.map(async (f) => {
|
|
2173
|
+
try {
|
|
2174
|
+
const lines = await readJsonlFile(path.join(subagentsDir, f));
|
|
2175
|
+
aggregateUsageFromLines(lines, seenKeys, usage);
|
|
2176
|
+
} catch {
|
|
2177
|
+
}
|
|
2178
|
+
}));
|
|
2179
|
+
return usage;
|
|
2180
|
+
}
|
|
2181
|
+
async function getCachedMeta(f) {
|
|
2182
|
+
const cached = metaCache.get(f.fullPath);
|
|
2183
|
+
if (cached && cached.mtimeMs === f.mtimeMs && cached.sizeBytes === f.sizeBytes) {
|
|
2184
|
+
return cached.meta;
|
|
2185
|
+
}
|
|
2186
|
+
try {
|
|
2187
|
+
const rawLines = await readJsonlFile(f.fullPath);
|
|
2188
|
+
const { meta } = parseTranscript(rawLines);
|
|
2189
|
+
const sessionId = sessionIdFromFileName(f.fileName);
|
|
2190
|
+
const projectDirPath = path.dirname(f.fullPath);
|
|
2191
|
+
meta.totalUsage = await computeSessionUsageWithSubagents(rawLines, projectDirPath, sessionId);
|
|
2192
|
+
const full = {
|
|
2193
|
+
...meta,
|
|
2194
|
+
id: sessionId,
|
|
2195
|
+
filePath: f.fullPath,
|
|
2196
|
+
projectDir: f.projectDir,
|
|
2197
|
+
projectName: meta.cwd ?? decodeProjectName(f.projectDir)
|
|
2198
|
+
};
|
|
2199
|
+
metaCache.set(f.fullPath, { meta: full, mtimeMs: f.mtimeMs, sizeBytes: f.sizeBytes });
|
|
2200
|
+
return full;
|
|
2201
|
+
} catch {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
async function getCachedDetail(f) {
|
|
2206
|
+
const cached = detailCache.get(f.fullPath);
|
|
2207
|
+
if (cached && cached.mtimeMs === f.mtimeMs && cached.sizeBytes === f.sizeBytes) {
|
|
2208
|
+
return cached.detail;
|
|
2209
|
+
}
|
|
2210
|
+
try {
|
|
2211
|
+
const rawLines = await readJsonlFile(f.fullPath);
|
|
2212
|
+
const { meta, events } = parseTranscript(rawLines);
|
|
2213
|
+
const detail = {
|
|
2214
|
+
...meta,
|
|
2215
|
+
id: sessionIdFromFileName(f.fileName),
|
|
2216
|
+
filePath: f.fullPath,
|
|
2217
|
+
projectDir: f.projectDir,
|
|
2218
|
+
projectName: meta.cwd ?? decodeProjectName(f.projectDir),
|
|
2219
|
+
events
|
|
2220
|
+
};
|
|
2221
|
+
detailCache.set(f.fullPath, { detail, mtimeMs: f.mtimeMs, sizeBytes: f.sizeBytes });
|
|
2222
|
+
const metaOnly = { ...detail };
|
|
2223
|
+
delete metaOnly.events;
|
|
2224
|
+
metaCache.set(f.fullPath, { meta: metaOnly, mtimeMs: f.mtimeMs, sizeBytes: f.sizeBytes });
|
|
2225
|
+
return detail;
|
|
2226
|
+
} catch {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
async function listSessions(opts = {}) {
|
|
2231
|
+
const { root = DEFAULT_ROOT, limit = 500, projectDir } = opts;
|
|
2232
|
+
let files = await walkJsonlFiles(root);
|
|
2233
|
+
if (projectDir)
|
|
2234
|
+
files = files.filter((f) => f.projectDir === projectDir);
|
|
2235
|
+
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2236
|
+
const sliced = files.slice(0, limit);
|
|
2237
|
+
const metas = await Promise.all(sliced.map((f) => getCachedMeta(f)));
|
|
2238
|
+
return metas.filter((m) => m !== null);
|
|
2239
|
+
}
|
|
2240
|
+
async function getSession(id, opts = {}) {
|
|
2241
|
+
const { root = DEFAULT_ROOT } = opts;
|
|
2242
|
+
const files = await walkJsonlFiles(root);
|
|
2243
|
+
const hit = files.find((f) => sessionIdFromFileName(f.fileName) === id);
|
|
2244
|
+
if (!hit)
|
|
2245
|
+
return null;
|
|
2246
|
+
const detail = await getCachedDetail(hit);
|
|
2247
|
+
if (!detail)
|
|
2248
|
+
return detail;
|
|
2249
|
+
const sessionStartMs = detail.firstTimestamp ? Date.parse(detail.firstTimestamp) : void 0;
|
|
2250
|
+
const subagentsDir = path.join(path.dirname(hit.fullPath), sessionIdFromFileName(hit.fileName), "subagents");
|
|
2251
|
+
const subagents = await loadSubagents(subagentsDir, sessionStartMs, detail.events);
|
|
2252
|
+
return { ...detail, subagents };
|
|
2253
|
+
}
|
|
2254
|
+
function summarizeSubagentLines(lines) {
|
|
2255
|
+
let startMs;
|
|
2256
|
+
let endMs;
|
|
2257
|
+
const totalUsage = { ...BLANK_USAGE3 };
|
|
2258
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
2259
|
+
let finalText;
|
|
2260
|
+
let model;
|
|
2261
|
+
let eventCount = 0;
|
|
2262
|
+
let assistantMessageCount = 0;
|
|
2263
|
+
let toolCallCount = 0;
|
|
2264
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
2265
|
+
let initialPrompt;
|
|
2266
|
+
for (const raw of lines) {
|
|
2267
|
+
if (!raw || typeof raw !== "object")
|
|
2268
|
+
continue;
|
|
2269
|
+
const r = raw;
|
|
2270
|
+
eventCount++;
|
|
2271
|
+
const ts = typeof r.timestamp === "string" ? Date.parse(r.timestamp) : NaN;
|
|
2272
|
+
if (!Number.isNaN(ts)) {
|
|
2273
|
+
if (startMs === void 0 || ts < startMs)
|
|
2274
|
+
startMs = ts;
|
|
2275
|
+
if (endMs === void 0 || ts > endMs)
|
|
2276
|
+
endMs = ts;
|
|
2277
|
+
}
|
|
2278
|
+
if (initialPrompt === void 0 && r.type === "user" && (r.parentUuid === null || r.parentUuid === void 0)) {
|
|
2279
|
+
const msg = r.message;
|
|
2280
|
+
const content = msg?.content;
|
|
2281
|
+
if (typeof content === "string") {
|
|
2282
|
+
initialPrompt = content;
|
|
2283
|
+
} else if (Array.isArray(content)) {
|
|
2284
|
+
const txt = content.find((b) => b && typeof b === "object" && b.type === "text");
|
|
2285
|
+
if (txt?.text)
|
|
2286
|
+
initialPrompt = txt.text;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (r.type === "assistant") {
|
|
2290
|
+
const m = r.message;
|
|
2291
|
+
if (!m)
|
|
2292
|
+
continue;
|
|
2293
|
+
if (!model && typeof m.model === "string")
|
|
2294
|
+
model = m.model;
|
|
2295
|
+
const mid = typeof m.id === "string" ? m.id : void 0;
|
|
2296
|
+
const rid = typeof r.requestId === "string" ? r.requestId : void 0;
|
|
2297
|
+
const dedupKey = mid != null && rid != null ? `${mid}:${rid}` : void 0;
|
|
2298
|
+
const fresh = dedupKey ? !seenMessageIds.has(dedupKey) : true;
|
|
2299
|
+
if (dedupKey && fresh)
|
|
2300
|
+
seenMessageIds.add(dedupKey);
|
|
2301
|
+
if (fresh) {
|
|
2302
|
+
assistantMessageCount++;
|
|
2303
|
+
const u = m.usage;
|
|
2304
|
+
if (u) {
|
|
2305
|
+
const toNum = (v) => typeof v === "number" ? v : 0;
|
|
2306
|
+
totalUsage.input += toNum(u.input_tokens);
|
|
2307
|
+
totalUsage.output += toNum(u.output_tokens);
|
|
2308
|
+
totalUsage.cacheRead += toNum(u.cache_read_input_tokens);
|
|
2309
|
+
totalUsage.cacheWrite += toNum(u.cache_creation_input_tokens);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
const content = m.content;
|
|
2313
|
+
if (Array.isArray(content)) {
|
|
2314
|
+
for (const block of content) {
|
|
2315
|
+
if (!block || typeof block !== "object")
|
|
2316
|
+
continue;
|
|
2317
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
2318
|
+
finalText = block.text;
|
|
2319
|
+
} else if (block.type === "tool_use" && typeof block.name === "string") {
|
|
2320
|
+
toolCallCount++;
|
|
2321
|
+
toolCounts.set(block.name, (toolCounts.get(block.name) ?? 0) + 1);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
const toolCalls = Array.from(toolCounts.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
|
|
2328
|
+
const finalPreview = finalText !== void 0 ? finalText.replace(/\s+/g, " ").trim().slice(0, 240) : void 0;
|
|
2329
|
+
return {
|
|
2330
|
+
startMs,
|
|
2331
|
+
endMs,
|
|
2332
|
+
totalUsage,
|
|
2333
|
+
eventCount,
|
|
2334
|
+
finalPreview,
|
|
2335
|
+
finalText,
|
|
2336
|
+
model,
|
|
2337
|
+
toolCalls,
|
|
2338
|
+
toolCallCount,
|
|
2339
|
+
assistantMessageCount,
|
|
2340
|
+
initialPrompt
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
async function loadSubagents(dir, sessionStartMs, parentEvents) {
|
|
2344
|
+
let entries;
|
|
2345
|
+
try {
|
|
2346
|
+
entries = await fs.readdir(dir);
|
|
2347
|
+
} catch {
|
|
2348
|
+
return [];
|
|
2349
|
+
}
|
|
2350
|
+
const byDesc = /* @__PURE__ */ new Map();
|
|
2351
|
+
const dispatchesByTs = [];
|
|
2352
|
+
for (const e of parentEvents) {
|
|
2353
|
+
if (e.role !== "tool-call" || e.toolName !== "Agent")
|
|
2354
|
+
continue;
|
|
2355
|
+
for (const b of e.blocks) {
|
|
2356
|
+
if (b?.type !== "tool_use" || b.name !== "Agent")
|
|
2357
|
+
continue;
|
|
2358
|
+
const input = b.input ?? {};
|
|
2359
|
+
const desc = typeof input.description === "string" ? input.description : void 0;
|
|
2360
|
+
const tsMs = e.timestamp ? Date.parse(e.timestamp) : void 0;
|
|
2361
|
+
const ref = {
|
|
2362
|
+
toolUseId: b.id,
|
|
2363
|
+
parentUuid: e.uuid ?? "",
|
|
2364
|
+
runInBackground: input.run_in_background === true,
|
|
2365
|
+
prompt: typeof input.prompt === "string" ? input.prompt : void 0,
|
|
2366
|
+
tsMs
|
|
2367
|
+
};
|
|
2368
|
+
if (desc)
|
|
2369
|
+
byDesc.set(desc, ref);
|
|
2370
|
+
if (tsMs !== void 0)
|
|
2371
|
+
dispatchesByTs.push(ref);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
dispatchesByTs.sort((a, b) => (a.tsMs ?? 0) - (b.tsMs ?? 0));
|
|
2375
|
+
function matchByTime(startMs) {
|
|
2376
|
+
if (startMs === void 0)
|
|
2377
|
+
return void 0;
|
|
2378
|
+
const TOLERANCE_MS = 2e3;
|
|
2379
|
+
let best;
|
|
2380
|
+
let bestDelta = Infinity;
|
|
2381
|
+
for (const ref of dispatchesByTs) {
|
|
2382
|
+
if (ref.tsMs === void 0)
|
|
2383
|
+
continue;
|
|
2384
|
+
const delta = Math.abs(startMs - ref.tsMs);
|
|
2385
|
+
if (delta < bestDelta && delta <= TOLERANCE_MS) {
|
|
2386
|
+
best = ref;
|
|
2387
|
+
bestDelta = delta;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return best;
|
|
2391
|
+
}
|
|
2392
|
+
const jsonlFiles = entries.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
2393
|
+
const runs = await Promise.all(jsonlFiles.map(async (f) => {
|
|
2394
|
+
const agentId = f.replace(/^agent-/, "").replace(/\.jsonl$/, "");
|
|
2395
|
+
const jsonlPath = path.join(dir, f);
|
|
2396
|
+
const metaPath = path.join(dir, `agent-${agentId}.meta.json`);
|
|
2397
|
+
let meta = {};
|
|
2398
|
+
try {
|
|
2399
|
+
const raw = await fs.readFile(metaPath, "utf8");
|
|
2400
|
+
meta = JSON.parse(raw);
|
|
2401
|
+
} catch {
|
|
2402
|
+
}
|
|
2403
|
+
let lines;
|
|
2404
|
+
try {
|
|
2405
|
+
lines = await readJsonlFile(jsonlPath);
|
|
2406
|
+
} catch {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
const summary = summarizeSubagentLines(lines);
|
|
2410
|
+
const metaDesc = meta.description && meta.description.trim().length > 0 ? meta.description : void 0;
|
|
2411
|
+
const parentRef = metaDesc ? byDesc.get(metaDesc) : matchByTime(summary.startMs);
|
|
2412
|
+
const description = metaDesc ?? (summary.initialPrompt ? summary.initialPrompt.replace(/\s+/g, " ").trim().slice(0, 80) : "(no description)");
|
|
2413
|
+
const prompt = parentRef?.prompt ?? summary.initialPrompt;
|
|
2414
|
+
const startTOffsetMs = sessionStartMs !== void 0 && summary.startMs !== void 0 ? Math.max(0, summary.startMs - sessionStartMs) : void 0;
|
|
2415
|
+
const endTOffsetMs = sessionStartMs !== void 0 && summary.endMs !== void 0 ? Math.max(0, summary.endMs - sessionStartMs) : void 0;
|
|
2416
|
+
const durationMs = summary.startMs !== void 0 && summary.endMs !== void 0 ? summary.endMs - summary.startMs : void 0;
|
|
2417
|
+
return {
|
|
2418
|
+
agentId,
|
|
2419
|
+
agentType: meta.agentType ?? "unknown",
|
|
2420
|
+
description,
|
|
2421
|
+
startMs: summary.startMs,
|
|
2422
|
+
endMs: summary.endMs,
|
|
2423
|
+
durationMs,
|
|
2424
|
+
startTOffsetMs,
|
|
2425
|
+
endTOffsetMs,
|
|
2426
|
+
eventCount: summary.eventCount,
|
|
2427
|
+
totalUsage: summary.totalUsage,
|
|
2428
|
+
parentUuid: parentRef?.parentUuid,
|
|
2429
|
+
parentToolUseId: parentRef?.toolUseId,
|
|
2430
|
+
runInBackground: parentRef?.runInBackground,
|
|
2431
|
+
prompt,
|
|
2432
|
+
finalPreview: summary.finalPreview,
|
|
2433
|
+
finalText: summary.finalText,
|
|
2434
|
+
model: summary.model,
|
|
2435
|
+
toolCalls: summary.toolCalls,
|
|
2436
|
+
toolCallCount: summary.toolCallCount,
|
|
2437
|
+
assistantMessageCount: summary.assistantMessageCount
|
|
2438
|
+
};
|
|
2439
|
+
}));
|
|
2440
|
+
return runs.filter((r) => r !== null).sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
|
|
2441
|
+
}
|
|
2442
|
+
async function listProjects(root = DEFAULT_ROOT) {
|
|
2443
|
+
const files = await walkJsonlFiles(root);
|
|
2444
|
+
const byRawDir = /* @__PURE__ */ new Map();
|
|
2445
|
+
for (const f of files) {
|
|
2446
|
+
const cur = byRawDir.get(f.projectDir) ?? { count: 0, lastActiveMs: 0 };
|
|
2447
|
+
cur.count++;
|
|
2448
|
+
if (f.mtimeMs > cur.lastActiveMs)
|
|
2449
|
+
cur.lastActiveMs = f.mtimeMs;
|
|
2450
|
+
byRawDir.set(f.projectDir, cur);
|
|
2451
|
+
}
|
|
2452
|
+
const cwdForProject = (dir) => {
|
|
2453
|
+
for (const [, entry] of metaCache.entries()) {
|
|
2454
|
+
if (entry.meta.projectDir === dir && entry.meta.cwd) {
|
|
2455
|
+
return entry.meta.cwd;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return void 0;
|
|
2459
|
+
};
|
|
2460
|
+
const canonicalMap = /* @__PURE__ */ new Map();
|
|
2461
|
+
for (const [rawDir, { count, lastActiveMs }] of byRawDir) {
|
|
2462
|
+
const cwdOrDecoded = cwdForProject(rawDir) ?? decodeProjectName(rawDir);
|
|
2463
|
+
const canonical = canonicalProjectName(cwdOrDecoded);
|
|
2464
|
+
const wt = worktreeName(cwdOrDecoded);
|
|
2465
|
+
const agg = canonicalMap.get(canonical) ?? {
|
|
2466
|
+
canonicalName: canonical,
|
|
2467
|
+
count: 0,
|
|
2468
|
+
lastActiveMs: 0,
|
|
2469
|
+
worktreeBranches: /* @__PURE__ */ new Set()
|
|
2470
|
+
};
|
|
2471
|
+
agg.count += count;
|
|
2472
|
+
if (lastActiveMs > agg.lastActiveMs)
|
|
2473
|
+
agg.lastActiveMs = lastActiveMs;
|
|
2474
|
+
if (wt)
|
|
2475
|
+
agg.worktreeBranches.add(wt);
|
|
2476
|
+
canonicalMap.set(canonical, agg);
|
|
2477
|
+
}
|
|
2478
|
+
return Array.from(canonicalMap.values()).map((a) => ({
|
|
2479
|
+
projectDir: a.canonicalName,
|
|
2480
|
+
projectName: a.canonicalName,
|
|
2481
|
+
sessionCount: a.count,
|
|
2482
|
+
lastActiveMs: a.lastActiveMs,
|
|
2483
|
+
worktreeCount: a.worktreeBranches.size
|
|
2484
|
+
})).sort((a, b) => b.lastActiveMs - a.lastActiveMs);
|
|
2485
|
+
}
|
|
2486
|
+
async function loadTeamForSession(sessionId, opts = {}) {
|
|
2487
|
+
const { root = DEFAULT_ROOT } = opts;
|
|
2488
|
+
const all = await listSessions({ root });
|
|
2489
|
+
const self = all.find((s) => s.sessionId === sessionId);
|
|
2490
|
+
if (!self || !self.teamName)
|
|
2491
|
+
return null;
|
|
2492
|
+
const teamName = self.teamName;
|
|
2493
|
+
const candidates = all.filter((s) => s.teamName === teamName && !s.filePath.includes("/subagents/"));
|
|
2494
|
+
const loaded = await Promise.all(candidates.map((c) => getSession(c.sessionId, { root })));
|
|
2495
|
+
const details = /* @__PURE__ */ new Map();
|
|
2496
|
+
candidates.forEach((c, i) => {
|
|
2497
|
+
const d = loaded[i];
|
|
2498
|
+
if (d)
|
|
2499
|
+
details.set(c.sessionId, d);
|
|
2500
|
+
});
|
|
2501
|
+
const views = groupByTeam(candidates, details);
|
|
2502
|
+
const view = views.find((v) => v.teamName === teamName);
|
|
2503
|
+
if (!view)
|
|
2504
|
+
return null;
|
|
2505
|
+
return { view, details };
|
|
2506
|
+
}
|
|
2507
|
+
async function findTeamLead(sessionId, opts = {}) {
|
|
2508
|
+
const { root = DEFAULT_ROOT } = opts;
|
|
2509
|
+
const all = await listSessions({ root });
|
|
2510
|
+
const self = all.find((s) => s.sessionId === sessionId);
|
|
2511
|
+
if (!self || !self.teamName || !self.agentName)
|
|
2512
|
+
return null;
|
|
2513
|
+
if (self.isTeamLead)
|
|
2514
|
+
return null;
|
|
2515
|
+
const candidates = all.filter((s) => s.teamName === self.teamName && s.isTeamLead && !s.filePath.includes("/subagents/"));
|
|
2516
|
+
if (candidates.length === 0)
|
|
2517
|
+
return null;
|
|
2518
|
+
return {
|
|
2519
|
+
leadSessionId: candidates[0].sessionId,
|
|
2520
|
+
teamName: self.teamName,
|
|
2521
|
+
agentName: self.agentName
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
async function loadUsageByDay(start, end) {
|
|
2525
|
+
let raw;
|
|
2526
|
+
try {
|
|
2527
|
+
raw = await fs.readFile(USAGE_LOG, "utf8");
|
|
2528
|
+
} catch {
|
|
2529
|
+
return { by_day: [] };
|
|
2530
|
+
}
|
|
2531
|
+
const startMs = new Date(start.getFullYear(), start.getMonth(), start.getDate()).getTime();
|
|
2532
|
+
const endMs = new Date(end.getFullYear(), end.getMonth(), end.getDate(), 23, 59, 59, 999).getTime();
|
|
2533
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
2534
|
+
for (const line of raw.split("\n")) {
|
|
2535
|
+
const trimmed = line.trim();
|
|
2536
|
+
if (!trimmed)
|
|
2537
|
+
continue;
|
|
2538
|
+
try {
|
|
2539
|
+
const snap = JSON.parse(trimmed);
|
|
2540
|
+
if (!snap.captured_at)
|
|
2541
|
+
continue;
|
|
2542
|
+
const ms = Date.parse(snap.captured_at);
|
|
2543
|
+
if (Number.isNaN(ms))
|
|
2544
|
+
continue;
|
|
2545
|
+
if (ms < startMs)
|
|
2546
|
+
continue;
|
|
2547
|
+
if (ms > endMs)
|
|
2548
|
+
break;
|
|
2549
|
+
const key = toLocalDay(ms);
|
|
2550
|
+
const util = snap.five_hour?.utilization ?? 0;
|
|
2551
|
+
const cur2 = byDay.get(key) ?? 0;
|
|
2552
|
+
if (util > cur2)
|
|
2553
|
+
byDay.set(key, util);
|
|
2554
|
+
} catch {
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
const out = [];
|
|
2558
|
+
const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
|
|
2559
|
+
const stop = new Date(end.getFullYear(), end.getMonth(), end.getDate());
|
|
2560
|
+
while (cur.getTime() <= stop.getTime()) {
|
|
2561
|
+
const k = toLocalDay(cur.getTime());
|
|
2562
|
+
out.push({ date: k, peak_util_pct: byDay.get(k) ?? 0 });
|
|
2563
|
+
cur.setDate(cur.getDate() + 1);
|
|
2564
|
+
}
|
|
2565
|
+
return { by_day: out };
|
|
2566
|
+
}
|
|
2567
|
+
var DEFAULT_ROOT, metaCache, detailCache, fileListCache, FILE_LIST_TTL_MS, BLANK_USAGE3, USAGE_LOG;
|
|
2568
|
+
var init_fs = __esm({
|
|
2569
|
+
"../parser/dist/fs.js"() {
|
|
2570
|
+
"use strict";
|
|
2571
|
+
init_parser();
|
|
2572
|
+
init_analytics();
|
|
2573
|
+
init_team();
|
|
2574
|
+
DEFAULT_ROOT = path.join(os.homedir(), ".claude", "projects");
|
|
2575
|
+
metaCache = /* @__PURE__ */ new Map();
|
|
2576
|
+
detailCache = /* @__PURE__ */ new Map();
|
|
2577
|
+
fileListCache = null;
|
|
2578
|
+
FILE_LIST_TTL_MS = 1e3;
|
|
2579
|
+
BLANK_USAGE3 = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2580
|
+
USAGE_LOG = path.join(os.homedir(), ".cclens", "usage.jsonl");
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
|
|
1
2584
|
// src/daemon-worker.ts
|
|
2
|
-
import { appendFileSync as
|
|
3
|
-
import { dirname as
|
|
4
|
-
import { homedir as
|
|
2585
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
|
|
2586
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
2587
|
+
import { homedir as homedir4 } from "node:os";
|
|
5
2588
|
|
|
6
2589
|
// src/usage/token.ts
|
|
7
2590
|
import { execFileSync } from "node:child_process";
|
|
@@ -31,9 +2614,9 @@ function readFromCredentialsFile() {
|
|
|
31
2614
|
join(homedir(), ".claude", ".credentials.json"),
|
|
32
2615
|
join(homedir(), ".config", "claude", "credentials.json")
|
|
33
2616
|
];
|
|
34
|
-
for (const
|
|
2617
|
+
for (const path2 of candidates) {
|
|
35
2618
|
try {
|
|
36
|
-
const blob = readFileSync(
|
|
2619
|
+
const blob = readFileSync(path2, "utf8");
|
|
37
2620
|
const creds = extractCredentials(blob);
|
|
38
2621
|
if (creds) return creds;
|
|
39
2622
|
} catch {
|
|
@@ -156,17 +2739,187 @@ function nextIntervalMs(current, outcome) {
|
|
|
156
2739
|
return Math.min(Math.max(current, BASE_INTERVAL_MS) * 2, MAX_INTERVAL_MS);
|
|
157
2740
|
}
|
|
158
2741
|
|
|
2742
|
+
// src/team/config.ts
|
|
2743
|
+
import { readFileSync as readFileSync3, writeFileSync, unlinkSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2744
|
+
import { join as join2 } from "node:path";
|
|
2745
|
+
import { homedir as homedir2 } from "node:os";
|
|
2746
|
+
var DEFAULT_DIR = join2(homedir2(), ".cclens");
|
|
2747
|
+
var CONFIG_FILE = "team.json";
|
|
2748
|
+
function readTeamConfig(dir = DEFAULT_DIR) {
|
|
2749
|
+
try {
|
|
2750
|
+
return JSON.parse(readFileSync3(join2(dir, CONFIG_FILE), "utf8"));
|
|
2751
|
+
} catch {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function writeTeamConfig(config, dir = DEFAULT_DIR) {
|
|
2756
|
+
mkdirSync2(dir, { recursive: true });
|
|
2757
|
+
writeFileSync(join2(dir, CONFIG_FILE), JSON.stringify(config, null, 2), { mode: 384 });
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/team/push.ts
|
|
2761
|
+
init_dist();
|
|
2762
|
+
import { randomUUID } from "node:crypto";
|
|
2763
|
+
function bucketToRollup(b) {
|
|
2764
|
+
return {
|
|
2765
|
+
day: b.date,
|
|
2766
|
+
agentTimeMs: b.airTimeMs,
|
|
2767
|
+
sessions: b.sessions,
|
|
2768
|
+
toolCalls: b.toolCalls,
|
|
2769
|
+
turns: b.turns,
|
|
2770
|
+
tokens: { ...b.tokens }
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
function buildRollupsForRange(sessions, sinceDay) {
|
|
2774
|
+
const buckets = dailyActivity(sessions);
|
|
2775
|
+
const startCounts = /* @__PURE__ */ new Map();
|
|
2776
|
+
for (const s of sessions) {
|
|
2777
|
+
const d = sessionDay(s);
|
|
2778
|
+
if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
|
|
2779
|
+
}
|
|
2780
|
+
return buckets.filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({
|
|
2781
|
+
...bucketToRollup(b),
|
|
2782
|
+
sessions: startCounts.get(b.date) ?? 0
|
|
2783
|
+
}));
|
|
2784
|
+
}
|
|
2785
|
+
function buildIngestPayload(rollup) {
|
|
2786
|
+
return {
|
|
2787
|
+
ingestId: randomUUID(),
|
|
2788
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2789
|
+
dailyRollup: rollup
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
async function pushToTeamServer(config, payload) {
|
|
2793
|
+
const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
|
|
2794
|
+
method: "POST",
|
|
2795
|
+
headers: {
|
|
2796
|
+
"Content-Type": "application/json",
|
|
2797
|
+
Authorization: `Bearer ${config.bearerToken}`
|
|
2798
|
+
},
|
|
2799
|
+
body: JSON.stringify(payload)
|
|
2800
|
+
});
|
|
2801
|
+
const body = await res.json().catch(() => null);
|
|
2802
|
+
return { ok: res.ok, status: res.status, body };
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
// src/team/queue.ts
|
|
2806
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, statSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2807
|
+
import { join as join3, dirname as dirname2 } from "node:path";
|
|
2808
|
+
import { homedir as homedir3 } from "node:os";
|
|
2809
|
+
var QUEUE_PATH = join3(homedir3(), ".cclens", "ingest-queue.jsonl");
|
|
2810
|
+
var MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
2811
|
+
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2812
|
+
function enqueuePayload(payload, queuePath = QUEUE_PATH) {
|
|
2813
|
+
mkdirSync3(dirname2(queuePath), { recursive: true });
|
|
2814
|
+
const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2815
|
+
appendFileSync2(queuePath, JSON.stringify(entry) + "\n", "utf8");
|
|
2816
|
+
try {
|
|
2817
|
+
const { size } = statSync(queuePath);
|
|
2818
|
+
if (size > MAX_SIZE_BYTES) {
|
|
2819
|
+
pruneOldest(queuePath);
|
|
2820
|
+
}
|
|
2821
|
+
} catch {
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function dequeuePayloads(queuePath = QUEUE_PATH) {
|
|
2825
|
+
let lines;
|
|
2826
|
+
try {
|
|
2827
|
+
lines = readFileSync4(queuePath, "utf8").trim().split("\n").filter(Boolean);
|
|
2828
|
+
} catch {
|
|
2829
|
+
return [];
|
|
2830
|
+
}
|
|
2831
|
+
const now = Date.now();
|
|
2832
|
+
const valid = [];
|
|
2833
|
+
for (const line of lines) {
|
|
2834
|
+
try {
|
|
2835
|
+
const entry = JSON.parse(line);
|
|
2836
|
+
if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
|
|
2837
|
+
valid.push(entry);
|
|
2838
|
+
}
|
|
2839
|
+
} catch {
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
writeFileSync2(queuePath, "", "utf8");
|
|
2843
|
+
return valid.map((e) => e.payload);
|
|
2844
|
+
}
|
|
2845
|
+
function pruneOldest(queuePath) {
|
|
2846
|
+
let lines;
|
|
2847
|
+
try {
|
|
2848
|
+
lines = readFileSync4(queuePath, "utf8").trim().split("\n").filter(Boolean);
|
|
2849
|
+
} catch {
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
2853
|
+
writeFileSync2(queuePath, keep.join("\n") + "\n", "utf8");
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/team/sync.ts
|
|
2857
|
+
var noopLog = () => {
|
|
2858
|
+
};
|
|
2859
|
+
async function runTeamSync(log2 = noopLog) {
|
|
2860
|
+
const config = readTeamConfig();
|
|
2861
|
+
if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
|
|
2862
|
+
try {
|
|
2863
|
+
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
2864
|
+
const { toLocalDay: toLocalDay2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
2865
|
+
const today = toLocalDay2(Date.now());
|
|
2866
|
+
const sessions = await listSessions2({ limit: 1e4 });
|
|
2867
|
+
const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
|
|
2868
|
+
if (rollups.length === 0) {
|
|
2869
|
+
log2("info", "team push: nothing to sync");
|
|
2870
|
+
return { paired: true, pushed: 0, queued: 0, queuedDrained: 0 };
|
|
2871
|
+
}
|
|
2872
|
+
let pushed = 0;
|
|
2873
|
+
let queued = 0;
|
|
2874
|
+
let failedDay;
|
|
2875
|
+
for (const rollup of rollups) {
|
|
2876
|
+
const payload = buildIngestPayload(rollup);
|
|
2877
|
+
const result = await pushToTeamServer(config, payload);
|
|
2878
|
+
if (!result.ok) {
|
|
2879
|
+
log2("warn", `team push failed on ${rollup.day} (${result.status}); queueing`);
|
|
2880
|
+
enqueuePayload(payload);
|
|
2881
|
+
queued++;
|
|
2882
|
+
failedDay = rollup.day;
|
|
2883
|
+
break;
|
|
2884
|
+
}
|
|
2885
|
+
pushed++;
|
|
2886
|
+
}
|
|
2887
|
+
const nextConfig = { ...config, lastSyncedDay: today };
|
|
2888
|
+
if (pushed > 0 || !failedDay) writeTeamConfig(nextConfig);
|
|
2889
|
+
let queuedDrained = 0;
|
|
2890
|
+
if (!failedDay) {
|
|
2891
|
+
const backlog = dequeuePayloads();
|
|
2892
|
+
for (let i = 0; i < backlog.length; i++) {
|
|
2893
|
+
const qResult = await pushToTeamServer(config, backlog[i]);
|
|
2894
|
+
if (!qResult.ok) {
|
|
2895
|
+
for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
|
|
2896
|
+
break;
|
|
2897
|
+
}
|
|
2898
|
+
queuedDrained++;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
if (pushed > 0) {
|
|
2902
|
+
log2("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
|
|
2903
|
+
}
|
|
2904
|
+
return { paired: true, pushed, queued, queuedDrained, failedDay };
|
|
2905
|
+
} catch (err) {
|
|
2906
|
+
const message = err.message;
|
|
2907
|
+
log2("warn", `team push error: ${message}`);
|
|
2908
|
+
return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
|
|
159
2912
|
// src/daemon-worker.ts
|
|
160
|
-
var STATE_DIR =
|
|
161
|
-
var
|
|
162
|
-
var DAEMON_LOG =
|
|
2913
|
+
var STATE_DIR = join4(homedir4(), ".cclens");
|
|
2914
|
+
var USAGE_LOG2 = join4(STATE_DIR, "usage.jsonl");
|
|
2915
|
+
var DAEMON_LOG = join4(STATE_DIR, "daemon.log");
|
|
163
2916
|
var WATCHDOG_INTERVAL_MS = 5 * 1e3;
|
|
164
|
-
|
|
2917
|
+
mkdirSync4(dirname3(USAGE_LOG2), { recursive: true });
|
|
165
2918
|
function log(level, message) {
|
|
166
2919
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${message}
|
|
167
2920
|
`;
|
|
168
2921
|
try {
|
|
169
|
-
|
|
2922
|
+
appendFileSync3(DAEMON_LOG, line, "utf8");
|
|
170
2923
|
} catch {
|
|
171
2924
|
}
|
|
172
2925
|
}
|
|
@@ -176,7 +2929,7 @@ var waitingForRefresh = false;
|
|
|
176
2929
|
async function tick() {
|
|
177
2930
|
try {
|
|
178
2931
|
const snapshot = await fetchUsage();
|
|
179
|
-
appendSnapshot(
|
|
2932
|
+
appendSnapshot(USAGE_LOG2, snapshot);
|
|
180
2933
|
log(
|
|
181
2934
|
"info",
|
|
182
2935
|
`snapshot 5h=${snapshot.five_hour.utilization}% 7d=${snapshot.seven_day.utilization}%`
|
|
@@ -233,6 +2986,7 @@ async function runLoop() {
|
|
|
233
2986
|
const outcome = await tick();
|
|
234
2987
|
scheduleAfter(Date.now(), outcome);
|
|
235
2988
|
}
|
|
2989
|
+
await runTeamSync(log);
|
|
236
2990
|
}
|
|
237
2991
|
await sleep(WATCHDOG_INTERVAL_MS);
|
|
238
2992
|
}
|