fleetlens 0.13.2 → 0.15.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.
Files changed (230) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +4 -1
  3. package/app/apps/web/.next/build-manifest.json +3 -3
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +21 -3
  6. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
  14. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/api/ask/route.js +3 -3
  17. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  18. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +4 -4
  19. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +4 -4
  21. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/digest/month-index/route.js +2 -2
  23. package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
  24. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +4 -4
  25. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
  26. package/app/apps/web/.next/server/app/api/digest/week-index/route.js +2 -2
  27. package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +4 -4
  29. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  31. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  32. package/app/apps/web/.next/server/app/api/settings/route.js +4 -3
  33. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  34. package/app/apps/web/.next/server/app/api/team/autostart/route/app-paths-manifest.json +3 -0
  35. package/app/apps/web/.next/server/app/api/team/autostart/route.js +7 -0
  36. package/app/apps/web/.next/server/app/api/team/autostart/route.js.nft.json +1 -0
  37. package/app/apps/web/.next/server/app/api/team/autostart/route_client-reference-manifest.js +3 -0
  38. package/app/apps/web/.next/server/app/api/team/onboarding/start/route/app-paths-manifest.json +3 -0
  39. package/app/apps/web/.next/server/app/api/team/onboarding/start/route/build-manifest.json +9 -0
  40. package/app/apps/web/.next/server/app/api/team/onboarding/start/route/server-reference-manifest.json +4 -0
  41. package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js +8 -0
  42. package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.map +5 -0
  43. package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.nft.json +1 -0
  44. package/app/apps/web/.next/server/app/api/team/onboarding/start/route_client-reference-manifest.js +3 -0
  45. package/app/apps/web/.next/server/app/api/team/sync-projects/route/app-paths-manifest.json +3 -0
  46. package/app/apps/web/.next/server/app/api/team/sync-projects/route/build-manifest.json +9 -0
  47. package/app/apps/web/.next/server/app/api/team/sync-projects/route/server-reference-manifest.json +4 -0
  48. package/app/apps/web/.next/server/app/api/team/sync-projects/route.js +8 -0
  49. package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.map +5 -0
  50. package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.nft.json +1 -0
  51. package/app/apps/web/.next/server/app/api/team/sync-projects/route_client-reference-manifest.js +3 -0
  52. package/app/apps/web/.next/server/app/changelog/page.js +1 -1
  53. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  54. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  55. package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
  56. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  57. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  58. package/app/apps/web/.next/server/app/day/page.js +1 -1
  59. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  60. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  61. package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
  62. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  63. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next/server/app/digest/page.js +1 -1
  65. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  66. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  67. package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
  68. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  69. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  70. package/app/apps/web/.next/server/app/insights/page.js +1 -1
  71. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  72. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  73. package/app/apps/web/.next/server/app/page.js +2 -2
  74. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  75. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  77. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  78. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  79. package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
  80. package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
  81. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  82. package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -2
  83. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  84. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  85. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  86. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/app/apps/web/.next/server/app/runs/page.js +1 -1
  89. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  90. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  91. package/app/apps/web/.next/server/app/sessions/[id]/page.js +3 -3
  92. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  93. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  94. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  95. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  96. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  97. package/app/apps/web/.next/server/app/settings/page.js +2 -2
  98. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  99. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  100. package/app/apps/web/.next/server/app/team/onboarding/page/app-paths-manifest.json +3 -0
  101. package/app/apps/web/.next/server/app/team/onboarding/page/build-manifest.json +17 -0
  102. package/app/apps/web/.next/server/app/team/onboarding/page/next-font-manifest.json +6 -0
  103. package/app/apps/web/.next/server/app/team/onboarding/page/react-loadable-manifest.json +1 -0
  104. package/app/apps/web/.next/server/app/team/onboarding/page/server-reference-manifest.json +4 -0
  105. package/app/apps/web/.next/server/app/team/onboarding/page.js +19 -0
  106. package/app/apps/web/.next/server/app/team/onboarding/page.js.map +5 -0
  107. package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -0
  108. package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +3 -0
  109. package/app/apps/web/.next/server/app/team/page.js +3 -2
  110. package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
  111. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  112. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  113. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  114. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  115. package/app/apps/web/.next/server/app-paths-manifest.json +4 -1
  116. package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_team_onboarding_start_route_actions_0zeptl_.js +3 -0
  117. package/app/apps/web/.next/server/chunks/0fuv_zod_v4_classic_external_0.z.vap.js +39 -0
  118. package/app/apps/web/.next/server/chunks/[root-of-the-server]__038-.1r._.js +6 -0
  119. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0~utv~v._.js → [root-of-the-server]__03ewxxu._.js} +2 -2
  120. package/app/apps/web/.next/server/chunks/[root-of-the-server]__03rmbz6._.js +3 -0
  121. package/app/apps/web/.next/server/chunks/[root-of-the-server]__065lib8._.js +3 -0
  122. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0e.f-t.._.js → [root-of-the-server]__0a0~7d6._.js} +2 -2
  123. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0gril0u._.js → [root-of-the-server]__0acikq.._.js} +2 -2
  124. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0et2sqb._.js +3 -0
  125. package/app/apps/web/.next/server/chunks/{[externals]__09v47rg._.js → [root-of-the-server]__0jbc7tz._.js} +2 -2
  126. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mq0or2._.js +3 -0
  127. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mvl0qn._.js +3 -0
  128. package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0n79hpm._.js → [root-of-the-server]__0ow1kn1._.js} +4 -4
  129. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~xt99s._.js +3 -0
  130. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_autostart_route_actions_10-4s71.js +3 -0
  131. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_sync-projects_route_actions_0r_2f5n.js +3 -0
  132. package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +13 -0
  133. package/app/apps/web/.next/server/chunks/packages_09wmnv.._.js +3 -0
  134. package/app/apps/web/.next/server/chunks/packages_0o6.gpk._.js +3 -0
  135. package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
  136. package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
  137. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0z7mg0b._.js → [root-of-the-server]__08l86e-._.js} +2 -2
  138. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0a7n44-._.js +3 -0
  139. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0c00i76._.js +3 -0
  140. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0i.q-1h._.js +3 -0
  141. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
  142. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0stu~~r._.js +3 -0
  143. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ufi3.w._.js +3 -0
  144. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0o03ymy._.js → [root-of-the-server]__10~b4bf._.js} +2 -2
  145. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  146. package/app/apps/web/.next/server/chunks/ssr/apps_web_0.97l5.._.js +3 -0
  147. package/app/apps/web/.next/server/chunks/ssr/{apps_web_app_settings_0u6z6rj._.js → apps_web_0q2x4a~._.js} +2 -2
  148. package/app/apps/web/.next/server/chunks/ssr/apps_web_114yo0v._.js +3 -0
  149. package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_team_onboarding_page_actions_0pss~.k.js +3 -0
  150. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0c-tpft._.js +1 -1
  151. package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
  152. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  153. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  154. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_team-config_ts_10egg08._.js +39 -0
  155. package/app/apps/web/.next/server/chunks/ssr/{node_modules_next_dist_0t-hj0x._.js → node_modules_0yrkyef._.js} +2 -2
  156. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +2 -2
  157. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0jxvcfi.js +2 -2
  158. package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0s5yhs4.js +4 -0
  159. package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
  160. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
  161. package/app/apps/web/.next/server/functions-config-manifest.json +3 -1
  162. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  163. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  164. package/app/apps/web/.next/server/pages/500.html +1 -1
  165. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  166. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  167. package/app/apps/web/.next/static/chunks/{0wwcx42ng8i2e.js → 0-3_u.90d79wi.js} +1 -1
  168. package/app/apps/web/.next/static/chunks/00a9oyw~h5i1p.js +1 -0
  169. package/app/apps/web/.next/static/chunks/040s42ypidk7s.css +1 -0
  170. package/app/apps/web/.next/static/chunks/06eiwghjkkbnh.js +1 -0
  171. package/app/apps/web/.next/static/chunks/06qeg4pt5jdqs.js +1 -0
  172. package/app/apps/web/.next/static/chunks/{0e_fzy0n8ij-o.js → 0u6gxpqhl0gc7.js} +1 -1
  173. package/app/apps/web/.next/static/chunks/18cj08-la37o-.js +1 -0
  174. package/app/apps/web/app/api/team/autostart/route.ts +41 -0
  175. package/app/apps/web/app/api/team/onboarding/start/route.ts +96 -0
  176. package/app/apps/web/app/api/team/sync-projects/route.ts +39 -0
  177. package/app/apps/web/app/globals.css +82 -0
  178. package/app/apps/web/app/settings/ai-features-form.tsx +1 -1
  179. package/app/apps/web/app/settings/page.tsx +21 -7
  180. package/app/apps/web/app/{settings → team}/force-sync-button.tsx +2 -4
  181. package/app/apps/web/app/team/onboarding/onboarding-wizard.tsx +503 -0
  182. package/app/apps/web/app/team/onboarding/page.tsx +28 -0
  183. package/app/apps/web/app/team/page.tsx +60 -81
  184. package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx} +6 -23
  185. package/app/apps/web/app/team/synced-projects-form.tsx +201 -0
  186. package/app/apps/web/app/team/synced-projects-section.tsx +37 -0
  187. package/app/apps/web/components/autostart-toggle.tsx +59 -0
  188. package/app/apps/web/components/previous-cycles-trend.tsx +4 -1
  189. package/app/apps/web/components/project-sync-picker.tsx +109 -0
  190. package/app/apps/web/components/team-chip.tsx +1 -1
  191. package/app/apps/web/components/team-welcome-banner.tsx +1 -1
  192. package/app/apps/web/lib/autostart-data.ts +31 -0
  193. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  194. package/app/apps/web/lib/sync-projects-data.ts +36 -0
  195. package/app/apps/web/lib/team-config.ts +21 -4
  196. package/app/apps/web/package.json +1 -1
  197. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  198. package/dist/daemon-worker.js +2089 -1622
  199. package/dist/index.js +1462 -230
  200. package/package.json +1 -1
  201. package/app/apps/web/.next/server/app/api/team-config/route/app-paths-manifest.json +0 -3
  202. package/app/apps/web/.next/server/app/api/team-config/route.js +0 -8
  203. package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +0 -1
  204. package/app/apps/web/.next/server/app/api/team-config/route_client-reference-manifest.js +0 -3
  205. package/app/apps/web/.next/server/chunks/[root-of-the-server]__08agijy._.js +0 -3
  206. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0atn.dt._.js +0 -3
  207. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b.b7~1._.js +0 -3
  208. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0i06ito._.js +0 -3
  209. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0y5p73y._.js +0 -3
  210. package/app/apps/web/.next/server/chunks/_0euqdko._.js +0 -49
  211. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team-config_route_actions_0ad2gak.js +0 -3
  212. package/app/apps/web/.next/server/chunks/packages_0c-sz9n._.js +0 -3
  213. package/app/apps/web/.next/server/chunks/packages_0wywuu.._.js +0 -3
  214. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09yxy53._.js +0 -3
  215. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +0 -3
  216. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +0 -3
  217. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0q9qy0z._.js +0 -3
  218. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_team_team-sync-form_tsx_0choz0h._.js +0 -3
  219. package/app/apps/web/.next/static/chunks/083~hzkv577.5.css +0 -1
  220. package/app/apps/web/.next/static/chunks/0h.ua.-myj.6z.js +0 -1
  221. package/app/apps/web/.next/static/chunks/0uyctl-m8iydb.js +0 -1
  222. package/app/apps/web/.next/static/chunks/14joqbsr9ihhu.js +0 -1
  223. package/app/apps/web/app/api/team-config/route.ts +0 -39
  224. package/app/apps/web/app/team/team-sync-form.tsx +0 -144
  225. /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/build-manifest.json +0 -0
  226. /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route/server-reference-manifest.json +0 -0
  227. /package/app/apps/web/.next/server/app/api/{team-config → team/autostart}/route.js.map +0 -0
  228. /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_buildManifest.js +0 -0
  229. /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_clientMiddlewareManifest.js +0 -0
  230. /package/app/apps/web/.next/static/{bZs5UyzsYBqC6MFY5n-M_ → C4y6rare_ZCcXXQLc8ysA}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -4002,13 +4002,138 @@ async function readJsonl2(filePath) {
4002
4002
  }
4003
4003
  return out;
4004
4004
  }
4005
+ function dedupeReplayedLines(lines) {
4006
+ const keepAt = /* @__PURE__ */ new Map();
4007
+ lines.forEach((line, i) => {
4008
+ const rec = line;
4009
+ if (typeof rec.uuid !== "string")
4010
+ return;
4011
+ const prev = keepAt.get(rec.uuid);
4012
+ if (prev === void 0) {
4013
+ keepAt.set(rec.uuid, i);
4014
+ return;
4015
+ }
4016
+ const prevHasTs = lines[prev].timestamp !== void 0;
4017
+ if (!prevHasTs && rec.timestamp !== void 0)
4018
+ keepAt.set(rec.uuid, i);
4019
+ });
4020
+ return lines.filter((line, i) => {
4021
+ const rec = line;
4022
+ return typeof rec.uuid !== "string" || keepAt.get(rec.uuid) === i;
4023
+ });
4024
+ }
4025
+ function textFromContent(content) {
4026
+ if (typeof content === "string")
4027
+ return content;
4028
+ if (Array.isArray(content)) {
4029
+ const parts = [];
4030
+ for (const b of content) {
4031
+ if (b && typeof b === "object" && b.type === "text") {
4032
+ const t = b.text;
4033
+ if (typeof t === "string")
4034
+ parts.push(t);
4035
+ }
4036
+ }
4037
+ if (parts.length)
4038
+ return parts.join("\n");
4039
+ }
4040
+ return void 0;
4041
+ }
4042
+ function partitionCoworkLines(lines) {
4043
+ const mainLines = [];
4044
+ const subagentGroups = /* @__PURE__ */ new Map();
4045
+ for (const line of lines) {
4046
+ const ptid = line.parent_tool_use_id;
4047
+ if (typeof ptid === "string") {
4048
+ const g = subagentGroups.get(ptid);
4049
+ if (g)
4050
+ g.push(line);
4051
+ else
4052
+ subagentGroups.set(ptid, [line]);
4053
+ } else {
4054
+ mainLines.push(line);
4055
+ }
4056
+ }
4057
+ return { mainLines, subagentGroups };
4058
+ }
4059
+ function buildCoworkSubagents(subagentGroups, allLines, sessionStartMs) {
4060
+ if (subagentGroups.size === 0)
4061
+ return [];
4062
+ const refs = /* @__PURE__ */ new Map();
4063
+ for (const line of allLines) {
4064
+ const rec = line;
4065
+ const content = rec.message?.content;
4066
+ if (!Array.isArray(content))
4067
+ continue;
4068
+ for (const b of content) {
4069
+ if (!b || typeof b !== "object")
4070
+ continue;
4071
+ const block = b;
4072
+ if (block.type === "tool_use" && block.name === "Agent" && typeof block.id === "string") {
4073
+ const input = block.input ?? {};
4074
+ const ref = refs.get(block.id) ?? {};
4075
+ if (typeof rec.uuid === "string")
4076
+ ref.parentUuid = rec.uuid;
4077
+ if (typeof input.description === "string")
4078
+ ref.description = input.description;
4079
+ if (typeof input.subagent_type === "string")
4080
+ ref.agentType = input.subagent_type;
4081
+ if (typeof input.prompt === "string")
4082
+ ref.prompt = input.prompt;
4083
+ refs.set(block.id, ref);
4084
+ } else if (block.type === "tool_result" && typeof block.tool_use_id === "string" && subagentGroups.has(block.tool_use_id)) {
4085
+ const ref = refs.get(block.tool_use_id) ?? {};
4086
+ ref.finalText = textFromContent(block.content) ?? ref.finalText;
4087
+ refs.set(block.tool_use_id, ref);
4088
+ }
4089
+ }
4090
+ }
4091
+ const runs2 = [];
4092
+ for (const [toolUseId, groupLines] of subagentGroups) {
4093
+ const summary = summarizeSubagentLines(groupLines);
4094
+ const ref = refs.get(toolUseId) ?? {};
4095
+ const finalText = ref.finalText ?? summary.finalText;
4096
+ const finalPreview = finalText ? finalText.replace(/\s+/g, " ").trim().slice(0, 240) : summary.finalPreview;
4097
+ const startTOffsetMs = sessionStartMs !== void 0 && summary.startMs !== void 0 ? Math.max(0, summary.startMs - sessionStartMs) : void 0;
4098
+ const endTOffsetMs = sessionStartMs !== void 0 && summary.endMs !== void 0 ? Math.max(0, summary.endMs - sessionStartMs) : void 0;
4099
+ const durationMs = summary.startMs !== void 0 && summary.endMs !== void 0 ? summary.endMs - summary.startMs : void 0;
4100
+ runs2.push({
4101
+ agentId: toolUseId,
4102
+ agentType: ref.agentType ?? "unknown",
4103
+ description: ref.description ?? (ref.prompt ? ref.prompt.replace(/\s+/g, " ").trim().slice(0, 80) : "(no description)"),
4104
+ startMs: summary.startMs,
4105
+ endMs: summary.endMs,
4106
+ durationMs,
4107
+ startTOffsetMs,
4108
+ endTOffsetMs,
4109
+ eventCount: summary.eventCount,
4110
+ totalUsage: summary.totalUsage,
4111
+ parentUuid: ref.parentUuid,
4112
+ parentToolUseId: toolUseId,
4113
+ runInBackground: false,
4114
+ finalPreview,
4115
+ finalText,
4116
+ prompt: ref.prompt,
4117
+ model: summary.model,
4118
+ toolCalls: summary.toolCalls,
4119
+ toolCallCount: summary.toolCallCount,
4120
+ assistantMessageCount: summary.assistantMessageCount
4121
+ });
4122
+ }
4123
+ runs2.sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
4124
+ return runs2;
4125
+ }
4005
4126
  async function parseCoworkSession(file) {
4006
4127
  const [lines, coworkMeta, spaceIdToPath] = await Promise.all([
4007
4128
  readJsonl2(file.auditPath),
4008
4129
  readCoworkMeta(file.metaPath),
4009
4130
  loadSpacesForWorkspace(file.workspaceDir)
4010
4131
  ]);
4011
- const { meta: baseMeta, events } = parseTranscript(lines);
4132
+ const deduped = dedupeReplayedLines(lines);
4133
+ const { mainLines, subagentGroups } = partitionCoworkLines(deduped);
4134
+ const { meta: baseMeta, events } = parseTranscript(mainLines);
4135
+ const sessionStartMs = baseMeta.firstTimestamp ? Date.parse(baseMeta.firstTimestamp) : void 0;
4136
+ const subagents = buildCoworkSubagents(subagentGroups, deduped, sessionStartMs);
4012
4137
  const { projectName, projectDir, cwd } = resolveProject(coworkMeta, spaceIdToPath);
4013
4138
  const meta = {
4014
4139
  ...baseMeta,
@@ -4021,7 +4146,7 @@ async function parseCoworkSession(file) {
4021
4146
  cwd: cwd ?? baseMeta.cwd,
4022
4147
  model: baseMeta.model ?? coworkMeta.model
4023
4148
  };
4024
- return { meta, events };
4149
+ return { meta, events, subagents };
4025
4150
  }
4026
4151
  function cacheKeyFor(file) {
4027
4152
  return {
@@ -4072,8 +4197,12 @@ async function getCoworkSession(id, opts = {}) {
4072
4197
  const cached = detailCache5.get(file.auditPath);
4073
4198
  if (cached && cacheKeyMatches(cached.key, key))
4074
4199
  return cached.detail;
4075
- const { meta, events } = await parseCoworkSession(file);
4076
- const detail = { ...meta, events };
4200
+ const { meta, events, subagents } = await parseCoworkSession(file);
4201
+ const detail = {
4202
+ ...meta,
4203
+ events,
4204
+ ...subagents.length ? { subagents } : {}
4205
+ };
4077
4206
  detailCache5.set(file.auditPath, { detail, key });
4078
4207
  return detail;
4079
4208
  }
@@ -4082,6 +4211,7 @@ var init_cowork = __esm({
4082
4211
  "../parser/dist/cowork.js"() {
4083
4212
  "use strict";
4084
4213
  init_analytics();
4214
+ init_claude_code();
4085
4215
  init_parser();
4086
4216
  DEFAULT_COWORK_ROOT = defaultCoworkRoot();
4087
4217
  SESSION_ID_RE = /^local_[0-9a-f-]+$/;
@@ -4092,8 +4222,17 @@ var init_cowork = __esm({
4092
4222
  });
4093
4223
 
4094
4224
  // ../parser/dist/team-config.js
4095
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "node:fs";
4225
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2, renameSync } from "node:fs";
4096
4226
  import { join } from "node:path";
4227
+ function shouldSyncProject(repoName, sp) {
4228
+ if (!sp)
4229
+ return true;
4230
+ if (sp.excluded.includes(repoName))
4231
+ return false;
4232
+ if (sp.included.includes(repoName))
4233
+ return true;
4234
+ return sp.autoIncludeNew;
4235
+ }
4097
4236
  function readTeamConfig(dir) {
4098
4237
  const d = dir ?? cclensHome();
4099
4238
  try {
@@ -4105,7 +4244,9 @@ function readTeamConfig(dir) {
4105
4244
  function writeTeamConfig(config, dir) {
4106
4245
  const d = dir ?? cclensHome();
4107
4246
  mkdirSync2(d, { recursive: true });
4108
- writeFileSync2(join(d, CONFIG_FILE), JSON.stringify(config, null, 2), { mode: 384 });
4247
+ const tmp = join(d, `${CONFIG_FILE}.${process.pid}.tmp`);
4248
+ writeFileSync2(tmp, JSON.stringify(config, null, 2), { mode: 384 });
4249
+ renameSync(tmp, join(d, CONFIG_FILE));
4109
4250
  }
4110
4251
  function clearTeamConfig(dir) {
4111
4252
  const d = dir ?? cclensHome();
@@ -4179,6 +4320,7 @@ __export(fs_exports, {
4179
4320
  readJsonlFile: () => readJsonlFile,
4180
4321
  readTeamConfig: () => readTeamConfig,
4181
4322
  sessionIdFromFileName: () => sessionIdFromFileName,
4323
+ shouldSyncProject: () => shouldSyncProject,
4182
4324
  walkJsonlFiles: () => walkJsonlFiles,
4183
4325
  writeTeamConfig: () => writeTeamConfig
4184
4326
  });
@@ -4400,9 +4542,14 @@ function buildCalibrationCurve(events, snapshots, tier = "pro-max-20x", granular
4400
4542
  curve.push({
4401
4543
  ts: new Date(cur).toISOString(),
4402
4544
  real_5h: real5,
4403
- pred_5h: Math.max(0, Math.min(200, p5)),
4545
+ // Predicted utilization LEGITIMATELY exceeds 200 on overage (the
4546
+ // extrapolated cycle close). The old min(200) clamp silently capped it,
4547
+ // so cyclePeaks.peakPct could never carry a *predicted* overage to the
4548
+ // team server (which now accepts up to 10000). Keep only a generous
4549
+ // finite ceiling as a corruption guard, matching the wire cap.
4550
+ pred_5h: Math.max(0, Math.min(1e4, p5)),
4404
4551
  real_7d: real7,
4405
- pred_7d: Math.max(0, Math.min(200, p7)),
4552
+ pred_7d: Math.max(0, Math.min(1e4, p7)),
4406
4553
  cycle_end_5h: r5k != null ? new Date(r5k).toISOString() : null,
4407
4554
  cycle_end_7d: r7k != null ? new Date(r7k).toISOString() : null
4408
4555
  });
@@ -4542,7 +4689,7 @@ function getServerStatus() {
4542
4689
  return { running: false };
4543
4690
  }
4544
4691
  function isServerStale(status2) {
4545
- return status2.running === true && status2.version !== "0.13.2";
4692
+ return status2.running === true && status2.version !== "0.15.0";
4546
4693
  }
4547
4694
  async function ensureCurrentServer(opts = {}) {
4548
4695
  const status2 = getServerStatus();
@@ -4598,8 +4745,8 @@ async function startServer(opts = {}) {
4598
4745
  });
4599
4746
  child.unref();
4600
4747
  const pid = child.pid;
4601
- writePid(PID_FILE, pid, port, "0.13.2");
4602
- await waitForHealth(`http://localhost:${port}`, 1e4);
4748
+ writePid(PID_FILE, pid, port, "0.15.0");
4749
+ await waitForHealth(`http://localhost:${port}`, 3e4);
4603
4750
  return { pid, port };
4604
4751
  }
4605
4752
  function stopServer() {
@@ -4661,6 +4808,13 @@ var init_server = __esm({
4661
4808
  });
4662
4809
 
4663
4810
  // src/usage/storage.ts
4811
+ var storage_exports = {};
4812
+ __export(storage_exports, {
4813
+ appendSnapshot: () => appendSnapshot,
4814
+ latestClaudeCodeSnapshot: () => latestClaudeCodeSnapshot,
4815
+ latestSnapshot: () => latestSnapshot,
4816
+ readSnapshots: () => readSnapshots
4817
+ });
4664
4818
  import { appendFileSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
4665
4819
  import { dirname as dirname3 } from "node:path";
4666
4820
  function appendSnapshot(filePath, snapshot) {
@@ -4958,9 +5112,9 @@ Installed: ${PACKAGE_NAME}@${verify.installedVersion} at ${verify.installedPath
4958
5112
  console.warn(
4959
5113
  ` \u2022 reinstall in the correct Node env: 'npm install -g ${PACKAGE_NAME}@latest'`
4960
5114
  );
4961
- } else if (verify.installedVersion !== "0.13.2") {
5115
+ } else if (verify.installedVersion !== "0.15.0") {
4962
5116
  console.log(
4963
- ` \u2192 This process is still running ${"0.13.2"}. Next invocation will use ${verify.installedVersion}.`
5117
+ ` \u2192 This process is still running ${"0.15.0"}. Next invocation will use ${verify.installedVersion}.`
4964
5118
  );
4965
5119
  }
4966
5120
  }
@@ -4995,14 +5149,14 @@ async function getUpdateAvailability() {
4995
5149
  if (isDevMode()) return null;
4996
5150
  const latest = await fetchLatestVersion();
4997
5151
  if (latest === null) return {
4998
- current: "0.13.2",
5152
+ current: "0.15.0",
4999
5153
  latest: null,
5000
5154
  updateAvailable: false
5001
5155
  };
5002
5156
  return {
5003
- current: "0.13.2",
5157
+ current: "0.15.0",
5004
5158
  latest,
5005
- updateAvailable: shouldUpdate("0.13.2", latest)
5159
+ updateAvailable: shouldUpdate("0.15.0", latest)
5006
5160
  };
5007
5161
  }
5008
5162
  async function checkForUpdate() {
@@ -5092,7 +5246,7 @@ async function restartServices(state) {
5092
5246
  }
5093
5247
  async function forceUpdate() {
5094
5248
  const latest = await fetchLatestVersion();
5095
- const current = "0.13.2";
5249
+ const current = "0.15.0";
5096
5250
  if (latest === null) {
5097
5251
  console.error("Could not reach npm registry. Check your network.");
5098
5252
  process.exit(1);
@@ -5135,10 +5289,13 @@ __export(autostart_exports, {
5135
5289
  autostart: () => autostart,
5136
5290
  buildPlist: () => buildPlist,
5137
5291
  dismissPrompt: () => dismissPrompt,
5292
+ ensureTeamAutostart: () => ensureTeamAutostart,
5293
+ hasOptedOut: () => hasOptedOut,
5138
5294
  installAutostart: () => installAutostart,
5139
5295
  isAutostartInstalled: () => isAutostartInstalled,
5140
5296
  isPromptDismissed: () => isPromptDismissed,
5141
- maybePromptAutostart: () => maybePromptAutostart
5297
+ maybePromptAutostart: () => maybePromptAutostart,
5298
+ recordOptOut: () => recordOptOut
5142
5299
  });
5143
5300
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, rmSync, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
5144
5301
  import { homedir as homedir2 } from "node:os";
@@ -5175,7 +5332,6 @@ function buildPlist(opts) {
5175
5332
  <array>
5176
5333
  <string>${nodePath}</string>
5177
5334
  <string>${scriptPath}</string>
5178
- <string>daemon</string>
5179
5335
  <string>start</string>
5180
5336
  </array>
5181
5337
  <key>RunAtLoad</key>
@@ -5207,9 +5363,10 @@ async function installAutostart() {
5207
5363
  await runLaunchctl(["unload", p]).catch(() => {
5208
5364
  });
5209
5365
  await runLaunchctl(["load", "-w", p]);
5210
- console.log("Autostart installed \u2014 the Fleetlens usage daemon will start at login.");
5366
+ writeFlags({ optedOut: false });
5367
+ console.log("Autostart installed \u2014 Fleetlens (dashboard + usage daemon) will start at login.");
5211
5368
  console.log(` LaunchAgent: ${p}`);
5212
- console.log(` Runs: ${nodePath} ${scriptPath} daemon start`);
5369
+ console.log(` Runs: ${nodePath} ${scriptPath} start`);
5213
5370
  console.log(
5214
5371
  " Note: the Node path above is baked in. If you upgrade or switch Node (e.g. via nvm), re-run `fleetlens autostart install`."
5215
5372
  );
@@ -5221,14 +5378,27 @@ async function uninstallAutostart() {
5221
5378
  return;
5222
5379
  }
5223
5380
  const p = plistPath();
5381
+ recordOptOut();
5224
5382
  if (!existsSync5(p)) {
5225
- console.log("Autostart is not installed \u2014 nothing to remove.");
5383
+ console.log("Autostart is not installed \u2014 nothing to remove. Noted your opt-out; team join won't enable it.");
5226
5384
  return;
5227
5385
  }
5228
5386
  await runLaunchctl(["unload", "-w", p]).catch(() => {
5229
5387
  });
5230
5388
  rmSync(p, { force: true });
5231
5389
  console.log(`Autostart removed (${p}). The daemon will no longer start at login.`);
5390
+ console.log(" Opt-out recorded \u2014 team join/start won't re-enable it.");
5391
+ }
5392
+ async function ensureTeamAutostart() {
5393
+ try {
5394
+ if (!isMac()) return;
5395
+ if (isAutostartInstalled()) return;
5396
+ if (hasOptedOut()) return;
5397
+ console.log(" Team reporting stays on across reboots: enabling Fleetlens auto-start at login (dashboard + daemon).");
5398
+ console.log(" (Opt out anytime: fleetlens autostart uninstall \u2014 join won't re-enable it.)");
5399
+ await installAutostart();
5400
+ } catch {
5401
+ }
5232
5402
  }
5233
5403
  async function statusAutostart() {
5234
5404
  if (!isMac()) {
@@ -5249,22 +5419,33 @@ async function statusAutostart() {
5249
5419
  function promptFlagPath() {
5250
5420
  return cclensPath("autostart.json");
5251
5421
  }
5252
- function isPromptDismissed() {
5422
+ function readFlags() {
5253
5423
  try {
5254
- const j = JSON.parse(readFileSync6(promptFlagPath(), "utf8"));
5255
- return j.promptDismissed === true;
5424
+ return JSON.parse(readFileSync6(promptFlagPath(), "utf8"));
5256
5425
  } catch {
5257
- return false;
5426
+ return {};
5258
5427
  }
5259
5428
  }
5260
- function dismissPrompt() {
5429
+ function writeFlags(patch) {
5261
5430
  const p = promptFlagPath();
5262
5431
  try {
5263
5432
  mkdirSync4(dirname6(p), { recursive: true });
5264
- writeFileSync3(p, JSON.stringify({ promptDismissed: true }, null, 2), "utf8");
5433
+ writeFileSync3(p, JSON.stringify({ ...readFlags(), ...patch }, null, 2), "utf8");
5265
5434
  } catch {
5266
5435
  }
5267
5436
  }
5437
+ function isPromptDismissed() {
5438
+ return readFlags().promptDismissed === true;
5439
+ }
5440
+ function dismissPrompt() {
5441
+ writeFlags({ promptDismissed: true });
5442
+ }
5443
+ function hasOptedOut() {
5444
+ return readFlags().optedOut === true;
5445
+ }
5446
+ function recordOptOut() {
5447
+ writeFlags({ optedOut: true });
5448
+ }
5268
5449
  function promptYesNo(question, defaultYes) {
5269
5450
  return new Promise((resolve3) => {
5270
5451
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -5279,15 +5460,20 @@ function promptYesNo(question, defaultYes) {
5279
5460
  async function maybePromptAutostart(opts) {
5280
5461
  try {
5281
5462
  if (!isMac()) return;
5282
- if (!opts.daemonStarted) return;
5283
5463
  if (process.env.__FLEETLENS_UPDATED) return;
5464
+ const { readTeamConfig: readTeamConfig2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
5465
+ if (readTeamConfig2()) {
5466
+ await ensureTeamAutostart();
5467
+ return;
5468
+ }
5469
+ if (!opts.daemonStarted) return;
5284
5470
  if (!process.stdin.isTTY) return;
5285
5471
  if (isAutostartInstalled()) return;
5286
5472
  if (isPromptDismissed()) return;
5287
5473
  console.log("");
5288
- console.log("Tip: keep the Fleetlens usage daemon running after you restart your Mac?");
5289
- console.log(" This installs a launchd LaunchAgent that runs `fleetlens daemon start`");
5290
- console.log(" at login (daemon only \u2014 not the dashboard server).");
5474
+ console.log("Tip: keep Fleetlens running after you restart your Mac?");
5475
+ console.log(" This installs a launchd LaunchAgent that runs `fleetlens start`");
5476
+ console.log(" at login (dashboard server + usage daemon).");
5291
5477
  const yes = await promptYesNo("Set that up now? [Y/n] ", true);
5292
5478
  if (yes) {
5293
5479
  await installAutostart();
@@ -5450,7 +5636,7 @@ async function status() {
5450
5636
  console.log(`Server: running on ${url} (PID ${server.pid}, v${ver})`);
5451
5637
  if (isServerStale(server)) {
5452
5638
  console.log(
5453
- ` \u26A0 serving stale ${ver} (CLI is ${"0.13.2"}) \u2014 run 'fleetlens start' to restart on the current version`
5639
+ ` \u26A0 serving stale ${ver} (CLI is ${"0.15.0"}) \u2014 run 'fleetlens start' to restart on the current version`
5454
5640
  );
5455
5641
  }
5456
5642
  } else {
@@ -5588,6 +5774,11 @@ var init_token = __esm({
5588
5774
  });
5589
5775
 
5590
5776
  // src/usage/api.ts
5777
+ var api_exports = {};
5778
+ __export(api_exports, {
5779
+ UsageApiError: () => UsageApiError,
5780
+ fetchUsage: () => fetchUsage
5781
+ });
5591
5782
  async function fetchUsage() {
5592
5783
  const creds = readOAuthCredentials();
5593
5784
  if (!creds) {
@@ -6058,8 +6249,42 @@ var init_usage = __esm({
6058
6249
  });
6059
6250
 
6060
6251
  // ../entries/dist/types.js
6061
- function entryKey(sessionId, localDay) {
6062
- return `${sessionId}__${localDay}`;
6252
+ function pendingEnrichment() {
6253
+ return {
6254
+ status: "pending",
6255
+ generated_at: null,
6256
+ model: null,
6257
+ cost_usd: null,
6258
+ error: null,
6259
+ brief_summary: null,
6260
+ underlying_goal: null,
6261
+ friction_detail: null,
6262
+ user_instructions: [],
6263
+ outcome: null,
6264
+ claude_helpfulness: null,
6265
+ goal_categories: {},
6266
+ retry_count: 0
6267
+ };
6268
+ }
6269
+ function skippedTrivialEnrichment(generatedAt) {
6270
+ return {
6271
+ status: "skipped_trivial",
6272
+ generated_at: generatedAt,
6273
+ model: null,
6274
+ cost_usd: null,
6275
+ error: null,
6276
+ brief_summary: null,
6277
+ underlying_goal: null,
6278
+ friction_detail: null,
6279
+ user_instructions: [],
6280
+ outcome: "trivial",
6281
+ claude_helpfulness: null,
6282
+ goal_categories: {},
6283
+ retry_count: 0
6284
+ };
6285
+ }
6286
+ function entryKey(sessionId, localDay2) {
6287
+ return `${sessionId}__${localDay2}`;
6063
6288
  }
6064
6289
  function parseEntryKey(key) {
6065
6290
  const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
@@ -6067,10 +6292,11 @@ function parseEntryKey(key) {
6067
6292
  return null;
6068
6293
  return { session_id: m[1], local_day: m[2] };
6069
6294
  }
6070
- var GOAL_CATEGORIES, CURRENT_DAY_DIGEST_SCHEMA_VERSION, CURRENT_WEEK_DIGEST_SCHEMA_VERSION, CURRENT_MONTH_DIGEST_SCHEMA_VERSION, PROMPT_FRAME_ORIGIN;
6295
+ var CURRENT_ENTRY_SCHEMA_VERSION, GOAL_CATEGORIES, CURRENT_DAY_DIGEST_SCHEMA_VERSION, CURRENT_WEEK_DIGEST_SCHEMA_VERSION, CURRENT_MONTH_DIGEST_SCHEMA_VERSION, PROMPT_FRAME_ORIGIN;
6071
6296
  var init_types = __esm({
6072
6297
  "../entries/dist/types.js"() {
6073
6298
  "use strict";
6299
+ CURRENT_ENTRY_SCHEMA_VERSION = 2;
6074
6300
  GOAL_CATEGORIES = [
6075
6301
  "build",
6076
6302
  "plan",
@@ -6588,6 +6814,35 @@ function classifyUserInputSource(text) {
6588
6814
  return "slash_command";
6589
6815
  return "human";
6590
6816
  }
6817
+ function countSatisfactionSignals(text) {
6818
+ if (!text)
6819
+ return { happy: 0, satisfied: 0, dissatisfied: 0, frustrated: 0 };
6820
+ return {
6821
+ happy: (text.match(HAPPY_RE) ?? []).length,
6822
+ satisfied: (text.match(SATISFIED_RE) ?? []).length,
6823
+ dissatisfied: (text.match(DISSATISFIED_RE) ?? []).length,
6824
+ frustrated: (text.match(FRUSTRATED_RE) ?? []).length
6825
+ };
6826
+ }
6827
+ function extractUserInstructions(text) {
6828
+ if (!text)
6829
+ return [];
6830
+ INSTRUCTION_RE.lastIndex = 0;
6831
+ const out = [];
6832
+ const seen = /* @__PURE__ */ new Set();
6833
+ let m;
6834
+ while ((m = INSTRUCTION_RE.exec(text)) !== null) {
6835
+ const phrase = m[1].trim().replace(/\s+/g, " ");
6836
+ const lc = phrase.toLowerCase();
6837
+ if (seen.has(lc))
6838
+ continue;
6839
+ seen.add(lc);
6840
+ out.push(phrase);
6841
+ if (out.length >= 5)
6842
+ break;
6843
+ }
6844
+ return out;
6845
+ }
6591
6846
  function classifySubagentRole(sa) {
6592
6847
  const text = `${sa.description} ${sa.prompt_preview}`.toLowerCase();
6593
6848
  if (/\b(re-?review|review[s]?|verify|audit|spec[- ]review|code[- ]quality|code[- ]reuse|efficiency review|sanity check|quick test)\b/.test(text)) {
@@ -6725,7 +6980,7 @@ function computeEntrySignals(entry) {
6725
6980
  continuation_kind
6726
6981
  };
6727
6982
  }
6728
- var TEAMMATE_RE, SKILL_LOAD_RE, SLASH_COMMAND_RE2, STOCK_SUBAGENT_PREFIXES, STOCK_SKILL_PREFIXES, EXTERNAL_REF_PATTERNS;
6983
+ var TEAMMATE_RE, SKILL_LOAD_RE, SLASH_COMMAND_RE2, HAPPY_RE, SATISFIED_RE, DISSATISFIED_RE, FRUSTRATED_RE, INSTRUCTION_RE, STOCK_SUBAGENT_PREFIXES, STOCK_SKILL_PREFIXES, EXTERNAL_REF_PATTERNS;
6729
6984
  var init_signals = __esm({
6730
6985
  "../entries/dist/signals.js"() {
6731
6986
  "use strict";
@@ -6733,6 +6988,11 @@ var init_signals = __esm({
6733
6988
  TEAMMATE_RE = /^<teammate-message\b/;
6734
6989
  SKILL_LOAD_RE = /^Base directory for this skill:/;
6735
6990
  SLASH_COMMAND_RE2 = /^<command-name>|^<local-command-stdout>/;
6991
+ HAPPY_RE = /\b(?:yay|yaay|yass|woohoo|nice|great|love(?:ly)?|perfect|amazing|awesome)\s*!|!!!/gi;
6992
+ SATISFIED_RE = /\b(?:thanks|thank you|looks good|lgtm|works|that works|all good|sounds good)\b/gi;
6993
+ DISSATISFIED_RE = /\b(?:that'?s (?:not|wrong)|try again|no, (?:that|this)|not quite|incorrect)\b/gi;
6994
+ FRUSTRATED_RE = /\b(?:broken|stop|why (?:did|are|would) you|give up|ugh|argh|wtf)\b/gi;
6995
+ INSTRUCTION_RE = /\b(?:can you|please|let's|let us|i need|could you|would you)\s+([^.?!\n]{5,200})/gi;
6736
6996
  STOCK_SUBAGENT_PREFIXES = /^(general-purpose|Explore|Plan|claude-code-guide|playwright-qa-verifier|statusline-setup|frontend-design:|code-review:|code-simplifier:|codex:|superpowers:)/;
6737
6997
  STOCK_SKILL_PREFIXES = /^(superpowers|mcp__|frontend-design|code-review|codex|claude-code-guide|claude-api):/i;
6738
6998
  EXTERNAL_REF_PATTERNS = [
@@ -6745,6 +7005,9 @@ var init_signals = __esm({
6745
7005
  });
6746
7006
 
6747
7007
  // ../entries/dist/trivial.js
7008
+ function isTrivial(n) {
7009
+ return n.active_min < 1 && n.turn_count < 3 && n.tools_total === 0;
7010
+ }
6748
7011
  var init_trivial = __esm({
6749
7012
  "../entries/dist/trivial.js"() {
6750
7013
  "use strict";
@@ -6752,7 +7015,521 @@ var init_trivial = __esm({
6752
7015
  });
6753
7016
 
6754
7017
  // ../entries/dist/build.js
6755
- var IDLE_GAP_MS4;
7018
+ function viewEvent(ev) {
7019
+ let rawType;
7020
+ if (ev.role === "user" || ev.role === "tool-result")
7021
+ rawType = "user";
7022
+ else if (ev.role === "agent" || ev.role === "agent-thinking" || ev.role === "tool-call")
7023
+ rawType = "assistant";
7024
+ else if (ev.rawType === "summary")
7025
+ rawType = "summary";
7026
+ else
7027
+ rawType = "system";
7028
+ const rawCwd = ev.raw?.cwd;
7029
+ return {
7030
+ rawType,
7031
+ content: ev.blocks ?? [],
7032
+ model: ev.model,
7033
+ msgId: ev.messageId,
7034
+ usage: ev.usage ? {
7035
+ input_tokens: ev.usage.input,
7036
+ output_tokens: ev.usage.output,
7037
+ cache_read_input_tokens: ev.usage.cacheRead,
7038
+ cache_creation_input_tokens: ev.usage.cacheWrite
7039
+ } : void 0,
7040
+ cwd: rawCwd
7041
+ };
7042
+ }
7043
+ function parseMs(iso) {
7044
+ if (!iso)
7045
+ return void 0;
7046
+ const ms = Date.parse(iso);
7047
+ return Number.isNaN(ms) ? void 0 : ms;
7048
+ }
7049
+ function trunc(s, n) {
7050
+ if (!s)
7051
+ return "";
7052
+ const one = s.replace(/\s+/g, " ").trim();
7053
+ return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
7054
+ }
7055
+ function minimizeImages(s) {
7056
+ if (!s)
7057
+ return s;
7058
+ let counter = 0;
7059
+ return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
7060
+ }
7061
+ function bashVerb(cmd) {
7062
+ if (!cmd)
7063
+ return "?";
7064
+ let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
7065
+ if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
7066
+ const parts = cmd.split(/&&|;/, 3);
7067
+ if (parts.length >= 2)
7068
+ firstSeg = parts[1].trim();
7069
+ }
7070
+ const tokens = firstSeg.split(/\s+/).filter(Boolean);
7071
+ while (tokens.length && SHELL_NOISE.has(tokens[0]))
7072
+ tokens.shift();
7073
+ if (!tokens.length)
7074
+ return "?";
7075
+ let first = tokens[0];
7076
+ if (first.includes("=") && !first.startsWith("/")) {
7077
+ tokens.shift();
7078
+ if (!tokens.length)
7079
+ return "?";
7080
+ first = tokens[0];
7081
+ }
7082
+ if (first.includes("/") && !first.startsWith("./")) {
7083
+ first = first.replace(/\/+$/, "").split("/").pop();
7084
+ }
7085
+ return first.replace(/^["']|["']$/g, "") || "?";
7086
+ }
7087
+ function firstTextBlock(blocks) {
7088
+ if (!Array.isArray(blocks))
7089
+ return "";
7090
+ for (const b of blocks) {
7091
+ if (b && typeof b === "object" && b.type === "text") {
7092
+ return String(b.text ?? "");
7093
+ }
7094
+ }
7095
+ return "";
7096
+ }
7097
+ function blockText(blocks) {
7098
+ if (!blocks)
7099
+ return "";
7100
+ return blocks.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ");
7101
+ }
7102
+ function groupEventsByLocalDay(events) {
7103
+ const byDay = /* @__PURE__ */ new Map();
7104
+ for (const ev of events) {
7105
+ if (!ev.timestamp)
7106
+ continue;
7107
+ const ms = Date.parse(ev.timestamp);
7108
+ if (Number.isNaN(ms))
7109
+ continue;
7110
+ const day2 = toLocalDay(ms);
7111
+ let bucket = byDay.get(day2);
7112
+ if (!bucket) {
7113
+ bucket = [];
7114
+ byDay.set(day2, bucket);
7115
+ }
7116
+ bucket.push(ev);
7117
+ }
7118
+ return byDay;
7119
+ }
7120
+ function newTurn(startMs, userInput = "") {
7121
+ return {
7122
+ startMs,
7123
+ lastMs: startMs,
7124
+ eventTs: startMs !== void 0 ? [startMs] : [],
7125
+ userInput,
7126
+ toolsTotal: 0,
7127
+ toolMix: /* @__PURE__ */ new Map(),
7128
+ bashVerbs: /* @__PURE__ */ new Map(),
7129
+ skills: /* @__PURE__ */ new Map(),
7130
+ subagents: [],
7131
+ taskOps: 0,
7132
+ toolErrors: 0,
7133
+ consecSameToolMax: 0,
7134
+ consec: 0,
7135
+ endedInInterrupt: false,
7136
+ models: /* @__PURE__ */ new Map(),
7137
+ tokensIn: 0,
7138
+ tokensOut: 0,
7139
+ tokensCacheR: 0,
7140
+ tokensCacheW: 0,
7141
+ seenMsgIds: /* @__PURE__ */ new Set()
7142
+ };
7143
+ }
7144
+ function closeTurn(t, closed, cwd) {
7145
+ const ts = [...t.eventTs].sort((a, b) => a - b);
7146
+ let active = 0;
7147
+ for (let i = 1; i < ts.length; i++) {
7148
+ const dt = ts[i] - ts[i - 1];
7149
+ if (dt < IDLE_GAP_MS4)
7150
+ active += dt;
7151
+ }
7152
+ closed.push({
7153
+ startMs: t.startMs ?? 0,
7154
+ activeMs: active,
7155
+ userInput: t.userInput,
7156
+ firstAgentText: t.firstAgentText,
7157
+ lastAgentText: t.lastAgentText,
7158
+ toolsTotal: t.toolsTotal,
7159
+ toolMix: t.toolMix,
7160
+ bashVerbs: t.bashVerbs,
7161
+ skills: t.skills,
7162
+ subagents: t.subagents,
7163
+ taskOps: t.taskOps,
7164
+ toolErrors: t.toolErrors,
7165
+ consecSameToolMax: t.consecSameToolMax,
7166
+ endedInInterrupt: t.endedInInterrupt,
7167
+ models: t.models,
7168
+ totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW,
7169
+ cwd
7170
+ });
7171
+ }
7172
+ function aggregateDay(dayEvents, _sessionFallbackProject) {
7173
+ const prTitles = [];
7174
+ let commits = 0;
7175
+ let pushes = 0;
7176
+ const gitToolUseIds = /* @__PURE__ */ new Set();
7177
+ const githubRepos = /* @__PURE__ */ new Set();
7178
+ const editedDirCounts = /* @__PURE__ */ new Map();
7179
+ const modelTurns = /* @__PURE__ */ new Map();
7180
+ let totalToolErrors = 0;
7181
+ let totalInterrupts = 0;
7182
+ let exitPlanCalls = 0;
7183
+ let taskOpsTotal = 0;
7184
+ let firstUser;
7185
+ let finalAgent;
7186
+ const cwdCounts = /* @__PURE__ */ new Map();
7187
+ const closed = [];
7188
+ let cur;
7189
+ const allTs = [];
7190
+ let lastEventCwd;
7191
+ for (const ev of dayEvents) {
7192
+ const tsMs = parseMs(ev.timestamp);
7193
+ if (tsMs !== void 0)
7194
+ allTs.push(tsMs);
7195
+ const view = viewEvent(ev);
7196
+ const rawType = view.rawType;
7197
+ const msg = { content: view.content, id: view.msgId, model: view.model, usage: view.usage };
7198
+ const content = view.content;
7199
+ const eventCwd = view.cwd;
7200
+ if (eventCwd)
7201
+ lastEventCwd = eventCwd;
7202
+ if (rawType === "user") {
7203
+ const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
7204
+ if (!isToolResultOnly) {
7205
+ const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
7206
+ if (cur) {
7207
+ if (text && INTERRUPT_RE2.test(text)) {
7208
+ cur.endedInInterrupt = true;
7209
+ totalInterrupts++;
7210
+ }
7211
+ closeTurn(cur, closed, eventCwd);
7212
+ }
7213
+ cur = newTurn(tsMs, text);
7214
+ if (!firstUser && text && classifyUserInputSource(text) === "human") {
7215
+ firstUser = text;
7216
+ }
7217
+ } else {
7218
+ if (Array.isArray(content)) {
7219
+ for (const c of content) {
7220
+ if (!c || typeof c !== "object" || c.type !== "tool_result")
7221
+ continue;
7222
+ if (c.is_error) {
7223
+ totalToolErrors++;
7224
+ if (cur)
7225
+ cur.toolErrors++;
7226
+ }
7227
+ const tuId = c.tool_use_id;
7228
+ if (tuId && gitToolUseIds.has(tuId)) {
7229
+ const rc = c.content;
7230
+ const text = typeof rc === "string" ? rc : Array.isArray(rc) ? rc.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n") : "";
7231
+ for (const m of text.matchAll(/(?<![.\w])github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git\b|[/\s"'),]|$)/g)) {
7232
+ if (m[1].toLowerCase() === "user-attachments")
7233
+ continue;
7234
+ githubRepos.add(`${m[1]}/${m[2]}`.toLowerCase());
7235
+ }
7236
+ }
7237
+ }
7238
+ }
7239
+ if (cur && tsMs !== void 0) {
7240
+ cur.eventTs.push(tsMs);
7241
+ cur.lastMs = tsMs;
7242
+ }
7243
+ }
7244
+ } else if (rawType === "assistant") {
7245
+ if (cur && tsMs !== void 0) {
7246
+ cur.eventTs.push(tsMs);
7247
+ cur.lastMs = tsMs;
7248
+ }
7249
+ const model = msg.model;
7250
+ if (model) {
7251
+ modelTurns.set(model, (modelTurns.get(model) ?? 0) + 1);
7252
+ if (cur)
7253
+ cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
7254
+ }
7255
+ const msgId = msg.id;
7256
+ const usage2 = msg.usage;
7257
+ if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage2) {
7258
+ cur.seenMsgIds.add(msgId);
7259
+ cur.tokensIn += usage2.input_tokens ?? 0;
7260
+ cur.tokensOut += usage2.output_tokens ?? 0;
7261
+ cur.tokensCacheR += usage2.cache_read_input_tokens ?? 0;
7262
+ cur.tokensCacheW += usage2.cache_creation_input_tokens ?? 0;
7263
+ }
7264
+ if (Array.isArray(content)) {
7265
+ for (const c of content) {
7266
+ if (!c || typeof c !== "object")
7267
+ continue;
7268
+ const ct = c;
7269
+ if (ct.type === "text" && ct.text) {
7270
+ finalAgent = ct.text;
7271
+ if (cur) {
7272
+ if (cur.firstAgentText === void 0)
7273
+ cur.firstAgentText = ct.text;
7274
+ cur.lastAgentText = ct.text;
7275
+ }
7276
+ } else if (ct.type === "tool_use") {
7277
+ const name = ct.name ?? "unknown";
7278
+ if (cur) {
7279
+ cur.toolsTotal++;
7280
+ cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
7281
+ if (name === cur.lastTool)
7282
+ cur.consec++;
7283
+ else {
7284
+ cur.lastTool = name;
7285
+ cur.consec = 1;
7286
+ }
7287
+ if (cur.consec > cur.consecSameToolMax)
7288
+ cur.consecSameToolMax = cur.consec;
7289
+ if (name === "Skill") {
7290
+ const sk = String(ct.input?.skill ?? "unknown");
7291
+ cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
7292
+ }
7293
+ if (name === "Agent") {
7294
+ const inp = ct.input ?? {};
7295
+ cur.subagents.push({
7296
+ type: String(inp.subagent_type ?? "general-purpose"),
7297
+ description: trunc(String(inp.description ?? ""), 80),
7298
+ prompt_preview: trunc(String(inp.prompt ?? ""), 240),
7299
+ background: Boolean(inp.run_in_background)
7300
+ });
7301
+ }
7302
+ if (name === "ExitPlanMode")
7303
+ exitPlanCalls++;
7304
+ if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
7305
+ cur.taskOps++;
7306
+ taskOpsTotal++;
7307
+ }
7308
+ }
7309
+ if (name === "Bash") {
7310
+ const cmd = String(ct.input?.command ?? "");
7311
+ if (cur)
7312
+ cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
7313
+ if (/\bgh\s+pr\s+create\b/.test(cmd)) {
7314
+ const m = cmd.match(PR_TITLE_RE);
7315
+ prTitles.push(m?.[1] ?? cmd.slice(0, 120));
7316
+ }
7317
+ if (/\bgit\s+commit\b/.test(cmd))
7318
+ commits++;
7319
+ if (/\bgit\s+push\b/.test(cmd))
7320
+ pushes++;
7321
+ if (ct.id && /\bgit\s+push\b|\bgh\s+pr\s+/.test(cmd))
7322
+ gitToolUseIds.add(ct.id);
7323
+ }
7324
+ if (eventCwd && (name === "Bash" || name === "Edit" || name === "Write" || name === "Read")) {
7325
+ cwdCounts.set(eventCwd, (cwdCounts.get(eventCwd) ?? 0) + 1);
7326
+ }
7327
+ if (name === "Edit" || name === "Write" || name === "MultiEdit" || name === "NotebookEdit") {
7328
+ const fp = String(ct.input?.file_path ?? ct.input?.notebook_path ?? "");
7329
+ const slash = fp.lastIndexOf("/");
7330
+ if (slash > 0) {
7331
+ const dir = fp.slice(0, slash);
7332
+ editedDirCounts.set(dir, (editedDirCounts.get(dir) ?? 0) + 1);
7333
+ }
7334
+ }
7335
+ }
7336
+ }
7337
+ }
7338
+ }
7339
+ }
7340
+ if (cur)
7341
+ closeTurn(cur, closed, lastEventCwd);
7342
+ const tsSorted = [...allTs].sort((a, b) => a - b);
7343
+ let activeMs2 = 0;
7344
+ for (let i = 1; i < tsSorted.length; i++) {
7345
+ const dt = tsSorted[i] - tsSorted[i - 1];
7346
+ if (dt < IDLE_GAP_MS4)
7347
+ activeMs2 += dt;
7348
+ }
7349
+ const tsSortedStrings = dayEvents.map((e) => e.timestamp).filter((t) => !!t).sort();
7350
+ const start_iso = tsSortedStrings[0] ?? "";
7351
+ const end_iso = tsSortedStrings[tsSortedStrings.length - 1] ?? "";
7352
+ return {
7353
+ closed,
7354
+ firstUser,
7355
+ finalAgent,
7356
+ prTitles,
7357
+ commits,
7358
+ pushes,
7359
+ githubRepos: [...githubRepos].slice(0, 5),
7360
+ editedDirs: [...editedDirCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([dir]) => dir),
7361
+ modelTurns,
7362
+ totalToolErrors,
7363
+ totalInterrupts,
7364
+ exitPlanCalls,
7365
+ taskOpsTotal,
7366
+ cwdCounts,
7367
+ start_iso,
7368
+ end_iso,
7369
+ activeMs: activeMs2
7370
+ };
7371
+ }
7372
+ function buildTopTools(closed) {
7373
+ const toolMix = /* @__PURE__ */ new Map();
7374
+ const bashVerbsAll = /* @__PURE__ */ new Map();
7375
+ for (const t of closed) {
7376
+ for (const [k, v] of t.toolMix)
7377
+ toolMix.set(k, (toolMix.get(k) ?? 0) + v);
7378
+ for (const [k, v] of t.bashVerbs)
7379
+ bashVerbsAll.set(k, (bashVerbsAll.get(k) ?? 0) + v);
7380
+ }
7381
+ const top = [...toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7382
+ return top.map(([name, count]) => {
7383
+ if (name === "Bash" && bashVerbsAll.size > 0) {
7384
+ const sub = [...bashVerbsAll.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
7385
+ return `Bash\xD7${count} (${sub})`;
7386
+ }
7387
+ return count > 1 ? `${name}\xD7${count}` : name;
7388
+ });
7389
+ }
7390
+ function buildEntries(sessionDetail) {
7391
+ const byDay = groupEventsByLocalDay(sessionDetail.events);
7392
+ const entries2 = [];
7393
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7394
+ const sessionFallbackProject = canonicalProjectName(sessionDetail.projectName ?? "");
7395
+ for (const [local_day, dayEvents] of byDay) {
7396
+ if (dayEvents.length === 0)
7397
+ continue;
7398
+ const agg = aggregateDay(dayEvents, sessionFallbackProject);
7399
+ const { closed } = agg;
7400
+ const turnCount = closed.length;
7401
+ const toolsTotal = closed.reduce((s, t) => s + t.toolsTotal, 0);
7402
+ const tokensTotal = closed.reduce((s, t) => s + t.totalTokens, 0);
7403
+ const subagentCalls = closed.reduce((s, t) => s + t.subagents.length, 0);
7404
+ const skillsMap = /* @__PURE__ */ new Map();
7405
+ for (const t of closed) {
7406
+ for (const [k, v] of t.skills)
7407
+ skillsMap.set(k, (skillsMap.get(k) ?? 0) + v);
7408
+ }
7409
+ const skillCalls = [...skillsMap.values()].reduce((a, b) => a + b, 0);
7410
+ const taskOps = agg.taskOpsTotal;
7411
+ const interrupts = agg.totalInterrupts;
7412
+ const toolErrors = agg.totalToolErrors;
7413
+ const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
7414
+ const exitPlanCalls = agg.exitPlanCalls;
7415
+ const prs = agg.prTitles.length;
7416
+ const commits = agg.commits;
7417
+ const pushes = agg.pushes;
7418
+ const activeMin = Math.round(agg.activeMs / 6e3) / 10;
7419
+ const numbers = {
7420
+ active_min: activeMin,
7421
+ turn_count: turnCount,
7422
+ tools_total: toolsTotal,
7423
+ subagent_calls: subagentCalls,
7424
+ skill_calls: skillCalls,
7425
+ task_ops: taskOps,
7426
+ interrupts,
7427
+ tool_errors: toolErrors,
7428
+ consec_same_tool_max: consecMax,
7429
+ exit_plan_calls: exitPlanCalls,
7430
+ prs,
7431
+ commits,
7432
+ pushes,
7433
+ tokens_total: tokensTotal
7434
+ };
7435
+ const longestTurnActiveMs = Math.max(0, ...closed.map((t) => t.activeMs));
7436
+ const flags = [];
7437
+ if (interrupts >= 3)
7438
+ flags.push("interrupt_heavy");
7439
+ if (toolErrors >= 20)
7440
+ flags.push("high_errors");
7441
+ if (consecMax >= 8)
7442
+ flags.push("loop_suspected");
7443
+ if (agg.activeMs < 5 * 6e4 && prs >= 1)
7444
+ flags.push("fast_ship");
7445
+ if (exitPlanCalls > 0)
7446
+ flags.push("plan_used");
7447
+ const subagentTurns = closed.filter((t) => t.subagents.length >= 1).length;
7448
+ if (subagentTurns >= 3)
7449
+ flags.push("orchestrated");
7450
+ if (longestTurnActiveMs >= 20 * 6e4 && interrupts === 0)
7451
+ flags.push("long_autonomous");
7452
+ const primaryModel = [...agg.modelTurns.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? null;
7453
+ const modelMix = Object.fromEntries(agg.modelTurns);
7454
+ const firstUserRaw = agg.firstUser ?? "";
7455
+ const first_user = trunc(minimizeImages(firstUserRaw), 400);
7456
+ const finalAgentRaw = agg.finalAgent ?? "";
7457
+ const final_agent = trunc(minimizeImages(finalAgentRaw), 400);
7458
+ let project = sessionFallbackProject;
7459
+ if (agg.cwdCounts.size > 0) {
7460
+ const best = [...agg.cwdCounts.entries()].sort((a, b) => b[1] - a[1])[0];
7461
+ project = canonicalProjectName(best[0]);
7462
+ }
7463
+ const top_tools = buildTopTools(closed);
7464
+ const skills = Object.fromEntries(skillsMap);
7465
+ const subagents = closed.flatMap((t) => t.subagents);
7466
+ const humanText = dayEvents.filter((ev) => {
7467
+ const v = viewEvent(ev);
7468
+ if (v.rawType !== "user")
7469
+ return false;
7470
+ const content = v.content;
7471
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7472
+ if (isToolResultOnly)
7473
+ return false;
7474
+ const text = blockText(ev.blocks) || ev.preview || "";
7475
+ return classifyUserInputSource(text) === "human";
7476
+ }).map((ev) => blockText(ev.blocks) || ev.preview || "").join("\n");
7477
+ const satisfaction_signals = countSatisfactionSignals(humanText);
7478
+ const user_input_sources = {
7479
+ human: 0,
7480
+ teammate: 0,
7481
+ skill_load: 0,
7482
+ slash_command: 0,
7483
+ system_instruction: 0
7484
+ };
7485
+ for (const ev of dayEvents) {
7486
+ const v = viewEvent(ev);
7487
+ if (v.rawType !== "user")
7488
+ continue;
7489
+ const content = v.content;
7490
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7491
+ if (isToolResultOnly)
7492
+ continue;
7493
+ const text = blockText(ev.blocks) || ev.preview || "";
7494
+ const src = classifyUserInputSource(text);
7495
+ user_input_sources[src]++;
7496
+ }
7497
+ const pendingUserInstructions = extractUserInstructions(humanText).slice(0, 5);
7498
+ const trivial = isTrivial(numbers);
7499
+ const entry = {
7500
+ version: CURRENT_ENTRY_SCHEMA_VERSION,
7501
+ agent: sessionDetail.agent,
7502
+ session_id: sessionDetail.id,
7503
+ local_day,
7504
+ project,
7505
+ start_iso: agg.start_iso,
7506
+ end_iso: agg.end_iso,
7507
+ numbers,
7508
+ flags,
7509
+ primary_model: primaryModel,
7510
+ model_mix: modelMix,
7511
+ first_user,
7512
+ final_agent,
7513
+ pr_titles: agg.prTitles,
7514
+ ...agg.githubRepos.length ? { github_repos: agg.githubRepos } : {},
7515
+ ...agg.editedDirs.length ? { edited_dirs: agg.editedDirs } : {},
7516
+ top_tools,
7517
+ skills,
7518
+ subagents,
7519
+ satisfaction_signals,
7520
+ user_input_sources,
7521
+ enrichment: trivial ? skippedTrivialEnrichment(generatedAt) : { ...pendingEnrichment(), user_instructions: pendingUserInstructions },
7522
+ generated_at: generatedAt,
7523
+ source_jsonl: sessionDetail.filePath ?? "",
7524
+ source_checkpoint: { byte_offset: 0, last_event_ts: agg.end_iso || null }
7525
+ };
7526
+ entry.signals = computeEntrySignals(entry);
7527
+ entries2.push(entry);
7528
+ }
7529
+ entries2.sort((a, b) => a.local_day.localeCompare(b.local_day));
7530
+ return entries2;
7531
+ }
7532
+ var IDLE_GAP_MS4, INTERRUPT_RE2, PR_TITLE_RE, BASH_CHAIN_SPLIT_RE, SHELL_NOISE, IMAGE_BLOCK_RE;
6756
7533
  var init_build = __esm({
6757
7534
  "../entries/dist/build.js"() {
6758
7535
  "use strict";
@@ -6761,6 +7538,11 @@ var init_build = __esm({
6761
7538
  init_signals();
6762
7539
  init_trivial();
6763
7540
  IDLE_GAP_MS4 = 3 * 60 * 1e3;
7541
+ INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
7542
+ PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
7543
+ BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
7544
+ SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
7545
+ IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
6764
7546
  }
6765
7547
  });
6766
7548
 
@@ -6784,7 +7566,7 @@ var init_dist2 = __esm({
6784
7566
  });
6785
7567
 
6786
7568
  // ../entries/dist/digest-fs.js
6787
- import { readFileSync as readFileSync8, readdirSync, writeFileSync as writeFileSync4, renameSync, chmodSync, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "node:fs";
7569
+ import { readFileSync as readFileSync8, readdirSync, writeFileSync as writeFileSync4, renameSync as renameSync2, chmodSync, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "node:fs";
6788
7570
  import { dirname as dirname7, join as join7 } from "node:path";
6789
7571
  function parseDigestFile(path7, expectedVersion) {
6790
7572
  try {
@@ -6833,7 +7615,7 @@ function writeDayDigest(digest2) {
6833
7615
  } catch {
6834
7616
  }
6835
7617
  }
6836
- renameSync(tmp, final);
7618
+ renameSync2(tmp, final);
6837
7619
  }
6838
7620
  function readDayDigest(date) {
6839
7621
  const p = dayDigestPath(date);
@@ -6869,7 +7651,7 @@ function writeWeekDigest(digest2) {
6869
7651
  } catch {
6870
7652
  }
6871
7653
  }
6872
- renameSync(tmp, final);
7654
+ renameSync2(tmp, final);
6873
7655
  }
6874
7656
  function readWeekDigest(monday) {
6875
7657
  const p = weekDigestPath(monday);
@@ -6906,7 +7688,7 @@ function writeMonthDigest(digest2) {
6906
7688
  } catch {
6907
7689
  }
6908
7690
  }
6909
- renameSync(tmp, final);
7691
+ renameSync2(tmp, final);
6910
7692
  }
6911
7693
  function readMonthDigest(yearMonth) {
6912
7694
  const p = monthDigestPath(yearMonth);
@@ -6955,6 +7737,7 @@ __export(fs_exports2, {
6955
7737
  __setDigestsDirForTest: () => __setDigestsDirForTest,
6956
7738
  __setEntriesDirForTest: () => __setEntriesDirForTest,
6957
7739
  entriesDir: () => entriesDir,
7740
+ entryExists: () => entryExists,
6958
7741
  getCurrentMonthDigestFromCache: () => getCurrentMonthDigestFromCache,
6959
7742
  getCurrentWeekDigestFromCache: () => getCurrentWeekDigestFromCache,
6960
7743
  getTodayDigestFromCache: () => getTodayDigestFromCache,
@@ -6977,7 +7760,7 @@ __export(fs_exports2, {
6977
7760
  writeMonthDigest: () => writeMonthDigest,
6978
7761
  writeWeekDigest: () => writeWeekDigest
6979
7762
  });
6980
- import { readFileSync as readFileSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, renameSync as renameSync2, existsSync as existsSync7 } from "node:fs";
7763
+ import { readFileSync as readFileSync9, readdirSync as readdirSync2, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, renameSync as renameSync3, existsSync as existsSync7 } from "node:fs";
6981
7764
  import { randomBytes } from "node:crypto";
6982
7765
  import { join as join8 } from "node:path";
6983
7766
  function entriesDir() {
@@ -6991,8 +7774,8 @@ function __setEntriesDirForTest(path7) {
6991
7774
  entriesDirCached = path7;
6992
7775
  mkdirSync6(path7, { recursive: true });
6993
7776
  }
6994
- function pathFor(sessionId, localDay) {
6995
- return join8(entriesDir(), `${entryKey(sessionId, localDay)}.json`);
7777
+ function pathFor(sessionId, localDay2) {
7778
+ return join8(entriesDir(), `${entryKey(sessionId, localDay2)}.json`);
6996
7779
  }
6997
7780
  function tmpPathFor(final) {
6998
7781
  return `${final}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
@@ -7004,7 +7787,7 @@ function writeEntry(entry) {
7004
7787
  const tmp = tmpPathFor(final);
7005
7788
  const json = JSON.stringify(entry, null, 2);
7006
7789
  writeFileSync5(tmp, json, { encoding: "utf8" });
7007
- renameSync2(tmp, final);
7790
+ renameSync3(tmp, final);
7008
7791
  }
7009
7792
  function enrichmentInputHash(e) {
7010
7793
  return [
@@ -7032,26 +7815,29 @@ function writeEntryPreservingEnrichment(fresh) {
7032
7815
  }
7033
7816
  const tmp = tmpPathFor(final);
7034
7817
  writeFileSync5(tmp, JSON.stringify(toWrite, null, 2), { encoding: "utf8" });
7035
- renameSync2(tmp, final);
7818
+ renameSync3(tmp, final);
7036
7819
  }
7037
- function readEntry(sessionId, localDay) {
7038
- const p = pathFor(sessionId, localDay);
7820
+ function readEntry(sessionId, localDay2) {
7821
+ const p = pathFor(sessionId, localDay2);
7039
7822
  if (!existsSync7(p))
7040
7823
  return null;
7041
7824
  const raw = readFileSync9(p, "utf8");
7042
7825
  return JSON.parse(raw);
7043
7826
  }
7827
+ function entryExists(sessionId, localDay2) {
7828
+ return existsSync7(pathFor(sessionId, localDay2));
7829
+ }
7044
7830
  function listEntryKeys() {
7045
7831
  const dir = entriesDir();
7046
7832
  if (!existsSync7(dir))
7047
7833
  return [];
7048
7834
  return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
7049
7835
  }
7050
- function listEntriesForDay(localDay) {
7836
+ function listEntriesForDay(localDay2) {
7051
7837
  const out = [];
7052
7838
  for (const key of listEntryKeys()) {
7053
7839
  const parsed = parseEntryKey(key);
7054
- if (!parsed || parsed.local_day !== localDay)
7840
+ if (!parsed || parsed.local_day !== localDay2)
7055
7841
  continue;
7056
7842
  const e = readEntry(parsed.session_id, parsed.local_day);
7057
7843
  if (e)
@@ -11220,7 +12006,7 @@ var init_zod = __esm({
11220
12006
  });
11221
12007
 
11222
12008
  // ../entries/dist/prompts/enrich.js
11223
- function trunc(s, n) {
12009
+ function trunc2(s, n) {
11224
12010
  if (s.length <= n)
11225
12011
  return s;
11226
12012
  return s.slice(0, n - 1) + "\u2026";
@@ -11241,7 +12027,7 @@ function buildEnrichmentUserPrompt(entry, humanTurns) {
11241
12027
  satisfaction_signals: entry.satisfaction_signals,
11242
12028
  user_input_sources: entry.user_input_sources
11243
12029
  };
11244
- const turns = humanTurns.slice(0, 8).map((t, i) => `${i + 1}. ${trunc(t, 300)}`).join("\n");
12030
+ const turns = humanTurns.slice(0, 8).map((t, i) => `${i + 1}. ${trunc2(t, 300)}`).join("\n");
11245
12031
  return `SLICE FACTS:
11246
12032
  ${JSON.stringify(facts, null, 2)}
11247
12033
 
@@ -11322,7 +12108,7 @@ RESPOND WITH ONLY VALID JSON (no prose, no code fence):
11322
12108
  });
11323
12109
 
11324
12110
  // ../entries/dist/tmux-runner.js
11325
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, readdirSync as readdirSync3, realpathSync as realpathSync3, readFileSync as readFileSync10, unlinkSync as unlinkSync3, renameSync as renameSync3 } from "node:fs";
12111
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, readdirSync as readdirSync3, realpathSync as realpathSync3, readFileSync as readFileSync10, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
11326
12112
  import { join as join9 } from "node:path";
11327
12113
  import { homedir as homedir4 } from "node:os";
11328
12114
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -11365,7 +12151,7 @@ function ensureRuntimeCwdTrusted(cwd) {
11365
12151
  const tmp = `${cfgPath}.cclens-tmp-${process.pid}`;
11366
12152
  try {
11367
12153
  writeFileSync6(tmp, JSON.stringify(cfg, null, 2));
11368
- renameSync3(tmp, cfgPath);
12154
+ renameSync4(tmp, cfgPath);
11369
12155
  } catch {
11370
12156
  try {
11371
12157
  unlinkSync3(tmp);
@@ -12308,7 +13094,7 @@ var init_queue = __esm({
12308
13094
  });
12309
13095
 
12310
13096
  // ../entries/dist/settings.js
12311
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, renameSync as renameSync4, chmodSync as chmodSync2, mkdirSync as mkdirSync10, existsSync as existsSync12 } from "node:fs";
13097
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync10, existsSync as existsSync12 } from "node:fs";
12312
13098
  import { dirname as dirname9 } from "node:path";
12313
13099
  function settingsPath() {
12314
13100
  if (settingsPathCached)
@@ -12361,7 +13147,7 @@ function writeSettings(s) {
12361
13147
  if (process.platform !== "win32") {
12362
13148
  chmodSync2(tmp, 384);
12363
13149
  }
12364
- renameSync4(tmp, p);
13150
+ renameSync5(tmp, p);
12365
13151
  }
12366
13152
  var DEFAULT_SETTINGS, settingsPathCached;
12367
13153
  var init_settings = __esm({
@@ -12382,7 +13168,7 @@ var init_settings = __esm({
12382
13168
  });
12383
13169
 
12384
13170
  // ../entries/dist/prompts/digest-day.js
12385
- function trunc2(s, n) {
13171
+ function trunc3(s, n) {
12386
13172
  if (s.length <= n)
12387
13173
  return s;
12388
13174
  return s.slice(0, n - 1) + "\u2026";
@@ -12393,9 +13179,9 @@ function prettyProject(p) {
12393
13179
  }
12394
13180
  function buildDigestUserPrompt(base, entries2) {
12395
13181
  const enriched = entries2.filter((e) => e.enrichment.status === "done");
12396
- const summaries = enriched.slice(0, 12).map((e) => `- (${prettyProject(e.project)}, ${Math.round(e.numbers.active_min)}m) ${trunc2(e.enrichment.brief_summary ?? "", 240)}`).join("\n");
12397
- const frictions = enriched.map((e) => e.enrichment.friction_detail).filter((x) => !!x).slice(0, 6).map((f, i) => `${i + 1}. ${trunc2(f, 240)}`).join("\n");
12398
- const instructions = enriched.flatMap((e) => e.enrichment.user_instructions).slice(0, 10).map((s, i) => `${i + 1}. ${trunc2(s, 240)}`).join("\n");
13182
+ const summaries = enriched.slice(0, 12).map((e) => `- (${prettyProject(e.project)}, ${Math.round(e.numbers.active_min)}m) ${trunc3(e.enrichment.brief_summary ?? "", 240)}`).join("\n");
13183
+ const frictions = enriched.map((e) => e.enrichment.friction_detail).filter((x) => !!x).slice(0, 6).map((f, i) => `${i + 1}. ${trunc3(f, 240)}`).join("\n");
13184
+ const instructions = enriched.flatMap((e) => e.enrichment.user_instructions).slice(0, 10).map((s, i) => `${i + 1}. ${trunc3(s, 240)}`).join("\n");
12399
13185
  const per_entry = entries2.map((e) => ({
12400
13186
  agent: e.agent ?? "claude-code",
12401
13187
  project: prettyProject(e.project),
@@ -14425,7 +15211,7 @@ function activeMs(timestamps) {
14425
15211
  }
14426
15212
  return active;
14427
15213
  }
14428
- function bashVerb(cmd) {
15214
+ function bashVerb2(cmd) {
14429
15215
  if (!cmd)
14430
15216
  return "?";
14431
15217
  let firstSeg = cmd.split(/\|\||&&|;|\|/, 1)[0].trim();
@@ -14435,7 +15221,7 @@ function bashVerb(cmd) {
14435
15221
  firstSeg = parts[1].trim();
14436
15222
  }
14437
15223
  const tokens = firstSeg.split(/\s+/).filter(Boolean);
14438
- while (tokens.length && SHELL_NOISE.has(tokens[0]))
15224
+ while (tokens.length && SHELL_NOISE2.has(tokens[0]))
14439
15225
  tokens.shift();
14440
15226
  if (!tokens.length)
14441
15227
  return "?";
@@ -14508,7 +15294,7 @@ function eventsToTurns(events, windowStartMs, windowEndMs) {
14508
15294
  const isToolOnly = blocks.length > 0 && blocks.every((c) => c.type === "tool_result");
14509
15295
  if (!isToolOnly) {
14510
15296
  const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ");
14511
- const interrupted = INTERRUPT_RE2.test(text);
15297
+ const interrupted = INTERRUPT_RE3.test(text);
14512
15298
  if (cur && interrupted)
14513
15299
  cur.interrupts += 1;
14514
15300
  if (cur)
@@ -14562,10 +15348,10 @@ function eventsToTurns(events, windowStartMs, windowEndMs) {
14562
15348
  cur.todo_ops += 1;
14563
15349
  } else if (name === "Bash") {
14564
15350
  const cmd = String(ct.input?.command ?? "");
14565
- const v = bashVerb(cmd);
15351
+ const v = bashVerb2(cmd);
14566
15352
  cur.bash_verbs.set(v, (cur.bash_verbs.get(v) ?? 0) + 1);
14567
15353
  if (/\bgh\s+pr\s+create\b/.test(cmd)) {
14568
- const m = cmd.match(PR_TITLE_RE);
15354
+ const m = cmd.match(PR_TITLE_RE2);
14569
15355
  cur.pr_created = m?.[1] ?? cmd.slice(0, 120);
14570
15356
  }
14571
15357
  }
@@ -14988,7 +15774,7 @@ async function generateTopSession(slice, opts = {}) {
14988
15774
  void lastModel;
14989
15775
  }
14990
15776
  }
14991
- var IDLE_GAP_MS5, LONG_AUTONOMOUS_MIN, N_USER, N_FIRST_AGENT, N_LAST_AGENT, N_USER_TURN1, N_SUBAGENT, SHELL_NOISE, INTERRUPT_RE2, PR_TITLE_RE, PinResponseSchema, TopSessionResponseSchema, SYSTEM_PROMPT5, TOP_SESSION_SYSTEM_PROMPT, DEFAULT_MODEL5, defaultCallLLMTopSession, validateTopSession;
15777
+ var IDLE_GAP_MS5, LONG_AUTONOMOUS_MIN, N_USER, N_FIRST_AGENT, N_LAST_AGENT, N_USER_TURN1, N_SUBAGENT, SHELL_NOISE2, INTERRUPT_RE3, PR_TITLE_RE2, PinResponseSchema, TopSessionResponseSchema, SYSTEM_PROMPT5, TOP_SESSION_SYSTEM_PROMPT, DEFAULT_MODEL5, defaultCallLLMTopSession, validateTopSession;
14992
15778
  var init_top_sessions = __esm({
14993
15779
  "../entries/dist/top-sessions.js"() {
14994
15780
  "use strict";
@@ -15002,9 +15788,9 @@ var init_top_sessions = __esm({
15002
15788
  N_LAST_AGENT = 200;
15003
15789
  N_USER_TURN1 = 400;
15004
15790
  N_SUBAGENT = 240;
15005
- SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
15006
- INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
15007
- PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
15791
+ SHELL_NOISE2 = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
15792
+ INTERRUPT_RE3 = /\[request interrupted|interrupted by user/i;
15793
+ PR_TITLE_RE2 = /--title\s+["']([^"']+)["']/;
15008
15794
  PinResponseSchema = external_exports.object({
15009
15795
  start_min: external_exports.number(),
15010
15796
  end_min: external_exports.number().optional(),
@@ -15119,8 +15905,8 @@ VOCABULARY:
15119
15905
  });
15120
15906
 
15121
15907
  // ../entries/dist/digest-week-pipeline.js
15122
- function mondayFor(localDay) {
15123
- const d = /* @__PURE__ */ new Date(`${localDay}T00:00:00`);
15908
+ function mondayFor(localDay2) {
15909
+ const d = /* @__PURE__ */ new Date(`${localDay2}T00:00:00`);
15124
15910
  const dow = d.getDay();
15125
15911
  const offset = dow === 0 ? -6 : 1 - dow;
15126
15912
  d.setDate(d.getDate() + offset);
@@ -15796,8 +16582,8 @@ async function digest(args2) {
15796
16582
  printHelp2();
15797
16583
  process.exit(1);
15798
16584
  }
15799
- function mondayOf(localDay) {
15800
- const d = /* @__PURE__ */ new Date(`${localDay}T00:00:00`);
16585
+ function mondayOf(localDay2) {
16586
+ const d = /* @__PURE__ */ new Date(`${localDay2}T00:00:00`);
15801
16587
  const dow = d.getDay();
15802
16588
  const offset = dow === 0 ? -6 : 1 - dow;
15803
16589
  d.setDate(d.getDate() + offset);
@@ -16097,6 +16883,52 @@ var init_config = __esm({
16097
16883
  }
16098
16884
  });
16099
16885
 
16886
+ // src/perception/build-entries.ts
16887
+ import { statSync as statSync3, readFileSync as readFileSync14 } from "node:fs";
16888
+ import { basename, dirname as dirname10 } from "node:path";
16889
+ function decodeProjectDirName(projectDir) {
16890
+ if (projectDir.startsWith("-")) {
16891
+ return "/" + projectDir.slice(1).replace(/-/g, "/");
16892
+ }
16893
+ return projectDir.replace(/-/g, "/");
16894
+ }
16895
+ function buildEntriesForFile(filePath) {
16896
+ const stat = statSync3(filePath);
16897
+ const raw = readFileSync14(filePath, "utf8");
16898
+ const rawLines = raw.split("\n").filter(Boolean).map((l) => {
16899
+ try {
16900
+ return JSON.parse(l);
16901
+ } catch {
16902
+ return null;
16903
+ }
16904
+ }).filter((x) => x !== null);
16905
+ if (rawLines.length === 0) return null;
16906
+ const { meta, events } = parseTranscript(rawLines);
16907
+ const fileName = basename(filePath);
16908
+ const projectDir = basename(dirname10(filePath));
16909
+ const sessionId = fileName.replace(/\.jsonl$/, "");
16910
+ const sd = {
16911
+ ...meta,
16912
+ id: sessionId,
16913
+ filePath,
16914
+ projectDir,
16915
+ projectName: meta.cwd ?? decodeProjectDirName(projectDir),
16916
+ events
16917
+ };
16918
+ const built = buildEntries(sd);
16919
+ for (const e of built) {
16920
+ e.source_checkpoint.byte_offset = stat.size;
16921
+ }
16922
+ return built;
16923
+ }
16924
+ var init_build_entries = __esm({
16925
+ "src/perception/build-entries.ts"() {
16926
+ "use strict";
16927
+ init_dist();
16928
+ init_dist2();
16929
+ }
16930
+ });
16931
+
16100
16932
  // src/team/push.ts
16101
16933
  import { randomUUID as randomUUID2 } from "node:crypto";
16102
16934
  function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
@@ -16157,6 +16989,10 @@ function clipSegmentsToDay(segments, bounds) {
16157
16989
  }
16158
16990
  return out;
16159
16991
  }
16992
+ function filterSyncedSessions(sessions, syncProjects) {
16993
+ if (!syncProjects) return sessions;
16994
+ return sessions.filter((s) => shouldSyncProject(projectRepoName(s.projectName), syncProjects));
16995
+ }
16160
16996
  function sessionTouchesDay(s, day2) {
16161
16997
  const bounds = dayBoundsMs(day2);
16162
16998
  if (s.activeSegments && s.activeSegments.length > 0) {
@@ -16164,7 +17000,7 @@ function sessionTouchesDay(s, day2) {
16164
17000
  }
16165
17001
  return sessionDay(s) === day2;
16166
17002
  }
16167
- function buildRichRollupBlocks(day2, daySessions, entries2, privateProjects, resolveRepo) {
17003
+ function buildRichRollupBlocks(day2, daySessions, entries2, resolveRepo) {
16168
17004
  const bounds = dayBoundsMs(day2);
16169
17005
  const clippedSessions = [];
16170
17006
  for (const s of daySessions) {
@@ -16190,7 +17026,6 @@ function buildRichRollupBlocks(day2, daySessions, entries2, privateProjects, res
16190
17026
  };
16191
17027
  const sessionRepo = /* @__PURE__ */ new Map();
16192
17028
  for (const e of entries2) {
16193
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
16194
17029
  const r = repoForEntry(e);
16195
17030
  if (r) sessionRepo.set(e.session_id, r);
16196
17031
  }
@@ -16198,7 +17033,6 @@ function buildRichRollupBlocks(day2, daySessions, entries2, privateProjects, res
16198
17033
  for (const s of clippedSessions) {
16199
17034
  const canonical = canonicalProjectName(s.projectName);
16200
17035
  const name = projectRepoName(s.projectName);
16201
- if (privateProjects.has(name) || privateProjects.has(canonical)) continue;
16202
17036
  const repo = resolveRepo?.(canonical) ?? sessionRepo.get(s.id);
16203
17037
  const key = repo ?? name;
16204
17038
  const ms = s.activeSegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
@@ -16221,7 +17055,6 @@ function buildRichRollupBlocks(day2, daySessions, entries2, privateProjects, res
16221
17055
  let longTotalMin = 0;
16222
17056
  let longMaxSingleMin = 0;
16223
17057
  for (const e of entries2) {
16224
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
16225
17058
  const shape = e.signals?.working_shape ?? null;
16226
17059
  if (shape) {
16227
17060
  const cur = workingShapes.get(shape) ?? { sessions: 0, agentTimeMs: 0 };
@@ -16297,19 +17130,36 @@ function buildEnrichedExtras(entries2) {
16297
17130
  }
16298
17131
  return { outcomeMix, helpfulnessMix, goalMix };
16299
17132
  }
16300
- function buildRichBlocksForDay(day2, daySessions, privateProjects, enrichmentOptIn, resolveRepo) {
16301
- const entries2 = listEntriesForDay(day2);
17133
+ function resetEnsuredSessions() {
17134
+ ensuredSessions.clear();
17135
+ }
17136
+ function ensureEntriesForDay(day2, daySessions) {
17137
+ for (const s of daySessions) {
17138
+ if (ensuredSessions.has(s.id)) continue;
17139
+ if ((s.agent ?? "claude-code") !== "claude-code") continue;
17140
+ if (entryExists(s.id, day2)) continue;
17141
+ ensuredSessions.add(s.id);
17142
+ try {
17143
+ for (const e of buildEntriesForFile(s.filePath) ?? []) writeEntryPreservingEnrichment(e);
17144
+ } catch {
17145
+ }
17146
+ }
17147
+ }
17148
+ function buildRichBlocksForDay(day2, daySessions, resolveRepo) {
17149
+ ensureEntriesForDay(day2, daySessions);
17150
+ const sessionIds = new Set(daySessions.map((s) => s.id));
17151
+ const entries2 = listEntriesForDay(day2).filter((e) => sessionIds.has(e.session_id));
16302
17152
  if (entries2.length === 0) return void 0;
16303
- const rich = buildRichRollupBlocks(day2, daySessions, entries2, privateProjects, resolveRepo);
16304
- const enriched = enrichmentOptIn ? buildEnrichedExtras(entries2) : void 0;
16305
- return enriched ? { rich, enriched } : { rich };
17153
+ const rich = buildRichRollupBlocks(day2, daySessions, entries2, resolveRepo);
17154
+ const enriched = buildEnrichedExtras(entries2);
17155
+ return { rich, enriched };
16306
17156
  }
16307
17157
  function buildIngestPayload(inputs) {
16308
17158
  const richRollup = inputs.rollup && inputs.richExtras ? { ...inputs.rollup, ...inputs.richExtras } : void 0;
16309
17159
  return {
16310
17160
  ingestId: randomUUID2(),
16311
17161
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
16312
- cliVersion: "0.13.2",
17162
+ cliVersion: "0.15.0",
16313
17163
  ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
16314
17164
  ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
16315
17165
  ...richRollup ? { richRollup } : {},
@@ -16317,7 +17167,8 @@ function buildIngestPayload(inputs) {
16317
17167
  ...inputs.artifactSignals ? { artifactSignals: inputs.artifactSignals } : {},
16318
17168
  ...inputs.usageSnapshot ? { usageSnapshot: inputs.usageSnapshot } : {},
16319
17169
  ...inputs.planTier ? { planTier: inputs.planTier } : {},
16320
- ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {}
17170
+ ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {},
17171
+ ...inputs.syncLog && inputs.syncLog.length ? { syncLog: inputs.syncLog } : {}
16321
17172
  };
16322
17173
  }
16323
17174
  async function pushToTeamServer(config, payload) {
@@ -16333,22 +17184,24 @@ async function pushToTeamServer(config, payload) {
16333
17184
  const body = await res.json().catch(() => null);
16334
17185
  return { ok: res.ok, status: res.status, body };
16335
17186
  }
16336
- var SNAPSHOT_FRESHNESS_MS, POST_TIMEOUT_MS;
17187
+ var SNAPSHOT_FRESHNESS_MS, POST_TIMEOUT_MS, ensuredSessions;
16337
17188
  var init_push = __esm({
16338
17189
  "src/team/push.ts"() {
16339
17190
  "use strict";
16340
17191
  init_dist();
16341
17192
  init_fs();
16342
17193
  init_fs2();
17194
+ init_build_entries();
16343
17195
  init_storage();
16344
17196
  SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
16345
17197
  POST_TIMEOUT_MS = 15e3;
17198
+ ensuredSessions = /* @__PURE__ */ new Set();
16346
17199
  }
16347
17200
  });
16348
17201
 
16349
17202
  // src/team/git-remote.ts
16350
- import { existsSync as existsSync13, readFileSync as readFileSync14, statSync as statSync3 } from "node:fs";
16351
- import { dirname as dirname10, isAbsolute, join as join11, resolve } from "node:path";
17203
+ import { existsSync as existsSync13, readFileSync as readFileSync15, statSync as statSync4 } from "node:fs";
17204
+ import { dirname as dirname11, isAbsolute, join as join11, resolve } from "node:path";
16352
17205
  function parseGithubRemote(url) {
16353
17206
  const m = url.trim().match(GITHUB_URL_RE);
16354
17207
  return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
@@ -16369,15 +17222,15 @@ function originUrl(configText) {
16369
17222
  }
16370
17223
  function gitConfigPathFor(dir) {
16371
17224
  const dotGit = join11(dir, ".git");
16372
- const st = statSync3(dotGit, { throwIfNoEntry: false });
17225
+ const st = statSync4(dotGit, { throwIfNoEntry: false });
16373
17226
  if (!st) return null;
16374
17227
  if (st.isDirectory()) return join11(dotGit, "config");
16375
- const m = readFileSync14(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
17228
+ const m = readFileSync15(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
16376
17229
  if (!m) return null;
16377
17230
  const gd = isAbsolute(m[1]) ? m[1] : resolve(dir, m[1]);
16378
17231
  const commondirFile = join11(gd, "commondir");
16379
17232
  if (existsSync13(commondirFile)) {
16380
- const common = readFileSync14(commondirFile, "utf8").trim();
17233
+ const common = readFileSync15(commondirFile, "utf8").trim();
16381
17234
  return join11(isAbsolute(common) ? common : resolve(gd, common), "config");
16382
17235
  }
16383
17236
  return join11(gd, "config");
@@ -16390,14 +17243,14 @@ function createRepoResolver() {
16390
17243
  if (hit !== void 0) return hit;
16391
17244
  let repo = null;
16392
17245
  try {
16393
- for (let d = dir; ; d = dirname10(d)) {
17246
+ for (let d = dir; ; d = dirname11(d)) {
16394
17247
  const config = gitConfigPathFor(d);
16395
17248
  if (config && existsSync13(config)) {
16396
- const url = originUrl(readFileSync14(config, "utf8"));
17249
+ const url = originUrl(readFileSync15(config, "utf8"));
16397
17250
  repo = url ? parseGithubRemote(url) : null;
16398
17251
  break;
16399
17252
  }
16400
- if (d === dirname10(d)) break;
17253
+ if (d === dirname11(d)) break;
16401
17254
  }
16402
17255
  } catch {
16403
17256
  repo = null;
@@ -16415,14 +17268,14 @@ var init_git_remote = __esm({
16415
17268
  });
16416
17269
 
16417
17270
  // src/team/queue.ts
16418
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, appendFileSync as appendFileSync4, statSync as statSync4, mkdirSync as mkdirSync11 } from "node:fs";
16419
- import { dirname as dirname11 } from "node:path";
17271
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, appendFileSync as appendFileSync4, statSync as statSync5, mkdirSync as mkdirSync11 } from "node:fs";
17272
+ import { dirname as dirname12 } from "node:path";
16420
17273
  function enqueuePayload(payload, queuePath = QUEUE_PATH) {
16421
- mkdirSync11(dirname11(queuePath), { recursive: true });
17274
+ mkdirSync11(dirname12(queuePath), { recursive: true });
16422
17275
  const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
16423
17276
  appendFileSync4(queuePath, JSON.stringify(entry) + "\n", "utf8");
16424
17277
  try {
16425
- const { size } = statSync4(queuePath);
17278
+ const { size } = statSync5(queuePath);
16426
17279
  if (size > MAX_SIZE_BYTES) {
16427
17280
  pruneOldest(queuePath);
16428
17281
  }
@@ -16432,7 +17285,7 @@ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
16432
17285
  function dequeuePayloads(queuePath = QUEUE_PATH) {
16433
17286
  let lines;
16434
17287
  try {
16435
- lines = readFileSync15(queuePath, "utf8").trim().split("\n").filter(Boolean);
17288
+ lines = readFileSync16(queuePath, "utf8").trim().split("\n").filter(Boolean);
16436
17289
  } catch {
16437
17290
  return [];
16438
17291
  }
@@ -16453,7 +17306,7 @@ function dequeuePayloads(queuePath = QUEUE_PATH) {
16453
17306
  function pruneOldest(queuePath) {
16454
17307
  let lines;
16455
17308
  try {
16456
- lines = readFileSync15(queuePath, "utf8").trim().split("\n").filter(Boolean);
17309
+ lines = readFileSync16(queuePath, "utf8").trim().split("\n").filter(Boolean);
16457
17310
  } catch {
16458
17311
  return;
16459
17312
  }
@@ -16472,8 +17325,8 @@ var init_queue2 = __esm({
16472
17325
  });
16473
17326
 
16474
17327
  // src/usage/profile.ts
16475
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "node:fs";
16476
- import { dirname as dirname12 } from "node:path";
17328
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "node:fs";
17329
+ import { dirname as dirname13 } from "node:path";
16477
17330
  function mapRateLimitTier(rateLimitTier) {
16478
17331
  switch (rateLimitTier) {
16479
17332
  case "default_claude_pro":
@@ -16515,7 +17368,7 @@ async function fetchProfile(nowMs = Date.now()) {
16515
17368
  function readCachedProfile(cachePath) {
16516
17369
  if (!existsSync14(cachePath)) return null;
16517
17370
  try {
16518
- const raw = JSON.parse(readFileSync16(cachePath, "utf8"));
17371
+ const raw = JSON.parse(readFileSync17(cachePath, "utf8"));
16519
17372
  if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
16520
17373
  return raw;
16521
17374
  } catch {
@@ -16523,7 +17376,7 @@ function readCachedProfile(cachePath) {
16523
17376
  }
16524
17377
  }
16525
17378
  function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
16526
- mkdirSync12(dirname12(cachePath), { recursive: true });
17379
+ mkdirSync12(dirname13(cachePath), { recursive: true });
16527
17380
  writeFileSync11(
16528
17381
  cachePath,
16529
17382
  JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
@@ -16703,6 +17556,38 @@ var init_backfill = __esm({
16703
17556
  }
16704
17557
  });
16705
17558
 
17559
+ // src/team/sync-log.ts
17560
+ import { readFileSync as readFileSync18 } from "node:fs";
17561
+ function readPendingSyncLog(watermark) {
17562
+ let raw;
17563
+ try {
17564
+ raw = readFileSync18(cclensPath("daemon.log"), "utf8");
17565
+ } catch {
17566
+ return { lines: [] };
17567
+ }
17568
+ const lines = [];
17569
+ for (const line of raw.split("\n")) {
17570
+ const m = line.match(/^(\S+) (INFO|WARN|ERROR) (.*)$/);
17571
+ if (!m) continue;
17572
+ const [, ts, levelRaw, msg] = m;
17573
+ if (!msg.startsWith("[sync] ")) continue;
17574
+ if (watermark && ts <= watermark) continue;
17575
+ const clipped = msg.length > MAX_MSG_CHARS ? msg.slice(0, MAX_MSG_CHARS) + "\u2026" : msg;
17576
+ lines.push({ ts, level: levelRaw.toLowerCase(), msg: clipped });
17577
+ }
17578
+ const capped = lines.slice(0, MAX_LINES);
17579
+ return { lines: capped, watermark: capped.length ? capped[capped.length - 1].ts : void 0 };
17580
+ }
17581
+ var MAX_LINES, MAX_MSG_CHARS;
17582
+ var init_sync_log = __esm({
17583
+ "src/team/sync-log.ts"() {
17584
+ "use strict";
17585
+ init_fs();
17586
+ MAX_LINES = 300;
17587
+ MAX_MSG_CHARS = 1900;
17588
+ }
17589
+ });
17590
+
16706
17591
  // src/team/last-push.ts
16707
17592
  import { writeFileSync as writeFileSync12, unlinkSync as unlinkSync5, mkdirSync as mkdirSync13 } from "node:fs";
16708
17593
  import { join as join12 } from "node:path";
@@ -16740,7 +17625,7 @@ __export(file_probe_exports, {
16740
17625
  probeArtifactSignals: () => probeArtifactSignals
16741
17626
  });
16742
17627
  import { createHash } from "node:crypto";
16743
- import { existsSync as existsSync15, readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
17628
+ import { existsSync as existsSync15, readdirSync as readdirSync4, statSync as statSync6 } from "node:fs";
16744
17629
  import { homedir as homedir5 } from "node:os";
16745
17630
  import { join as join13, relative, resolve as resolve2, sep } from "node:path";
16746
17631
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -16771,7 +17656,7 @@ function listMarkdownFiles(dir) {
16771
17656
  const full = join13(dir, name);
16772
17657
  let stat;
16773
17658
  try {
16774
- stat = statSync5(full);
17659
+ stat = statSync6(full);
16775
17660
  } catch {
16776
17661
  continue;
16777
17662
  }
@@ -16788,19 +17673,19 @@ function firstSeenDateOf(file) {
16788
17673
  const out = execFileSync2(
16789
17674
  "git",
16790
17675
  ["log", "--diff-filter=A", "--follow", "--format=%cs", "--", file],
16791
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname13(file) }
17676
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname14(file) }
16792
17677
  ).trim().split("\n").filter(Boolean);
16793
17678
  if (out.length > 0) return out[out.length - 1];
16794
17679
  } catch {
16795
17680
  }
16796
17681
  try {
16797
- const s = statSync5(file);
16798
- return new Date((s.birthtime || s.mtime).getTime()).toISOString().slice(0, 10);
17682
+ const s = statSync6(file);
17683
+ return localDay(s.birthtime || s.mtime);
16799
17684
  } catch {
16800
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
17685
+ return todayLocal();
16801
17686
  }
16802
17687
  }
16803
- function dirname13(p) {
17688
+ function dirname14(p) {
16804
17689
  const i = p.lastIndexOf(sep);
16805
17690
  return i < 0 ? "." : p.slice(0, i);
16806
17691
  }
@@ -16815,13 +17700,15 @@ function detectGitEmail(cwd) {
16815
17700
  return null;
16816
17701
  }
16817
17702
  }
16818
- function todayLocal() {
16819
- const d = /* @__PURE__ */ new Date();
17703
+ function localDay(d) {
16820
17704
  const y = d.getFullYear();
16821
17705
  const m = String(d.getMonth() + 1).padStart(2, "0");
16822
17706
  const day2 = String(d.getDate()).padStart(2, "0");
16823
17707
  return `${y}-${m}-${day2}`;
16824
17708
  }
17709
+ function todayLocal() {
17710
+ return localDay(/* @__PURE__ */ new Date());
17711
+ }
16825
17712
  function scanAuthored(roots, subdir, day2) {
16826
17713
  const out = [];
16827
17714
  for (const r of roots) {
@@ -16846,11 +17733,11 @@ function scanEdited(roots, day2) {
16846
17733
  if (firstSeen === day2) continue;
16847
17734
  let stat;
16848
17735
  try {
16849
- stat = statSync5(f);
17736
+ stat = statSync6(f);
16850
17737
  } catch {
16851
17738
  continue;
16852
17739
  }
16853
- const mtimeDay = new Date(stat.mtime.getTime()).toISOString().slice(0, 10);
17740
+ const mtimeDay = localDay(stat.mtime);
16854
17741
  if (mtimeDay !== day2) continue;
16855
17742
  const rel = relative(dir, f);
16856
17743
  out.push({ pathHash: pathHash(`${r.label}:skills:${rel}`), lineDelta: 0 });
@@ -16872,7 +17759,7 @@ function claudemdLineDelta(extraRoots, authorEmail, day2) {
16872
17759
  const out = execFileSync2(
16873
17760
  "git",
16874
17761
  ["log", "--numstat", "--format=COMMIT %cs %ae", "--", file],
16875
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname13(file) }
17762
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname14(file) }
16876
17763
  );
16877
17764
  let currentDate = "";
16878
17765
  let currentEmail = "";
@@ -16936,8 +17823,8 @@ async function dispatchCommand(command2, config, log) {
16936
17823
  }
16937
17824
  }
16938
17825
  async function runActivityBackfill(command2, config, log) {
16939
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
16940
- const { toLocalDay: toLocalDay4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
17826
+ const { listSessions: listSessions2, shouldSyncProject: shouldSyncProject2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
17827
+ const { toLocalDay: toLocalDay4, projectRepoName: projectRepoName2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
16941
17828
  const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
16942
17829
  const days = command2.params.days;
16943
17830
  const todayMs = Date.now();
@@ -16946,7 +17833,7 @@ async function runActivityBackfill(command2, config, log) {
16946
17833
  log("info", `command ${command2.id}: backfill-activity from ${targetDay} (${days} days)`);
16947
17834
  let sessions;
16948
17835
  try {
16949
- sessions = await listSessions2({ limit: 1e4 });
17836
+ sessions = filterSyncedSessions(await listSessions2({ limit: 1e4 }), config.syncProjects);
16950
17837
  } catch (err) {
16951
17838
  return {
16952
17839
  id: command2.id,
@@ -16964,16 +17851,15 @@ async function runActivityBackfill(command2, config, log) {
16964
17851
  summary: { pushed: 0, fromDay: targetDay }
16965
17852
  };
16966
17853
  }
16967
- const privateProjects = new Set(config.privateProjects ?? []);
16968
- const enrichmentOptIn = !!config.enrichmentOptIn;
16969
17854
  const resolveRepo = createRepoResolver();
16970
17855
  let pushed = 0;
16971
17856
  for (const rollup of rollups) {
16972
17857
  const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
16973
- const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, privateProjects, enrichmentOptIn, resolveRepo);
17858
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
16974
17859
  let artifactSignals = null;
16975
17860
  try {
16976
- artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: [process.cwd()] });
17861
+ const cwdAllowed = shouldSyncProject2(projectRepoName2(process.cwd()), config.syncProjects);
17862
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: cwdAllowed ? [process.cwd()] : [] });
16977
17863
  } catch {
16978
17864
  }
16979
17865
  const payload = buildIngestPayload({
@@ -17012,21 +17898,128 @@ var init_commands = __esm({
17012
17898
  // src/team/sync.ts
17013
17899
  var sync_exports = {};
17014
17900
  __export(sync_exports, {
17901
+ buildSyncLine: () => buildSyncLine,
17015
17902
  runTeamSync: () => runTeamSync
17016
17903
  });
17017
17904
  import { randomUUID as randomUUID4 } from "node:crypto";
17905
+ function syncLogDelivered(body) {
17906
+ const blocks = body?.blocks;
17907
+ return !blocks || blocks.accepted.includes("syncLog");
17908
+ }
17909
+ function levelForStatus(s) {
17910
+ if (s === "ok" || s === "idle") return "info";
17911
+ if (s === "degraded" || s === "failed") return "warn";
17912
+ return "error";
17913
+ }
17914
+ function fmtDuration(ms) {
17915
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
17916
+ }
17917
+ function liveSnapshotStr(snap) {
17918
+ if (!snap) return void 0;
17919
+ const f = snap.fiveHour?.utilization;
17920
+ const s = snap.sevenDay?.utilization;
17921
+ const parts = [
17922
+ typeof f === "number" ? `5h ${Math.round(f)}%` : null,
17923
+ typeof s === "number" ? `7d ${Math.round(s)}%` : null
17924
+ ].filter(Boolean);
17925
+ return parts.length ? parts.join(" / ") : void 0;
17926
+ }
17927
+ function buildSyncLine(status2, trigger, s, elapsedMs, nextSyncMs) {
17928
+ const parts = [`[sync] ${status2}`, trigger];
17929
+ if (s.pushedDays.length) {
17930
+ parts.push(
17931
+ s.pushedDays.length === 1 ? `pushed 1 day (${s.pushedDays[0]})` : `pushed ${s.pushedDays.length} days (${s.pushedDays[0]}\u2192${s.pushedDays[s.pushedDays.length - 1]})`
17932
+ );
17933
+ }
17934
+ if (s.queued) {
17935
+ parts.push(
17936
+ s.queuedDay ? `queued ${s.queued} for retry (${s.queuedDay}, HTTP ${s.queuedStatus})` : `queued ${s.queued} for retry`
17937
+ );
17938
+ }
17939
+ if (s.droppedDays.length) {
17940
+ parts.push(`dropped ${s.droppedDays.length} unrecoverable (${s.droppedDays.join(",")})`);
17941
+ }
17942
+ if (s.recoveredDays?.length) {
17943
+ parts.push(
17944
+ `recovered ${s.recoveredDays.length} dropped day${s.recoveredDays.length === 1 ? "" : "s"} (${s.recoveredDays.join(",")})`
17945
+ );
17946
+ }
17947
+ if (s.queuedDrained) parts.push(`drained ${s.queuedDrained} from backlog`);
17948
+ if (s.idleReason) parts.push(s.idleReason);
17949
+ if (s.live) parts.push(`live ${s.live}`);
17950
+ if (s.usageSnapshots > 0) {
17951
+ parts.push(`usage +${s.usageSnapshots} snapshot${s.usageSnapshots === 1 ? "" : "s"}`);
17952
+ }
17953
+ if (s.accepted?.length) parts.push(`server accepted ${s.accepted.join(",")}`);
17954
+ if (s.skipped && Object.keys(s.skipped).length) {
17955
+ parts.push(
17956
+ `server SKIPPED ${Object.entries(s.skipped).map(([k, v]) => `${k} (${v})`).join("; ")}`
17957
+ );
17958
+ }
17959
+ if (s.errorMsg) parts.push(s.errorMsg);
17960
+ parts.push(fmtDuration(elapsedMs));
17961
+ if (nextSyncMs) parts.push(`next ~${Math.round(nextSyncMs / 6e4)}m`);
17962
+ return parts.join(" \xB7 ");
17963
+ }
17018
17964
  async function runTeamSync(log = noopLog2, configOverride, options = {}) {
17019
17965
  const config = configOverride === void 0 ? readTeamConfig() : configOverride;
17020
17966
  if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
17967
+ if (config.setupPending) {
17968
+ log(
17969
+ "info",
17970
+ buildSyncLine(
17971
+ "idle",
17972
+ options.trigger ?? "auto",
17973
+ {
17974
+ pushedDays: [],
17975
+ droppedDays: [],
17976
+ queued: 0,
17977
+ queuedDrained: 0,
17978
+ usageSnapshots: 0,
17979
+ idleReason: "setup pending \u2014 finish onboarding in the dashboard (/team/onboarding)"
17980
+ },
17981
+ 0,
17982
+ options.nextSyncMs
17983
+ )
17984
+ );
17985
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, setupPending: true };
17986
+ }
17987
+ const startedAt = Date.now();
17988
+ const trigger = options.trigger ?? "auto";
17989
+ const summary = {
17990
+ pushedDays: [],
17991
+ droppedDays: [],
17992
+ queued: 0,
17993
+ queuedDrained: 0,
17994
+ usageSnapshots: 0
17995
+ };
17996
+ const finish = (status2) => {
17997
+ log(
17998
+ levelForStatus(status2),
17999
+ buildSyncLine(status2, trigger, summary, Date.now() - startedAt, options.nextSyncMs)
18000
+ );
18001
+ };
18002
+ const recordVerdict = (body) => {
18003
+ if (body?.blocks) {
18004
+ summary.accepted = [.../* @__PURE__ */ new Set([...summary.accepted ?? [], ...body.blocks.accepted])];
18005
+ summary.skipped = { ...summary.skipped, ...body.blocks.skipped };
18006
+ }
18007
+ };
18008
+ const emit = options.onProgress ?? (() => {
18009
+ });
17021
18010
  try {
18011
+ resetEnsuredSessions();
17022
18012
  const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
17023
- const { toLocalDay: toLocalDay4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
18013
+ const { toLocalDay: toLocalDay4, projectRepoName: projectRepoName2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
17024
18014
  const today = toLocalDay4(Date.now());
17025
- let nextConfig = { ...config };
18015
+ let patches = {};
17026
18016
  const persistConfig = (patch) => {
17027
- nextConfig = { ...nextConfig, ...patch };
17028
- writeTeamConfig(nextConfig);
18017
+ patches = { ...patches, ...patch };
18018
+ const onDisk = readTeamConfig();
18019
+ if (!onDisk) return;
18020
+ writeTeamConfig({ ...onDisk, ...patches });
17029
18021
  };
18022
+ const pendingLog = readPendingSyncLog(config.lastSyncedLogAt);
17030
18023
  const collectedCommands = /* @__PURE__ */ new Map();
17031
18024
  const collectCommands = (commands) => {
17032
18025
  if (!commands) return;
@@ -17064,128 +18057,259 @@ async function runTeamSync(log = noopLog2, configOverride, options = {}) {
17064
18057
  log("warn", `team command dispatch error: ${err.message}`);
17065
18058
  }
17066
18059
  };
18060
+ const isValidationPoison = (status3) => status3 === 400 || status3 === 422;
18061
+ const drainBacklog = async () => {
18062
+ let drained = 0;
18063
+ const backlog = dequeuePayloads();
18064
+ for (let i = 0; i < backlog.length; i++) {
18065
+ const queuedPayload = backlog[i];
18066
+ if (queuedPayload.richRollup?.projects?.some((p) => !shouldSyncProject(p.project, config.syncProjects))) {
18067
+ log("info", "team push: dropped queued payload containing now-excluded project(s)");
18068
+ continue;
18069
+ }
18070
+ const qResult = await pushToTeamServer(config, queuedPayload);
18071
+ if (!qResult.ok) {
18072
+ if (isValidationPoison(qResult.status)) {
18073
+ log("warn", `team push: queued payload rejected (HTTP ${qResult.status}); dropping unrecoverable item`);
18074
+ continue;
18075
+ }
18076
+ for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
18077
+ break;
18078
+ }
18079
+ collectCommands(qResult.body?.commands);
18080
+ drained++;
18081
+ }
18082
+ return drained;
18083
+ };
18084
+ emit({ type: "phase", phase: "usage-backfill" });
17067
18085
  const usageBackfill = await runTeamBackfill(log, USAGE_LOG4, config, {
17068
18086
  sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
17069
18087
  });
18088
+ emit({
18089
+ type: "usage",
18090
+ inserted: usageBackfill.insertedSnapshots ?? 0,
18091
+ alreadyKnown: usageBackfill.skippedSnapshots ?? 0
18092
+ });
17070
18093
  if (usageBackfill.lastSnapshotAt) {
17071
18094
  persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
17072
18095
  }
17073
- const sessions = await listSessions2({ limit: 1e4 });
17074
- const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
18096
+ const unfilteredSessions = await listSessions2({ limit: 1e4 });
18097
+ const sessions = filterSyncedSessions(unfilteredSessions, config.syncProjects);
18098
+ const allRollups = buildRollupsForRange(sessions);
18099
+ const inWindow = (day2) => !config.lastSyncedDay || day2 >= config.lastSyncedDay;
18100
+ const rollups = allRollups.filter((r) => inWindow(r.day));
18101
+ if (config.syncProjects) {
18102
+ const filteredDays = new Set(allRollups.map((r) => r.day));
18103
+ for (const r of buildRollupsForRange(unfilteredSessions)) {
18104
+ if (!filteredDays.has(r.day) && inWindow(r.day)) {
18105
+ rollups.push({
18106
+ day: r.day,
18107
+ agentTimeMs: 0,
18108
+ sessions: 0,
18109
+ uniqueSessions: 0,
18110
+ toolCalls: 0,
18111
+ turns: 0,
18112
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
18113
+ });
18114
+ }
18115
+ }
18116
+ rollups.sort((a, b) => a.day < b.day ? -1 : 1);
18117
+ }
18118
+ emit({ type: "phase", phase: "activity", totalDays: rollups.length });
17075
18119
  const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG4) ?? void 0;
17076
18120
  const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
17077
18121
  const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
17078
18122
  if (rollups.length === 0) {
17079
18123
  const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
17080
18124
  if (!hasLiveData) {
17081
- if (usageBackfill.sentSnapshots === 0) log("info", "team push: nothing to sync");
18125
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
18126
+ if (usageBackfill.sentSnapshots > 0) {
18127
+ finish("ok");
18128
+ } else {
18129
+ summary.idleReason = "nothing to sync";
18130
+ finish("idle");
18131
+ }
18132
+ emit({ type: "done", pushed: 0, queued: 0, pushedDays: summary.pushedDays });
17082
18133
  return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
17083
18134
  }
17084
- const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks });
18135
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
18136
+ summary.live = liveSnapshotStr(usageSnapshot);
18137
+ const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks, syncLog: pendingLog.lines });
17085
18138
  const result = await pushToTeamServer(config, payload);
17086
18139
  if (!result.ok) {
18140
+ if (isValidationPoison(result.status)) {
18141
+ const errLine2 = `team push: live-only payload rejected (HTTP ${result.status}); dropping unrecoverable payload`;
18142
+ writeLastPushFailure(payload, errLine2);
18143
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} (validation) \u2014 dropped unrecoverable`;
18144
+ finish("error");
18145
+ emit({ type: "done", pushed: 0, queued: 0, pushedDays: summary.pushedDays });
18146
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
18147
+ }
17087
18148
  const errLine = `team push failed (${result.status})`;
17088
- log("warn", `${errLine}; queueing`);
17089
18149
  writeLastPushFailure(payload, errLine);
17090
18150
  enqueuePayload(payload);
18151
+ summary.queued = 1;
18152
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} \u2014 queued for retry`;
18153
+ finish("failed");
18154
+ emit({ type: "done", pushed: 0, queued: 1, pushedDays: summary.pushedDays });
17091
18155
  return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
17092
18156
  }
17093
18157
  writeLastPushSuccess(payload);
17094
- collectCommands(result.body?.commands);
17095
- let queuedDrained2 = 0;
17096
- const backlog = dequeuePayloads();
17097
- for (let i = 0; i < backlog.length; i++) {
17098
- const qResult = await pushToTeamServer(config, backlog[i]);
17099
- if (!qResult.ok) {
17100
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
17101
- break;
17102
- }
17103
- collectCommands(qResult.body?.commands);
17104
- queuedDrained2++;
18158
+ if (pendingLog.watermark && syncLogDelivered(result.body)) {
18159
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
17105
18160
  }
17106
- log("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
18161
+ collectCommands(result.body?.commands);
18162
+ recordVerdict(result.body);
18163
+ const queuedDrained2 = await drainBacklog();
18164
+ summary.queuedDrained = queuedDrained2;
18165
+ summary.idleReason = "no new daily activity";
18166
+ finish(summary.skipped && Object.keys(summary.skipped).length ? "degraded" : "idle");
17107
18167
  await dispatchAndReport();
18168
+ emit({ type: "done", pushed: 1, queued: 0, pushedDays: summary.pushedDays });
17108
18169
  return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
17109
18170
  }
17110
18171
  let pushed = 0;
17111
18172
  let queued = 0;
17112
18173
  let failedDay;
17113
- let lastPushedDay;
17114
- const privateProjects = new Set(config.privateProjects ?? []);
17115
- const enrichmentOptIn = !!config.enrichmentOptIn;
18174
+ const droppedDaysBefore = config.droppedDays ?? [];
18175
+ const workingDropped = [...droppedDaysBefore];
18176
+ let syncLogWasDelivered = false;
17116
18177
  const resolveRepo = createRepoResolver();
17117
18178
  const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
17118
- for (let i = 0; i < rollups.length; i++) {
17119
- const rollup = rollups[i];
17120
- const isLatest = i === rollups.length - 1;
18179
+ const buildDayPayload = (rollup, opts) => {
17121
18180
  const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
17122
- const richBlocks = buildRichBlocksForDay(
17123
- rollup.day,
17124
- daySessions,
17125
- privateProjects,
17126
- enrichmentOptIn,
17127
- resolveRepo
17128
- );
18181
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
18182
+ const tombstone = daySessions.length === 0 && !!config.syncProjects;
18183
+ const emptyRich = tombstone ? {
18184
+ projects: [],
18185
+ workingShapes: [],
18186
+ concurrencyPeak: 0,
18187
+ parallelMinutes: 0,
18188
+ longAutonomous: { count: 0, totalMin: 0, maxSingleMin: 0 },
18189
+ toolErrors: 0,
18190
+ skillsLoaded: [],
18191
+ subagentsDispatched: [],
18192
+ brainstormWarmupSessions: 0,
18193
+ planModeUsed: 0,
18194
+ prs: 0,
18195
+ commits: 0,
18196
+ pushes: 0
18197
+ } : void 0;
17129
18198
  let artifactSignals = null;
17130
18199
  try {
17131
- artifactSignals = probeArtifactSignals2({
17132
- day: rollup.day,
17133
- extraRoots: [process.cwd()]
17134
- });
18200
+ const cwdAllowed = shouldSyncProject(projectRepoName2(process.cwd()), config.syncProjects);
18201
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: cwdAllowed ? [process.cwd()] : [] });
17135
18202
  } catch {
17136
18203
  }
17137
- const payload = buildIngestPayload({
18204
+ return buildIngestPayload({
17138
18205
  rollup,
17139
- richExtras: richBlocks?.rich,
17140
- enrichedExtras: richBlocks?.enriched,
18206
+ richExtras: richBlocks?.rich ?? emptyRich,
18207
+ enrichedExtras: richBlocks?.enriched ?? (tombstone ? { outcomeMix: {}, helpfulnessMix: {}, goalMix: {} } : void 0),
17141
18208
  artifactSignals: artifactSignals ?? void 0,
17142
- usageSnapshot: isLatest ? usageSnapshot : void 0,
18209
+ // Current-cycle data only on the latest day so older days aren't tagged
18210
+ // with today's snapshot / peaks.
18211
+ usageSnapshot: opts.latest ? usageSnapshot : void 0,
17143
18212
  planTier,
17144
- // Same rationale as usageSnapshot — current cycle data only on the
17145
- // latest rollup so older days don't get tagged with today's peaks.
17146
- cyclePeaks: isLatest ? cyclePeaks : void 0
18213
+ cyclePeaks: opts.latest ? cyclePeaks : void 0,
18214
+ syncLog: opts.syncLog
18215
+ });
18216
+ };
18217
+ const rollupsDesc = [...rollups].reverse();
18218
+ for (let i = 0; i < rollupsDesc.length; i++) {
18219
+ const rollup = rollupsDesc[i];
18220
+ const isLatest = i === 0;
18221
+ const payload = buildDayPayload(rollup, {
18222
+ latest: isLatest,
18223
+ syncLog: i === 0 ? pendingLog.lines : void 0
17147
18224
  });
17148
18225
  const result = await pushToTeamServer(config, payload);
17149
18226
  if (!result.ok) {
18227
+ if (isValidationPoison(result.status)) {
18228
+ const errLine2 = `team push: ${rollup.day} rejected (HTTP ${result.status}); skipping past unrecoverable day`;
18229
+ writeLastPushFailure(payload, errLine2);
18230
+ summary.droppedDays.push(rollup.day);
18231
+ if (!workingDropped.includes(rollup.day)) workingDropped.push(rollup.day);
18232
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "dropped" });
18233
+ continue;
18234
+ }
17150
18235
  const errLine = `team push failed on ${rollup.day} (${result.status})`;
17151
- log("warn", `${errLine}; queueing`);
17152
18236
  writeLastPushFailure(payload, errLine);
17153
18237
  enqueuePayload(payload);
17154
18238
  queued++;
18239
+ summary.queuedDay = rollup.day;
18240
+ summary.queuedStatus = result.status;
17155
18241
  failedDay = rollup.day;
18242
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "queued" });
17156
18243
  break;
17157
18244
  }
17158
18245
  writeLastPushSuccess(payload);
17159
18246
  collectCommands(result.body?.commands);
18247
+ recordVerdict(result.body);
18248
+ if (i === 0) syncLogWasDelivered = syncLogDelivered(result.body);
17160
18249
  pushed++;
17161
- lastPushedDay = rollup.day;
17162
- }
17163
- if (failedDay) {
17164
- if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
17165
- } else {
17166
- persistConfig({ lastSyncedDay: today });
18250
+ summary.pushedDays.push(rollup.day);
18251
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "pushed" });
17167
18252
  }
17168
- let queuedDrained = 0;
17169
18253
  if (!failedDay) {
17170
- const backlog = dequeuePayloads();
17171
- for (let i = 0; i < backlog.length; i++) {
17172
- const qResult = await pushToTeamServer(config, backlog[i]);
17173
- if (!qResult.ok) {
17174
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
17175
- break;
18254
+ const retryDay = [...droppedDaysBefore].sort().find((d) => !summary.droppedDays.includes(d));
18255
+ if (retryDay && summary.pushedDays.includes(retryDay)) {
18256
+ const idx = workingDropped.indexOf(retryDay);
18257
+ if (idx >= 0) workingDropped.splice(idx, 1);
18258
+ summary.recoveredDays = [retryDay];
18259
+ }
18260
+ const retryRollup = retryDay && !summary.pushedDays.includes(retryDay) ? allRollups.find((r) => r.day === retryDay) ?? (config.syncProjects ? {
18261
+ day: retryDay,
18262
+ agentTimeMs: 0,
18263
+ sessions: 0,
18264
+ uniqueSessions: 0,
18265
+ toolCalls: 0,
18266
+ turns: 0,
18267
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
18268
+ } : void 0) : void 0;
18269
+ if (retryDay && retryRollup) {
18270
+ const payload = buildDayPayload(retryRollup, { latest: false });
18271
+ const result = await pushToTeamServer(config, payload);
18272
+ if (result.ok) {
18273
+ writeLastPushSuccess(payload);
18274
+ collectCommands(result.body?.commands);
18275
+ recordVerdict(result.body);
18276
+ const idx = workingDropped.indexOf(retryDay);
18277
+ if (idx >= 0) workingDropped.splice(idx, 1);
18278
+ summary.recoveredDays = [retryDay];
18279
+ } else {
18280
+ writeLastPushFailure(payload, `team push: dropped-day retry ${retryDay} failed (HTTP ${result.status})`);
17176
18281
  }
17177
- collectCommands(qResult.body?.commands);
17178
- queuedDrained++;
17179
18282
  }
17180
18283
  }
17181
- if (pushed > 0) {
17182
- log("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
17183
- }
18284
+ const nextDropped = [...new Set(workingDropped)].sort().slice(-90);
18285
+ const prevDropped = [...new Set(droppedDaysBefore)].sort();
18286
+ const droppedChanged = nextDropped.length !== prevDropped.length || nextDropped.some((d, i) => d !== prevDropped[i]);
18287
+ const droppedPatch = droppedChanged ? { droppedDays: nextDropped } : {};
18288
+ if (failedDay) {
18289
+ if (droppedChanged) persistConfig(droppedPatch);
18290
+ } else {
18291
+ persistConfig({ lastSyncedDay: today, ...droppedPatch });
18292
+ }
18293
+ if (syncLogWasDelivered && pendingLog.watermark) {
18294
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
18295
+ }
18296
+ const queuedDrained = !failedDay ? await drainBacklog() : 0;
18297
+ summary.queued = queued;
18298
+ summary.queuedDrained = queuedDrained;
18299
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
18300
+ let status2;
18301
+ if (failedDay) status2 = "failed";
18302
+ else if (summary.droppedDays.length || summary.skipped && Object.keys(summary.skipped).length) status2 = "degraded";
18303
+ else status2 = "ok";
18304
+ finish(status2);
17184
18305
  await dispatchAndReport();
18306
+ emit({ type: "done", pushed, queued, pushedDays: summary.pushedDays });
17185
18307
  return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
17186
18308
  } catch (err) {
17187
18309
  const message = err.message;
17188
- log("warn", `team push error: ${message}`);
18310
+ summary.errorMsg = `sync aborted: ${message}`;
18311
+ emit({ type: "error", message });
18312
+ finish("error");
17189
18313
  writeLastPushFailure(
17190
18314
  { ingestId: "n/a", observedAt: (/* @__PURE__ */ new Date()).toISOString() },
17191
18315
  `team push error: ${message}`
@@ -17249,8 +18373,13 @@ async function buildCyclePeaksForPush(planTier, loadCurve) {
17249
18373
  return out.slice(-maxCycles);
17250
18374
  };
17251
18375
  return {
18376
+ // 7d cycle history cap (26 ≈ six months) MUST stay in lock-step with two
18377
+ // downstream sites or the strip silently truncates: the server read
18378
+ // (loadMembership7dCyclePeaks maxCyclesPerMember in plan-queries.ts) and
18379
+ // the renderer (CyclePeaksStrip maxBars in member-plan-block.tsx). The 5h
18380
+ // cap (24) is independent — leave it.
17252
18381
  fiveHour: peaksFor("cycle_end_5h", "real_5h", "pred_5h", 24),
17253
- sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 12)
18382
+ sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 26)
17254
18383
  };
17255
18384
  }
17256
18385
  var inFlightCommands, USAGE_LOG4, PROFILE_CACHE2, noopLog2;
@@ -17262,6 +18391,7 @@ var init_sync = __esm({
17262
18391
  init_git_remote();
17263
18392
  init_queue2();
17264
18393
  init_backfill();
18394
+ init_sync_log();
17265
18395
  init_last_push();
17266
18396
  init_commands();
17267
18397
  init_profile();
@@ -17274,15 +18404,47 @@ var init_sync = __esm({
17274
18404
  }
17275
18405
  });
17276
18406
 
18407
+ // src/daemon-log.ts
18408
+ var daemon_log_exports = {};
18409
+ __export(daemon_log_exports, {
18410
+ appendDaemonLogLine: () => appendDaemonLogLine
18411
+ });
18412
+ import { appendFileSync as appendFileSync5 } from "node:fs";
18413
+ function appendDaemonLogLine(level, msg) {
18414
+ try {
18415
+ appendFileSync5(
18416
+ cclensPath("daemon.log"),
18417
+ `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${msg}
18418
+ `,
18419
+ "utf8"
18420
+ );
18421
+ } catch {
18422
+ }
18423
+ }
18424
+ var init_daemon_log = __esm({
18425
+ "src/daemon-log.ts"() {
18426
+ "use strict";
18427
+ init_fs();
18428
+ }
18429
+ });
18430
+
17277
18431
  // src/team/join.ts
17278
18432
  var join_exports = {};
17279
18433
  __export(join_exports, {
18434
+ carryOverSyncProjects: () => carryOverSyncProjects,
17280
18435
  joinTeam: () => joinTeam
17281
18436
  });
18437
+ function carryOverSyncProjects(existing, serverUrl, teamSlug) {
18438
+ if (existing && existing.serverUrl === serverUrl && existing.teamSlug === teamSlug) {
18439
+ return existing.syncProjects;
18440
+ }
18441
+ return void 0;
18442
+ }
17282
18443
  async function joinTeam(args2) {
17283
- const [serverUrl, bearerToken] = args2;
18444
+ const noBrowser = args2.includes("--no-browser");
18445
+ const [serverUrl, bearerToken] = args2.filter((a) => !a.startsWith("--"));
17284
18446
  if (!serverUrl || !bearerToken) {
17285
- console.error("Usage: fleetlens team join <server-url> <device-token>");
18447
+ console.error("Usage: fleetlens team join <server-url> <device-token> [--no-browser]");
17286
18448
  console.error("");
17287
18449
  console.error("Get the device token from the dashboard after signup,");
17288
18450
  console.error("or from Settings \u2192 My device token.");
@@ -17298,45 +18460,93 @@ async function joinTeam(args2) {
17298
18460
  process.exit(1);
17299
18461
  }
17300
18462
  const data = await res.json();
18463
+ const existing = readTeamConfig();
17301
18464
  const config = {
17302
18465
  serverUrl,
17303
18466
  memberId: data.membership.id,
17304
18467
  bearerToken,
17305
18468
  teamSlug: data.team.slug,
17306
18469
  teamName: data.team.name,
17307
- pairedAt: (/* @__PURE__ */ new Date()).toISOString()
18470
+ pairedAt: (/* @__PURE__ */ new Date()).toISOString(),
18471
+ // Fence off any daemon.log history from a PREVIOUS team so the first push
18472
+ // doesn't sweep the prior team's [sync] lines onto this team's log. The
18473
+ // pair run's own [sync] line is written after this instant, so it still
18474
+ // uploads on the next sync.
18475
+ lastSyncedLogAt: (/* @__PURE__ */ new Date()).toISOString(),
18476
+ syncProjects: carryOverSyncProjects(existing, serverUrl, data.team.slug),
18477
+ // Browser path: gate the daemon until the wizard's "Start syncing".
18478
+ ...noBrowser ? {} : { setupPending: true }
17308
18479
  };
17309
18480
  writeTeamConfig(config);
17310
18481
  console.log(`Paired with "${data.team.name}" as ${data.user.displayName || data.user.email}`);
17311
18482
  console.log(` role: ${data.membership.role}`);
17312
- console.log(" Syncing local history\u2026");
17313
- const sync = await runTeamSync(void 0, config, { forceUsageBackfill: true });
17314
- const backfill = sync.usageBackfill;
17315
- if (backfill?.error) {
17316
- console.log(` \u26A0 Usage history sync failed: ${backfill.error} \u2014 daemon will retry automatically.`);
17317
- } else if ((backfill?.insertedSnapshots ?? 0) > 0) {
17318
- console.log(
17319
- ` \u2713 Synced ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
17320
- );
17321
- } else if ((backfill?.sentSnapshots ?? 0) > 0) {
17322
- console.log(` \u2713 ${backfill.sentSnapshots} usage snapshots already on server (deduped on capture time).`);
17323
- } else {
17324
- console.log(" \xB7 No local usage snapshots yet \u2014 daemon will start collecting.");
18483
+ if (noBrowser) {
18484
+ console.log(" Syncing local history\u2026");
18485
+ const sync = await runTeamSync(logToDaemonLog, config, {
18486
+ forceUsageBackfill: true,
18487
+ trigger: "pair"
18488
+ });
18489
+ const backfill = sync.usageBackfill;
18490
+ if (backfill?.error) {
18491
+ console.log(` \u26A0 Usage history sync failed: ${backfill.error} \u2014 daemon will retry automatically.`);
18492
+ } else if ((backfill?.insertedSnapshots ?? 0) > 0) {
18493
+ console.log(
18494
+ ` \u2713 Synced ${backfill.insertedSnapshots} usage snapshot${backfill.insertedSnapshots === 1 ? "" : "s"} from local history.`
18495
+ );
18496
+ } else if ((backfill?.sentSnapshots ?? 0) > 0) {
18497
+ console.log(` \u2713 ${backfill.sentSnapshots} usage snapshots already on server (deduped on capture time).`);
18498
+ } else {
18499
+ console.log(" \xB7 No local usage snapshots yet \u2014 daemon will start collecting.");
18500
+ }
18501
+ if (sync.error) {
18502
+ console.log(` \u26A0 Team sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
18503
+ } else if (sync.pushed > 0) {
18504
+ console.log(` \u2713 Synced ${sync.pushed} activity payload${sync.pushed === 1 ? "" : "s"}.`);
18505
+ } else {
18506
+ console.log(" \xB7 No new session activity to push.");
18507
+ }
18508
+ console.log(" Daemon polls every 5 minutes \u2014 next push happens automatically.");
18509
+ const { ensureTeamAutostart: ensureNoBrowser } = await Promise.resolve().then(() => (init_autostart(), autostart_exports));
18510
+ await ensureNoBrowser();
18511
+ return;
17325
18512
  }
17326
- if (sync.error) {
17327
- console.log(` \u26A0 Team sync failed: ${sync.error} \u2014 will retry on next daemon cycle.`);
17328
- } else if (sync.pushed > 0) {
17329
- console.log(` \u2713 Synced ${sync.pushed} activity payload${sync.pushed === 1 ? "" : "s"}.`);
17330
- } else {
17331
- console.log(" \xB7 No new session activity to push.");
18513
+ try {
18514
+ const { fetchUsage: fetchUsage2 } = await Promise.resolve().then(() => (init_api(), api_exports));
18515
+ const { appendSnapshot: appendSnapshot2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
18516
+ const { cclensPath: cclensPath2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
18517
+ appendSnapshot2(cclensPath2("usage.jsonl"), await fetchUsage2());
18518
+ } catch {
17332
18519
  }
17333
- console.log(" Daemon polls every 5 minutes \u2014 next push happens automatically.");
18520
+ const { ensureTeamAutostart: ensureTeamAutostart2 } = await Promise.resolve().then(() => (init_autostart(), autostart_exports));
18521
+ await ensureTeamAutostart2();
18522
+ console.log(" Opening your browser to finish setup\u2026");
18523
+ let url;
18524
+ try {
18525
+ const server = await ensureCurrentServer({});
18526
+ url = `http://localhost:${server.port}/team/onboarding`;
18527
+ const daemon2 = startDaemonSilent();
18528
+ if (!daemon2.started && !daemon2.alreadyRunning) {
18529
+ console.warn(` \u26A0 Daemon failed to start \u2014 ${daemon2.error}`);
18530
+ }
18531
+ } catch (err) {
18532
+ console.error(` \u26A0 Could not start the dashboard: ${err.message}`);
18533
+ console.error(" Re-run 'fleetlens team join' after fixing it, or use --no-browser to sync everything from the terminal.");
18534
+ process.exit(1);
18535
+ }
18536
+ openBrowser(url);
18537
+ console.log(` Continue in your browser: ${url}`);
18538
+ console.log(" Nothing syncs until you finish setup there.");
17334
18539
  }
18540
+ var logToDaemonLog;
17335
18541
  var init_join = __esm({
17336
18542
  "src/team/join.ts"() {
17337
18543
  "use strict";
17338
18544
  init_config();
17339
18545
  init_sync();
18546
+ init_daemon_log();
18547
+ init_server();
18548
+ init_daemon();
18549
+ logToDaemonLog = appendDaemonLogLine;
17340
18550
  }
17341
18551
  });
17342
18552
 
@@ -17415,12 +18625,12 @@ var logs_exports = {};
17415
18625
  __export(logs_exports, {
17416
18626
  teamLogs: () => teamLogs
17417
18627
  });
17418
- import { readFileSync as readFileSync17 } from "node:fs";
18628
+ import { readFileSync as readFileSync19 } from "node:fs";
17419
18629
  async function teamLogs() {
17420
18630
  const logPath = cclensPath("daemon.log");
17421
18631
  try {
17422
- const lines = readFileSync17(logPath, "utf8").trim().split("\n");
17423
- const teamLines = lines.filter((l) => l.includes("team "));
18632
+ const lines = readFileSync19(logPath, "utf8").trim().split("\n");
18633
+ const teamLines = lines.filter((l) => l.includes("[sync] ") || l.includes("team "));
17424
18634
  if (teamLines.length === 0) {
17425
18635
  console.log("No team-related log entries found.");
17426
18636
  return;
@@ -17468,20 +18678,42 @@ async function team(args2) {
17468
18678
  break;
17469
18679
  }
17470
18680
  case "sync": {
18681
+ const progressJson = args2.includes("--progress-json");
17471
18682
  const { runTeamSync: runTeamSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
17472
- const outcome = await runTeamSync2((level, msg) => console.log(`[${level}] ${msg}`));
18683
+ const { appendDaemonLogLine: appendDaemonLogLine2 } = await Promise.resolve().then(() => (init_daemon_log(), daemon_log_exports));
18684
+ const outcome = await runTeamSync2(
18685
+ (level, msg) => {
18686
+ if (!progressJson) console.log(`[${level}] ${msg}`);
18687
+ appendDaemonLogLine2(level, msg);
18688
+ },
18689
+ void 0,
18690
+ {
18691
+ trigger: "manual",
18692
+ onProgress: progressJson ? (ev) => console.log(JSON.stringify(ev)) : void 0
18693
+ }
18694
+ );
17473
18695
  if (!outcome.paired) {
17474
18696
  console.error("Not paired. Run 'fleetlens team join <url> <device-token>' first.");
17475
18697
  process.exit(1);
17476
18698
  }
18699
+ if (outcome.setupPending) {
18700
+ if (progressJson) {
18701
+ console.log(JSON.stringify({ type: "error", message: "setup pending \u2014 selection not saved yet" }));
18702
+ } else {
18703
+ console.error("Setup pending \u2014 nothing synced. Finish onboarding in the dashboard (/team/onboarding) or re-run 'fleetlens team join'.");
18704
+ }
18705
+ process.exit(1);
18706
+ }
17477
18707
  const inserted = outcome.usageBackfill?.insertedSnapshots ?? 0;
17478
18708
  const madeProgress = outcome.pushed > 0 || outcome.queuedDrained > 0 || inserted > 0;
17479
18709
  if (outcome.error || outcome.usageBackfill?.error && !madeProgress) process.exit(1);
17480
- const sent = outcome.usageBackfill?.sentSnapshots ?? 0;
17481
- const usageChecked = sent > 0 ? `, ${sent} usage snapshot${sent === 1 ? "" : "s"} checked` : "";
17482
- console.log(
17483
- `\u2713 ${outcome.pushed} activity payload${outcome.pushed === 1 ? "" : "s"} pushed` + usageChecked + (outcome.queuedDrained ? `, ${outcome.queuedDrained} queued retried` : "") + (outcome.queued ? `, ${outcome.queued} queued for retry (will fire next cycle)` : "")
17484
- );
18710
+ if (!progressJson) {
18711
+ const sent = outcome.usageBackfill?.sentSnapshots ?? 0;
18712
+ const usageChecked = sent > 0 ? `, ${sent} usage snapshot${sent === 1 ? "" : "s"} checked` : "";
18713
+ console.log(
18714
+ `\u2713 ${outcome.pushed} activity payload${outcome.pushed === 1 ? "" : "s"} pushed` + usageChecked + (outcome.queuedDrained ? `, ${outcome.queuedDrained} queued retried` : "") + (outcome.queued ? `, ${outcome.queued} queued for retry (will fire next cycle)` : "")
18715
+ );
18716
+ }
17485
18717
  break;
17486
18718
  }
17487
18719
  case "backfill": {
@@ -17505,7 +18737,7 @@ async function team(args2) {
17505
18737
  default:
17506
18738
  console.log(`Usage: fleetlens team <join|status|leave|logs|sync|backfill>
17507
18739
 
17508
- join <url> <device-token> Pair daemon with a team server (auto-backfills usage history)
18740
+ join <url> <device-token> [--no-browser] Pair with a team server (opens browser setup; --no-browser syncs everything immediately)
17509
18741
  status Show team pairing state and sync info
17510
18742
  leave Unpair from the team server
17511
18743
  logs Show recent team-related daemon log entries
@@ -17525,7 +18757,7 @@ __export(runs_exports, {
17525
18757
  runs: () => runs
17526
18758
  });
17527
18759
  import { execSync as execSync2 } from "node:child_process";
17528
- import { existsSync as existsSync16, readFileSync as readFileSync18, readdirSync as readdirSync5, statSync as statSync6 } from "node:fs";
18760
+ import { existsSync as existsSync16, readFileSync as readFileSync20, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
17529
18761
  import { join as join14 } from "node:path";
17530
18762
  async function runs(args2) {
17531
18763
  const json = args2.includes("--json");
@@ -17611,7 +18843,7 @@ function listActiveRuns() {
17611
18843
  }
17612
18844
  function readSpend(sinceMs) {
17613
18845
  if (!existsSync16(SPEND_PATH)) return [];
17614
- const lines = readFileSync18(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
18846
+ const lines = readFileSync20(SPEND_PATH, "utf-8").split("\n").filter((l) => l.trim());
17615
18847
  const out = [];
17616
18848
  for (const line of lines) {
17617
18849
  try {
@@ -17736,7 +18968,7 @@ function listTraceFiles() {
17736
18968
  const entries2 = readdirSync5(RUNS_DIR2).filter((f) => f.endsWith(".jsonl"));
17737
18969
  return entries2.map((f) => {
17738
18970
  const path7 = join14(RUNS_DIR2, f);
17739
- const st = statSync6(path7);
18971
+ const st = statSync7(path7);
17740
18972
  return { runId: f.replace(/\.jsonl$/, ""), path: path7, mtimeMs: st.mtimeMs };
17741
18973
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
17742
18974
  }
@@ -17760,7 +18992,7 @@ function resolveRunId(arg) {
17760
18992
  return null;
17761
18993
  }
17762
18994
  function readTrace(path7) {
17763
- return readFileSync18(path7, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
18995
+ return readFileSync20(path7, "utf-8").split("\n").filter((l) => l.trim()).map((l) => {
17764
18996
  try {
17765
18997
  return JSON.parse(l);
17766
18998
  } catch {
@@ -17792,13 +19024,13 @@ recent runs:`);
17792
19024
  const events = readTrace(r.path);
17793
19025
  printTrace(r.runId, events);
17794
19026
  if (!follow) return;
17795
- let lastSize = statSync6(r.path).size;
19027
+ let lastSize = statSync7(r.path).size;
17796
19028
  let lastIdx = events.length;
17797
19029
  console.log("\n \u27F6 following \xB7 ^C to quit\n");
17798
19030
  process.on("SIGINT", () => process.exit(0));
17799
19031
  while (true) {
17800
19032
  await new Promise((res) => setTimeout(res, 500));
17801
- const st = statSync6(r.path);
19033
+ const st = statSync7(r.path);
17802
19034
  if (st.size === lastSize) continue;
17803
19035
  lastSize = st.size;
17804
19036
  const all = readTrace(r.path);
@@ -17947,7 +19179,7 @@ async function main() {
17947
19179
  case "version":
17948
19180
  case "--version":
17949
19181
  case "-v":
17950
- console.log(`fleetlens ${"0.13.2"}`);
19182
+ console.log(`fleetlens ${"0.15.0"}`);
17951
19183
  break;
17952
19184
  case "help":
17953
19185
  case "--help":
@@ -17976,7 +19208,7 @@ Advanced:
17976
19208
  autostart <install|uninstall|status> Run the usage daemon at login (macOS launchd)
17977
19209
 
17978
19210
  Team:
17979
- team join <url> <token> Pair with a team server
19211
+ team join <url> <token> Pair with a team server (browser setup)
17980
19212
  team status Show team pairing state
17981
19213
  team leave Unpair from team server
17982
19214
  team logs Show team-related daemon logs