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
@@ -3977,13 +3977,138 @@ async function readJsonl2(filePath) {
3977
3977
  }
3978
3978
  return out;
3979
3979
  }
3980
+ function dedupeReplayedLines(lines) {
3981
+ const keepAt = /* @__PURE__ */ new Map();
3982
+ lines.forEach((line, i) => {
3983
+ const rec = line;
3984
+ if (typeof rec.uuid !== "string")
3985
+ return;
3986
+ const prev = keepAt.get(rec.uuid);
3987
+ if (prev === void 0) {
3988
+ keepAt.set(rec.uuid, i);
3989
+ return;
3990
+ }
3991
+ const prevHasTs = lines[prev].timestamp !== void 0;
3992
+ if (!prevHasTs && rec.timestamp !== void 0)
3993
+ keepAt.set(rec.uuid, i);
3994
+ });
3995
+ return lines.filter((line, i) => {
3996
+ const rec = line;
3997
+ return typeof rec.uuid !== "string" || keepAt.get(rec.uuid) === i;
3998
+ });
3999
+ }
4000
+ function textFromContent(content) {
4001
+ if (typeof content === "string")
4002
+ return content;
4003
+ if (Array.isArray(content)) {
4004
+ const parts = [];
4005
+ for (const b of content) {
4006
+ if (b && typeof b === "object" && b.type === "text") {
4007
+ const t = b.text;
4008
+ if (typeof t === "string")
4009
+ parts.push(t);
4010
+ }
4011
+ }
4012
+ if (parts.length)
4013
+ return parts.join("\n");
4014
+ }
4015
+ return void 0;
4016
+ }
4017
+ function partitionCoworkLines(lines) {
4018
+ const mainLines = [];
4019
+ const subagentGroups = /* @__PURE__ */ new Map();
4020
+ for (const line of lines) {
4021
+ const ptid = line.parent_tool_use_id;
4022
+ if (typeof ptid === "string") {
4023
+ const g = subagentGroups.get(ptid);
4024
+ if (g)
4025
+ g.push(line);
4026
+ else
4027
+ subagentGroups.set(ptid, [line]);
4028
+ } else {
4029
+ mainLines.push(line);
4030
+ }
4031
+ }
4032
+ return { mainLines, subagentGroups };
4033
+ }
4034
+ function buildCoworkSubagents(subagentGroups, allLines, sessionStartMs) {
4035
+ if (subagentGroups.size === 0)
4036
+ return [];
4037
+ const refs = /* @__PURE__ */ new Map();
4038
+ for (const line of allLines) {
4039
+ const rec = line;
4040
+ const content = rec.message?.content;
4041
+ if (!Array.isArray(content))
4042
+ continue;
4043
+ for (const b of content) {
4044
+ if (!b || typeof b !== "object")
4045
+ continue;
4046
+ const block = b;
4047
+ if (block.type === "tool_use" && block.name === "Agent" && typeof block.id === "string") {
4048
+ const input = block.input ?? {};
4049
+ const ref = refs.get(block.id) ?? {};
4050
+ if (typeof rec.uuid === "string")
4051
+ ref.parentUuid = rec.uuid;
4052
+ if (typeof input.description === "string")
4053
+ ref.description = input.description;
4054
+ if (typeof input.subagent_type === "string")
4055
+ ref.agentType = input.subagent_type;
4056
+ if (typeof input.prompt === "string")
4057
+ ref.prompt = input.prompt;
4058
+ refs.set(block.id, ref);
4059
+ } else if (block.type === "tool_result" && typeof block.tool_use_id === "string" && subagentGroups.has(block.tool_use_id)) {
4060
+ const ref = refs.get(block.tool_use_id) ?? {};
4061
+ ref.finalText = textFromContent(block.content) ?? ref.finalText;
4062
+ refs.set(block.tool_use_id, ref);
4063
+ }
4064
+ }
4065
+ }
4066
+ const runs = [];
4067
+ for (const [toolUseId, groupLines] of subagentGroups) {
4068
+ const summary = summarizeSubagentLines(groupLines);
4069
+ const ref = refs.get(toolUseId) ?? {};
4070
+ const finalText = ref.finalText ?? summary.finalText;
4071
+ const finalPreview = finalText ? finalText.replace(/\s+/g, " ").trim().slice(0, 240) : summary.finalPreview;
4072
+ const startTOffsetMs = sessionStartMs !== void 0 && summary.startMs !== void 0 ? Math.max(0, summary.startMs - sessionStartMs) : void 0;
4073
+ const endTOffsetMs = sessionStartMs !== void 0 && summary.endMs !== void 0 ? Math.max(0, summary.endMs - sessionStartMs) : void 0;
4074
+ const durationMs = summary.startMs !== void 0 && summary.endMs !== void 0 ? summary.endMs - summary.startMs : void 0;
4075
+ runs.push({
4076
+ agentId: toolUseId,
4077
+ agentType: ref.agentType ?? "unknown",
4078
+ description: ref.description ?? (ref.prompt ? ref.prompt.replace(/\s+/g, " ").trim().slice(0, 80) : "(no description)"),
4079
+ startMs: summary.startMs,
4080
+ endMs: summary.endMs,
4081
+ durationMs,
4082
+ startTOffsetMs,
4083
+ endTOffsetMs,
4084
+ eventCount: summary.eventCount,
4085
+ totalUsage: summary.totalUsage,
4086
+ parentUuid: ref.parentUuid,
4087
+ parentToolUseId: toolUseId,
4088
+ runInBackground: false,
4089
+ finalPreview,
4090
+ finalText,
4091
+ prompt: ref.prompt,
4092
+ model: summary.model,
4093
+ toolCalls: summary.toolCalls,
4094
+ toolCallCount: summary.toolCallCount,
4095
+ assistantMessageCount: summary.assistantMessageCount
4096
+ });
4097
+ }
4098
+ runs.sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
4099
+ return runs;
4100
+ }
3980
4101
  async function parseCoworkSession(file) {
3981
4102
  const [lines, coworkMeta, spaceIdToPath] = await Promise.all([
3982
4103
  readJsonl2(file.auditPath),
3983
4104
  readCoworkMeta(file.metaPath),
3984
4105
  loadSpacesForWorkspace(file.workspaceDir)
3985
4106
  ]);
3986
- const { meta: baseMeta, events } = parseTranscript(lines);
4107
+ const deduped = dedupeReplayedLines(lines);
4108
+ const { mainLines, subagentGroups } = partitionCoworkLines(deduped);
4109
+ const { meta: baseMeta, events } = parseTranscript(mainLines);
4110
+ const sessionStartMs = baseMeta.firstTimestamp ? Date.parse(baseMeta.firstTimestamp) : void 0;
4111
+ const subagents = buildCoworkSubagents(subagentGroups, deduped, sessionStartMs);
3987
4112
  const { projectName, projectDir, cwd } = resolveProject(coworkMeta, spaceIdToPath);
3988
4113
  const meta = {
3989
4114
  ...baseMeta,
@@ -3996,7 +4121,7 @@ async function parseCoworkSession(file) {
3996
4121
  cwd: cwd ?? baseMeta.cwd,
3997
4122
  model: baseMeta.model ?? coworkMeta.model
3998
4123
  };
3999
- return { meta, events };
4124
+ return { meta, events, subagents };
4000
4125
  }
4001
4126
  function cacheKeyFor(file) {
4002
4127
  return {
@@ -4047,8 +4172,12 @@ async function getCoworkSession(id, opts = {}) {
4047
4172
  const cached = detailCache5.get(file.auditPath);
4048
4173
  if (cached && cacheKeyMatches(cached.key, key))
4049
4174
  return cached.detail;
4050
- const { meta, events } = await parseCoworkSession(file);
4051
- const detail = { ...meta, events };
4175
+ const { meta, events, subagents } = await parseCoworkSession(file);
4176
+ const detail = {
4177
+ ...meta,
4178
+ events,
4179
+ ...subagents.length ? { subagents } : {}
4180
+ };
4052
4181
  detailCache5.set(file.auditPath, { detail, key });
4053
4182
  return detail;
4054
4183
  }
@@ -4057,6 +4186,7 @@ var init_cowork = __esm({
4057
4186
  "../parser/dist/cowork.js"() {
4058
4187
  "use strict";
4059
4188
  init_analytics();
4189
+ init_claude_code();
4060
4190
  init_parser();
4061
4191
  DEFAULT_COWORK_ROOT = defaultCoworkRoot();
4062
4192
  SESSION_ID_RE = /^local_[0-9a-f-]+$/;
@@ -4067,8 +4197,17 @@ var init_cowork = __esm({
4067
4197
  });
4068
4198
 
4069
4199
  // ../parser/dist/team-config.js
4070
- import { readFileSync as readFileSync3, writeFileSync, unlinkSync, mkdirSync as mkdirSync2 } from "node:fs";
4200
+ import { readFileSync as readFileSync3, writeFileSync, unlinkSync, mkdirSync as mkdirSync2, renameSync } from "node:fs";
4071
4201
  import { join as join2 } from "node:path";
4202
+ function shouldSyncProject(repoName, sp) {
4203
+ if (!sp)
4204
+ return true;
4205
+ if (sp.excluded.includes(repoName))
4206
+ return false;
4207
+ if (sp.included.includes(repoName))
4208
+ return true;
4209
+ return sp.autoIncludeNew;
4210
+ }
4072
4211
  function readTeamConfig(dir) {
4073
4212
  const d = dir ?? cclensHome();
4074
4213
  try {
@@ -4080,7 +4219,9 @@ function readTeamConfig(dir) {
4080
4219
  function writeTeamConfig(config, dir) {
4081
4220
  const d = dir ?? cclensHome();
4082
4221
  mkdirSync2(d, { recursive: true });
4083
- writeFileSync(join2(d, CONFIG_FILE), JSON.stringify(config, null, 2), { mode: 384 });
4222
+ const tmp = join2(d, `${CONFIG_FILE}.${process.pid}.tmp`);
4223
+ writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
4224
+ renameSync(tmp, join2(d, CONFIG_FILE));
4084
4225
  }
4085
4226
  function clearTeamConfig(dir) {
4086
4227
  const d = dir ?? cclensHome();
@@ -4154,6 +4295,7 @@ __export(fs_exports, {
4154
4295
  readJsonlFile: () => readJsonlFile,
4155
4296
  readTeamConfig: () => readTeamConfig,
4156
4297
  sessionIdFromFileName: () => sessionIdFromFileName,
4298
+ shouldSyncProject: () => shouldSyncProject,
4157
4299
  walkJsonlFiles: () => walkJsonlFiles,
4158
4300
  writeTeamConfig: () => writeTeamConfig
4159
4301
  });
@@ -4375,9 +4517,14 @@ function buildCalibrationCurve(events, snapshots, tier = "pro-max-20x", granular
4375
4517
  curve.push({
4376
4518
  ts: new Date(cur).toISOString(),
4377
4519
  real_5h: real5,
4378
- pred_5h: Math.max(0, Math.min(200, p5)),
4520
+ // Predicted utilization LEGITIMATELY exceeds 200 on overage (the
4521
+ // extrapolated cycle close). The old min(200) clamp silently capped it,
4522
+ // so cyclePeaks.peakPct could never carry a *predicted* overage to the
4523
+ // team server (which now accepts up to 10000). Keep only a generous
4524
+ // finite ceiling as a corruption guard, matching the wire cap.
4525
+ pred_5h: Math.max(0, Math.min(1e4, p5)),
4379
4526
  real_7d: real7,
4380
- pred_7d: Math.max(0, Math.min(200, p7)),
4527
+ pred_7d: Math.max(0, Math.min(1e4, p7)),
4381
4528
  cycle_end_5h: r5k != null ? new Date(r5k).toISOString() : null,
4382
4529
  cycle_end_7d: r7k != null ? new Date(r7k).toISOString() : null
4383
4530
  });
@@ -4971,7 +5118,7 @@ __export(file_probe_exports, {
4971
5118
  probeArtifactSignals: () => probeArtifactSignals
4972
5119
  });
4973
5120
  import { createHash } from "node:crypto";
4974
- import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
5121
+ import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
4975
5122
  import { homedir as homedir2 } from "node:os";
4976
5123
  import { join as join7, relative, resolve as resolve2, sep } from "node:path";
4977
5124
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -5002,7 +5149,7 @@ function listMarkdownFiles(dir) {
5002
5149
  const full = join7(dir, name);
5003
5150
  let stat;
5004
5151
  try {
5005
- stat = statSync3(full);
5152
+ stat = statSync4(full);
5006
5153
  } catch {
5007
5154
  continue;
5008
5155
  }
@@ -5019,19 +5166,19 @@ function firstSeenDateOf(file) {
5019
5166
  const out = execFileSync2(
5020
5167
  "git",
5021
5168
  ["log", "--diff-filter=A", "--follow", "--format=%cs", "--", file],
5022
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname6(file) }
5169
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname7(file) }
5023
5170
  ).trim().split("\n").filter(Boolean);
5024
5171
  if (out.length > 0) return out[out.length - 1];
5025
5172
  } catch {
5026
5173
  }
5027
5174
  try {
5028
- const s = statSync3(file);
5029
- return new Date((s.birthtime || s.mtime).getTime()).toISOString().slice(0, 10);
5175
+ const s = statSync4(file);
5176
+ return localDay(s.birthtime || s.mtime);
5030
5177
  } catch {
5031
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5178
+ return todayLocal();
5032
5179
  }
5033
5180
  }
5034
- function dirname6(p) {
5181
+ function dirname7(p) {
5035
5182
  const i = p.lastIndexOf(sep);
5036
5183
  return i < 0 ? "." : p.slice(0, i);
5037
5184
  }
@@ -5046,13 +5193,15 @@ function detectGitEmail(cwd) {
5046
5193
  return null;
5047
5194
  }
5048
5195
  }
5049
- function todayLocal() {
5050
- const d = /* @__PURE__ */ new Date();
5196
+ function localDay(d) {
5051
5197
  const y = d.getFullYear();
5052
5198
  const m = String(d.getMonth() + 1).padStart(2, "0");
5053
5199
  const day = String(d.getDate()).padStart(2, "0");
5054
5200
  return `${y}-${m}-${day}`;
5055
5201
  }
5202
+ function todayLocal() {
5203
+ return localDay(/* @__PURE__ */ new Date());
5204
+ }
5056
5205
  function scanAuthored(roots, subdir, day) {
5057
5206
  const out = [];
5058
5207
  for (const r of roots) {
@@ -5077,11 +5226,11 @@ function scanEdited(roots, day) {
5077
5226
  if (firstSeen === day) continue;
5078
5227
  let stat;
5079
5228
  try {
5080
- stat = statSync3(f);
5229
+ stat = statSync4(f);
5081
5230
  } catch {
5082
5231
  continue;
5083
5232
  }
5084
- const mtimeDay = new Date(stat.mtime.getTime()).toISOString().slice(0, 10);
5233
+ const mtimeDay = localDay(stat.mtime);
5085
5234
  if (mtimeDay !== day) continue;
5086
5235
  const rel = relative(dir, f);
5087
5236
  out.push({ pathHash: pathHash(`${r.label}:skills:${rel}`), lineDelta: 0 });
@@ -5103,7 +5252,7 @@ function claudemdLineDelta(extraRoots, authorEmail, day) {
5103
5252
  const out = execFileSync2(
5104
5253
  "git",
5105
5254
  ["log", "--numstat", "--format=COMMIT %cs %ae", "--", file],
5106
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname6(file) }
5255
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname7(file) }
5107
5256
  );
5108
5257
  let currentDate = "";
5109
5258
  let currentEmail = "";
@@ -5154,7 +5303,7 @@ var init_file_probe = __esm({
5154
5303
 
5155
5304
  // src/daemon-worker.ts
5156
5305
  import { spawn as spawn2 } from "node:child_process";
5157
- import { appendFileSync as appendFileSync5, closeSync, existsSync as existsSync13, mkdirSync as mkdirSync13, openSync, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "node:fs";
5306
+ import { closeSync, existsSync as existsSync13, mkdirSync as mkdirSync13, openSync, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
5158
5307
  import { dirname as dirname10, join as join11 } from "node:path";
5159
5308
  import { fileURLToPath } from "node:url";
5160
5309
 
@@ -5300,6 +5449,21 @@ function normalizeExtra(raw) {
5300
5449
  init_storage();
5301
5450
  init_fs();
5302
5451
 
5452
+ // src/daemon-log.ts
5453
+ init_fs();
5454
+ import { appendFileSync as appendFileSync2 } from "node:fs";
5455
+ function appendDaemonLogLine(level, msg) {
5456
+ try {
5457
+ appendFileSync2(
5458
+ cclensPath("daemon.log"),
5459
+ `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${msg}
5460
+ `,
5461
+ "utf8"
5462
+ );
5463
+ } catch {
5464
+ }
5465
+ }
5466
+
5303
5467
  // src/usage/backoff.ts
5304
5468
  var BASE_INTERVAL_MS = 5 * 60 * 1e3;
5305
5469
  var MAX_INTERVAL_MS = 60 * 60 * 1e3;
@@ -5322,7 +5486,7 @@ import { randomUUID } from "node:crypto";
5322
5486
 
5323
5487
  // ../entries/dist/fs.js
5324
5488
  init_fs();
5325
- import { readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, renameSync as renameSync2, existsSync as existsSync3 } from "node:fs";
5489
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, renameSync as renameSync3, existsSync as existsSync3 } from "node:fs";
5326
5490
  import { randomBytes } from "node:crypto";
5327
5491
  import { join as join4 } from "node:path";
5328
5492
 
@@ -5375,8 +5539,8 @@ function skippedTrivialEnrichment(generatedAt) {
5375
5539
  retry_count: 0
5376
5540
  };
5377
5541
  }
5378
- function entryKey(sessionId, localDay) {
5379
- return `${sessionId}__${localDay}`;
5542
+ function entryKey(sessionId, localDay2) {
5543
+ return `${sessionId}__${localDay2}`;
5380
5544
  }
5381
5545
  function parseEntryKey(key) {
5382
5546
  const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
@@ -5397,7 +5561,7 @@ var PROMPT_FRAME_ORIGIN = {
5397
5561
 
5398
5562
  // ../entries/dist/digest-fs.js
5399
5563
  init_fs();
5400
- import { readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync2, renameSync, chmodSync, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "node:fs";
5564
+ import { readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync2, renameSync as renameSync2, chmodSync, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "node:fs";
5401
5565
  import { dirname as dirname2, join as join3 } from "node:path";
5402
5566
  var digestsDirCached = null;
5403
5567
  function parseDigestFile(path7, expectedVersion) {
@@ -5438,7 +5602,7 @@ function writeDayDigest(digest) {
5438
5602
  } catch {
5439
5603
  }
5440
5604
  }
5441
- renameSync(tmp, final);
5605
+ renameSync2(tmp, final);
5442
5606
  }
5443
5607
  function readDayDigest(date) {
5444
5608
  const p = dayDigestPath(date);
@@ -5471,7 +5635,7 @@ function writeWeekDigest(digest) {
5471
5635
  } catch {
5472
5636
  }
5473
5637
  }
5474
- renameSync(tmp, final);
5638
+ renameSync2(tmp, final);
5475
5639
  }
5476
5640
  function readWeekDigest(monday) {
5477
5641
  const p = weekDigestPath(monday);
@@ -5504,8 +5668,8 @@ function entriesDir() {
5504
5668
  entriesDirCached = envOverride ?? cclensPath("entries");
5505
5669
  return entriesDirCached;
5506
5670
  }
5507
- function pathFor(sessionId, localDay) {
5508
- return join4(entriesDir(), `${entryKey(sessionId, localDay)}.json`);
5671
+ function pathFor(sessionId, localDay2) {
5672
+ return join4(entriesDir(), `${entryKey(sessionId, localDay2)}.json`);
5509
5673
  }
5510
5674
  function tmpPathFor(final) {
5511
5675
  return `${final}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
@@ -5517,7 +5681,7 @@ function writeEntry(entry) {
5517
5681
  const tmp = tmpPathFor(final);
5518
5682
  const json = JSON.stringify(entry, null, 2);
5519
5683
  writeFileSync3(tmp, json, { encoding: "utf8" });
5520
- renameSync2(tmp, final);
5684
+ renameSync3(tmp, final);
5521
5685
  }
5522
5686
  var PRESERVABLE_STATUSES = /* @__PURE__ */ new Set([
5523
5687
  "done",
@@ -5549,26 +5713,29 @@ function writeEntryPreservingEnrichment(fresh) {
5549
5713
  }
5550
5714
  const tmp = tmpPathFor(final);
5551
5715
  writeFileSync3(tmp, JSON.stringify(toWrite, null, 2), { encoding: "utf8" });
5552
- renameSync2(tmp, final);
5716
+ renameSync3(tmp, final);
5553
5717
  }
5554
- function readEntry(sessionId, localDay) {
5555
- const p = pathFor(sessionId, localDay);
5718
+ function readEntry(sessionId, localDay2) {
5719
+ const p = pathFor(sessionId, localDay2);
5556
5720
  if (!existsSync3(p))
5557
5721
  return null;
5558
5722
  const raw = readFileSync5(p, "utf8");
5559
5723
  return JSON.parse(raw);
5560
5724
  }
5725
+ function entryExists(sessionId, localDay2) {
5726
+ return existsSync3(pathFor(sessionId, localDay2));
5727
+ }
5561
5728
  function listEntryKeys() {
5562
5729
  const dir = entriesDir();
5563
5730
  if (!existsSync3(dir))
5564
5731
  return [];
5565
5732
  return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
5566
5733
  }
5567
- function listEntriesForDay(localDay) {
5734
+ function listEntriesForDay(localDay2) {
5568
5735
  const out = [];
5569
5736
  for (const key of listEntryKeys()) {
5570
5737
  const parsed = parseEntryKey(key);
5571
- if (!parsed || parsed.local_day !== localDay)
5738
+ if (!parsed || parsed.local_day !== localDay2)
5572
5739
  continue;
5573
5740
  const e = readEntry(parsed.session_id, parsed.local_day);
5574
5741
  if (e)
@@ -5577,1657 +5744,1984 @@ function listEntriesForDay(localDay) {
5577
5744
  return out;
5578
5745
  }
5579
5746
 
5580
- // src/team/push.ts
5581
- init_storage();
5582
- var SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
5583
- var POST_TIMEOUT_MS = 15e3;
5584
- function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
5585
- const raw = latestClaudeCodeSnapshot(filePath);
5586
- if (!raw) return null;
5587
- const capturedMs = Date.parse(raw.captured_at);
5588
- if (Number.isNaN(capturedMs)) return null;
5589
- if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
5590
- const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
5591
- return {
5592
- capturedAt: raw.captured_at,
5593
- fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
5594
- sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
5595
- sevenDayOpus: toWire(raw.seven_day_opus),
5596
- sevenDaySonnet: toWire(raw.seven_day_sonnet),
5597
- sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
5598
- sevenDayCowork: toWire(raw.seven_day_cowork),
5599
- extraUsage: raw.extra_usage ? {
5600
- isEnabled: raw.extra_usage.is_enabled,
5601
- monthlyLimitUsd: raw.extra_usage.monthly_limit,
5602
- usedCreditsUsd: raw.extra_usage.used_credits,
5603
- utilization: raw.extra_usage.utilization
5604
- } : null
5605
- };
5606
- }
5607
- function bucketToRollup(b) {
5608
- return {
5609
- day: b.date,
5610
- agentTimeMs: b.airTimeMs,
5611
- sessions: b.sessions,
5612
- toolCalls: b.toolCalls,
5613
- turns: b.turns,
5614
- tokens: { ...b.tokens }
5615
- };
5616
- }
5617
- function buildRollupsForRange(sessions, sinceDay) {
5618
- const startCounts = /* @__PURE__ */ new Map();
5619
- for (const s of sessions) {
5620
- const d = sessionDay(s);
5621
- if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
5622
- }
5623
- return dailyActivity(sessions).filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({ ...bucketToRollup(b), uniqueSessions: startCounts.get(b.date) ?? 0 }));
5747
+ // src/perception/build-entries.ts
5748
+ init_dist();
5749
+ import { statSync, readFileSync as readFileSync6 } from "node:fs";
5750
+ import { basename, dirname as dirname3 } from "node:path";
5751
+
5752
+ // ../entries/dist/signals.js
5753
+ init_dist();
5754
+ var TEAMMATE_RE = /^<teammate-message\b/;
5755
+ var SKILL_LOAD_RE = /^Base directory for this skill:/;
5756
+ var SLASH_COMMAND_RE2 = /^<command-name>|^<local-command-stdout>/;
5757
+ function classifyUserInputSource(text) {
5758
+ if (!text)
5759
+ return "human";
5760
+ const stripped = stripFrameworkBoilerplate(text);
5761
+ if (!stripped)
5762
+ return "system_instruction";
5763
+ if (TEAMMATE_RE.test(stripped))
5764
+ return "teammate";
5765
+ if (SKILL_LOAD_RE.test(stripped))
5766
+ return "skill_load";
5767
+ if (SLASH_COMMAND_RE2.test(stripped))
5768
+ return "slash_command";
5769
+ return "human";
5624
5770
  }
5625
- function dayBoundsMs(day) {
5626
- const [y, m, d] = day.split("-").map(Number);
5771
+ var HAPPY_RE = /\b(?:yay|yaay|yass|woohoo|nice|great|love(?:ly)?|perfect|amazing|awesome)\s*!|!!!/gi;
5772
+ var SATISFIED_RE = /\b(?:thanks|thank you|looks good|lgtm|works|that works|all good|sounds good)\b/gi;
5773
+ var DISSATISFIED_RE = /\b(?:that'?s (?:not|wrong)|try again|no, (?:that|this)|not quite|incorrect)\b/gi;
5774
+ var FRUSTRATED_RE = /\b(?:broken|stop|why (?:did|are|would) you|give up|ugh|argh|wtf)\b/gi;
5775
+ function countSatisfactionSignals(text) {
5776
+ if (!text)
5777
+ return { happy: 0, satisfied: 0, dissatisfied: 0, frustrated: 0 };
5627
5778
  return {
5628
- startMs: new Date(y, m - 1, d, 0, 0, 0, 0).getTime(),
5629
- endMs: new Date(y, m - 1, d + 1, 0, 0, 0, 0).getTime()
5779
+ happy: (text.match(HAPPY_RE) ?? []).length,
5780
+ satisfied: (text.match(SATISFIED_RE) ?? []).length,
5781
+ dissatisfied: (text.match(DISSATISFIED_RE) ?? []).length,
5782
+ frustrated: (text.match(FRUSTRATED_RE) ?? []).length
5630
5783
  };
5631
5784
  }
5632
- function clipSegmentsToDay(segments, bounds) {
5633
- if (!segments || segments.length === 0) return [];
5785
+ var INSTRUCTION_RE = /\b(?:can you|please|let's|let us|i need|could you|would you)\s+([^.?!\n]{5,200})/gi;
5786
+ function extractUserInstructions(text) {
5787
+ if (!text)
5788
+ return [];
5789
+ INSTRUCTION_RE.lastIndex = 0;
5634
5790
  const out = [];
5635
- for (const seg of segments) {
5636
- const s = Math.max(seg.startMs, bounds.startMs);
5637
- const e = Math.min(seg.endMs, bounds.endMs);
5638
- if (e > s) out.push({ startMs: s, endMs: e });
5791
+ const seen = /* @__PURE__ */ new Set();
5792
+ let m;
5793
+ while ((m = INSTRUCTION_RE.exec(text)) !== null) {
5794
+ const phrase = m[1].trim().replace(/\s+/g, " ");
5795
+ const lc = phrase.toLowerCase();
5796
+ if (seen.has(lc))
5797
+ continue;
5798
+ seen.add(lc);
5799
+ out.push(phrase);
5800
+ if (out.length >= 5)
5801
+ break;
5639
5802
  }
5640
5803
  return out;
5641
5804
  }
5642
- function sessionTouchesDay(s, day) {
5643
- const bounds = dayBoundsMs(day);
5644
- if (s.activeSegments && s.activeSegments.length > 0) {
5645
- return s.activeSegments.some((seg) => seg.endMs > bounds.startMs && seg.startMs < bounds.endMs);
5805
+ function classifySubagentRole(sa) {
5806
+ const text = `${sa.description} ${sa.prompt_preview}`.toLowerCase();
5807
+ if (/\b(re-?review|review[s]?|verify|audit|spec[- ]review|code[- ]quality|code[- ]reuse|efficiency review|sanity check|quick test)\b/.test(text)) {
5808
+ return "reviewer";
5646
5809
  }
5647
- return sessionDay(s) === day;
5810
+ if (/\b(implement|build chunk|build task|initialize|create the|add the)\b/.test(text)) {
5811
+ return "implementer";
5812
+ }
5813
+ if (/\bexplore\b|\binventory\b|\bmap\b.*\b(codebase|repo|branch)\b|\baudit\b.*coverage/.test(text)) {
5814
+ return "explorer";
5815
+ }
5816
+ if (/\b(investigate|research|reverse[- ]engineer|study|analy[sz]e|look up|tell me|brief me on|find out)\b/.test(text)) {
5817
+ return "researcher";
5818
+ }
5819
+ if (/\b(env(?:ironment)? setup|configure|setup|bootstrap)\b/.test(text)) {
5820
+ return "env-setup";
5821
+ }
5822
+ if (/\b(polish|cleanup|fix|refactor)\b/.test(text)) {
5823
+ return "polish";
5824
+ }
5825
+ return "other";
5648
5826
  }
5649
- function buildRichRollupBlocks(day, daySessions, entries, privateProjects, resolveRepo) {
5650
- const bounds = dayBoundsMs(day);
5651
- const clippedSessions = [];
5652
- for (const s of daySessions) {
5653
- const clipped = clipSegmentsToDay(s.activeSegments, bounds);
5654
- if (clipped.length === 0) continue;
5655
- clippedSessions.push({ ...s, activeSegments: clipped });
5827
+ var STOCK_SUBAGENT_PREFIXES = /^(general-purpose|Explore|Plan|claude-code-guide|playwright-qa-verifier|statusline-setup|frontend-design:|code-review:|code-simplifier:|codex:|superpowers:)/;
5828
+ function isStockSubagentType(type) {
5829
+ return STOCK_SUBAGENT_PREFIXES.test(type);
5830
+ }
5831
+ function inferWorkingShape(entry) {
5832
+ if (entry.numbers.turn_count < 2)
5833
+ return null;
5834
+ const subagents = entry.subagents ?? [];
5835
+ const roles = subagents.map(classifySubagentRole);
5836
+ const reviewerCount = roles.filter((r) => r === "reviewer").length;
5837
+ const implementerCount = roles.filter((r) => r === "implementer").length;
5838
+ const explorerOrResearcher = roles.filter((r) => r === "explorer" || r === "researcher").length;
5839
+ const hasBackground = subagents.some((sa) => sa.background);
5840
+ if (implementerCount >= 2) {
5841
+ const chunkRefs = subagents.filter((sa) => /\b(chunk|task)\s*\d+/i.test(`${sa.description} ${sa.prompt_preview}`));
5842
+ if (chunkRefs.length >= 2)
5843
+ return "chunk-implementation";
5656
5844
  }
5657
- const bursts = computeBurstsFromSessions(clippedSessions);
5658
- const stats = summarizeBursts(bursts);
5659
- const repoForEntry = (e) => {
5660
- if (resolveRepo) {
5661
- const own = resolveRepo(e.project);
5662
- if (own) return own;
5663
- const counts = /* @__PURE__ */ new Map();
5664
- for (const d of e.edited_dirs ?? []) {
5665
- const r = resolveRepo(d);
5666
- if (r) counts.set(r, (counts.get(r) ?? 0) + 1);
5667
- }
5668
- const dominant = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
5669
- if (dominant) return dominant;
5845
+ if (reviewerCount >= 3 && implementerCount === 0) {
5846
+ const lensSigs = /* @__PURE__ */ new Set();
5847
+ for (let i = 0; i < subagents.length; i++) {
5848
+ if (roles[i] !== "reviewer")
5849
+ continue;
5850
+ const sig = subagents[i].description.toLowerCase().replace(/[^a-z0-9 ]/g, "").split(/\s+/).filter((w) => w.length > 2).slice(0, 3).join("|");
5851
+ lensSigs.add(sig);
5670
5852
  }
5671
- return e.github_repos?.length === 1 ? e.github_repos[0] : null;
5672
- };
5673
- const sessionRepo = /* @__PURE__ */ new Map();
5674
- for (const e of entries) {
5675
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
5676
- const r = repoForEntry(e);
5677
- if (r) sessionRepo.set(e.session_id, r);
5853
+ if (lensSigs.size >= 3)
5854
+ return "reviewer-triad";
5678
5855
  }
5679
- const projects = /* @__PURE__ */ new Map();
5680
- for (const s of clippedSessions) {
5681
- const canonical = canonicalProjectName(s.projectName);
5682
- const name = projectRepoName(s.projectName);
5683
- if (privateProjects.has(name) || privateProjects.has(canonical)) continue;
5684
- const repo = resolveRepo?.(canonical) ?? sessionRepo.get(s.id);
5685
- const key = repo ?? name;
5686
- const ms = s.activeSegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
5687
- const cur = projects.get(key) ?? { project: name, agentTimeMs: 0, sessions: 0, ...repo ? { resolved: repo } : {} };
5688
- cur.agentTimeMs += ms;
5689
- cur.sessions += 1;
5690
- projects.set(key, cur);
5856
+ if (reviewerCount >= 2)
5857
+ return "spec-review-loop";
5858
+ if (explorerOrResearcher >= 2 || explorerOrResearcher >= 1 && implementerCount >= 1) {
5859
+ return "research-then-build";
5691
5860
  }
5692
- const projectRepos = /* @__PURE__ */ new Map();
5693
- const workingShapes = /* @__PURE__ */ new Map();
5694
- const skills = /* @__PURE__ */ new Map();
5695
- const subagents = /* @__PURE__ */ new Map();
5696
- let toolErrors = 0;
5697
- let brainstorm = 0;
5698
- let planMode = 0;
5699
- let prs = 0;
5700
- let commits = 0;
5701
- let pushes = 0;
5702
- let longCount = 0;
5703
- let longTotalMin = 0;
5704
- let longMaxSingleMin = 0;
5705
- for (const e of entries) {
5706
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
5707
- const shape = e.signals?.working_shape ?? null;
5708
- if (shape) {
5709
- const cur = workingShapes.get(shape) ?? { sessions: 0, agentTimeMs: 0 };
5710
- cur.sessions += 1;
5711
- cur.agentTimeMs += Math.round(e.numbers.active_min * 6e4);
5712
- workingShapes.set(shape, cur);
5713
- }
5714
- for (const [name, count] of Object.entries(e.skills)) {
5715
- if (count > 0) skills.set(name, (skills.get(name) ?? 0) + 1);
5716
- }
5717
- for (const sa of e.subagents) {
5718
- subagents.set(sa.type, (subagents.get(sa.type) ?? 0) + 1);
5719
- }
5720
- toolErrors += e.numbers.tool_errors;
5721
- if (e.signals?.brainstorm_warmup) brainstorm += 1;
5722
- if (e.numbers.exit_plan_calls > 0) planMode += 1;
5723
- prs += e.numbers.prs;
5724
- commits += e.numbers.commits;
5725
- pushes += e.numbers.pushes;
5726
- if (e.github_repos?.length) {
5727
- const key = sessionRepo.get(e.session_id) ?? projectRepoName(e.project);
5728
- const set = projectRepos.get(key) ?? /* @__PURE__ */ new Set();
5729
- for (const r of e.github_repos) set.add(r);
5730
- projectRepos.set(key, set);
5731
- }
5732
- if (e.flags.includes("long_autonomous")) {
5733
- longCount += 1;
5734
- longTotalMin += e.numbers.active_min;
5735
- if (e.numbers.active_min > longMaxSingleMin) longMaxSingleMin = e.numbers.active_min;
5736
- }
5861
+ if (hasBackground && subagents.length >= 1)
5862
+ return "background-coordinated";
5863
+ if (subagents.length === 0) {
5864
+ const first = (entry.first_user || "").trim().toLowerCase();
5865
+ if (/^continue\b/.test(first) || first === "continue.")
5866
+ return "solo-continuation";
5867
+ const skills = Object.keys(entry.skills ?? {});
5868
+ if (skills.some((s) => /brainstorm|writing-plans|brainstorming/i.test(s)))
5869
+ return "solo-design";
5870
+ return "solo-build";
5737
5871
  }
5738
- return {
5739
- projects: Array.from(projects.entries()).map(([key, v]) => {
5740
- const extras = [...projectRepos.get(key) ?? []].filter((r) => r !== v.resolved);
5741
- const repos = v.resolved ? [v.resolved, ...extras] : extras;
5742
- return {
5743
- project: v.project,
5744
- agentTimeMs: v.agentTimeMs,
5745
- sessions: v.sessions,
5746
- ...repos.length ? { githubRepos: repos.slice(0, 5) } : {}
5747
- };
5748
- }).sort((a, b) => b.agentTimeMs - a.agentTimeMs),
5749
- workingShapes: Array.from(workingShapes.entries()).map(([shape, v]) => ({ shape, ...v })).sort((a, b) => b.sessions - a.sessions),
5750
- concurrencyPeak: stats.peakConcurrent,
5751
- parallelMinutes: Math.round(stats.totalParallelMs / 6e4),
5752
- // Wire schema requires integer minutes (active_min is a float).
5753
- longAutonomous: { count: longCount, totalMin: Math.round(longTotalMin), maxSingleMin: Math.round(longMaxSingleMin) },
5754
- toolErrors,
5755
- skillsLoaded: Array.from(skills.entries()).map(([name, sessions]) => ({ name, sessions })).sort((a, b) => b.sessions - a.sessions),
5756
- subagentsDispatched: Array.from(subagents.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count),
5757
- brainstormWarmupSessions: brainstorm,
5758
- planModeUsed: planMode,
5759
- prs,
5760
- commits,
5761
- pushes
5762
- };
5872
+ return "solo-build";
5763
5873
  }
5764
- function buildEnrichedExtras(entries) {
5765
- const outcomeMix = {};
5766
- const helpfulnessMix = {};
5767
- const goalMix = {};
5768
- for (const e of entries) {
5769
- if (e.enrichment.status !== "done") continue;
5770
- if (e.enrichment.outcome) {
5771
- outcomeMix[e.enrichment.outcome] = (outcomeMix[e.enrichment.outcome] ?? 0) + 1;
5772
- }
5773
- if (e.enrichment.claude_helpfulness) {
5774
- helpfulnessMix[e.enrichment.claude_helpfulness] = (helpfulnessMix[e.enrichment.claude_helpfulness] ?? 0) + 1;
5775
- }
5776
- for (const [cat, mins] of Object.entries(e.enrichment.goal_categories)) {
5777
- if (typeof mins === "number") goalMix[cat] = (goalMix[cat] ?? 0) + mins;
5778
- }
5874
+ function detectPromptFrames(text) {
5875
+ if (!text)
5876
+ return [];
5877
+ const out = [];
5878
+ if (/<teammate-message\b/i.test(text))
5879
+ out.push("teammate");
5880
+ if (/<task-notification\b/i.test(text))
5881
+ out.push("task-notification");
5882
+ if (/<local-command-caveat\b/i.test(text))
5883
+ out.push("local-command-caveat");
5884
+ if (/<command-(message|name)\b/i.test(text))
5885
+ out.push("slash-command");
5886
+ if (/\[Image #\d+\]/.test(text))
5887
+ out.push("image-attached");
5888
+ if (/\b(here'?s the (handoff|handover) prompt|session-close summary|wrap-up summary|all wrapped up|all captured\.)\b/i.test(text) || /^##? (Wrap-up|Session conclusion|Handoff)\b/m.test(text) || /^# Handoff:/m.test(text)) {
5889
+ out.push("handoff-prose");
5779
5890
  }
5780
- return { outcomeMix, helpfulnessMix, goalMix };
5781
- }
5782
- function buildRichBlocksForDay(day, daySessions, privateProjects, enrichmentOptIn, resolveRepo) {
5783
- const entries = listEntriesForDay(day);
5784
- if (entries.length === 0) return void 0;
5785
- const rich = buildRichRollupBlocks(day, daySessions, entries, privateProjects, resolveRepo);
5786
- const enriched = enrichmentOptIn ? buildEnrichedExtras(entries) : void 0;
5787
- return enriched ? { rich, enriched } : { rich };
5788
- }
5789
- function buildIngestPayload(inputs) {
5790
- const richRollup = inputs.rollup && inputs.richExtras ? { ...inputs.rollup, ...inputs.richExtras } : void 0;
5791
- return {
5792
- ingestId: randomUUID(),
5793
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
5794
- cliVersion: "0.13.2",
5795
- ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
5796
- ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
5797
- ...richRollup ? { richRollup } : {},
5798
- ...inputs.enrichedExtras ? { enrichedExtras: inputs.enrichedExtras } : {},
5799
- ...inputs.artifactSignals ? { artifactSignals: inputs.artifactSignals } : {},
5800
- ...inputs.usageSnapshot ? { usageSnapshot: inputs.usageSnapshot } : {},
5801
- ...inputs.planTier ? { planTier: inputs.planTier } : {},
5802
- ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {}
5803
- };
5804
- }
5805
- async function pushToTeamServer(config, payload) {
5806
- const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
5807
- method: "POST",
5808
- signal: AbortSignal.timeout(POST_TIMEOUT_MS),
5809
- headers: {
5810
- "Content-Type": "application/json",
5811
- Authorization: `Bearer ${config.bearerToken}`
5812
- },
5813
- body: JSON.stringify(payload)
5814
- });
5815
- const body = await res.json().catch(() => null);
5816
- return { ok: res.ok, status: res.status, body };
5891
+ return out;
5817
5892
  }
5818
-
5819
- // src/team/git-remote.ts
5820
- import { existsSync as existsSync4, readFileSync as readFileSync6, statSync } from "node:fs";
5821
- import { dirname as dirname3, isAbsolute, join as join5, resolve } from "node:path";
5822
- var GITHUB_URL_RE = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/;
5823
- function parseGithubRemote(url) {
5824
- const m = url.trim().match(GITHUB_URL_RE);
5825
- return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
5893
+ var STOCK_SKILL_PREFIXES = /^(superpowers|mcp__|frontend-design|code-review|codex|claude-code-guide|claude-api):/i;
5894
+ function classifySkill(name) {
5895
+ if (name.startsWith("(ToolSearch:"))
5896
+ return "infra";
5897
+ if (STOCK_SKILL_PREFIXES.test(name))
5898
+ return "stock";
5899
+ if (name === "using-superpowers")
5900
+ return "stock";
5901
+ return "user";
5826
5902
  }
5827
- function originUrl(configText) {
5828
- let inOrigin = false;
5829
- for (const line of configText.split("\n")) {
5830
- const t = line.trim();
5831
- if (t.startsWith("[")) {
5832
- inOrigin = /^\[remote\s+"origin"\]/.test(t);
5833
- continue;
5834
- }
5835
- if (!inOrigin) continue;
5836
- const m = t.match(/^url\s*=\s*(.+)$/);
5837
- if (m) return m[1].trim();
5903
+ var EXTERNAL_REF_PATTERNS = [
5904
+ { kind: "linear-kip", re: /\bKIP-\d+\b/i },
5905
+ { kind: "github-issue-pr", re: /\b(issue|pr|pull request)\s*#\d+\b|github\.com\/[^\s]+\/(issues|pull)\/\d+/i },
5906
+ { kind: "branch-ref", re: /\b(branch|feat|fix|chore|refactor)[/:]\s*[\w./-]+|on `[\w./-]+`/i },
5907
+ { kind: "url", re: /https?:\/\/\S+/ }
5908
+ ];
5909
+ function detectExternalRef(text) {
5910
+ if (!text)
5911
+ return null;
5912
+ if (/<teammate-message|<task-notification|<local-command-caveat/i.test(text.slice(0, 400)))
5913
+ return null;
5914
+ for (const { kind, re } of EXTERNAL_REF_PATTERNS) {
5915
+ const m = text.match(re);
5916
+ if (m)
5917
+ return { kind, preview: text.slice(0, 160) };
5838
5918
  }
5839
5919
  return null;
5840
5920
  }
5841
- function gitConfigPathFor(dir) {
5842
- const dotGit = join5(dir, ".git");
5843
- const st = statSync(dotGit, { throwIfNoEntry: false });
5844
- if (!st) return null;
5845
- if (st.isDirectory()) return join5(dotGit, "config");
5846
- const m = readFileSync6(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
5847
- if (!m) return null;
5848
- const gd = isAbsolute(m[1]) ? m[1] : resolve(dir, m[1]);
5849
- const commondirFile = join5(gd, "commondir");
5850
- if (existsSync4(commondirFile)) {
5851
- const common = readFileSync6(commondirFile, "utf8").trim();
5852
- return join5(isAbsolute(common) ? common : resolve(gd, common), "config");
5853
- }
5854
- return join5(gd, "config");
5921
+ function bucketVerbosity(n) {
5922
+ if (n < 100)
5923
+ return "short";
5924
+ if (n < 500)
5925
+ return "medium";
5926
+ if (n < 2e3)
5927
+ return "long";
5928
+ return "very_long";
5855
5929
  }
5856
- function createRepoResolver() {
5857
- const cache = /* @__PURE__ */ new Map();
5858
- return (dir) => {
5859
- if (!dir || !isAbsolute(dir)) return null;
5860
- const hit = cache.get(dir);
5861
- if (hit !== void 0) return hit;
5862
- let repo = null;
5863
- try {
5864
- for (let d = dir; ; d = dirname3(d)) {
5865
- const config = gitConfigPathFor(d);
5866
- if (config && existsSync4(config)) {
5867
- const url = originUrl(readFileSync6(config, "utf8"));
5868
- repo = url ? parseGithubRemote(url) : null;
5869
- break;
5870
- }
5871
- if (d === dirname3(d)) break;
5872
- }
5873
- } catch {
5874
- repo = null;
5875
- }
5876
- cache.set(dir, repo);
5877
- return repo;
5930
+ function computeEntrySignals(entry) {
5931
+ const fu = entry.first_user || "";
5932
+ const verbosity = bucketVerbosity(fu.length);
5933
+ const ext = detectExternalRef(fu);
5934
+ const external_refs = ext ? [ext] : [];
5935
+ const skillNames = Object.keys(entry.skills ?? {});
5936
+ const brainstorm_warmup = skillNames.some((s) => /brainstorm|writing-plans/i.test(s));
5937
+ const fuTrim = fu.trim().toLowerCase();
5938
+ const frames = detectPromptFrames(fu);
5939
+ const continuation_kind = /^continue\b/.test(fuTrim) || fuTrim === "continue." ? "literal-continue" : frames.includes("handoff-prose") ? "handoff-prose" : "none";
5940
+ return {
5941
+ working_shape: inferWorkingShape(entry),
5942
+ prompt_frames: frames,
5943
+ subagent_roles: (entry.subagents ?? []).map(classifySubagentRole),
5944
+ verbosity,
5945
+ external_refs,
5946
+ brainstorm_warmup,
5947
+ continuation_kind
5878
5948
  };
5879
5949
  }
5880
5950
 
5881
- // src/team/queue.ts
5882
- init_fs();
5883
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, statSync as statSync2, mkdirSync as mkdirSync5 } from "node:fs";
5884
- import { dirname as dirname4 } from "node:path";
5885
- var QUEUE_PATH = cclensPath("ingest-queue.jsonl");
5886
- var MAX_SIZE_BYTES = 10 * 1024 * 1024;
5887
- var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5888
- function enqueuePayload(payload, queuePath = QUEUE_PATH) {
5889
- mkdirSync5(dirname4(queuePath), { recursive: true });
5890
- const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
5891
- appendFileSync2(queuePath, JSON.stringify(entry) + "\n", "utf8");
5892
- try {
5893
- const { size } = statSync2(queuePath);
5894
- if (size > MAX_SIZE_BYTES) {
5895
- pruneOldest(queuePath);
5896
- }
5897
- } catch {
5898
- }
5899
- }
5900
- function dequeuePayloads(queuePath = QUEUE_PATH) {
5901
- let lines;
5902
- try {
5903
- lines = readFileSync7(queuePath, "utf8").trim().split("\n").filter(Boolean);
5904
- } catch {
5905
- return [];
5906
- }
5907
- const now = Date.now();
5908
- const valid = [];
5909
- for (const line of lines) {
5910
- try {
5911
- const entry = JSON.parse(line);
5912
- if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
5913
- valid.push(entry);
5914
- }
5915
- } catch {
5916
- }
5917
- }
5918
- writeFileSync4(queuePath, "", "utf8");
5919
- return valid.map((e) => e.payload);
5920
- }
5921
- function pruneOldest(queuePath) {
5922
- let lines;
5923
- try {
5924
- lines = readFileSync7(queuePath, "utf8").trim().split("\n").filter(Boolean);
5925
- } catch {
5926
- return;
5927
- }
5928
- const keep = lines.slice(Math.floor(lines.length / 2));
5929
- writeFileSync4(queuePath, keep.join("\n") + "\n", "utf8");
5951
+ // ../entries/dist/trivial.js
5952
+ function isTrivial(n) {
5953
+ return n.active_min < 1 && n.turn_count < 3 && n.tools_total === 0;
5930
5954
  }
5931
5955
 
5932
- // src/team/backfill.ts
5933
- init_storage();
5934
- import { randomUUID as randomUUID2 } from "node:crypto";
5935
-
5936
- // src/usage/profile.ts
5937
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
5938
- import { dirname as dirname5 } from "node:path";
5939
- var PROFILE_ENDPOINT = "https://api.anthropic.com/api/oauth/profile";
5940
- var BETA_HEADER2 = "oauth-2025-04-20";
5941
- var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
5942
- function mapRateLimitTier(rateLimitTier) {
5943
- switch (rateLimitTier) {
5944
- case "default_claude_pro":
5945
- return "pro";
5946
- case "default_claude_max":
5947
- case "default_claude_max_5x":
5948
- return "pro-max";
5949
- case "default_claude_max_20x":
5950
- return "pro-max-20x";
5951
- default:
5952
- return "custom";
5953
- }
5954
- }
5955
- async function fetchProfile(nowMs = Date.now()) {
5956
- const creds = readOAuthCredentials();
5957
- if (!creds) return null;
5958
- if (creds.expiresAt - 6e4 <= nowMs) return null;
5959
- let res;
5960
- try {
5961
- res = await fetch(PROFILE_ENDPOINT, {
5962
- headers: {
5963
- Authorization: `Bearer ${creds.accessToken}`,
5964
- "anthropic-beta": BETA_HEADER2
5965
- }
5966
- });
5967
- } catch {
5968
- return null;
5969
- }
5970
- if (!res.ok) return null;
5971
- const body = await res.json().catch(() => null);
5972
- if (!body) return null;
5973
- const rateLimitTier = body.organization?.rate_limit_tier ?? null;
5956
+ // ../entries/dist/build.js
5957
+ init_analytics();
5958
+ function viewEvent(ev) {
5959
+ let rawType;
5960
+ if (ev.role === "user" || ev.role === "tool-result")
5961
+ rawType = "user";
5962
+ else if (ev.role === "agent" || ev.role === "agent-thinking" || ev.role === "tool-call")
5963
+ rawType = "assistant";
5964
+ else if (ev.rawType === "summary")
5965
+ rawType = "summary";
5966
+ else
5967
+ rawType = "system";
5968
+ const rawCwd = ev.raw?.cwd;
5974
5969
  return {
5975
- planTier: mapRateLimitTier(rateLimitTier),
5976
- rateLimitTier,
5977
- organizationType: body.organization?.organization_type ?? null
5970
+ rawType,
5971
+ content: ev.blocks ?? [],
5972
+ model: ev.model,
5973
+ msgId: ev.messageId,
5974
+ usage: ev.usage ? {
5975
+ input_tokens: ev.usage.input,
5976
+ output_tokens: ev.usage.output,
5977
+ cache_read_input_tokens: ev.usage.cacheRead,
5978
+ cache_creation_input_tokens: ev.usage.cacheWrite
5979
+ } : void 0,
5980
+ cwd: rawCwd
5978
5981
  };
5979
5982
  }
5980
- function readCachedProfile(cachePath) {
5981
- if (!existsSync5(cachePath)) return null;
5982
- try {
5983
- const raw = JSON.parse(readFileSync8(cachePath, "utf8"));
5984
- if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
5985
- return raw;
5986
- } catch {
5987
- return null;
5988
- }
5983
+ var IDLE_GAP_MS4 = 3 * 60 * 1e3;
5984
+ var INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
5985
+ var PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
5986
+ var BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
5987
+ var SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
5988
+ var IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
5989
+ function parseMs(iso) {
5990
+ if (!iso)
5991
+ return void 0;
5992
+ const ms = Date.parse(iso);
5993
+ return Number.isNaN(ms) ? void 0 : ms;
5989
5994
  }
5990
- function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
5991
- mkdirSync6(dirname5(cachePath), { recursive: true });
5992
- writeFileSync5(
5993
- cachePath,
5994
- JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
5995
- { mode: 384 }
5996
- );
5995
+ function trunc(s, n) {
5996
+ if (!s)
5997
+ return "";
5998
+ const one = s.replace(/\s+/g, " ").trim();
5999
+ return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
5997
6000
  }
5998
- async function getPlanTier(cachePath, nowMs = Date.now(), fetcher = fetchProfile) {
5999
- const cached = readCachedProfile(cachePath);
6000
- if (cached && nowMs - cached.fetchedAtMs < PROFILE_CACHE_TTL_MS) {
6001
- return cached.profile.planTier;
6001
+ function minimizeImages(s) {
6002
+ if (!s)
6003
+ return s;
6004
+ let counter = 0;
6005
+ return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
6006
+ }
6007
+ function bashVerb(cmd) {
6008
+ if (!cmd)
6009
+ return "?";
6010
+ let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
6011
+ if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
6012
+ const parts = cmd.split(/&&|;/, 3);
6013
+ if (parts.length >= 2)
6014
+ firstSeg = parts[1].trim();
6002
6015
  }
6003
- const fresh = await fetcher(nowMs);
6004
- if (!fresh) {
6005
- return cached?.profile.planTier ?? null;
6016
+ const tokens = firstSeg.split(/\s+/).filter(Boolean);
6017
+ while (tokens.length && SHELL_NOISE.has(tokens[0]))
6018
+ tokens.shift();
6019
+ if (!tokens.length)
6020
+ return "?";
6021
+ let first = tokens[0];
6022
+ if (first.includes("=") && !first.startsWith("/")) {
6023
+ tokens.shift();
6024
+ if (!tokens.length)
6025
+ return "?";
6026
+ first = tokens[0];
6006
6027
  }
6007
- writeCachedProfile(cachePath, fresh, nowMs);
6008
- return fresh.planTier;
6028
+ if (first.includes("/") && !first.startsWith("./")) {
6029
+ first = first.replace(/\/+$/, "").split("/").pop();
6030
+ }
6031
+ return first.replace(/^["']|["']$/g, "") || "?";
6009
6032
  }
6010
-
6011
- // src/team/backfill.ts
6012
- init_fs();
6013
- var USAGE_LOG = cclensPath("usage.jsonl");
6014
- var PROFILE_CACHE = cclensPath("profile.json");
6015
- var BATCH_SIZE = 500;
6016
- var noopLog = () => {
6017
- };
6018
- function rawToWire(raw) {
6019
- const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6020
- return {
6021
- capturedAt: raw.captured_at,
6022
- fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6023
- sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6024
- sevenDayOpus: win(raw.seven_day_opus),
6025
- sevenDaySonnet: win(raw.seven_day_sonnet),
6026
- sevenDayOauthApps: win(raw.seven_day_oauth_apps),
6027
- sevenDayCowork: win(raw.seven_day_cowork),
6028
- extraUsage: raw.extra_usage ? {
6029
- isEnabled: raw.extra_usage.is_enabled,
6030
- monthlyLimitUsd: raw.extra_usage.monthly_limit,
6031
- usedCreditsUsd: raw.extra_usage.used_credits,
6032
- utilization: raw.extra_usage.utilization
6033
- } : null
6034
- };
6033
+ function firstTextBlock(blocks) {
6034
+ if (!Array.isArray(blocks))
6035
+ return "";
6036
+ for (const b of blocks) {
6037
+ if (b && typeof b === "object" && b.type === "text") {
6038
+ return String(b.text ?? "");
6039
+ }
6040
+ }
6041
+ return "";
6035
6042
  }
6036
- function chunk(items, size) {
6037
- if (size <= 0) return [items];
6038
- const out = [];
6039
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
6040
- return out;
6043
+ function blockText(blocks) {
6044
+ if (!blocks)
6045
+ return "";
6046
+ return blocks.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ");
6041
6047
  }
6042
- async function postBatch(config, snapshots, planTier) {
6043
- const payload = {
6044
- ingestId: `backfill-${randomUUID2()}`,
6045
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6046
- snapshotHistory: snapshots,
6047
- ...planTier ? { planTier } : {}
6048
- };
6049
- const result = await pushToTeamServer(config, payload);
6050
- if (!result.ok) {
6051
- return { inserted: 0, skipped: snapshots.length, status: result.status };
6052
- }
6053
- const body = result.body;
6054
- if (!body?.snapshotHistory) {
6055
- throw new Error(
6056
- "team server accepted snapshotHistory but did not return a result block \u2014 older image, upgrade required"
6057
- );
6048
+ function groupEventsByLocalDay(events) {
6049
+ const byDay = /* @__PURE__ */ new Map();
6050
+ for (const ev of events) {
6051
+ if (!ev.timestamp)
6052
+ continue;
6053
+ const ms = Date.parse(ev.timestamp);
6054
+ if (Number.isNaN(ms))
6055
+ continue;
6056
+ const day = toLocalDay(ms);
6057
+ let bucket = byDay.get(day);
6058
+ if (!bucket) {
6059
+ bucket = [];
6060
+ byDay.set(day, bucket);
6061
+ }
6062
+ bucket.push(ev);
6058
6063
  }
6059
- return {
6060
- inserted: body.snapshotHistory.inserted ?? 0,
6061
- skipped: body.snapshotHistory.skipped ?? 0,
6062
- status: result.status
6063
- };
6064
+ return byDay;
6064
6065
  }
6065
- function isAfterSince(snapshot, sinceCapturedAt) {
6066
- if (!sinceCapturedAt) return true;
6067
- const sinceMs = Date.parse(sinceCapturedAt);
6068
- if (Number.isNaN(sinceMs)) return true;
6069
- const capturedMs = Date.parse(snapshot.captured_at);
6070
- return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
6066
+ function newTurn(startMs, userInput = "") {
6067
+ return {
6068
+ startMs,
6069
+ lastMs: startMs,
6070
+ eventTs: startMs !== void 0 ? [startMs] : [],
6071
+ userInput,
6072
+ toolsTotal: 0,
6073
+ toolMix: /* @__PURE__ */ new Map(),
6074
+ bashVerbs: /* @__PURE__ */ new Map(),
6075
+ skills: /* @__PURE__ */ new Map(),
6076
+ subagents: [],
6077
+ taskOps: 0,
6078
+ toolErrors: 0,
6079
+ consecSameToolMax: 0,
6080
+ consec: 0,
6081
+ endedInInterrupt: false,
6082
+ models: /* @__PURE__ */ new Map(),
6083
+ tokensIn: 0,
6084
+ tokensOut: 0,
6085
+ tokensCacheR: 0,
6086
+ tokensCacheW: 0,
6087
+ seenMsgIds: /* @__PURE__ */ new Set()
6088
+ };
6071
6089
  }
6072
- function capturedAtMs(snapshot) {
6073
- const ms = Date.parse(snapshot.captured_at);
6074
- return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
6090
+ function closeTurn(t, closed, cwd) {
6091
+ const ts = [...t.eventTs].sort((a, b) => a - b);
6092
+ let active = 0;
6093
+ for (let i = 1; i < ts.length; i++) {
6094
+ const dt = ts[i] - ts[i - 1];
6095
+ if (dt < IDLE_GAP_MS4)
6096
+ active += dt;
6097
+ }
6098
+ closed.push({
6099
+ startMs: t.startMs ?? 0,
6100
+ activeMs: active,
6101
+ userInput: t.userInput,
6102
+ firstAgentText: t.firstAgentText,
6103
+ lastAgentText: t.lastAgentText,
6104
+ toolsTotal: t.toolsTotal,
6105
+ toolMix: t.toolMix,
6106
+ bashVerbs: t.bashVerbs,
6107
+ skills: t.skills,
6108
+ subagents: t.subagents,
6109
+ taskOps: t.taskOps,
6110
+ toolErrors: t.toolErrors,
6111
+ consecSameToolMax: t.consecSameToolMax,
6112
+ endedInInterrupt: t.endedInInterrupt,
6113
+ models: t.models,
6114
+ totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW,
6115
+ cwd
6116
+ });
6075
6117
  }
6076
- function latestCapturedAt(snapshots) {
6077
- let latest = null;
6078
- for (const s of snapshots) {
6079
- const ms = Date.parse(s.capturedAt);
6080
- if (Number.isNaN(ms)) continue;
6081
- if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
6118
+ function aggregateDay(dayEvents, _sessionFallbackProject) {
6119
+ const prTitles = [];
6120
+ let commits = 0;
6121
+ let pushes = 0;
6122
+ const gitToolUseIds = /* @__PURE__ */ new Set();
6123
+ const githubRepos = /* @__PURE__ */ new Set();
6124
+ const editedDirCounts = /* @__PURE__ */ new Map();
6125
+ const modelTurns = /* @__PURE__ */ new Map();
6126
+ let totalToolErrors = 0;
6127
+ let totalInterrupts = 0;
6128
+ let exitPlanCalls = 0;
6129
+ let taskOpsTotal = 0;
6130
+ let firstUser;
6131
+ let finalAgent;
6132
+ const cwdCounts = /* @__PURE__ */ new Map();
6133
+ const closed = [];
6134
+ let cur;
6135
+ const allTs = [];
6136
+ let lastEventCwd;
6137
+ for (const ev of dayEvents) {
6138
+ const tsMs = parseMs(ev.timestamp);
6139
+ if (tsMs !== void 0)
6140
+ allTs.push(tsMs);
6141
+ const view = viewEvent(ev);
6142
+ const rawType = view.rawType;
6143
+ const msg = { content: view.content, id: view.msgId, model: view.model, usage: view.usage };
6144
+ const content = view.content;
6145
+ const eventCwd = view.cwd;
6146
+ if (eventCwd)
6147
+ lastEventCwd = eventCwd;
6148
+ if (rawType === "user") {
6149
+ const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
6150
+ if (!isToolResultOnly) {
6151
+ const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
6152
+ if (cur) {
6153
+ if (text && INTERRUPT_RE2.test(text)) {
6154
+ cur.endedInInterrupt = true;
6155
+ totalInterrupts++;
6156
+ }
6157
+ closeTurn(cur, closed, eventCwd);
6158
+ }
6159
+ cur = newTurn(tsMs, text);
6160
+ if (!firstUser && text && classifyUserInputSource(text) === "human") {
6161
+ firstUser = text;
6162
+ }
6163
+ } else {
6164
+ if (Array.isArray(content)) {
6165
+ for (const c of content) {
6166
+ if (!c || typeof c !== "object" || c.type !== "tool_result")
6167
+ continue;
6168
+ if (c.is_error) {
6169
+ totalToolErrors++;
6170
+ if (cur)
6171
+ cur.toolErrors++;
6172
+ }
6173
+ const tuId = c.tool_use_id;
6174
+ if (tuId && gitToolUseIds.has(tuId)) {
6175
+ const rc = c.content;
6176
+ const text = typeof rc === "string" ? rc : Array.isArray(rc) ? rc.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n") : "";
6177
+ for (const m of text.matchAll(/(?<![.\w])github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git\b|[/\s"'),]|$)/g)) {
6178
+ if (m[1].toLowerCase() === "user-attachments")
6179
+ continue;
6180
+ githubRepos.add(`${m[1]}/${m[2]}`.toLowerCase());
6181
+ }
6182
+ }
6183
+ }
6184
+ }
6185
+ if (cur && tsMs !== void 0) {
6186
+ cur.eventTs.push(tsMs);
6187
+ cur.lastMs = tsMs;
6188
+ }
6189
+ }
6190
+ } else if (rawType === "assistant") {
6191
+ if (cur && tsMs !== void 0) {
6192
+ cur.eventTs.push(tsMs);
6193
+ cur.lastMs = tsMs;
6194
+ }
6195
+ const model = msg.model;
6196
+ if (model) {
6197
+ modelTurns.set(model, (modelTurns.get(model) ?? 0) + 1);
6198
+ if (cur)
6199
+ cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
6200
+ }
6201
+ const msgId = msg.id;
6202
+ const usage = msg.usage;
6203
+ if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage) {
6204
+ cur.seenMsgIds.add(msgId);
6205
+ cur.tokensIn += usage.input_tokens ?? 0;
6206
+ cur.tokensOut += usage.output_tokens ?? 0;
6207
+ cur.tokensCacheR += usage.cache_read_input_tokens ?? 0;
6208
+ cur.tokensCacheW += usage.cache_creation_input_tokens ?? 0;
6209
+ }
6210
+ if (Array.isArray(content)) {
6211
+ for (const c of content) {
6212
+ if (!c || typeof c !== "object")
6213
+ continue;
6214
+ const ct = c;
6215
+ if (ct.type === "text" && ct.text) {
6216
+ finalAgent = ct.text;
6217
+ if (cur) {
6218
+ if (cur.firstAgentText === void 0)
6219
+ cur.firstAgentText = ct.text;
6220
+ cur.lastAgentText = ct.text;
6221
+ }
6222
+ } else if (ct.type === "tool_use") {
6223
+ const name = ct.name ?? "unknown";
6224
+ if (cur) {
6225
+ cur.toolsTotal++;
6226
+ cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
6227
+ if (name === cur.lastTool)
6228
+ cur.consec++;
6229
+ else {
6230
+ cur.lastTool = name;
6231
+ cur.consec = 1;
6232
+ }
6233
+ if (cur.consec > cur.consecSameToolMax)
6234
+ cur.consecSameToolMax = cur.consec;
6235
+ if (name === "Skill") {
6236
+ const sk = String(ct.input?.skill ?? "unknown");
6237
+ cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
6238
+ }
6239
+ if (name === "Agent") {
6240
+ const inp = ct.input ?? {};
6241
+ cur.subagents.push({
6242
+ type: String(inp.subagent_type ?? "general-purpose"),
6243
+ description: trunc(String(inp.description ?? ""), 80),
6244
+ prompt_preview: trunc(String(inp.prompt ?? ""), 240),
6245
+ background: Boolean(inp.run_in_background)
6246
+ });
6247
+ }
6248
+ if (name === "ExitPlanMode")
6249
+ exitPlanCalls++;
6250
+ if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
6251
+ cur.taskOps++;
6252
+ taskOpsTotal++;
6253
+ }
6254
+ }
6255
+ if (name === "Bash") {
6256
+ const cmd = String(ct.input?.command ?? "");
6257
+ if (cur)
6258
+ cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
6259
+ if (/\bgh\s+pr\s+create\b/.test(cmd)) {
6260
+ const m = cmd.match(PR_TITLE_RE);
6261
+ prTitles.push(m?.[1] ?? cmd.slice(0, 120));
6262
+ }
6263
+ if (/\bgit\s+commit\b/.test(cmd))
6264
+ commits++;
6265
+ if (/\bgit\s+push\b/.test(cmd))
6266
+ pushes++;
6267
+ if (ct.id && /\bgit\s+push\b|\bgh\s+pr\s+/.test(cmd))
6268
+ gitToolUseIds.add(ct.id);
6269
+ }
6270
+ if (eventCwd && (name === "Bash" || name === "Edit" || name === "Write" || name === "Read")) {
6271
+ cwdCounts.set(eventCwd, (cwdCounts.get(eventCwd) ?? 0) + 1);
6272
+ }
6273
+ if (name === "Edit" || name === "Write" || name === "MultiEdit" || name === "NotebookEdit") {
6274
+ const fp = String(ct.input?.file_path ?? ct.input?.notebook_path ?? "");
6275
+ const slash = fp.lastIndexOf("/");
6276
+ if (slash > 0) {
6277
+ const dir = fp.slice(0, slash);
6278
+ editedDirCounts.set(dir, (editedDirCounts.get(dir) ?? 0) + 1);
6279
+ }
6280
+ }
6281
+ }
6282
+ }
6283
+ }
6284
+ }
6082
6285
  }
6083
- return latest?.at;
6286
+ if (cur)
6287
+ closeTurn(cur, closed, lastEventCwd);
6288
+ const tsSorted = [...allTs].sort((a, b) => a - b);
6289
+ let activeMs2 = 0;
6290
+ for (let i = 1; i < tsSorted.length; i++) {
6291
+ const dt = tsSorted[i] - tsSorted[i - 1];
6292
+ if (dt < IDLE_GAP_MS4)
6293
+ activeMs2 += dt;
6294
+ }
6295
+ const tsSortedStrings = dayEvents.map((e) => e.timestamp).filter((t) => !!t).sort();
6296
+ const start_iso = tsSortedStrings[0] ?? "";
6297
+ const end_iso = tsSortedStrings[tsSortedStrings.length - 1] ?? "";
6298
+ return {
6299
+ closed,
6300
+ firstUser,
6301
+ finalAgent,
6302
+ prTitles,
6303
+ commits,
6304
+ pushes,
6305
+ githubRepos: [...githubRepos].slice(0, 5),
6306
+ editedDirs: [...editedDirCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([dir]) => dir),
6307
+ modelTurns,
6308
+ totalToolErrors,
6309
+ totalInterrupts,
6310
+ exitPlanCalls,
6311
+ taskOpsTotal,
6312
+ cwdCounts,
6313
+ start_iso,
6314
+ end_iso,
6315
+ activeMs: activeMs2
6316
+ };
6084
6317
  }
6085
- async function runTeamBackfill(log3 = noopLog, filePath = USAGE_LOG, configOverride, options = {}) {
6086
- const config = configOverride === void 0 ? readTeamConfig() : configOverride;
6087
- if (!config) {
6088
- return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6318
+ function buildTopTools(closed) {
6319
+ const toolMix = /* @__PURE__ */ new Map();
6320
+ const bashVerbsAll = /* @__PURE__ */ new Map();
6321
+ for (const t of closed) {
6322
+ for (const [k, v] of t.toolMix)
6323
+ toolMix.set(k, (toolMix.get(k) ?? 0) + v);
6324
+ for (const [k, v] of t.bashVerbs)
6325
+ bashVerbsAll.set(k, (bashVerbsAll.get(k) ?? 0) + v);
6089
6326
  }
6090
- const raw = readSnapshots(filePath);
6091
- const claudeOnly = raw.filter(
6092
- (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
6093
- ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
6094
- if (claudeOnly.length === 0) {
6095
- log3("info", "team backfill: no usage snapshots to send");
6096
- return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6327
+ const top = [...toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
6328
+ return top.map(([name, count]) => {
6329
+ if (name === "Bash" && bashVerbsAll.size > 0) {
6330
+ const sub = [...bashVerbsAll.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
6331
+ return `Bash\xD7${count} (${sub})`;
6332
+ }
6333
+ return count > 1 ? `${name}\xD7${count}` : name;
6334
+ });
6335
+ }
6336
+ function buildEntries(sessionDetail) {
6337
+ const byDay = groupEventsByLocalDay(sessionDetail.events);
6338
+ const entries = [];
6339
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
6340
+ const sessionFallbackProject = canonicalProjectName(sessionDetail.projectName ?? "");
6341
+ for (const [local_day, dayEvents] of byDay) {
6342
+ if (dayEvents.length === 0)
6343
+ continue;
6344
+ const agg = aggregateDay(dayEvents, sessionFallbackProject);
6345
+ const { closed } = agg;
6346
+ const turnCount = closed.length;
6347
+ const toolsTotal = closed.reduce((s, t) => s + t.toolsTotal, 0);
6348
+ const tokensTotal = closed.reduce((s, t) => s + t.totalTokens, 0);
6349
+ const subagentCalls = closed.reduce((s, t) => s + t.subagents.length, 0);
6350
+ const skillsMap = /* @__PURE__ */ new Map();
6351
+ for (const t of closed) {
6352
+ for (const [k, v] of t.skills)
6353
+ skillsMap.set(k, (skillsMap.get(k) ?? 0) + v);
6354
+ }
6355
+ const skillCalls = [...skillsMap.values()].reduce((a, b) => a + b, 0);
6356
+ const taskOps = agg.taskOpsTotal;
6357
+ const interrupts = agg.totalInterrupts;
6358
+ const toolErrors = agg.totalToolErrors;
6359
+ const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
6360
+ const exitPlanCalls = agg.exitPlanCalls;
6361
+ const prs = agg.prTitles.length;
6362
+ const commits = agg.commits;
6363
+ const pushes = agg.pushes;
6364
+ const activeMin = Math.round(agg.activeMs / 6e3) / 10;
6365
+ const numbers = {
6366
+ active_min: activeMin,
6367
+ turn_count: turnCount,
6368
+ tools_total: toolsTotal,
6369
+ subagent_calls: subagentCalls,
6370
+ skill_calls: skillCalls,
6371
+ task_ops: taskOps,
6372
+ interrupts,
6373
+ tool_errors: toolErrors,
6374
+ consec_same_tool_max: consecMax,
6375
+ exit_plan_calls: exitPlanCalls,
6376
+ prs,
6377
+ commits,
6378
+ pushes,
6379
+ tokens_total: tokensTotal
6380
+ };
6381
+ const longestTurnActiveMs = Math.max(0, ...closed.map((t) => t.activeMs));
6382
+ const flags = [];
6383
+ if (interrupts >= 3)
6384
+ flags.push("interrupt_heavy");
6385
+ if (toolErrors >= 20)
6386
+ flags.push("high_errors");
6387
+ if (consecMax >= 8)
6388
+ flags.push("loop_suspected");
6389
+ if (agg.activeMs < 5 * 6e4 && prs >= 1)
6390
+ flags.push("fast_ship");
6391
+ if (exitPlanCalls > 0)
6392
+ flags.push("plan_used");
6393
+ const subagentTurns = closed.filter((t) => t.subagents.length >= 1).length;
6394
+ if (subagentTurns >= 3)
6395
+ flags.push("orchestrated");
6396
+ if (longestTurnActiveMs >= 20 * 6e4 && interrupts === 0)
6397
+ flags.push("long_autonomous");
6398
+ const primaryModel = [...agg.modelTurns.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? null;
6399
+ const modelMix = Object.fromEntries(agg.modelTurns);
6400
+ const firstUserRaw = agg.firstUser ?? "";
6401
+ const first_user = trunc(minimizeImages(firstUserRaw), 400);
6402
+ const finalAgentRaw = agg.finalAgent ?? "";
6403
+ const final_agent = trunc(minimizeImages(finalAgentRaw), 400);
6404
+ let project = sessionFallbackProject;
6405
+ if (agg.cwdCounts.size > 0) {
6406
+ const best = [...agg.cwdCounts.entries()].sort((a, b) => b[1] - a[1])[0];
6407
+ project = canonicalProjectName(best[0]);
6408
+ }
6409
+ const top_tools = buildTopTools(closed);
6410
+ const skills = Object.fromEntries(skillsMap);
6411
+ const subagents = closed.flatMap((t) => t.subagents);
6412
+ const humanText = dayEvents.filter((ev) => {
6413
+ const v = viewEvent(ev);
6414
+ if (v.rawType !== "user")
6415
+ return false;
6416
+ const content = v.content;
6417
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
6418
+ if (isToolResultOnly)
6419
+ return false;
6420
+ const text = blockText(ev.blocks) || ev.preview || "";
6421
+ return classifyUserInputSource(text) === "human";
6422
+ }).map((ev) => blockText(ev.blocks) || ev.preview || "").join("\n");
6423
+ const satisfaction_signals = countSatisfactionSignals(humanText);
6424
+ const user_input_sources = {
6425
+ human: 0,
6426
+ teammate: 0,
6427
+ skill_load: 0,
6428
+ slash_command: 0,
6429
+ system_instruction: 0
6430
+ };
6431
+ for (const ev of dayEvents) {
6432
+ const v = viewEvent(ev);
6433
+ if (v.rawType !== "user")
6434
+ continue;
6435
+ const content = v.content;
6436
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
6437
+ if (isToolResultOnly)
6438
+ continue;
6439
+ const text = blockText(ev.blocks) || ev.preview || "";
6440
+ const src = classifyUserInputSource(text);
6441
+ user_input_sources[src]++;
6442
+ }
6443
+ const pendingUserInstructions = extractUserInstructions(humanText).slice(0, 5);
6444
+ const trivial = isTrivial(numbers);
6445
+ const entry = {
6446
+ version: CURRENT_ENTRY_SCHEMA_VERSION,
6447
+ agent: sessionDetail.agent,
6448
+ session_id: sessionDetail.id,
6449
+ local_day,
6450
+ project,
6451
+ start_iso: agg.start_iso,
6452
+ end_iso: agg.end_iso,
6453
+ numbers,
6454
+ flags,
6455
+ primary_model: primaryModel,
6456
+ model_mix: modelMix,
6457
+ first_user,
6458
+ final_agent,
6459
+ pr_titles: agg.prTitles,
6460
+ ...agg.githubRepos.length ? { github_repos: agg.githubRepos } : {},
6461
+ ...agg.editedDirs.length ? { edited_dirs: agg.editedDirs } : {},
6462
+ top_tools,
6463
+ skills,
6464
+ subagents,
6465
+ satisfaction_signals,
6466
+ user_input_sources,
6467
+ enrichment: trivial ? skippedTrivialEnrichment(generatedAt) : { ...pendingEnrichment(), user_instructions: pendingUserInstructions },
6468
+ generated_at: generatedAt,
6469
+ source_jsonl: sessionDetail.filePath ?? "",
6470
+ source_checkpoint: { byte_offset: 0, last_event_ts: agg.end_iso || null }
6471
+ };
6472
+ entry.signals = computeEntrySignals(entry);
6473
+ entries.push(entry);
6097
6474
  }
6098
- const wire = claudeOnly.map(rawToWire);
6099
- const batches = chunk(wire, BATCH_SIZE);
6100
- const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
6101
- let inserted = 0;
6102
- let skipped = 0;
6103
- let lastSnapshotAt;
6104
- for (let i = 0; i < batches.length; i++) {
6105
- const batch = batches[i];
6106
- try {
6107
- const result = await postBatch(config, batch, planTier);
6108
- if (result.status >= 400) {
6109
- log3("warn", `team backfill: batch ${i + 1}/${batches.length} failed (${result.status})`);
6110
- return {
6111
- paired: true,
6112
- sentSnapshots: wire.length,
6113
- insertedSnapshots: inserted,
6114
- skippedSnapshots: skipped,
6115
- batches: i,
6116
- lastSnapshotAt,
6117
- error: `HTTP ${result.status}`
6118
- };
6119
- }
6120
- inserted += result.inserted;
6121
- skipped += result.skipped;
6122
- lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
6123
- } catch (err) {
6124
- log3("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
6125
- return {
6126
- paired: true,
6127
- sentSnapshots: wire.length,
6128
- insertedSnapshots: inserted,
6129
- skippedSnapshots: skipped,
6130
- batches: i,
6131
- lastSnapshotAt,
6132
- error: err.message
6133
- };
6475
+ entries.sort((a, b) => a.local_day.localeCompare(b.local_day));
6476
+ return entries;
6477
+ }
6478
+
6479
+ // src/perception/build-entries.ts
6480
+ function decodeProjectDirName(projectDir) {
6481
+ if (projectDir.startsWith("-")) {
6482
+ return "/" + projectDir.slice(1).replace(/-/g, "/");
6483
+ }
6484
+ return projectDir.replace(/-/g, "/");
6485
+ }
6486
+ function buildEntriesForFile(filePath) {
6487
+ const stat = statSync(filePath);
6488
+ const raw = readFileSync6(filePath, "utf8");
6489
+ const rawLines = raw.split("\n").filter(Boolean).map((l) => {
6490
+ try {
6491
+ return JSON.parse(l);
6492
+ } catch {
6493
+ return null;
6134
6494
  }
6495
+ }).filter((x) => x !== null);
6496
+ if (rawLines.length === 0) return null;
6497
+ const { meta, events } = parseTranscript(rawLines);
6498
+ const fileName = basename(filePath);
6499
+ const projectDir = basename(dirname3(filePath));
6500
+ const sessionId = fileName.replace(/\.jsonl$/, "");
6501
+ const sd = {
6502
+ ...meta,
6503
+ id: sessionId,
6504
+ filePath,
6505
+ projectDir,
6506
+ projectName: meta.cwd ?? decodeProjectDirName(projectDir),
6507
+ events
6508
+ };
6509
+ const built = buildEntries(sd);
6510
+ for (const e of built) {
6511
+ e.source_checkpoint.byte_offset = stat.size;
6135
6512
  }
6136
- log3(
6137
- "info",
6138
- `team backfill: ${inserted} new, ${skipped} already-known across ${batches.length} batch${batches.length === 1 ? "" : "es"}`
6139
- );
6513
+ return built;
6514
+ }
6515
+
6516
+ // src/team/push.ts
6517
+ init_storage();
6518
+ var SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
6519
+ var POST_TIMEOUT_MS = 15e3;
6520
+ function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
6521
+ const raw = latestClaudeCodeSnapshot(filePath);
6522
+ if (!raw) return null;
6523
+ const capturedMs = Date.parse(raw.captured_at);
6524
+ if (Number.isNaN(capturedMs)) return null;
6525
+ if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
6526
+ const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6140
6527
  return {
6141
- paired: true,
6142
- sentSnapshots: wire.length,
6143
- insertedSnapshots: inserted,
6144
- skippedSnapshots: skipped,
6145
- batches: batches.length,
6146
- lastSnapshotAt
6528
+ capturedAt: raw.captured_at,
6529
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6530
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6531
+ sevenDayOpus: toWire(raw.seven_day_opus),
6532
+ sevenDaySonnet: toWire(raw.seven_day_sonnet),
6533
+ sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
6534
+ sevenDayCowork: toWire(raw.seven_day_cowork),
6535
+ extraUsage: raw.extra_usage ? {
6536
+ isEnabled: raw.extra_usage.is_enabled,
6537
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
6538
+ usedCreditsUsd: raw.extra_usage.used_credits,
6539
+ utilization: raw.extra_usage.utilization
6540
+ } : null
6147
6541
  };
6148
6542
  }
6149
-
6150
- // src/team/last-push.ts
6151
- init_fs();
6152
- import { writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, mkdirSync as mkdirSync7 } from "node:fs";
6153
- import { join as join6 } from "node:path";
6154
- var LAST_PUSH_FILE = "team-last-push.json";
6155
- function write(record, dir) {
6156
- const d = dir ?? cclensHome();
6157
- mkdirSync7(d, { recursive: true });
6158
- writeFileSync6(join6(d, LAST_PUSH_FILE), JSON.stringify(record, null, 2), { mode: 384 });
6543
+ function bucketToRollup(b) {
6544
+ return {
6545
+ day: b.date,
6546
+ agentTimeMs: b.airTimeMs,
6547
+ sessions: b.sessions,
6548
+ toolCalls: b.toolCalls,
6549
+ turns: b.turns,
6550
+ tokens: { ...b.tokens }
6551
+ };
6159
6552
  }
6160
- function writeLastPushSuccess(payload, dir) {
6161
- write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: true, payload }, dir);
6553
+ function buildRollupsForRange(sessions, sinceDay) {
6554
+ const startCounts = /* @__PURE__ */ new Map();
6555
+ for (const s of sessions) {
6556
+ const d = sessionDay(s);
6557
+ if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
6558
+ }
6559
+ return dailyActivity(sessions).filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({ ...bucketToRollup(b), uniqueSessions: startCounts.get(b.date) ?? 0 }));
6162
6560
  }
6163
- function writeLastPushFailure(payload, error, dir) {
6164
- write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: false, payload, error }, dir);
6561
+ function dayBoundsMs(day) {
6562
+ const [y, m, d] = day.split("-").map(Number);
6563
+ return {
6564
+ startMs: new Date(y, m - 1, d, 0, 0, 0, 0).getTime(),
6565
+ endMs: new Date(y, m - 1, d + 1, 0, 0, 0, 0).getTime()
6566
+ };
6165
6567
  }
6166
-
6167
- // src/team/commands.ts
6168
- async function dispatchCommand(command, config, log3) {
6169
- switch (command.type) {
6170
- case "backfill-activity":
6171
- return runActivityBackfill(command, config, log3);
6172
- default:
6173
- return {
6174
- id: command.id,
6175
- ok: false,
6176
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6177
- error: `Unknown command type: ${command.type}`
6178
- };
6568
+ function clipSegmentsToDay(segments, bounds) {
6569
+ if (!segments || segments.length === 0) return [];
6570
+ const out = [];
6571
+ for (const seg of segments) {
6572
+ const s = Math.max(seg.startMs, bounds.startMs);
6573
+ const e = Math.min(seg.endMs, bounds.endMs);
6574
+ if (e > s) out.push({ startMs: s, endMs: e });
6179
6575
  }
6576
+ return out;
6180
6577
  }
6181
- async function runActivityBackfill(command, config, log3) {
6182
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
6183
- const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
6184
- const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
6185
- const days = command.params.days;
6186
- const todayMs = Date.now();
6187
- const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
6188
- const targetDay = toLocalDay3(targetDayMs);
6189
- log3("info", `command ${command.id}: backfill-activity from ${targetDay} (${days} days)`);
6190
- let sessions;
6191
- try {
6192
- sessions = await listSessions2({ limit: 1e4 });
6193
- } catch (err) {
6194
- return {
6195
- id: command.id,
6196
- ok: false,
6197
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6198
- error: `Failed to read sessions: ${err.message}`
6199
- };
6200
- }
6201
- const rollups = buildRollupsForRange(sessions, targetDay);
6202
- if (rollups.length === 0) {
6203
- return {
6204
- id: command.id,
6205
- ok: true,
6206
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6207
- summary: { pushed: 0, fromDay: targetDay }
6208
- };
6209
- }
6210
- const privateProjects = new Set(config.privateProjects ?? []);
6211
- const enrichmentOptIn = !!config.enrichmentOptIn;
6212
- const resolveRepo = createRepoResolver();
6213
- let pushed = 0;
6214
- for (const rollup of rollups) {
6215
- const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
6216
- const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, privateProjects, enrichmentOptIn, resolveRepo);
6217
- let artifactSignals = null;
6218
- try {
6219
- artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: [process.cwd()] });
6220
- } catch {
6221
- }
6222
- const payload = buildIngestPayload({
6223
- rollup,
6224
- richExtras: richBlocks?.rich,
6225
- enrichedExtras: richBlocks?.enriched,
6226
- artifactSignals: artifactSignals ?? void 0
6227
- });
6228
- const result = await pushToTeamServer(config, payload);
6229
- if (!result.ok) {
6230
- return {
6231
- id: command.id,
6232
- ok: false,
6233
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6234
- error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
6235
- };
6236
- }
6237
- pushed++;
6578
+ function filterSyncedSessions(sessions, syncProjects) {
6579
+ if (!syncProjects) return sessions;
6580
+ return sessions.filter((s) => shouldSyncProject(projectRepoName(s.projectName), syncProjects));
6581
+ }
6582
+ function sessionTouchesDay(s, day) {
6583
+ const bounds = dayBoundsMs(day);
6584
+ if (s.activeSegments && s.activeSegments.length > 0) {
6585
+ return s.activeSegments.some((seg) => seg.endMs > bounds.startMs && seg.startMs < bounds.endMs);
6238
6586
  }
6239
- log3("info", `command ${command.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
6240
- return {
6241
- id: command.id,
6242
- ok: true,
6243
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6244
- summary: { pushed, fromDay: targetDay }
6245
- };
6587
+ return sessionDay(s) === day;
6246
6588
  }
6247
-
6248
- // src/team/sync.ts
6249
- init_fs();
6250
- var inFlightCommands = /* @__PURE__ */ new Set();
6251
- var USAGE_LOG2 = cclensPath("usage.jsonl");
6252
- var PROFILE_CACHE2 = cclensPath("profile.json");
6253
- var noopLog2 = () => {
6254
- };
6255
- async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
6256
- const config = configOverride === void 0 ? readTeamConfig() : configOverride;
6257
- if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
6258
- try {
6259
- const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
6260
- const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
6261
- const today = toLocalDay3(Date.now());
6262
- let nextConfig = { ...config };
6263
- const persistConfig = (patch) => {
6264
- nextConfig = { ...nextConfig, ...patch };
6265
- writeTeamConfig(nextConfig);
6266
- };
6267
- const collectedCommands = /* @__PURE__ */ new Map();
6268
- const collectCommands = (commands) => {
6269
- if (!commands) return;
6270
- for (const cmd of commands) {
6271
- if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
6272
- }
6273
- };
6274
- const dispatchAndReport = async () => {
6275
- try {
6276
- if (collectedCommands.size === 0) return;
6277
- const results = [];
6278
- for (const cmd of collectedCommands.values()) {
6279
- if (inFlightCommands.has(cmd.id)) continue;
6280
- inFlightCommands.add(cmd.id);
6281
- try {
6282
- const result = await dispatchCommand(cmd, config, log3);
6283
- results.push(result);
6284
- } finally {
6285
- inFlightCommands.delete(cmd.id);
6286
- }
6287
- }
6288
- if (results.length === 0) return;
6289
- const resultsPayload = {
6290
- ingestId: randomUUID3(),
6291
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6292
- commandResults: results
6293
- };
6294
- const r = await pushToTeamServer(config, resultsPayload);
6295
- if (!r.ok) {
6296
- log3("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
6297
- } else {
6298
- log3("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
6299
- }
6300
- } catch (err) {
6301
- log3("warn", `team command dispatch error: ${err.message}`);
6302
- }
6303
- };
6304
- const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
6305
- sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
6306
- });
6307
- if (usageBackfill.lastSnapshotAt) {
6308
- persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
6309
- }
6310
- const sessions = await listSessions2({ limit: 1e4 });
6311
- const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
6312
- const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG2) ?? void 0;
6313
- const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
6314
- const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
6315
- if (rollups.length === 0) {
6316
- const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
6317
- if (!hasLiveData) {
6318
- if (usageBackfill.sentSnapshots === 0) log3("info", "team push: nothing to sync");
6319
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
6320
- }
6321
- const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks });
6322
- const result = await pushToTeamServer(config, payload);
6323
- if (!result.ok) {
6324
- const errLine = `team push failed (${result.status})`;
6325
- log3("warn", `${errLine}; queueing`);
6326
- writeLastPushFailure(payload, errLine);
6327
- enqueuePayload(payload);
6328
- return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
6329
- }
6330
- writeLastPushSuccess(payload);
6331
- collectCommands(result.body?.commands);
6332
- let queuedDrained2 = 0;
6333
- const backlog = dequeuePayloads();
6334
- for (let i = 0; i < backlog.length; i++) {
6335
- const qResult = await pushToTeamServer(config, backlog[i]);
6336
- if (!qResult.ok) {
6337
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
6338
- break;
6339
- }
6340
- collectCommands(qResult.body?.commands);
6341
- queuedDrained2++;
6589
+ function buildRichRollupBlocks(day, daySessions, entries, resolveRepo) {
6590
+ const bounds = dayBoundsMs(day);
6591
+ const clippedSessions = [];
6592
+ for (const s of daySessions) {
6593
+ const clipped = clipSegmentsToDay(s.activeSegments, bounds);
6594
+ if (clipped.length === 0) continue;
6595
+ clippedSessions.push({ ...s, activeSegments: clipped });
6596
+ }
6597
+ const bursts = computeBurstsFromSessions(clippedSessions);
6598
+ const stats = summarizeBursts(bursts);
6599
+ const repoForEntry = (e) => {
6600
+ if (resolveRepo) {
6601
+ const own = resolveRepo(e.project);
6602
+ if (own) return own;
6603
+ const counts = /* @__PURE__ */ new Map();
6604
+ for (const d of e.edited_dirs ?? []) {
6605
+ const r = resolveRepo(d);
6606
+ if (r) counts.set(r, (counts.get(r) ?? 0) + 1);
6342
6607
  }
6343
- log3("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
6344
- await dispatchAndReport();
6345
- return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
6608
+ const dominant = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
6609
+ if (dominant) return dominant;
6346
6610
  }
6347
- let pushed = 0;
6348
- let queued = 0;
6349
- let failedDay;
6350
- let lastPushedDay;
6351
- const privateProjects = new Set(config.privateProjects ?? []);
6352
- const enrichmentOptIn = !!config.enrichmentOptIn;
6353
- const resolveRepo = createRepoResolver();
6354
- const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
6355
- for (let i = 0; i < rollups.length; i++) {
6356
- const rollup = rollups[i];
6357
- const isLatest = i === rollups.length - 1;
6358
- const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
6359
- const richBlocks = buildRichBlocksForDay(
6360
- rollup.day,
6361
- daySessions,
6362
- privateProjects,
6363
- enrichmentOptIn,
6364
- resolveRepo
6365
- );
6366
- let artifactSignals = null;
6367
- try {
6368
- artifactSignals = probeArtifactSignals2({
6369
- day: rollup.day,
6370
- extraRoots: [process.cwd()]
6371
- });
6372
- } catch {
6373
- }
6374
- const payload = buildIngestPayload({
6375
- rollup,
6376
- richExtras: richBlocks?.rich,
6377
- enrichedExtras: richBlocks?.enriched,
6378
- artifactSignals: artifactSignals ?? void 0,
6379
- usageSnapshot: isLatest ? usageSnapshot : void 0,
6380
- planTier,
6381
- // Same rationale as usageSnapshot — current cycle data only on the
6382
- // latest rollup so older days don't get tagged with today's peaks.
6383
- cyclePeaks: isLatest ? cyclePeaks : void 0
6384
- });
6385
- const result = await pushToTeamServer(config, payload);
6386
- if (!result.ok) {
6387
- const errLine = `team push failed on ${rollup.day} (${result.status})`;
6388
- log3("warn", `${errLine}; queueing`);
6389
- writeLastPushFailure(payload, errLine);
6390
- enqueuePayload(payload);
6391
- queued++;
6392
- failedDay = rollup.day;
6393
- break;
6394
- }
6395
- writeLastPushSuccess(payload);
6396
- collectCommands(result.body?.commands);
6397
- pushed++;
6398
- lastPushedDay = rollup.day;
6611
+ return e.github_repos?.length === 1 ? e.github_repos[0] : null;
6612
+ };
6613
+ const sessionRepo = /* @__PURE__ */ new Map();
6614
+ for (const e of entries) {
6615
+ const r = repoForEntry(e);
6616
+ if (r) sessionRepo.set(e.session_id, r);
6617
+ }
6618
+ const projects = /* @__PURE__ */ new Map();
6619
+ for (const s of clippedSessions) {
6620
+ const canonical = canonicalProjectName(s.projectName);
6621
+ const name = projectRepoName(s.projectName);
6622
+ const repo = resolveRepo?.(canonical) ?? sessionRepo.get(s.id);
6623
+ const key = repo ?? name;
6624
+ const ms = s.activeSegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
6625
+ const cur = projects.get(key) ?? { project: name, agentTimeMs: 0, sessions: 0, ...repo ? { resolved: repo } : {} };
6626
+ cur.agentTimeMs += ms;
6627
+ cur.sessions += 1;
6628
+ projects.set(key, cur);
6629
+ }
6630
+ const projectRepos = /* @__PURE__ */ new Map();
6631
+ const workingShapes = /* @__PURE__ */ new Map();
6632
+ const skills = /* @__PURE__ */ new Map();
6633
+ const subagents = /* @__PURE__ */ new Map();
6634
+ let toolErrors = 0;
6635
+ let brainstorm = 0;
6636
+ let planMode = 0;
6637
+ let prs = 0;
6638
+ let commits = 0;
6639
+ let pushes = 0;
6640
+ let longCount = 0;
6641
+ let longTotalMin = 0;
6642
+ let longMaxSingleMin = 0;
6643
+ for (const e of entries) {
6644
+ const shape = e.signals?.working_shape ?? null;
6645
+ if (shape) {
6646
+ const cur = workingShapes.get(shape) ?? { sessions: 0, agentTimeMs: 0 };
6647
+ cur.sessions += 1;
6648
+ cur.agentTimeMs += Math.round(e.numbers.active_min * 6e4);
6649
+ workingShapes.set(shape, cur);
6399
6650
  }
6400
- if (failedDay) {
6401
- if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
6402
- } else {
6403
- persistConfig({ lastSyncedDay: today });
6651
+ for (const [name, count] of Object.entries(e.skills)) {
6652
+ if (count > 0) skills.set(name, (skills.get(name) ?? 0) + 1);
6404
6653
  }
6405
- let queuedDrained = 0;
6406
- if (!failedDay) {
6407
- const backlog = dequeuePayloads();
6408
- for (let i = 0; i < backlog.length; i++) {
6409
- const qResult = await pushToTeamServer(config, backlog[i]);
6410
- if (!qResult.ok) {
6411
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
6412
- break;
6413
- }
6414
- collectCommands(qResult.body?.commands);
6415
- queuedDrained++;
6416
- }
6654
+ for (const sa of e.subagents) {
6655
+ subagents.set(sa.type, (subagents.get(sa.type) ?? 0) + 1);
6656
+ }
6657
+ toolErrors += e.numbers.tool_errors;
6658
+ if (e.signals?.brainstorm_warmup) brainstorm += 1;
6659
+ if (e.numbers.exit_plan_calls > 0) planMode += 1;
6660
+ prs += e.numbers.prs;
6661
+ commits += e.numbers.commits;
6662
+ pushes += e.numbers.pushes;
6663
+ if (e.github_repos?.length) {
6664
+ const key = sessionRepo.get(e.session_id) ?? projectRepoName(e.project);
6665
+ const set = projectRepos.get(key) ?? /* @__PURE__ */ new Set();
6666
+ for (const r of e.github_repos) set.add(r);
6667
+ projectRepos.set(key, set);
6417
6668
  }
6418
- if (pushed > 0) {
6419
- log3("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
6669
+ if (e.flags.includes("long_autonomous")) {
6670
+ longCount += 1;
6671
+ longTotalMin += e.numbers.active_min;
6672
+ if (e.numbers.active_min > longMaxSingleMin) longMaxSingleMin = e.numbers.active_min;
6420
6673
  }
6421
- await dispatchAndReport();
6422
- return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
6423
- } catch (err) {
6424
- const message = err.message;
6425
- log3("warn", `team push error: ${message}`);
6426
- writeLastPushFailure(
6427
- { ingestId: "n/a", observedAt: (/* @__PURE__ */ new Date()).toISOString() },
6428
- `team push error: ${message}`
6429
- );
6430
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
6431
6674
  }
6675
+ return {
6676
+ projects: Array.from(projects.entries()).map(([key, v]) => {
6677
+ const extras = [...projectRepos.get(key) ?? []].filter((r) => r !== v.resolved);
6678
+ const repos = v.resolved ? [v.resolved, ...extras] : extras;
6679
+ return {
6680
+ project: v.project,
6681
+ agentTimeMs: v.agentTimeMs,
6682
+ sessions: v.sessions,
6683
+ ...repos.length ? { githubRepos: repos.slice(0, 5) } : {}
6684
+ };
6685
+ }).sort((a, b) => b.agentTimeMs - a.agentTimeMs),
6686
+ workingShapes: Array.from(workingShapes.entries()).map(([shape, v]) => ({ shape, ...v })).sort((a, b) => b.sessions - a.sessions),
6687
+ concurrencyPeak: stats.peakConcurrent,
6688
+ parallelMinutes: Math.round(stats.totalParallelMs / 6e4),
6689
+ // Wire schema requires integer minutes (active_min is a float).
6690
+ longAutonomous: { count: longCount, totalMin: Math.round(longTotalMin), maxSingleMin: Math.round(longMaxSingleMin) },
6691
+ toolErrors,
6692
+ skillsLoaded: Array.from(skills.entries()).map(([name, sessions]) => ({ name, sessions })).sort((a, b) => b.sessions - a.sessions),
6693
+ subagentsDispatched: Array.from(subagents.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count),
6694
+ brainstormWarmupSessions: brainstorm,
6695
+ planModeUsed: planMode,
6696
+ prs,
6697
+ commits,
6698
+ pushes
6699
+ };
6432
6700
  }
6433
- async function buildCyclePeaksForPush(planTier, loadCurve) {
6434
- const validTiers = ["pro", "pro-max", "pro-max-20x", "custom"];
6435
- const tier = validTiers.find((t) => t === planTier) ?? "pro-max-20x";
6436
- const dump = await loadCurve(tier).catch(() => null);
6437
- if (!dump || dump.curve.length === 0) return void 0;
6438
- const HOUR = 36e5;
6439
- const nowMs = Date.now();
6440
- const peaksFor = (cycleKey, realKey, predKey, maxCycles) => {
6441
- const TOLERANCE_MS = cycleKey === "cycle_end_7d" ? 12 * HOUR : 0;
6442
- const byCycle = /* @__PURE__ */ new Map();
6443
- for (const p of dump.curve) {
6444
- const k = p[cycleKey];
6445
- if (!k) continue;
6446
- const ms = Date.parse(k);
6447
- if (Number.isNaN(ms)) continue;
6448
- const bucket = Math.round(ms / HOUR) * HOUR;
6449
- const arr = byCycle.get(bucket) ?? [];
6450
- arr.push(p);
6451
- byCycle.set(bucket, arr);
6701
+ function buildEnrichedExtras(entries) {
6702
+ const outcomeMix = {};
6703
+ const helpfulnessMix = {};
6704
+ const goalMix = {};
6705
+ for (const e of entries) {
6706
+ if (e.enrichment.status !== "done") continue;
6707
+ if (e.enrichment.outcome) {
6708
+ outcomeMix[e.enrichment.outcome] = (outcomeMix[e.enrichment.outcome] ?? 0) + 1;
6452
6709
  }
6453
- const merged = [];
6454
- for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
6455
- const last = merged[merged.length - 1];
6456
- if (last && endMs - last.endMs <= TOLERANCE_MS) {
6457
- last.endMs = endMs;
6458
- last.points.push(...points);
6459
- } else {
6460
- merged.push({ endMs, points: [...points] });
6461
- }
6710
+ if (e.enrichment.claude_helpfulness) {
6711
+ helpfulnessMix[e.enrichment.claude_helpfulness] = (helpfulnessMix[e.enrichment.claude_helpfulness] ?? 0) + 1;
6462
6712
  }
6463
- const out = [];
6464
- for (const { endMs, points } of merged) {
6465
- let peak = 0;
6466
- let source = "predicted";
6467
- for (const p of points) {
6468
- const r = p[realKey];
6469
- if (typeof r === "number" && r > peak) {
6470
- peak = r;
6471
- source = "real";
6472
- }
6473
- const v = p[predKey] ?? 0;
6474
- if (v > peak) {
6475
- peak = v;
6476
- source = "predicted";
6477
- }
6478
- }
6479
- out.push({
6480
- endsAt: new Date(endMs).toISOString(),
6481
- peakPct: Math.round(peak * 10) / 10,
6482
- source,
6483
- current: endMs > nowMs
6484
- });
6713
+ for (const [cat, mins] of Object.entries(e.enrichment.goal_categories)) {
6714
+ if (typeof mins === "number") goalMix[cat] = (goalMix[cat] ?? 0) + mins;
6485
6715
  }
6486
- return out.slice(-maxCycles);
6487
- };
6488
- return {
6489
- fiveHour: peaksFor("cycle_end_5h", "real_5h", "pred_5h", 24),
6490
- sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 12)
6491
- };
6716
+ }
6717
+ return { outcomeMix, helpfulnessMix, goalMix };
6492
6718
  }
6493
-
6494
- // src/perception/worker.ts
6495
- init_dist();
6496
- init_fs();
6497
- import { statSync as statSync4, readFileSync as readFileSync10 } from "node:fs";
6498
- import { basename, dirname as dirname8 } from "node:path";
6499
-
6500
- // ../entries/dist/signals.js
6501
- init_dist();
6502
- var TEAMMATE_RE = /^<teammate-message\b/;
6503
- var SKILL_LOAD_RE = /^Base directory for this skill:/;
6504
- var SLASH_COMMAND_RE2 = /^<command-name>|^<local-command-stdout>/;
6505
- function classifyUserInputSource(text) {
6506
- if (!text)
6507
- return "human";
6508
- const stripped = stripFrameworkBoilerplate(text);
6509
- if (!stripped)
6510
- return "system_instruction";
6511
- if (TEAMMATE_RE.test(stripped))
6512
- return "teammate";
6513
- if (SKILL_LOAD_RE.test(stripped))
6514
- return "skill_load";
6515
- if (SLASH_COMMAND_RE2.test(stripped))
6516
- return "slash_command";
6517
- return "human";
6719
+ var ensuredSessions = /* @__PURE__ */ new Set();
6720
+ function resetEnsuredSessions() {
6721
+ ensuredSessions.clear();
6518
6722
  }
6519
- var HAPPY_RE = /\b(?:yay|yaay|yass|woohoo|nice|great|love(?:ly)?|perfect|amazing|awesome)\s*!|!!!/gi;
6520
- var SATISFIED_RE = /\b(?:thanks|thank you|looks good|lgtm|works|that works|all good|sounds good)\b/gi;
6521
- var DISSATISFIED_RE = /\b(?:that'?s (?:not|wrong)|try again|no, (?:that|this)|not quite|incorrect)\b/gi;
6522
- var FRUSTRATED_RE = /\b(?:broken|stop|why (?:did|are|would) you|give up|ugh|argh|wtf)\b/gi;
6523
- function countSatisfactionSignals(text) {
6524
- if (!text)
6525
- return { happy: 0, satisfied: 0, dissatisfied: 0, frustrated: 0 };
6526
- return {
6527
- happy: (text.match(HAPPY_RE) ?? []).length,
6528
- satisfied: (text.match(SATISFIED_RE) ?? []).length,
6529
- dissatisfied: (text.match(DISSATISFIED_RE) ?? []).length,
6530
- frustrated: (text.match(FRUSTRATED_RE) ?? []).length
6723
+ function ensureEntriesForDay(day, daySessions) {
6724
+ for (const s of daySessions) {
6725
+ if (ensuredSessions.has(s.id)) continue;
6726
+ if ((s.agent ?? "claude-code") !== "claude-code") continue;
6727
+ if (entryExists(s.id, day)) continue;
6728
+ ensuredSessions.add(s.id);
6729
+ try {
6730
+ for (const e of buildEntriesForFile(s.filePath) ?? []) writeEntryPreservingEnrichment(e);
6731
+ } catch {
6732
+ }
6733
+ }
6734
+ }
6735
+ function buildRichBlocksForDay(day, daySessions, resolveRepo) {
6736
+ ensureEntriesForDay(day, daySessions);
6737
+ const sessionIds = new Set(daySessions.map((s) => s.id));
6738
+ const entries = listEntriesForDay(day).filter((e) => sessionIds.has(e.session_id));
6739
+ if (entries.length === 0) return void 0;
6740
+ const rich = buildRichRollupBlocks(day, daySessions, entries, resolveRepo);
6741
+ const enriched = buildEnrichedExtras(entries);
6742
+ return { rich, enriched };
6743
+ }
6744
+ function buildIngestPayload(inputs) {
6745
+ const richRollup = inputs.rollup && inputs.richExtras ? { ...inputs.rollup, ...inputs.richExtras } : void 0;
6746
+ return {
6747
+ ingestId: randomUUID(),
6748
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6749
+ cliVersion: "0.15.0",
6750
+ ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
6751
+ ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
6752
+ ...richRollup ? { richRollup } : {},
6753
+ ...inputs.enrichedExtras ? { enrichedExtras: inputs.enrichedExtras } : {},
6754
+ ...inputs.artifactSignals ? { artifactSignals: inputs.artifactSignals } : {},
6755
+ ...inputs.usageSnapshot ? { usageSnapshot: inputs.usageSnapshot } : {},
6756
+ ...inputs.planTier ? { planTier: inputs.planTier } : {},
6757
+ ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {},
6758
+ ...inputs.syncLog && inputs.syncLog.length ? { syncLog: inputs.syncLog } : {}
6531
6759
  };
6532
6760
  }
6533
- var INSTRUCTION_RE = /\b(?:can you|please|let's|let us|i need|could you|would you)\s+([^.?!\n]{5,200})/gi;
6534
- function extractUserInstructions(text) {
6535
- if (!text)
6536
- return [];
6537
- INSTRUCTION_RE.lastIndex = 0;
6538
- const out = [];
6539
- const seen = /* @__PURE__ */ new Set();
6540
- let m;
6541
- while ((m = INSTRUCTION_RE.exec(text)) !== null) {
6542
- const phrase = m[1].trim().replace(/\s+/g, " ");
6543
- const lc = phrase.toLowerCase();
6544
- if (seen.has(lc))
6761
+ async function pushToTeamServer(config, payload) {
6762
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
6763
+ method: "POST",
6764
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS),
6765
+ headers: {
6766
+ "Content-Type": "application/json",
6767
+ Authorization: `Bearer ${config.bearerToken}`
6768
+ },
6769
+ body: JSON.stringify(payload)
6770
+ });
6771
+ const body = await res.json().catch(() => null);
6772
+ return { ok: res.ok, status: res.status, body };
6773
+ }
6774
+
6775
+ // src/team/git-remote.ts
6776
+ import { existsSync as existsSync4, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
6777
+ import { dirname as dirname4, isAbsolute, join as join5, resolve } from "node:path";
6778
+ var GITHUB_URL_RE = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/;
6779
+ function parseGithubRemote(url) {
6780
+ const m = url.trim().match(GITHUB_URL_RE);
6781
+ return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
6782
+ }
6783
+ function originUrl(configText) {
6784
+ let inOrigin = false;
6785
+ for (const line of configText.split("\n")) {
6786
+ const t = line.trim();
6787
+ if (t.startsWith("[")) {
6788
+ inOrigin = /^\[remote\s+"origin"\]/.test(t);
6545
6789
  continue;
6546
- seen.add(lc);
6547
- out.push(phrase);
6548
- if (out.length >= 5)
6549
- break;
6790
+ }
6791
+ if (!inOrigin) continue;
6792
+ const m = t.match(/^url\s*=\s*(.+)$/);
6793
+ if (m) return m[1].trim();
6550
6794
  }
6551
- return out;
6795
+ return null;
6552
6796
  }
6553
- function classifySubagentRole(sa) {
6554
- const text = `${sa.description} ${sa.prompt_preview}`.toLowerCase();
6555
- if (/\b(re-?review|review[s]?|verify|audit|spec[- ]review|code[- ]quality|code[- ]reuse|efficiency review|sanity check|quick test)\b/.test(text)) {
6556
- return "reviewer";
6557
- }
6558
- if (/\b(implement|build chunk|build task|initialize|create the|add the)\b/.test(text)) {
6559
- return "implementer";
6560
- }
6561
- if (/\bexplore\b|\binventory\b|\bmap\b.*\b(codebase|repo|branch)\b|\baudit\b.*coverage/.test(text)) {
6562
- return "explorer";
6563
- }
6564
- if (/\b(investigate|research|reverse[- ]engineer|study|analy[sz]e|look up|tell me|brief me on|find out)\b/.test(text)) {
6565
- return "researcher";
6566
- }
6567
- if (/\b(env(?:ironment)? setup|configure|setup|bootstrap)\b/.test(text)) {
6568
- return "env-setup";
6569
- }
6570
- if (/\b(polish|cleanup|fix|refactor)\b/.test(text)) {
6571
- return "polish";
6797
+ function gitConfigPathFor(dir) {
6798
+ const dotGit = join5(dir, ".git");
6799
+ const st = statSync2(dotGit, { throwIfNoEntry: false });
6800
+ if (!st) return null;
6801
+ if (st.isDirectory()) return join5(dotGit, "config");
6802
+ const m = readFileSync7(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
6803
+ if (!m) return null;
6804
+ const gd = isAbsolute(m[1]) ? m[1] : resolve(dir, m[1]);
6805
+ const commondirFile = join5(gd, "commondir");
6806
+ if (existsSync4(commondirFile)) {
6807
+ const common = readFileSync7(commondirFile, "utf8").trim();
6808
+ return join5(isAbsolute(common) ? common : resolve(gd, common), "config");
6572
6809
  }
6573
- return "other";
6810
+ return join5(gd, "config");
6574
6811
  }
6575
- var STOCK_SUBAGENT_PREFIXES = /^(general-purpose|Explore|Plan|claude-code-guide|playwright-qa-verifier|statusline-setup|frontend-design:|code-review:|code-simplifier:|codex:|superpowers:)/;
6576
- function isStockSubagentType(type) {
6577
- return STOCK_SUBAGENT_PREFIXES.test(type);
6812
+ function createRepoResolver() {
6813
+ const cache = /* @__PURE__ */ new Map();
6814
+ return (dir) => {
6815
+ if (!dir || !isAbsolute(dir)) return null;
6816
+ const hit = cache.get(dir);
6817
+ if (hit !== void 0) return hit;
6818
+ let repo = null;
6819
+ try {
6820
+ for (let d = dir; ; d = dirname4(d)) {
6821
+ const config = gitConfigPathFor(d);
6822
+ if (config && existsSync4(config)) {
6823
+ const url = originUrl(readFileSync7(config, "utf8"));
6824
+ repo = url ? parseGithubRemote(url) : null;
6825
+ break;
6826
+ }
6827
+ if (d === dirname4(d)) break;
6828
+ }
6829
+ } catch {
6830
+ repo = null;
6831
+ }
6832
+ cache.set(dir, repo);
6833
+ return repo;
6834
+ };
6578
6835
  }
6579
- function inferWorkingShape(entry) {
6580
- if (entry.numbers.turn_count < 2)
6581
- return null;
6582
- const subagents = entry.subagents ?? [];
6583
- const roles = subagents.map(classifySubagentRole);
6584
- const reviewerCount = roles.filter((r) => r === "reviewer").length;
6585
- const implementerCount = roles.filter((r) => r === "implementer").length;
6586
- const explorerOrResearcher = roles.filter((r) => r === "explorer" || r === "researcher").length;
6587
- const hasBackground = subagents.some((sa) => sa.background);
6588
- if (implementerCount >= 2) {
6589
- const chunkRefs = subagents.filter((sa) => /\b(chunk|task)\s*\d+/i.test(`${sa.description} ${sa.prompt_preview}`));
6590
- if (chunkRefs.length >= 2)
6591
- return "chunk-implementation";
6592
- }
6593
- if (reviewerCount >= 3 && implementerCount === 0) {
6594
- const lensSigs = /* @__PURE__ */ new Set();
6595
- for (let i = 0; i < subagents.length; i++) {
6596
- if (roles[i] !== "reviewer")
6597
- continue;
6598
- const sig = subagents[i].description.toLowerCase().replace(/[^a-z0-9 ]/g, "").split(/\s+/).filter((w) => w.length > 2).slice(0, 3).join("|");
6599
- lensSigs.add(sig);
6836
+
6837
+ // src/team/queue.ts
6838
+ init_fs();
6839
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, statSync as statSync3, mkdirSync as mkdirSync5 } from "node:fs";
6840
+ import { dirname as dirname5 } from "node:path";
6841
+ var QUEUE_PATH = cclensPath("ingest-queue.jsonl");
6842
+ var MAX_SIZE_BYTES = 10 * 1024 * 1024;
6843
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
6844
+ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
6845
+ mkdirSync5(dirname5(queuePath), { recursive: true });
6846
+ const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
6847
+ appendFileSync3(queuePath, JSON.stringify(entry) + "\n", "utf8");
6848
+ try {
6849
+ const { size } = statSync3(queuePath);
6850
+ if (size > MAX_SIZE_BYTES) {
6851
+ pruneOldest(queuePath);
6600
6852
  }
6601
- if (lensSigs.size >= 3)
6602
- return "reviewer-triad";
6853
+ } catch {
6603
6854
  }
6604
- if (reviewerCount >= 2)
6605
- return "spec-review-loop";
6606
- if (explorerOrResearcher >= 2 || explorerOrResearcher >= 1 && implementerCount >= 1) {
6607
- return "research-then-build";
6855
+ }
6856
+ function dequeuePayloads(queuePath = QUEUE_PATH) {
6857
+ let lines;
6858
+ try {
6859
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
6860
+ } catch {
6861
+ return [];
6608
6862
  }
6609
- if (hasBackground && subagents.length >= 1)
6610
- return "background-coordinated";
6611
- if (subagents.length === 0) {
6612
- const first = (entry.first_user || "").trim().toLowerCase();
6613
- if (/^continue\b/.test(first) || first === "continue.")
6614
- return "solo-continuation";
6615
- const skills = Object.keys(entry.skills ?? {});
6616
- if (skills.some((s) => /brainstorm|writing-plans|brainstorming/i.test(s)))
6617
- return "solo-design";
6618
- return "solo-build";
6863
+ const now = Date.now();
6864
+ const valid = [];
6865
+ for (const line of lines) {
6866
+ try {
6867
+ const entry = JSON.parse(line);
6868
+ if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
6869
+ valid.push(entry);
6870
+ }
6871
+ } catch {
6872
+ }
6619
6873
  }
6620
- return "solo-build";
6874
+ writeFileSync4(queuePath, "", "utf8");
6875
+ return valid.map((e) => e.payload);
6621
6876
  }
6622
- function detectPromptFrames(text) {
6623
- if (!text)
6624
- return [];
6625
- const out = [];
6626
- if (/<teammate-message\b/i.test(text))
6627
- out.push("teammate");
6628
- if (/<task-notification\b/i.test(text))
6629
- out.push("task-notification");
6630
- if (/<local-command-caveat\b/i.test(text))
6631
- out.push("local-command-caveat");
6632
- if (/<command-(message|name)\b/i.test(text))
6633
- out.push("slash-command");
6634
- if (/\[Image #\d+\]/.test(text))
6635
- out.push("image-attached");
6636
- if (/\b(here'?s the (handoff|handover) prompt|session-close summary|wrap-up summary|all wrapped up|all captured\.)\b/i.test(text) || /^##? (Wrap-up|Session conclusion|Handoff)\b/m.test(text) || /^# Handoff:/m.test(text)) {
6637
- out.push("handoff-prose");
6877
+ function pruneOldest(queuePath) {
6878
+ let lines;
6879
+ try {
6880
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
6881
+ } catch {
6882
+ return;
6638
6883
  }
6639
- return out;
6884
+ const keep = lines.slice(Math.floor(lines.length / 2));
6885
+ writeFileSync4(queuePath, keep.join("\n") + "\n", "utf8");
6640
6886
  }
6641
- var STOCK_SKILL_PREFIXES = /^(superpowers|mcp__|frontend-design|code-review|codex|claude-code-guide|claude-api):/i;
6642
- function classifySkill(name) {
6643
- if (name.startsWith("(ToolSearch:"))
6644
- return "infra";
6645
- if (STOCK_SKILL_PREFIXES.test(name))
6646
- return "stock";
6647
- if (name === "using-superpowers")
6648
- return "stock";
6649
- return "user";
6887
+
6888
+ // src/team/backfill.ts
6889
+ init_storage();
6890
+ import { randomUUID as randomUUID2 } from "node:crypto";
6891
+
6892
+ // src/usage/profile.ts
6893
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
6894
+ import { dirname as dirname6 } from "node:path";
6895
+ var PROFILE_ENDPOINT = "https://api.anthropic.com/api/oauth/profile";
6896
+ var BETA_HEADER2 = "oauth-2025-04-20";
6897
+ var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6898
+ function mapRateLimitTier(rateLimitTier) {
6899
+ switch (rateLimitTier) {
6900
+ case "default_claude_pro":
6901
+ return "pro";
6902
+ case "default_claude_max":
6903
+ case "default_claude_max_5x":
6904
+ return "pro-max";
6905
+ case "default_claude_max_20x":
6906
+ return "pro-max-20x";
6907
+ default:
6908
+ return "custom";
6909
+ }
6650
6910
  }
6651
- var EXTERNAL_REF_PATTERNS = [
6652
- { kind: "linear-kip", re: /\bKIP-\d+\b/i },
6653
- { kind: "github-issue-pr", re: /\b(issue|pr|pull request)\s*#\d+\b|github\.com\/[^\s]+\/(issues|pull)\/\d+/i },
6654
- { kind: "branch-ref", re: /\b(branch|feat|fix|chore|refactor)[/:]\s*[\w./-]+|on `[\w./-]+`/i },
6655
- { kind: "url", re: /https?:\/\/\S+/ }
6656
- ];
6657
- function detectExternalRef(text) {
6658
- if (!text)
6911
+ async function fetchProfile(nowMs = Date.now()) {
6912
+ const creds = readOAuthCredentials();
6913
+ if (!creds) return null;
6914
+ if (creds.expiresAt - 6e4 <= nowMs) return null;
6915
+ let res;
6916
+ try {
6917
+ res = await fetch(PROFILE_ENDPOINT, {
6918
+ headers: {
6919
+ Authorization: `Bearer ${creds.accessToken}`,
6920
+ "anthropic-beta": BETA_HEADER2
6921
+ }
6922
+ });
6923
+ } catch {
6659
6924
  return null;
6660
- if (/<teammate-message|<task-notification|<local-command-caveat/i.test(text.slice(0, 400)))
6925
+ }
6926
+ if (!res.ok) return null;
6927
+ const body = await res.json().catch(() => null);
6928
+ if (!body) return null;
6929
+ const rateLimitTier = body.organization?.rate_limit_tier ?? null;
6930
+ return {
6931
+ planTier: mapRateLimitTier(rateLimitTier),
6932
+ rateLimitTier,
6933
+ organizationType: body.organization?.organization_type ?? null
6934
+ };
6935
+ }
6936
+ function readCachedProfile(cachePath) {
6937
+ if (!existsSync5(cachePath)) return null;
6938
+ try {
6939
+ const raw = JSON.parse(readFileSync9(cachePath, "utf8"));
6940
+ if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
6941
+ return raw;
6942
+ } catch {
6661
6943
  return null;
6662
- for (const { kind, re } of EXTERNAL_REF_PATTERNS) {
6663
- const m = text.match(re);
6664
- if (m)
6665
- return { kind, preview: text.slice(0, 160) };
6666
6944
  }
6667
- return null;
6668
6945
  }
6669
- function bucketVerbosity(n) {
6670
- if (n < 100)
6671
- return "short";
6672
- if (n < 500)
6673
- return "medium";
6674
- if (n < 2e3)
6675
- return "long";
6676
- return "very_long";
6946
+ function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
6947
+ mkdirSync6(dirname6(cachePath), { recursive: true });
6948
+ writeFileSync5(
6949
+ cachePath,
6950
+ JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
6951
+ { mode: 384 }
6952
+ );
6677
6953
  }
6678
- function computeEntrySignals(entry) {
6679
- const fu = entry.first_user || "";
6680
- const verbosity = bucketVerbosity(fu.length);
6681
- const ext = detectExternalRef(fu);
6682
- const external_refs = ext ? [ext] : [];
6683
- const skillNames = Object.keys(entry.skills ?? {});
6684
- const brainstorm_warmup = skillNames.some((s) => /brainstorm|writing-plans/i.test(s));
6685
- const fuTrim = fu.trim().toLowerCase();
6686
- const frames = detectPromptFrames(fu);
6687
- const continuation_kind = /^continue\b/.test(fuTrim) || fuTrim === "continue." ? "literal-continue" : frames.includes("handoff-prose") ? "handoff-prose" : "none";
6954
+ async function getPlanTier(cachePath, nowMs = Date.now(), fetcher = fetchProfile) {
6955
+ const cached = readCachedProfile(cachePath);
6956
+ if (cached && nowMs - cached.fetchedAtMs < PROFILE_CACHE_TTL_MS) {
6957
+ return cached.profile.planTier;
6958
+ }
6959
+ const fresh = await fetcher(nowMs);
6960
+ if (!fresh) {
6961
+ return cached?.profile.planTier ?? null;
6962
+ }
6963
+ writeCachedProfile(cachePath, fresh, nowMs);
6964
+ return fresh.planTier;
6965
+ }
6966
+
6967
+ // src/team/backfill.ts
6968
+ init_fs();
6969
+ var USAGE_LOG = cclensPath("usage.jsonl");
6970
+ var PROFILE_CACHE = cclensPath("profile.json");
6971
+ var BATCH_SIZE = 500;
6972
+ var noopLog = () => {
6973
+ };
6974
+ function rawToWire(raw) {
6975
+ const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6688
6976
  return {
6689
- working_shape: inferWorkingShape(entry),
6690
- prompt_frames: frames,
6691
- subagent_roles: (entry.subagents ?? []).map(classifySubagentRole),
6692
- verbosity,
6693
- external_refs,
6694
- brainstorm_warmup,
6695
- continuation_kind
6977
+ capturedAt: raw.captured_at,
6978
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6979
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6980
+ sevenDayOpus: win(raw.seven_day_opus),
6981
+ sevenDaySonnet: win(raw.seven_day_sonnet),
6982
+ sevenDayOauthApps: win(raw.seven_day_oauth_apps),
6983
+ sevenDayCowork: win(raw.seven_day_cowork),
6984
+ extraUsage: raw.extra_usage ? {
6985
+ isEnabled: raw.extra_usage.is_enabled,
6986
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
6987
+ usedCreditsUsd: raw.extra_usage.used_credits,
6988
+ utilization: raw.extra_usage.utilization
6989
+ } : null
6696
6990
  };
6697
6991
  }
6698
-
6699
- // ../entries/dist/trivial.js
6700
- function isTrivial(n) {
6701
- return n.active_min < 1 && n.turn_count < 3 && n.tools_total === 0;
6992
+ function chunk(items, size) {
6993
+ if (size <= 0) return [items];
6994
+ const out = [];
6995
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
6996
+ return out;
6702
6997
  }
6703
-
6704
- // ../entries/dist/build.js
6705
- init_analytics();
6706
- function viewEvent(ev) {
6707
- let rawType;
6708
- if (ev.role === "user" || ev.role === "tool-result")
6709
- rawType = "user";
6710
- else if (ev.role === "agent" || ev.role === "agent-thinking" || ev.role === "tool-call")
6711
- rawType = "assistant";
6712
- else if (ev.rawType === "summary")
6713
- rawType = "summary";
6714
- else
6715
- rawType = "system";
6716
- const rawCwd = ev.raw?.cwd;
6998
+ async function postBatch(config, snapshots, planTier) {
6999
+ const payload = {
7000
+ ingestId: `backfill-${randomUUID2()}`,
7001
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
7002
+ snapshotHistory: snapshots,
7003
+ ...planTier ? { planTier } : {}
7004
+ };
7005
+ const result = await pushToTeamServer(config, payload);
7006
+ if (!result.ok) {
7007
+ return { inserted: 0, skipped: snapshots.length, status: result.status };
7008
+ }
7009
+ const body = result.body;
7010
+ if (!body?.snapshotHistory) {
7011
+ throw new Error(
7012
+ "team server accepted snapshotHistory but did not return a result block \u2014 older image, upgrade required"
7013
+ );
7014
+ }
6717
7015
  return {
6718
- rawType,
6719
- content: ev.blocks ?? [],
6720
- model: ev.model,
6721
- msgId: ev.messageId,
6722
- usage: ev.usage ? {
6723
- input_tokens: ev.usage.input,
6724
- output_tokens: ev.usage.output,
6725
- cache_read_input_tokens: ev.usage.cacheRead,
6726
- cache_creation_input_tokens: ev.usage.cacheWrite
6727
- } : void 0,
6728
- cwd: rawCwd
7016
+ inserted: body.snapshotHistory.inserted ?? 0,
7017
+ skipped: body.snapshotHistory.skipped ?? 0,
7018
+ status: result.status
6729
7019
  };
6730
7020
  }
6731
- var IDLE_GAP_MS4 = 3 * 60 * 1e3;
6732
- var INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
6733
- var PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
6734
- var BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
6735
- var SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
6736
- var IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
6737
- function parseMs(iso) {
6738
- if (!iso)
6739
- return void 0;
6740
- const ms = Date.parse(iso);
6741
- return Number.isNaN(ms) ? void 0 : ms;
6742
- }
6743
- function trunc(s, n) {
6744
- if (!s)
6745
- return "";
6746
- const one = s.replace(/\s+/g, " ").trim();
6747
- return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
7021
+ function isAfterSince(snapshot, sinceCapturedAt) {
7022
+ if (!sinceCapturedAt) return true;
7023
+ const sinceMs = Date.parse(sinceCapturedAt);
7024
+ if (Number.isNaN(sinceMs)) return true;
7025
+ const capturedMs = Date.parse(snapshot.captured_at);
7026
+ return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
6748
7027
  }
6749
- function minimizeImages(s) {
6750
- if (!s)
6751
- return s;
6752
- let counter = 0;
6753
- return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
7028
+ function capturedAtMs(snapshot) {
7029
+ const ms = Date.parse(snapshot.captured_at);
7030
+ return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
6754
7031
  }
6755
- function bashVerb(cmd) {
6756
- if (!cmd)
6757
- return "?";
6758
- let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
6759
- if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
6760
- const parts = cmd.split(/&&|;/, 3);
6761
- if (parts.length >= 2)
6762
- firstSeg = parts[1].trim();
6763
- }
6764
- const tokens = firstSeg.split(/\s+/).filter(Boolean);
6765
- while (tokens.length && SHELL_NOISE.has(tokens[0]))
6766
- tokens.shift();
6767
- if (!tokens.length)
6768
- return "?";
6769
- let first = tokens[0];
6770
- if (first.includes("=") && !first.startsWith("/")) {
6771
- tokens.shift();
6772
- if (!tokens.length)
6773
- return "?";
6774
- first = tokens[0];
6775
- }
6776
- if (first.includes("/") && !first.startsWith("./")) {
6777
- first = first.replace(/\/+$/, "").split("/").pop();
7032
+ function latestCapturedAt(snapshots) {
7033
+ let latest = null;
7034
+ for (const s of snapshots) {
7035
+ const ms = Date.parse(s.capturedAt);
7036
+ if (Number.isNaN(ms)) continue;
7037
+ if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
6778
7038
  }
6779
- return first.replace(/^["']|["']$/g, "") || "?";
7039
+ return latest?.at;
6780
7040
  }
6781
- function firstTextBlock(blocks) {
6782
- if (!Array.isArray(blocks))
6783
- return "";
6784
- for (const b of blocks) {
6785
- if (b && typeof b === "object" && b.type === "text") {
6786
- return String(b.text ?? "");
6787
- }
7041
+ async function runTeamBackfill(log3 = noopLog, filePath = USAGE_LOG, configOverride, options = {}) {
7042
+ const config = configOverride === void 0 ? readTeamConfig() : configOverride;
7043
+ if (!config) {
7044
+ return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6788
7045
  }
6789
- return "";
6790
- }
6791
- function blockText(blocks) {
6792
- if (!blocks)
6793
- return "";
6794
- return blocks.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ");
6795
- }
6796
- function groupEventsByLocalDay(events) {
6797
- const byDay = /* @__PURE__ */ new Map();
6798
- for (const ev of events) {
6799
- if (!ev.timestamp)
6800
- continue;
6801
- const ms = Date.parse(ev.timestamp);
6802
- if (Number.isNaN(ms))
6803
- continue;
6804
- const day = toLocalDay(ms);
6805
- let bucket = byDay.get(day);
6806
- if (!bucket) {
6807
- bucket = [];
6808
- byDay.set(day, bucket);
7046
+ const raw = readSnapshots(filePath);
7047
+ const claudeOnly = raw.filter(
7048
+ (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
7049
+ ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
7050
+ if (claudeOnly.length === 0) {
7051
+ log3("info", "team backfill: no usage snapshots to send");
7052
+ return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
7053
+ }
7054
+ const wire = claudeOnly.map(rawToWire);
7055
+ const batches = chunk(wire, BATCH_SIZE);
7056
+ const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
7057
+ let inserted = 0;
7058
+ let skipped = 0;
7059
+ let lastSnapshotAt;
7060
+ for (let i = 0; i < batches.length; i++) {
7061
+ const batch = batches[i];
7062
+ try {
7063
+ const result = await postBatch(config, batch, planTier);
7064
+ if (result.status >= 400) {
7065
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} failed (${result.status})`);
7066
+ return {
7067
+ paired: true,
7068
+ sentSnapshots: wire.length,
7069
+ insertedSnapshots: inserted,
7070
+ skippedSnapshots: skipped,
7071
+ batches: i,
7072
+ lastSnapshotAt,
7073
+ error: `HTTP ${result.status}`
7074
+ };
7075
+ }
7076
+ inserted += result.inserted;
7077
+ skipped += result.skipped;
7078
+ lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
7079
+ } catch (err) {
7080
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
7081
+ return {
7082
+ paired: true,
7083
+ sentSnapshots: wire.length,
7084
+ insertedSnapshots: inserted,
7085
+ skippedSnapshots: skipped,
7086
+ batches: i,
7087
+ lastSnapshotAt,
7088
+ error: err.message
7089
+ };
6809
7090
  }
6810
- bucket.push(ev);
6811
7091
  }
6812
- return byDay;
6813
- }
6814
- function newTurn(startMs, userInput = "") {
7092
+ log3(
7093
+ "info",
7094
+ `team backfill: ${inserted} new, ${skipped} already-known across ${batches.length} batch${batches.length === 1 ? "" : "es"}`
7095
+ );
6815
7096
  return {
6816
- startMs,
6817
- lastMs: startMs,
6818
- eventTs: startMs !== void 0 ? [startMs] : [],
6819
- userInput,
6820
- toolsTotal: 0,
6821
- toolMix: /* @__PURE__ */ new Map(),
6822
- bashVerbs: /* @__PURE__ */ new Map(),
6823
- skills: /* @__PURE__ */ new Map(),
6824
- subagents: [],
6825
- taskOps: 0,
6826
- toolErrors: 0,
6827
- consecSameToolMax: 0,
6828
- consec: 0,
6829
- endedInInterrupt: false,
6830
- models: /* @__PURE__ */ new Map(),
6831
- tokensIn: 0,
6832
- tokensOut: 0,
6833
- tokensCacheR: 0,
6834
- tokensCacheW: 0,
6835
- seenMsgIds: /* @__PURE__ */ new Set()
7097
+ paired: true,
7098
+ sentSnapshots: wire.length,
7099
+ insertedSnapshots: inserted,
7100
+ skippedSnapshots: skipped,
7101
+ batches: batches.length,
7102
+ lastSnapshotAt
6836
7103
  };
6837
7104
  }
6838
- function closeTurn(t, closed, cwd) {
6839
- const ts = [...t.eventTs].sort((a, b) => a - b);
6840
- let active = 0;
6841
- for (let i = 1; i < ts.length; i++) {
6842
- const dt = ts[i] - ts[i - 1];
6843
- if (dt < IDLE_GAP_MS4)
6844
- active += dt;
6845
- }
6846
- closed.push({
6847
- startMs: t.startMs ?? 0,
6848
- activeMs: active,
6849
- userInput: t.userInput,
6850
- firstAgentText: t.firstAgentText,
6851
- lastAgentText: t.lastAgentText,
6852
- toolsTotal: t.toolsTotal,
6853
- toolMix: t.toolMix,
6854
- bashVerbs: t.bashVerbs,
6855
- skills: t.skills,
6856
- subagents: t.subagents,
6857
- taskOps: t.taskOps,
6858
- toolErrors: t.toolErrors,
6859
- consecSameToolMax: t.consecSameToolMax,
6860
- endedInInterrupt: t.endedInInterrupt,
6861
- models: t.models,
6862
- totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW,
6863
- cwd
6864
- });
6865
- }
6866
- function aggregateDay(dayEvents, _sessionFallbackProject) {
6867
- const prTitles = [];
6868
- let commits = 0;
6869
- let pushes = 0;
6870
- const gitToolUseIds = /* @__PURE__ */ new Set();
6871
- const githubRepos = /* @__PURE__ */ new Set();
6872
- const editedDirCounts = /* @__PURE__ */ new Map();
6873
- const modelTurns = /* @__PURE__ */ new Map();
6874
- let totalToolErrors = 0;
6875
- let totalInterrupts = 0;
6876
- let exitPlanCalls = 0;
6877
- let taskOpsTotal = 0;
6878
- let firstUser;
6879
- let finalAgent;
6880
- const cwdCounts = /* @__PURE__ */ new Map();
6881
- const closed = [];
6882
- let cur;
6883
- const allTs = [];
6884
- let lastEventCwd;
6885
- for (const ev of dayEvents) {
6886
- const tsMs = parseMs(ev.timestamp);
6887
- if (tsMs !== void 0)
6888
- allTs.push(tsMs);
6889
- const view = viewEvent(ev);
6890
- const rawType = view.rawType;
6891
- const msg = { content: view.content, id: view.msgId, model: view.model, usage: view.usage };
6892
- const content = view.content;
6893
- const eventCwd = view.cwd;
6894
- if (eventCwd)
6895
- lastEventCwd = eventCwd;
6896
- if (rawType === "user") {
6897
- const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
6898
- if (!isToolResultOnly) {
6899
- const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
6900
- if (cur) {
6901
- if (text && INTERRUPT_RE2.test(text)) {
6902
- cur.endedInInterrupt = true;
6903
- totalInterrupts++;
6904
- }
6905
- closeTurn(cur, closed, eventCwd);
6906
- }
6907
- cur = newTurn(tsMs, text);
6908
- if (!firstUser && text && classifyUserInputSource(text) === "human") {
6909
- firstUser = text;
6910
- }
6911
- } else {
6912
- if (Array.isArray(content)) {
6913
- for (const c of content) {
6914
- if (!c || typeof c !== "object" || c.type !== "tool_result")
6915
- continue;
6916
- if (c.is_error) {
6917
- totalToolErrors++;
6918
- if (cur)
6919
- cur.toolErrors++;
6920
- }
6921
- const tuId = c.tool_use_id;
6922
- if (tuId && gitToolUseIds.has(tuId)) {
6923
- const rc = c.content;
6924
- const text = typeof rc === "string" ? rc : Array.isArray(rc) ? rc.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n") : "";
6925
- for (const m of text.matchAll(/(?<![.\w])github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git\b|[/\s"'),]|$)/g)) {
6926
- if (m[1].toLowerCase() === "user-attachments")
6927
- continue;
6928
- githubRepos.add(`${m[1]}/${m[2]}`.toLowerCase());
6929
- }
6930
- }
6931
- }
6932
- }
6933
- if (cur && tsMs !== void 0) {
6934
- cur.eventTs.push(tsMs);
6935
- cur.lastMs = tsMs;
6936
- }
6937
- }
6938
- } else if (rawType === "assistant") {
6939
- if (cur && tsMs !== void 0) {
6940
- cur.eventTs.push(tsMs);
6941
- cur.lastMs = tsMs;
6942
- }
6943
- const model = msg.model;
6944
- if (model) {
6945
- modelTurns.set(model, (modelTurns.get(model) ?? 0) + 1);
6946
- if (cur)
6947
- cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
6948
- }
6949
- const msgId = msg.id;
6950
- const usage = msg.usage;
6951
- if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage) {
6952
- cur.seenMsgIds.add(msgId);
6953
- cur.tokensIn += usage.input_tokens ?? 0;
6954
- cur.tokensOut += usage.output_tokens ?? 0;
6955
- cur.tokensCacheR += usage.cache_read_input_tokens ?? 0;
6956
- cur.tokensCacheW += usage.cache_creation_input_tokens ?? 0;
6957
- }
6958
- if (Array.isArray(content)) {
6959
- for (const c of content) {
6960
- if (!c || typeof c !== "object")
6961
- continue;
6962
- const ct = c;
6963
- if (ct.type === "text" && ct.text) {
6964
- finalAgent = ct.text;
6965
- if (cur) {
6966
- if (cur.firstAgentText === void 0)
6967
- cur.firstAgentText = ct.text;
6968
- cur.lastAgentText = ct.text;
6969
- }
6970
- } else if (ct.type === "tool_use") {
6971
- const name = ct.name ?? "unknown";
6972
- if (cur) {
6973
- cur.toolsTotal++;
6974
- cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
6975
- if (name === cur.lastTool)
6976
- cur.consec++;
6977
- else {
6978
- cur.lastTool = name;
6979
- cur.consec = 1;
6980
- }
6981
- if (cur.consec > cur.consecSameToolMax)
6982
- cur.consecSameToolMax = cur.consec;
6983
- if (name === "Skill") {
6984
- const sk = String(ct.input?.skill ?? "unknown");
6985
- cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
6986
- }
6987
- if (name === "Agent") {
6988
- const inp = ct.input ?? {};
6989
- cur.subagents.push({
6990
- type: String(inp.subagent_type ?? "general-purpose"),
6991
- description: trunc(String(inp.description ?? ""), 80),
6992
- prompt_preview: trunc(String(inp.prompt ?? ""), 240),
6993
- background: Boolean(inp.run_in_background)
6994
- });
6995
- }
6996
- if (name === "ExitPlanMode")
6997
- exitPlanCalls++;
6998
- if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
6999
- cur.taskOps++;
7000
- taskOpsTotal++;
7001
- }
7002
- }
7003
- if (name === "Bash") {
7004
- const cmd = String(ct.input?.command ?? "");
7005
- if (cur)
7006
- cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
7007
- if (/\bgh\s+pr\s+create\b/.test(cmd)) {
7008
- const m = cmd.match(PR_TITLE_RE);
7009
- prTitles.push(m?.[1] ?? cmd.slice(0, 120));
7010
- }
7011
- if (/\bgit\s+commit\b/.test(cmd))
7012
- commits++;
7013
- if (/\bgit\s+push\b/.test(cmd))
7014
- pushes++;
7015
- if (ct.id && /\bgit\s+push\b|\bgh\s+pr\s+/.test(cmd))
7016
- gitToolUseIds.add(ct.id);
7017
- }
7018
- if (eventCwd && (name === "Bash" || name === "Edit" || name === "Write" || name === "Read")) {
7019
- cwdCounts.set(eventCwd, (cwdCounts.get(eventCwd) ?? 0) + 1);
7020
- }
7021
- if (name === "Edit" || name === "Write" || name === "MultiEdit" || name === "NotebookEdit") {
7022
- const fp = String(ct.input?.file_path ?? ct.input?.notebook_path ?? "");
7023
- const slash = fp.lastIndexOf("/");
7024
- if (slash > 0) {
7025
- const dir = fp.slice(0, slash);
7026
- editedDirCounts.set(dir, (editedDirCounts.get(dir) ?? 0) + 1);
7027
- }
7028
- }
7029
- }
7030
- }
7031
- }
7032
- }
7105
+
7106
+ // src/team/sync-log.ts
7107
+ init_fs();
7108
+ import { readFileSync as readFileSync10 } from "node:fs";
7109
+ var MAX_LINES = 300;
7110
+ var MAX_MSG_CHARS = 1900;
7111
+ function readPendingSyncLog(watermark) {
7112
+ let raw;
7113
+ try {
7114
+ raw = readFileSync10(cclensPath("daemon.log"), "utf8");
7115
+ } catch {
7116
+ return { lines: [] };
7033
7117
  }
7034
- if (cur)
7035
- closeTurn(cur, closed, lastEventCwd);
7036
- const tsSorted = [...allTs].sort((a, b) => a - b);
7037
- let activeMs2 = 0;
7038
- for (let i = 1; i < tsSorted.length; i++) {
7039
- const dt = tsSorted[i] - tsSorted[i - 1];
7040
- if (dt < IDLE_GAP_MS4)
7041
- activeMs2 += dt;
7118
+ const lines = [];
7119
+ for (const line of raw.split("\n")) {
7120
+ const m = line.match(/^(\S+) (INFO|WARN|ERROR) (.*)$/);
7121
+ if (!m) continue;
7122
+ const [, ts, levelRaw, msg] = m;
7123
+ if (!msg.startsWith("[sync] ")) continue;
7124
+ if (watermark && ts <= watermark) continue;
7125
+ const clipped = msg.length > MAX_MSG_CHARS ? msg.slice(0, MAX_MSG_CHARS) + "\u2026" : msg;
7126
+ lines.push({ ts, level: levelRaw.toLowerCase(), msg: clipped });
7042
7127
  }
7043
- const tsSortedStrings = dayEvents.map((e) => e.timestamp).filter((t) => !!t).sort();
7044
- const start_iso = tsSortedStrings[0] ?? "";
7045
- const end_iso = tsSortedStrings[tsSortedStrings.length - 1] ?? "";
7128
+ const capped = lines.slice(0, MAX_LINES);
7129
+ return { lines: capped, watermark: capped.length ? capped[capped.length - 1].ts : void 0 };
7130
+ }
7131
+
7132
+ // src/team/last-push.ts
7133
+ init_fs();
7134
+ import { writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, mkdirSync as mkdirSync7 } from "node:fs";
7135
+ import { join as join6 } from "node:path";
7136
+ var LAST_PUSH_FILE = "team-last-push.json";
7137
+ function write(record, dir) {
7138
+ const d = dir ?? cclensHome();
7139
+ mkdirSync7(d, { recursive: true });
7140
+ writeFileSync6(join6(d, LAST_PUSH_FILE), JSON.stringify(record, null, 2), { mode: 384 });
7141
+ }
7142
+ function writeLastPushSuccess(payload, dir) {
7143
+ write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: true, payload }, dir);
7144
+ }
7145
+ function writeLastPushFailure(payload, error, dir) {
7146
+ write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: false, payload, error }, dir);
7147
+ }
7148
+
7149
+ // src/team/commands.ts
7150
+ async function dispatchCommand(command, config, log3) {
7151
+ switch (command.type) {
7152
+ case "backfill-activity":
7153
+ return runActivityBackfill(command, config, log3);
7154
+ default:
7155
+ return {
7156
+ id: command.id,
7157
+ ok: false,
7158
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7159
+ error: `Unknown command type: ${command.type}`
7160
+ };
7161
+ }
7162
+ }
7163
+ async function runActivityBackfill(command, config, log3) {
7164
+ const { listSessions: listSessions2, shouldSyncProject: shouldSyncProject2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7165
+ const { toLocalDay: toLocalDay3, projectRepoName: projectRepoName2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7166
+ const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
7167
+ const days = command.params.days;
7168
+ const todayMs = Date.now();
7169
+ const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
7170
+ const targetDay = toLocalDay3(targetDayMs);
7171
+ log3("info", `command ${command.id}: backfill-activity from ${targetDay} (${days} days)`);
7172
+ let sessions;
7173
+ try {
7174
+ sessions = filterSyncedSessions(await listSessions2({ limit: 1e4 }), config.syncProjects);
7175
+ } catch (err) {
7176
+ return {
7177
+ id: command.id,
7178
+ ok: false,
7179
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7180
+ error: `Failed to read sessions: ${err.message}`
7181
+ };
7182
+ }
7183
+ const rollups = buildRollupsForRange(sessions, targetDay);
7184
+ if (rollups.length === 0) {
7185
+ return {
7186
+ id: command.id,
7187
+ ok: true,
7188
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7189
+ summary: { pushed: 0, fromDay: targetDay }
7190
+ };
7191
+ }
7192
+ const resolveRepo = createRepoResolver();
7193
+ let pushed = 0;
7194
+ for (const rollup of rollups) {
7195
+ const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
7196
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
7197
+ let artifactSignals = null;
7198
+ try {
7199
+ const cwdAllowed = shouldSyncProject2(projectRepoName2(process.cwd()), config.syncProjects);
7200
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: cwdAllowed ? [process.cwd()] : [] });
7201
+ } catch {
7202
+ }
7203
+ const payload = buildIngestPayload({
7204
+ rollup,
7205
+ richExtras: richBlocks?.rich,
7206
+ enrichedExtras: richBlocks?.enriched,
7207
+ artifactSignals: artifactSignals ?? void 0
7208
+ });
7209
+ const result = await pushToTeamServer(config, payload);
7210
+ if (!result.ok) {
7211
+ return {
7212
+ id: command.id,
7213
+ ok: false,
7214
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7215
+ error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
7216
+ };
7217
+ }
7218
+ pushed++;
7219
+ }
7220
+ log3("info", `command ${command.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
7046
7221
  return {
7047
- closed,
7048
- firstUser,
7049
- finalAgent,
7050
- prTitles,
7051
- commits,
7052
- pushes,
7053
- githubRepos: [...githubRepos].slice(0, 5),
7054
- editedDirs: [...editedDirCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([dir]) => dir),
7055
- modelTurns,
7056
- totalToolErrors,
7057
- totalInterrupts,
7058
- exitPlanCalls,
7059
- taskOpsTotal,
7060
- cwdCounts,
7061
- start_iso,
7062
- end_iso,
7063
- activeMs: activeMs2
7222
+ id: command.id,
7223
+ ok: true,
7224
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7225
+ summary: { pushed, fromDay: targetDay }
7064
7226
  };
7065
7227
  }
7066
- function buildTopTools(closed) {
7067
- const toolMix = /* @__PURE__ */ new Map();
7068
- const bashVerbsAll = /* @__PURE__ */ new Map();
7069
- for (const t of closed) {
7070
- for (const [k, v] of t.toolMix)
7071
- toolMix.set(k, (toolMix.get(k) ?? 0) + v);
7072
- for (const [k, v] of t.bashVerbs)
7073
- bashVerbsAll.set(k, (bashVerbsAll.get(k) ?? 0) + v);
7228
+
7229
+ // src/team/sync.ts
7230
+ init_fs();
7231
+ var inFlightCommands = /* @__PURE__ */ new Set();
7232
+ var USAGE_LOG2 = cclensPath("usage.jsonl");
7233
+ var PROFILE_CACHE2 = cclensPath("profile.json");
7234
+ var noopLog2 = () => {
7235
+ };
7236
+ function syncLogDelivered(body) {
7237
+ const blocks = body?.blocks;
7238
+ return !blocks || blocks.accepted.includes("syncLog");
7239
+ }
7240
+ function levelForStatus(s) {
7241
+ if (s === "ok" || s === "idle") return "info";
7242
+ if (s === "degraded" || s === "failed") return "warn";
7243
+ return "error";
7244
+ }
7245
+ function fmtDuration(ms) {
7246
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
7247
+ }
7248
+ function liveSnapshotStr(snap) {
7249
+ if (!snap) return void 0;
7250
+ const f = snap.fiveHour?.utilization;
7251
+ const s = snap.sevenDay?.utilization;
7252
+ const parts = [
7253
+ typeof f === "number" ? `5h ${Math.round(f)}%` : null,
7254
+ typeof s === "number" ? `7d ${Math.round(s)}%` : null
7255
+ ].filter(Boolean);
7256
+ return parts.length ? parts.join(" / ") : void 0;
7257
+ }
7258
+ function buildSyncLine(status, trigger, s, elapsedMs, nextSyncMs) {
7259
+ const parts = [`[sync] ${status}`, trigger];
7260
+ if (s.pushedDays.length) {
7261
+ parts.push(
7262
+ 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]})`
7263
+ );
7074
7264
  }
7075
- const top = [...toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7076
- return top.map(([name, count]) => {
7077
- if (name === "Bash" && bashVerbsAll.size > 0) {
7078
- const sub = [...bashVerbsAll.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
7079
- return `Bash\xD7${count} (${sub})`;
7080
- }
7081
- return count > 1 ? `${name}\xD7${count}` : name;
7082
- });
7265
+ if (s.queued) {
7266
+ parts.push(
7267
+ s.queuedDay ? `queued ${s.queued} for retry (${s.queuedDay}, HTTP ${s.queuedStatus})` : `queued ${s.queued} for retry`
7268
+ );
7269
+ }
7270
+ if (s.droppedDays.length) {
7271
+ parts.push(`dropped ${s.droppedDays.length} unrecoverable (${s.droppedDays.join(",")})`);
7272
+ }
7273
+ if (s.recoveredDays?.length) {
7274
+ parts.push(
7275
+ `recovered ${s.recoveredDays.length} dropped day${s.recoveredDays.length === 1 ? "" : "s"} (${s.recoveredDays.join(",")})`
7276
+ );
7277
+ }
7278
+ if (s.queuedDrained) parts.push(`drained ${s.queuedDrained} from backlog`);
7279
+ if (s.idleReason) parts.push(s.idleReason);
7280
+ if (s.live) parts.push(`live ${s.live}`);
7281
+ if (s.usageSnapshots > 0) {
7282
+ parts.push(`usage +${s.usageSnapshots} snapshot${s.usageSnapshots === 1 ? "" : "s"}`);
7283
+ }
7284
+ if (s.accepted?.length) parts.push(`server accepted ${s.accepted.join(",")}`);
7285
+ if (s.skipped && Object.keys(s.skipped).length) {
7286
+ parts.push(
7287
+ `server SKIPPED ${Object.entries(s.skipped).map(([k, v]) => `${k} (${v})`).join("; ")}`
7288
+ );
7289
+ }
7290
+ if (s.errorMsg) parts.push(s.errorMsg);
7291
+ parts.push(fmtDuration(elapsedMs));
7292
+ if (nextSyncMs) parts.push(`next ~${Math.round(nextSyncMs / 6e4)}m`);
7293
+ return parts.join(" \xB7 ");
7083
7294
  }
7084
- function buildEntries(sessionDetail) {
7085
- const byDay = groupEventsByLocalDay(sessionDetail.events);
7086
- const entries = [];
7087
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7088
- const sessionFallbackProject = canonicalProjectName(sessionDetail.projectName ?? "");
7089
- for (const [local_day, dayEvents] of byDay) {
7090
- if (dayEvents.length === 0)
7091
- continue;
7092
- const agg = aggregateDay(dayEvents, sessionFallbackProject);
7093
- const { closed } = agg;
7094
- const turnCount = closed.length;
7095
- const toolsTotal = closed.reduce((s, t) => s + t.toolsTotal, 0);
7096
- const tokensTotal = closed.reduce((s, t) => s + t.totalTokens, 0);
7097
- const subagentCalls = closed.reduce((s, t) => s + t.subagents.length, 0);
7098
- const skillsMap = /* @__PURE__ */ new Map();
7099
- for (const t of closed) {
7100
- for (const [k, v] of t.skills)
7101
- skillsMap.set(k, (skillsMap.get(k) ?? 0) + v);
7295
+ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
7296
+ const config = configOverride === void 0 ? readTeamConfig() : configOverride;
7297
+ if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
7298
+ if (config.setupPending) {
7299
+ log3(
7300
+ "info",
7301
+ buildSyncLine(
7302
+ "idle",
7303
+ options.trigger ?? "auto",
7304
+ {
7305
+ pushedDays: [],
7306
+ droppedDays: [],
7307
+ queued: 0,
7308
+ queuedDrained: 0,
7309
+ usageSnapshots: 0,
7310
+ idleReason: "setup pending \u2014 finish onboarding in the dashboard (/team/onboarding)"
7311
+ },
7312
+ 0,
7313
+ options.nextSyncMs
7314
+ )
7315
+ );
7316
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, setupPending: true };
7317
+ }
7318
+ const startedAt = Date.now();
7319
+ const trigger = options.trigger ?? "auto";
7320
+ const summary = {
7321
+ pushedDays: [],
7322
+ droppedDays: [],
7323
+ queued: 0,
7324
+ queuedDrained: 0,
7325
+ usageSnapshots: 0
7326
+ };
7327
+ const finish = (status) => {
7328
+ log3(
7329
+ levelForStatus(status),
7330
+ buildSyncLine(status, trigger, summary, Date.now() - startedAt, options.nextSyncMs)
7331
+ );
7332
+ };
7333
+ const recordVerdict = (body) => {
7334
+ if (body?.blocks) {
7335
+ summary.accepted = [.../* @__PURE__ */ new Set([...summary.accepted ?? [], ...body.blocks.accepted])];
7336
+ summary.skipped = { ...summary.skipped, ...body.blocks.skipped };
7102
7337
  }
7103
- const skillCalls = [...skillsMap.values()].reduce((a, b) => a + b, 0);
7104
- const taskOps = agg.taskOpsTotal;
7105
- const interrupts = agg.totalInterrupts;
7106
- const toolErrors = agg.totalToolErrors;
7107
- const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
7108
- const exitPlanCalls = agg.exitPlanCalls;
7109
- const prs = agg.prTitles.length;
7110
- const commits = agg.commits;
7111
- const pushes = agg.pushes;
7112
- const activeMin = Math.round(agg.activeMs / 6e3) / 10;
7113
- const numbers = {
7114
- active_min: activeMin,
7115
- turn_count: turnCount,
7116
- tools_total: toolsTotal,
7117
- subagent_calls: subagentCalls,
7118
- skill_calls: skillCalls,
7119
- task_ops: taskOps,
7120
- interrupts,
7121
- tool_errors: toolErrors,
7122
- consec_same_tool_max: consecMax,
7123
- exit_plan_calls: exitPlanCalls,
7124
- prs,
7125
- commits,
7126
- pushes,
7127
- tokens_total: tokensTotal
7338
+ };
7339
+ const emit = options.onProgress ?? (() => {
7340
+ });
7341
+ try {
7342
+ resetEnsuredSessions();
7343
+ const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7344
+ const { toLocalDay: toLocalDay3, projectRepoName: projectRepoName2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7345
+ const today = toLocalDay3(Date.now());
7346
+ let patches = {};
7347
+ const persistConfig = (patch) => {
7348
+ patches = { ...patches, ...patch };
7349
+ const onDisk = readTeamConfig();
7350
+ if (!onDisk) return;
7351
+ writeTeamConfig({ ...onDisk, ...patches });
7352
+ };
7353
+ const pendingLog = readPendingSyncLog(config.lastSyncedLogAt);
7354
+ const collectedCommands = /* @__PURE__ */ new Map();
7355
+ const collectCommands = (commands) => {
7356
+ if (!commands) return;
7357
+ for (const cmd of commands) {
7358
+ if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
7359
+ }
7360
+ };
7361
+ const dispatchAndReport = async () => {
7362
+ try {
7363
+ if (collectedCommands.size === 0) return;
7364
+ const results = [];
7365
+ for (const cmd of collectedCommands.values()) {
7366
+ if (inFlightCommands.has(cmd.id)) continue;
7367
+ inFlightCommands.add(cmd.id);
7368
+ try {
7369
+ const result = await dispatchCommand(cmd, config, log3);
7370
+ results.push(result);
7371
+ } finally {
7372
+ inFlightCommands.delete(cmd.id);
7373
+ }
7374
+ }
7375
+ if (results.length === 0) return;
7376
+ const resultsPayload = {
7377
+ ingestId: randomUUID3(),
7378
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
7379
+ commandResults: results
7380
+ };
7381
+ const r = await pushToTeamServer(config, resultsPayload);
7382
+ if (!r.ok) {
7383
+ log3("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
7384
+ } else {
7385
+ log3("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
7386
+ }
7387
+ } catch (err) {
7388
+ log3("warn", `team command dispatch error: ${err.message}`);
7389
+ }
7390
+ };
7391
+ const isValidationPoison = (status2) => status2 === 400 || status2 === 422;
7392
+ const drainBacklog = async () => {
7393
+ let drained = 0;
7394
+ const backlog = dequeuePayloads();
7395
+ for (let i = 0; i < backlog.length; i++) {
7396
+ const queuedPayload = backlog[i];
7397
+ if (queuedPayload.richRollup?.projects?.some((p) => !shouldSyncProject(p.project, config.syncProjects))) {
7398
+ log3("info", "team push: dropped queued payload containing now-excluded project(s)");
7399
+ continue;
7400
+ }
7401
+ const qResult = await pushToTeamServer(config, queuedPayload);
7402
+ if (!qResult.ok) {
7403
+ if (isValidationPoison(qResult.status)) {
7404
+ log3("warn", `team push: queued payload rejected (HTTP ${qResult.status}); dropping unrecoverable item`);
7405
+ continue;
7406
+ }
7407
+ for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
7408
+ break;
7409
+ }
7410
+ collectCommands(qResult.body?.commands);
7411
+ drained++;
7412
+ }
7413
+ return drained;
7128
7414
  };
7129
- const longestTurnActiveMs = Math.max(0, ...closed.map((t) => t.activeMs));
7130
- const flags = [];
7131
- if (interrupts >= 3)
7132
- flags.push("interrupt_heavy");
7133
- if (toolErrors >= 20)
7134
- flags.push("high_errors");
7135
- if (consecMax >= 8)
7136
- flags.push("loop_suspected");
7137
- if (agg.activeMs < 5 * 6e4 && prs >= 1)
7138
- flags.push("fast_ship");
7139
- if (exitPlanCalls > 0)
7140
- flags.push("plan_used");
7141
- const subagentTurns = closed.filter((t) => t.subagents.length >= 1).length;
7142
- if (subagentTurns >= 3)
7143
- flags.push("orchestrated");
7144
- if (longestTurnActiveMs >= 20 * 6e4 && interrupts === 0)
7145
- flags.push("long_autonomous");
7146
- const primaryModel = [...agg.modelTurns.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? null;
7147
- const modelMix = Object.fromEntries(agg.modelTurns);
7148
- const firstUserRaw = agg.firstUser ?? "";
7149
- const first_user = trunc(minimizeImages(firstUserRaw), 400);
7150
- const finalAgentRaw = agg.finalAgent ?? "";
7151
- const final_agent = trunc(minimizeImages(finalAgentRaw), 400);
7152
- let project = sessionFallbackProject;
7153
- if (agg.cwdCounts.size > 0) {
7154
- const best = [...agg.cwdCounts.entries()].sort((a, b) => b[1] - a[1])[0];
7155
- project = canonicalProjectName(best[0]);
7415
+ emit({ type: "phase", phase: "usage-backfill" });
7416
+ const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
7417
+ sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
7418
+ });
7419
+ emit({
7420
+ type: "usage",
7421
+ inserted: usageBackfill.insertedSnapshots ?? 0,
7422
+ alreadyKnown: usageBackfill.skippedSnapshots ?? 0
7423
+ });
7424
+ if (usageBackfill.lastSnapshotAt) {
7425
+ persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
7156
7426
  }
7157
- const top_tools = buildTopTools(closed);
7158
- const skills = Object.fromEntries(skillsMap);
7159
- const subagents = closed.flatMap((t) => t.subagents);
7160
- const humanText = dayEvents.filter((ev) => {
7161
- const v = viewEvent(ev);
7162
- if (v.rawType !== "user")
7163
- return false;
7164
- const content = v.content;
7165
- const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7166
- if (isToolResultOnly)
7167
- return false;
7168
- const text = blockText(ev.blocks) || ev.preview || "";
7169
- return classifyUserInputSource(text) === "human";
7170
- }).map((ev) => blockText(ev.blocks) || ev.preview || "").join("\n");
7171
- const satisfaction_signals = countSatisfactionSignals(humanText);
7172
- const user_input_sources = {
7173
- human: 0,
7174
- teammate: 0,
7175
- skill_load: 0,
7176
- slash_command: 0,
7177
- system_instruction: 0
7178
- };
7179
- for (const ev of dayEvents) {
7180
- const v = viewEvent(ev);
7181
- if (v.rawType !== "user")
7182
- continue;
7183
- const content = v.content;
7184
- const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7185
- if (isToolResultOnly)
7186
- continue;
7187
- const text = blockText(ev.blocks) || ev.preview || "";
7188
- const src = classifyUserInputSource(text);
7189
- user_input_sources[src]++;
7427
+ const unfilteredSessions = await listSessions2({ limit: 1e4 });
7428
+ const sessions = filterSyncedSessions(unfilteredSessions, config.syncProjects);
7429
+ const allRollups = buildRollupsForRange(sessions);
7430
+ const inWindow = (day) => !config.lastSyncedDay || day >= config.lastSyncedDay;
7431
+ const rollups = allRollups.filter((r) => inWindow(r.day));
7432
+ if (config.syncProjects) {
7433
+ const filteredDays = new Set(allRollups.map((r) => r.day));
7434
+ for (const r of buildRollupsForRange(unfilteredSessions)) {
7435
+ if (!filteredDays.has(r.day) && inWindow(r.day)) {
7436
+ rollups.push({
7437
+ day: r.day,
7438
+ agentTimeMs: 0,
7439
+ sessions: 0,
7440
+ uniqueSessions: 0,
7441
+ toolCalls: 0,
7442
+ turns: 0,
7443
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
7444
+ });
7445
+ }
7446
+ }
7447
+ rollups.sort((a, b) => a.day < b.day ? -1 : 1);
7190
7448
  }
7191
- const pendingUserInstructions = extractUserInstructions(humanText).slice(0, 5);
7192
- const trivial = isTrivial(numbers);
7193
- const entry = {
7194
- version: CURRENT_ENTRY_SCHEMA_VERSION,
7195
- agent: sessionDetail.agent,
7196
- session_id: sessionDetail.id,
7197
- local_day,
7198
- project,
7199
- start_iso: agg.start_iso,
7200
- end_iso: agg.end_iso,
7201
- numbers,
7202
- flags,
7203
- primary_model: primaryModel,
7204
- model_mix: modelMix,
7205
- first_user,
7206
- final_agent,
7207
- pr_titles: agg.prTitles,
7208
- ...agg.githubRepos.length ? { github_repos: agg.githubRepos } : {},
7209
- ...agg.editedDirs.length ? { edited_dirs: agg.editedDirs } : {},
7210
- top_tools,
7211
- skills,
7212
- subagents,
7213
- satisfaction_signals,
7214
- user_input_sources,
7215
- enrichment: trivial ? skippedTrivialEnrichment(generatedAt) : { ...pendingEnrichment(), user_instructions: pendingUserInstructions },
7216
- generated_at: generatedAt,
7217
- source_jsonl: sessionDetail.filePath ?? "",
7218
- source_checkpoint: { byte_offset: 0, last_event_ts: agg.end_iso || null }
7449
+ emit({ type: "phase", phase: "activity", totalDays: rollups.length });
7450
+ const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG2) ?? void 0;
7451
+ const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
7452
+ const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
7453
+ if (rollups.length === 0) {
7454
+ const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
7455
+ if (!hasLiveData) {
7456
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7457
+ if (usageBackfill.sentSnapshots > 0) {
7458
+ finish("ok");
7459
+ } else {
7460
+ summary.idleReason = "nothing to sync";
7461
+ finish("idle");
7462
+ }
7463
+ emit({ type: "done", pushed: 0, queued: 0, pushedDays: summary.pushedDays });
7464
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
7465
+ }
7466
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7467
+ summary.live = liveSnapshotStr(usageSnapshot);
7468
+ const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks, syncLog: pendingLog.lines });
7469
+ const result = await pushToTeamServer(config, payload);
7470
+ if (!result.ok) {
7471
+ if (isValidationPoison(result.status)) {
7472
+ const errLine2 = `team push: live-only payload rejected (HTTP ${result.status}); dropping unrecoverable payload`;
7473
+ writeLastPushFailure(payload, errLine2);
7474
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} (validation) \u2014 dropped unrecoverable`;
7475
+ finish("error");
7476
+ emit({ type: "done", pushed: 0, queued: 0, pushedDays: summary.pushedDays });
7477
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
7478
+ }
7479
+ const errLine = `team push failed (${result.status})`;
7480
+ writeLastPushFailure(payload, errLine);
7481
+ enqueuePayload(payload);
7482
+ summary.queued = 1;
7483
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} \u2014 queued for retry`;
7484
+ finish("failed");
7485
+ emit({ type: "done", pushed: 0, queued: 1, pushedDays: summary.pushedDays });
7486
+ return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
7487
+ }
7488
+ writeLastPushSuccess(payload);
7489
+ if (pendingLog.watermark && syncLogDelivered(result.body)) {
7490
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
7491
+ }
7492
+ collectCommands(result.body?.commands);
7493
+ recordVerdict(result.body);
7494
+ const queuedDrained2 = await drainBacklog();
7495
+ summary.queuedDrained = queuedDrained2;
7496
+ summary.idleReason = "no new daily activity";
7497
+ finish(summary.skipped && Object.keys(summary.skipped).length ? "degraded" : "idle");
7498
+ await dispatchAndReport();
7499
+ emit({ type: "done", pushed: 1, queued: 0, pushedDays: summary.pushedDays });
7500
+ return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
7501
+ }
7502
+ let pushed = 0;
7503
+ let queued = 0;
7504
+ let failedDay;
7505
+ const droppedDaysBefore = config.droppedDays ?? [];
7506
+ const workingDropped = [...droppedDaysBefore];
7507
+ let syncLogWasDelivered = false;
7508
+ const resolveRepo = createRepoResolver();
7509
+ const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
7510
+ const buildDayPayload = (rollup, opts) => {
7511
+ const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
7512
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
7513
+ const tombstone = daySessions.length === 0 && !!config.syncProjects;
7514
+ const emptyRich = tombstone ? {
7515
+ projects: [],
7516
+ workingShapes: [],
7517
+ concurrencyPeak: 0,
7518
+ parallelMinutes: 0,
7519
+ longAutonomous: { count: 0, totalMin: 0, maxSingleMin: 0 },
7520
+ toolErrors: 0,
7521
+ skillsLoaded: [],
7522
+ subagentsDispatched: [],
7523
+ brainstormWarmupSessions: 0,
7524
+ planModeUsed: 0,
7525
+ prs: 0,
7526
+ commits: 0,
7527
+ pushes: 0
7528
+ } : void 0;
7529
+ let artifactSignals = null;
7530
+ try {
7531
+ const cwdAllowed = shouldSyncProject(projectRepoName2(process.cwd()), config.syncProjects);
7532
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: cwdAllowed ? [process.cwd()] : [] });
7533
+ } catch {
7534
+ }
7535
+ return buildIngestPayload({
7536
+ rollup,
7537
+ richExtras: richBlocks?.rich ?? emptyRich,
7538
+ enrichedExtras: richBlocks?.enriched ?? (tombstone ? { outcomeMix: {}, helpfulnessMix: {}, goalMix: {} } : void 0),
7539
+ artifactSignals: artifactSignals ?? void 0,
7540
+ // Current-cycle data only on the latest day so older days aren't tagged
7541
+ // with today's snapshot / peaks.
7542
+ usageSnapshot: opts.latest ? usageSnapshot : void 0,
7543
+ planTier,
7544
+ cyclePeaks: opts.latest ? cyclePeaks : void 0,
7545
+ syncLog: opts.syncLog
7546
+ });
7219
7547
  };
7220
- entry.signals = computeEntrySignals(entry);
7221
- entries.push(entry);
7548
+ const rollupsDesc = [...rollups].reverse();
7549
+ for (let i = 0; i < rollupsDesc.length; i++) {
7550
+ const rollup = rollupsDesc[i];
7551
+ const isLatest = i === 0;
7552
+ const payload = buildDayPayload(rollup, {
7553
+ latest: isLatest,
7554
+ syncLog: i === 0 ? pendingLog.lines : void 0
7555
+ });
7556
+ const result = await pushToTeamServer(config, payload);
7557
+ if (!result.ok) {
7558
+ if (isValidationPoison(result.status)) {
7559
+ const errLine2 = `team push: ${rollup.day} rejected (HTTP ${result.status}); skipping past unrecoverable day`;
7560
+ writeLastPushFailure(payload, errLine2);
7561
+ summary.droppedDays.push(rollup.day);
7562
+ if (!workingDropped.includes(rollup.day)) workingDropped.push(rollup.day);
7563
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "dropped" });
7564
+ continue;
7565
+ }
7566
+ const errLine = `team push failed on ${rollup.day} (${result.status})`;
7567
+ writeLastPushFailure(payload, errLine);
7568
+ enqueuePayload(payload);
7569
+ queued++;
7570
+ summary.queuedDay = rollup.day;
7571
+ summary.queuedStatus = result.status;
7572
+ failedDay = rollup.day;
7573
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "queued" });
7574
+ break;
7575
+ }
7576
+ writeLastPushSuccess(payload);
7577
+ collectCommands(result.body?.commands);
7578
+ recordVerdict(result.body);
7579
+ if (i === 0) syncLogWasDelivered = syncLogDelivered(result.body);
7580
+ pushed++;
7581
+ summary.pushedDays.push(rollup.day);
7582
+ emit({ type: "day", day: rollup.day, index: i + 1, total: rollupsDesc.length, outcome: "pushed" });
7583
+ }
7584
+ if (!failedDay) {
7585
+ const retryDay = [...droppedDaysBefore].sort().find((d) => !summary.droppedDays.includes(d));
7586
+ if (retryDay && summary.pushedDays.includes(retryDay)) {
7587
+ const idx = workingDropped.indexOf(retryDay);
7588
+ if (idx >= 0) workingDropped.splice(idx, 1);
7589
+ summary.recoveredDays = [retryDay];
7590
+ }
7591
+ const retryRollup = retryDay && !summary.pushedDays.includes(retryDay) ? allRollups.find((r) => r.day === retryDay) ?? (config.syncProjects ? {
7592
+ day: retryDay,
7593
+ agentTimeMs: 0,
7594
+ sessions: 0,
7595
+ uniqueSessions: 0,
7596
+ toolCalls: 0,
7597
+ turns: 0,
7598
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
7599
+ } : void 0) : void 0;
7600
+ if (retryDay && retryRollup) {
7601
+ const payload = buildDayPayload(retryRollup, { latest: false });
7602
+ const result = await pushToTeamServer(config, payload);
7603
+ if (result.ok) {
7604
+ writeLastPushSuccess(payload);
7605
+ collectCommands(result.body?.commands);
7606
+ recordVerdict(result.body);
7607
+ const idx = workingDropped.indexOf(retryDay);
7608
+ if (idx >= 0) workingDropped.splice(idx, 1);
7609
+ summary.recoveredDays = [retryDay];
7610
+ } else {
7611
+ writeLastPushFailure(payload, `team push: dropped-day retry ${retryDay} failed (HTTP ${result.status})`);
7612
+ }
7613
+ }
7614
+ }
7615
+ const nextDropped = [...new Set(workingDropped)].sort().slice(-90);
7616
+ const prevDropped = [...new Set(droppedDaysBefore)].sort();
7617
+ const droppedChanged = nextDropped.length !== prevDropped.length || nextDropped.some((d, i) => d !== prevDropped[i]);
7618
+ const droppedPatch = droppedChanged ? { droppedDays: nextDropped } : {};
7619
+ if (failedDay) {
7620
+ if (droppedChanged) persistConfig(droppedPatch);
7621
+ } else {
7622
+ persistConfig({ lastSyncedDay: today, ...droppedPatch });
7623
+ }
7624
+ if (syncLogWasDelivered && pendingLog.watermark) {
7625
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
7626
+ }
7627
+ const queuedDrained = !failedDay ? await drainBacklog() : 0;
7628
+ summary.queued = queued;
7629
+ summary.queuedDrained = queuedDrained;
7630
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7631
+ let status;
7632
+ if (failedDay) status = "failed";
7633
+ else if (summary.droppedDays.length || summary.skipped && Object.keys(summary.skipped).length) status = "degraded";
7634
+ else status = "ok";
7635
+ finish(status);
7636
+ await dispatchAndReport();
7637
+ emit({ type: "done", pushed, queued, pushedDays: summary.pushedDays });
7638
+ return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
7639
+ } catch (err) {
7640
+ const message = err.message;
7641
+ summary.errorMsg = `sync aborted: ${message}`;
7642
+ emit({ type: "error", message });
7643
+ finish("error");
7644
+ writeLastPushFailure(
7645
+ { ingestId: "n/a", observedAt: (/* @__PURE__ */ new Date()).toISOString() },
7646
+ `team push error: ${message}`
7647
+ );
7648
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
7222
7649
  }
7223
- entries.sort((a, b) => a.local_day.localeCompare(b.local_day));
7224
- return entries;
7225
7650
  }
7651
+ async function buildCyclePeaksForPush(planTier, loadCurve) {
7652
+ const validTiers = ["pro", "pro-max", "pro-max-20x", "custom"];
7653
+ const tier = validTiers.find((t) => t === planTier) ?? "pro-max-20x";
7654
+ const dump = await loadCurve(tier).catch(() => null);
7655
+ if (!dump || dump.curve.length === 0) return void 0;
7656
+ const HOUR = 36e5;
7657
+ const nowMs = Date.now();
7658
+ const peaksFor = (cycleKey, realKey, predKey, maxCycles) => {
7659
+ const TOLERANCE_MS = cycleKey === "cycle_end_7d" ? 12 * HOUR : 0;
7660
+ const byCycle = /* @__PURE__ */ new Map();
7661
+ for (const p of dump.curve) {
7662
+ const k = p[cycleKey];
7663
+ if (!k) continue;
7664
+ const ms = Date.parse(k);
7665
+ if (Number.isNaN(ms)) continue;
7666
+ const bucket = Math.round(ms / HOUR) * HOUR;
7667
+ const arr = byCycle.get(bucket) ?? [];
7668
+ arr.push(p);
7669
+ byCycle.set(bucket, arr);
7670
+ }
7671
+ const merged = [];
7672
+ for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
7673
+ const last = merged[merged.length - 1];
7674
+ if (last && endMs - last.endMs <= TOLERANCE_MS) {
7675
+ last.endMs = endMs;
7676
+ last.points.push(...points);
7677
+ } else {
7678
+ merged.push({ endMs, points: [...points] });
7679
+ }
7680
+ }
7681
+ const out = [];
7682
+ for (const { endMs, points } of merged) {
7683
+ let peak = 0;
7684
+ let source = "predicted";
7685
+ for (const p of points) {
7686
+ const r = p[realKey];
7687
+ if (typeof r === "number" && r > peak) {
7688
+ peak = r;
7689
+ source = "real";
7690
+ }
7691
+ const v = p[predKey] ?? 0;
7692
+ if (v > peak) {
7693
+ peak = v;
7694
+ source = "predicted";
7695
+ }
7696
+ }
7697
+ out.push({
7698
+ endsAt: new Date(endMs).toISOString(),
7699
+ peakPct: Math.round(peak * 10) / 10,
7700
+ source,
7701
+ current: endMs > nowMs
7702
+ });
7703
+ }
7704
+ return out.slice(-maxCycles);
7705
+ };
7706
+ return {
7707
+ // 7d cycle history cap (26 ≈ six months) MUST stay in lock-step with two
7708
+ // downstream sites or the strip silently truncates: the server read
7709
+ // (loadMembership7dCyclePeaks maxCyclesPerMember in plan-queries.ts) and
7710
+ // the renderer (CyclePeaksStrip maxBars in member-plan-block.tsx). The 5h
7711
+ // cap (24) is independent — leave it.
7712
+ fiveHour: peaksFor("cycle_end_5h", "real_5h", "pred_5h", 24),
7713
+ sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 26)
7714
+ };
7715
+ }
7716
+
7717
+ // src/perception/worker.ts
7718
+ import { statSync as statSync5 } from "node:fs";
7719
+ init_fs();
7226
7720
 
7227
7721
  // src/perception/state.ts
7228
7722
  init_fs();
7229
- import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, renameSync as renameSync3 } from "node:fs";
7230
- import { dirname as dirname7 } from "node:path";
7723
+ import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, renameSync as renameSync4 } from "node:fs";
7724
+ import { dirname as dirname8 } from "node:path";
7231
7725
  var STALE_MS = 15 * 60 * 1e3;
7232
7726
  var pathCached = null;
7233
7727
  function statePath() {
@@ -7245,15 +7739,15 @@ function readState() {
7245
7739
  file_checkpoints: {}
7246
7740
  };
7247
7741
  }
7248
- const raw = readFileSync9(p, "utf8");
7742
+ const raw = readFileSync11(p, "utf8");
7249
7743
  return JSON.parse(raw);
7250
7744
  }
7251
7745
  function writeState(s) {
7252
7746
  const p = statePath();
7253
- mkdirSync8(dirname7(p), { recursive: true });
7747
+ mkdirSync8(dirname8(p), { recursive: true });
7254
7748
  const tmp = `${p}.tmp`;
7255
7749
  writeFileSync7(tmp, JSON.stringify(s, null, 2), "utf8");
7256
- renameSync3(tmp, p);
7750
+ renameSync4(tmp, p);
7257
7751
  }
7258
7752
  function markSweepStart() {
7259
7753
  const s = readState();
@@ -7306,12 +7800,6 @@ async function listAllSessionJsonls(root) {
7306
7800
  function log(msg) {
7307
7801
  console.error(`[perception] ${msg}`);
7308
7802
  }
7309
- function decodeProjectDirName(projectDir) {
7310
- if (projectDir.startsWith("-")) {
7311
- return "/" + projectDir.slice(1).replace(/-/g, "/");
7312
- }
7313
- return projectDir.replace(/-/g, "/");
7314
- }
7315
7803
  async function runPerceptionSweep(opts = {}) {
7316
7804
  const state = readState();
7317
7805
  if (state.sweep_in_progress && !isSweepStale()) {
@@ -7325,33 +7813,12 @@ async function runPerceptionSweep(opts = {}) {
7325
7813
  const files = await listAllSessionJsonls(opts.projectsRoot);
7326
7814
  for (const f of files) {
7327
7815
  try {
7328
- const stat = statSync4(f);
7816
+ const stat = statSync5(f);
7329
7817
  const prev = state.file_checkpoints[f];
7330
7818
  if (prev && prev.byte_offset >= stat.size) continue;
7331
- const raw = readFileSync10(f, "utf8");
7332
- const rawLines = raw.split("\n").filter(Boolean).map((l) => {
7333
- try {
7334
- return JSON.parse(l);
7335
- } catch {
7336
- return null;
7337
- }
7338
- }).filter((x) => x !== null);
7339
- if (rawLines.length === 0) continue;
7340
- const { meta, events } = parseTranscript(rawLines);
7341
- const fileName = basename(f);
7342
- const projectDir = basename(dirname8(f));
7343
- const sessionId = fileName.replace(/\.jsonl$/, "");
7344
- const sd = {
7345
- ...meta,
7346
- id: sessionId,
7347
- filePath: f,
7348
- projectDir,
7349
- projectName: meta.cwd ?? decodeProjectDirName(projectDir),
7350
- events
7351
- };
7352
- const built = buildEntries(sd);
7819
+ const built = buildEntriesForFile(f);
7820
+ if (built === null) continue;
7353
7821
  for (const e of built) {
7354
- e.source_checkpoint.byte_offset = stat.size;
7355
7822
  writeEntryPreservingEnrichment(e);
7356
7823
  entries++;
7357
7824
  }
@@ -7378,7 +7845,7 @@ async function runPerceptionSweep(opts = {}) {
7378
7845
  const built = buildEntries(detail);
7379
7846
  for (const e of built) {
7380
7847
  try {
7381
- const stat = statSync4(meta.filePath);
7848
+ const stat = statSync5(meta.filePath);
7382
7849
  e.source_checkpoint.byte_offset = stat.size;
7383
7850
  } catch {
7384
7851
  }
@@ -11535,12 +12002,12 @@ var ENRICHMENT_SYSTEM_PROMPT = SYSTEM_PROMPT;
11535
12002
  // ../entries/dist/llm-runner.js
11536
12003
  init_fs();
11537
12004
  import { spawn } from "node:child_process";
11538
- import { mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "node:fs";
12005
+ import { mkdirSync as mkdirSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "node:fs";
11539
12006
  import { join as join10 } from "node:path";
11540
12007
  import { randomUUID as randomUUID4 } from "node:crypto";
11541
12008
 
11542
12009
  // ../entries/dist/tmux-runner.js
11543
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, existsSync as existsSync8, readdirSync as readdirSync5, realpathSync, readFileSync as readFileSync11, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
12010
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, existsSync as existsSync8, readdirSync as readdirSync5, realpathSync, readFileSync as readFileSync12, unlinkSync as unlinkSync3, renameSync as renameSync5 } from "node:fs";
11544
12011
  import { join as join9 } from "node:path";
11545
12012
  import { homedir as homedir4 } from "node:os";
11546
12013
  import { spawnSync } from "node:child_process";
@@ -11579,7 +12046,7 @@ function ensureRuntimeCwdTrusted(cwd) {
11579
12046
  const cfgPath = join9(homedir4(), ".claude.json");
11580
12047
  let cfg;
11581
12048
  try {
11582
- cfg = JSON.parse(readFileSync11(cfgPath, "utf8"));
12049
+ cfg = JSON.parse(readFileSync12(cfgPath, "utf8"));
11583
12050
  } catch {
11584
12051
  return;
11585
12052
  }
@@ -11595,7 +12062,7 @@ function ensureRuntimeCwdTrusted(cwd) {
11595
12062
  const tmp = `${cfgPath}.cclens-tmp-${process.pid}`;
11596
12063
  try {
11597
12064
  writeFileSync8(tmp, JSON.stringify(cfg, null, 2));
11598
- renameSync4(tmp, cfgPath);
12065
+ renameSync5(tmp, cfgPath);
11599
12066
  } catch {
11600
12067
  try {
11601
12068
  unlinkSync3(tmp);
@@ -11731,7 +12198,7 @@ function snapshotAllTranscripts() {
11731
12198
  function readJsonlCwd(path7, maxLines = 8) {
11732
12199
  let raw;
11733
12200
  try {
11734
- raw = readFileSync11(path7, "utf8");
12201
+ raw = readFileSync12(path7, "utf8");
11735
12202
  } catch {
11736
12203
  return void 0;
11737
12204
  }
@@ -11788,7 +12255,7 @@ async function discoverTranscript(targetCwd, existingBefore, startMs, timeoutMs,
11788
12255
  function parseTranscript2(path7, defaultModel) {
11789
12256
  let raw;
11790
12257
  try {
11791
- raw = readFileSync11(path7, "utf8");
12258
+ raw = readFileSync12(path7, "utf8");
11792
12259
  } catch {
11793
12260
  return { assembled: "", inputTokens: 0, outputTokens: 0, modelUsed: defaultModel, done: false };
11794
12261
  }
@@ -11896,7 +12363,7 @@ function tracePath(runId) {
11896
12363
  }
11897
12364
  function safeAppend(path7, line) {
11898
12365
  try {
11899
- appendFileSync3(path7, line);
12366
+ appendFileSync4(path7, line);
11900
12367
  } catch {
11901
12368
  }
11902
12369
  }
@@ -12291,7 +12758,7 @@ function applyEnrichmentError(entry, errorMessage, model, costUsd, generatedAt,
12291
12758
 
12292
12759
  // ../entries/dist/budget.js
12293
12760
  init_fs();
12294
- import { appendFileSync as appendFileSync4, readFileSync as readFileSync12, existsSync as existsSync10, mkdirSync as mkdirSync11 } from "node:fs";
12761
+ import { appendFileSync as appendFileSync5, readFileSync as readFileSync13, existsSync as existsSync10, mkdirSync as mkdirSync11 } from "node:fs";
12295
12762
  import { dirname as dirname9 } from "node:path";
12296
12763
  var spendPathCached = null;
12297
12764
  function spendPath() {
@@ -12303,7 +12770,7 @@ function spendPath() {
12303
12770
  function appendSpend(record) {
12304
12771
  const p = spendPath();
12305
12772
  mkdirSync11(dirname9(p), { recursive: true });
12306
- appendFileSync4(p, JSON.stringify(record) + "\n", { encoding: "utf8" });
12773
+ appendFileSync5(p, JSON.stringify(record) + "\n", { encoding: "utf8" });
12307
12774
  }
12308
12775
  function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12309
12776
  const p = spendPath();
@@ -12312,7 +12779,7 @@ function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12312
12779
  const year = now.getUTCFullYear();
12313
12780
  const month = now.getUTCMonth();
12314
12781
  let total = 0;
12315
- const raw = readFileSync12(p, "utf8");
12782
+ const raw = readFileSync13(p, "utf8");
12316
12783
  for (const line of raw.split("\n")) {
12317
12784
  if (!line)
12318
12785
  continue;
@@ -12334,7 +12801,7 @@ function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12334
12801
 
12335
12802
  // ../entries/dist/pipeline-lock.js
12336
12803
  init_fs();
12337
- import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync4, existsSync as existsSync11, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
12804
+ import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync4, existsSync as existsSync11, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
12338
12805
  var STALE_MS2 = 60 * 1e3;
12339
12806
  var HEARTBEAT_MS = 30 * 1e3;
12340
12807
  var lockPathCached = null;
@@ -12383,10 +12850,10 @@ function interactiveLockFresh(nowMs) {
12383
12850
  if (!existsSync11(p))
12384
12851
  return false;
12385
12852
  try {
12386
- const mtime = statSync5(p).mtimeMs;
12853
+ const mtime = statSync6(p).mtimeMs;
12387
12854
  if (nowMs - mtime > STALE_MS2)
12388
12855
  return false;
12389
- const pid = Number(readFileSync13(p, "utf8").trim());
12856
+ const pid = Number(readFileSync14(p, "utf8").trim());
12390
12857
  if (!Number.isFinite(pid) || pid <= 0)
12391
12858
  return false;
12392
12859
  if (!pidAlive(pid))
@@ -12401,7 +12868,7 @@ function interactiveLockFresh(nowMs) {
12401
12868
  var THIRTY_MIN_MS = 30 * 60 * 1e3;
12402
12869
 
12403
12870
  // ../entries/dist/settings.js
12404
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync12, existsSync as existsSync12 } from "node:fs";
12871
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, renameSync as renameSync6, chmodSync as chmodSync2, mkdirSync as mkdirSync12, existsSync as existsSync12 } from "node:fs";
12405
12872
  init_fs();
12406
12873
  var DEFAULT_SETTINGS = {
12407
12874
  ai_features: {
@@ -12436,7 +12903,7 @@ function readSettings() {
12436
12903
  if (!existsSync12(p))
12437
12904
  return DEFAULT_SETTINGS;
12438
12905
  try {
12439
- const raw = readFileSync14(p, "utf8");
12906
+ const raw = readFileSync15(p, "utf8");
12440
12907
  return fromDisk(JSON.parse(raw));
12441
12908
  } catch {
12442
12909
  return DEFAULT_SETTINGS;
@@ -15053,8 +15520,8 @@ function toLocalDay2(ms) {
15053
15520
  const day = String(d.getDate()).padStart(2, "0");
15054
15521
  return `${y}-${m}-${day}`;
15055
15522
  }
15056
- function mondayOf(localDay) {
15057
- const d = /* @__PURE__ */ new Date(`${localDay}T00:00:00`);
15523
+ function mondayOf(localDay2) {
15524
+ const d = /* @__PURE__ */ new Date(`${localDay2}T00:00:00`);
15058
15525
  const dow = d.getDay();
15059
15526
  const offset = dow === 0 ? -6 : 1 - dow;
15060
15527
  d.setDate(d.getDate() + offset);
@@ -15193,14 +15660,14 @@ async function getUpdateAvailability() {
15193
15660
  if (isDevMode()) return null;
15194
15661
  const latest = await fetchLatestVersion();
15195
15662
  if (latest === null) return {
15196
- current: "0.13.2",
15663
+ current: "0.15.0",
15197
15664
  latest: null,
15198
15665
  updateAvailable: false
15199
15666
  };
15200
15667
  return {
15201
- current: "0.13.2",
15668
+ current: "0.15.0",
15202
15669
  latest,
15203
- updateAvailable: shouldUpdate("0.13.2", latest)
15670
+ updateAvailable: shouldUpdate("0.15.0", latest)
15204
15671
  };
15205
15672
  }
15206
15673
 
@@ -15212,15 +15679,11 @@ var WATCHDOG_INTERVAL_MS = 5 * 1e3;
15212
15679
  var AUTO_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
15213
15680
  mkdirSync13(dirname10(USAGE_LOG3), { recursive: true });
15214
15681
  function log2(level, message) {
15215
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${message}
15216
- `;
15217
- try {
15218
- appendFileSync5(DAEMON_LOG, line, "utf8");
15219
- } catch {
15220
- }
15682
+ appendDaemonLogLine(level, message);
15221
15683
  }
15222
15684
  var nextPollAtMs = 0;
15223
15685
  var nextTeamSyncAtMs = 0;
15686
+ var firstTeamSync = true;
15224
15687
  var currentIntervalMs = BASE_INTERVAL_MS;
15225
15688
  var waitingForRefresh = false;
15226
15689
  var updateCheckInFlight = false;
@@ -15252,7 +15715,7 @@ var perceptionHandle = setInterval(async () => {
15252
15715
  }, PERCEPTION_INTERVAL_MS);
15253
15716
  function readUpdateState() {
15254
15717
  try {
15255
- return JSON.parse(readFileSync15(UPDATE_STATE, "utf8"));
15718
+ return JSON.parse(readFileSync16(UPDATE_STATE, "utf8"));
15256
15719
  } catch {
15257
15720
  return null;
15258
15721
  }
@@ -15429,7 +15892,11 @@ async function runLoop() {
15429
15892
  }
15430
15893
  const teamNow = Date.now();
15431
15894
  if (teamNow >= nextTeamSyncAtMs) {
15432
- await runTeamSync(log2);
15895
+ await runTeamSync(log2, void 0, {
15896
+ trigger: firstTeamSync ? "boot" : "auto",
15897
+ nextSyncMs: BASE_INTERVAL_MS
15898
+ });
15899
+ firstTeamSync = false;
15433
15900
  nextTeamSyncAtMs = Date.now() + BASE_INTERVAL_MS;
15434
15901
  }
15435
15902
  if (Date.now() >= nextUpdateCheckAtMs) {