botmux 2.107.0 → 2.108.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 (515) hide show
  1. package/README.en.md +17 -14
  2. package/README.md +19 -14
  3. package/dist/adapters/backend/sandbox.d.ts +3 -1
  4. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  5. package/dist/adapters/backend/sandbox.js +19 -3
  6. package/dist/adapters/backend/sandbox.js.map +1 -1
  7. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/tmux-backend.js +16 -1
  9. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  10. package/dist/adapters/backend/tmux-pipe-backend.d.ts +24 -15
  11. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-pipe-backend.js +75 -43
  13. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  14. package/dist/adapters/cli/genius.d.ts.map +1 -1
  15. package/dist/adapters/cli/genius.js +9 -15
  16. package/dist/adapters/cli/genius.js.map +1 -1
  17. package/dist/adapters/cli/grok.d.ts +73 -0
  18. package/dist/adapters/cli/grok.d.ts.map +1 -0
  19. package/dist/adapters/cli/grok.js +298 -0
  20. package/dist/adapters/cli/grok.js.map +1 -0
  21. package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
  22. package/dist/adapters/cli/oh-my-pi.js +133 -12
  23. package/dist/adapters/cli/oh-my-pi.js.map +1 -1
  24. package/dist/adapters/cli/registry.d.ts +2 -1
  25. package/dist/adapters/cli/registry.d.ts.map +1 -1
  26. package/dist/adapters/cli/registry.js +3 -1
  27. package/dist/adapters/cli/registry.js.map +1 -1
  28. package/dist/adapters/cli/shared-hints.d.ts +8 -4
  29. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  30. package/dist/adapters/cli/shared-hints.js +8 -4
  31. package/dist/adapters/cli/shared-hints.js.map +1 -1
  32. package/dist/adapters/cli/types.d.ts +18 -16
  33. package/dist/adapters/cli/types.d.ts.map +1 -1
  34. package/dist/adapters/hook-installer.d.ts +5 -4
  35. package/dist/adapters/hook-installer.d.ts.map +1 -1
  36. package/dist/adapters/hook-installer.js +42 -0
  37. package/dist/adapters/hook-installer.js.map +1 -1
  38. package/dist/cli/send-dispatch.d.ts +21 -0
  39. package/dist/cli/send-dispatch.d.ts.map +1 -1
  40. package/dist/cli/send-dispatch.js +159 -0
  41. package/dist/cli/send-dispatch.js.map +1 -1
  42. package/dist/cli/session-list-liveness.d.ts +22 -0
  43. package/dist/cli/session-list-liveness.d.ts.map +1 -0
  44. package/dist/cli/session-list-liveness.js +22 -0
  45. package/dist/cli/session-list-liveness.js.map +1 -0
  46. package/dist/cli.d.ts.map +1 -1
  47. package/dist/cli.js +182 -33
  48. package/dist/cli.js.map +1 -1
  49. package/dist/core/ask-types.d.ts +4 -0
  50. package/dist/core/ask-types.d.ts.map +1 -1
  51. package/dist/core/ask-types.js +10 -0
  52. package/dist/core/ask-types.js.map +1 -1
  53. package/dist/core/cost-calculator.d.ts.map +1 -1
  54. package/dist/core/cost-calculator.js +35 -13
  55. package/dist/core/cost-calculator.js.map +1 -1
  56. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  57. package/dist/core/dashboard-ipc-server.js +89 -16
  58. package/dist/core/dashboard-ipc-server.js.map +1 -1
  59. package/dist/core/dashboard-rows.d.ts.map +1 -1
  60. package/dist/core/dashboard-rows.js +6 -2
  61. package/dist/core/dashboard-rows.js.map +1 -1
  62. package/dist/core/dashboard-url.d.ts +6 -5
  63. package/dist/core/dashboard-url.d.ts.map +1 -1
  64. package/dist/core/dashboard-url.js +11 -8
  65. package/dist/core/dashboard-url.js.map +1 -1
  66. package/dist/core/maintenance.d.ts +11 -8
  67. package/dist/core/maintenance.d.ts.map +1 -1
  68. package/dist/core/maintenance.js +45 -20
  69. package/dist/core/maintenance.js.map +1 -1
  70. package/dist/core/per-bot-env.d.ts.map +1 -1
  71. package/dist/core/per-bot-env.js +4 -0
  72. package/dist/core/per-bot-env.js.map +1 -1
  73. package/dist/core/session-discovery.d.ts.map +1 -1
  74. package/dist/core/session-discovery.js +4 -0
  75. package/dist/core/session-discovery.js.map +1 -1
  76. package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
  77. package/dist/core/skills/cli-admin-command.js +8 -2
  78. package/dist/core/skills/cli-admin-command.js.map +1 -1
  79. package/dist/core/skills/im-command.d.ts.map +1 -1
  80. package/dist/core/skills/im-command.js +3 -1
  81. package/dist/core/skills/im-command.js.map +1 -1
  82. package/dist/core/skills/sources.d.ts +33 -1
  83. package/dist/core/skills/sources.d.ts.map +1 -1
  84. package/dist/core/skills/sources.js +97 -0
  85. package/dist/core/skills/sources.js.map +1 -1
  86. package/dist/core/skills/types.d.ts +8 -0
  87. package/dist/core/skills/types.d.ts.map +1 -1
  88. package/dist/core/terminal-url.d.ts.map +1 -1
  89. package/dist/core/terminal-url.js +12 -2
  90. package/dist/core/terminal-url.js.map +1 -1
  91. package/dist/core/terminal-write-auth.d.ts +34 -0
  92. package/dist/core/terminal-write-auth.d.ts.map +1 -0
  93. package/dist/core/terminal-write-auth.js +76 -0
  94. package/dist/core/terminal-write-auth.js.map +1 -0
  95. package/dist/core/tmux-recovery.d.ts +11 -0
  96. package/dist/core/tmux-recovery.d.ts.map +1 -0
  97. package/dist/core/tmux-recovery.js +19 -0
  98. package/dist/core/tmux-recovery.js.map +1 -0
  99. package/dist/core/types.d.ts +10 -4
  100. package/dist/core/types.d.ts.map +1 -1
  101. package/dist/core/types.js.map +1 -1
  102. package/dist/core/update-check.d.ts +2 -2
  103. package/dist/core/update-check.js +2 -2
  104. package/dist/core/worker-pool.d.ts +3 -0
  105. package/dist/core/worker-pool.d.ts.map +1 -1
  106. package/dist/core/worker-pool.js +70 -3
  107. package/dist/core/worker-pool.js.map +1 -1
  108. package/dist/daemon.d.ts.map +1 -1
  109. package/dist/daemon.js +15 -8
  110. package/dist/daemon.js.map +1 -1
  111. package/dist/dashboard/auth.d.ts +1 -1
  112. package/dist/dashboard/auth.d.ts.map +1 -1
  113. package/dist/dashboard/auth.js +7 -2
  114. package/dist/dashboard/auth.js.map +1 -1
  115. package/dist/dashboard/bot-payload.d.ts +3 -0
  116. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  117. package/dist/dashboard/bot-payload.js +3 -0
  118. package/dist/dashboard/bot-payload.js.map +1 -1
  119. package/dist/dashboard/connector-api.d.ts.map +1 -1
  120. package/dist/dashboard/connector-api.js +39 -13
  121. package/dist/dashboard/connector-api.js.map +1 -1
  122. package/dist/dashboard/history-senders.d.ts +37 -0
  123. package/dist/dashboard/history-senders.d.ts.map +1 -0
  124. package/dist/dashboard/history-senders.js +60 -0
  125. package/dist/dashboard/history-senders.js.map +1 -0
  126. package/dist/dashboard/roles-batch.d.ts +31 -0
  127. package/dist/dashboard/roles-batch.d.ts.map +1 -0
  128. package/dist/dashboard/roles-batch.js +87 -0
  129. package/dist/dashboard/roles-batch.js.map +1 -0
  130. package/dist/dashboard/settings-card-model.d.ts +4 -2
  131. package/dist/dashboard/settings-card-model.d.ts.map +1 -1
  132. package/dist/dashboard/settings-card-model.js +13 -5
  133. package/dist/dashboard/settings-card-model.js.map +1 -1
  134. package/dist/dashboard/settings-write-applier.d.ts +4 -1
  135. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  136. package/dist/dashboard/settings-write-applier.js +6 -1
  137. package/dist/dashboard/settings-write-applier.js.map +1 -1
  138. package/dist/dashboard/skill-install-request.d.ts +4 -0
  139. package/dist/dashboard/skill-install-request.d.ts.map +1 -1
  140. package/dist/dashboard/skill-install-request.js +11 -1
  141. package/dist/dashboard/skill-install-request.js.map +1 -1
  142. package/dist/dashboard/trigger-api.d.ts +9 -1
  143. package/dist/dashboard/trigger-api.d.ts.map +1 -1
  144. package/dist/dashboard/trigger-api.js +48 -10
  145. package/dist/dashboard/trigger-api.js.map +1 -1
  146. package/dist/dashboard/web/app.d.ts.map +1 -1
  147. package/dist/dashboard/web/app.js +286 -271
  148. package/dist/dashboard/web/app.js.map +1 -1
  149. package/dist/dashboard/web/bot-defaults-page.d.ts +26 -0
  150. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  151. package/dist/dashboard/web/bot-defaults-page.js +1408 -12
  152. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  153. package/dist/dashboard/web/bot-defaults.d.ts +91 -4
  154. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  155. package/dist/dashboard/web/bot-defaults.js +43 -2141
  156. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  157. package/dist/dashboard/web/bot-onboarding.d.ts +6 -1
  158. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  159. package/dist/dashboard/web/bot-onboarding.js +261 -306
  160. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  161. package/dist/dashboard/web/connectors-page.d.ts +4 -0
  162. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  163. package/dist/dashboard/web/connectors-page.js +228 -48
  164. package/dist/dashboard/web/connectors-page.js.map +1 -1
  165. package/dist/dashboard/web/dashboard-api.d.ts +9 -0
  166. package/dist/dashboard/web/dashboard-api.d.ts.map +1 -0
  167. package/dist/dashboard/web/dashboard-api.js +18 -0
  168. package/dist/dashboard/web/dashboard-api.js.map +1 -0
  169. package/dist/dashboard/web/dashboard-components.d.ts +92 -0
  170. package/dist/dashboard/web/dashboard-components.d.ts.map +1 -0
  171. package/dist/dashboard/web/dashboard-components.js +250 -0
  172. package/dist/dashboard/web/dashboard-components.js.map +1 -0
  173. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  174. package/dist/dashboard/web/dashboard-routes.js +30 -183
  175. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  176. package/dist/dashboard/web/floating-scrollbars.d.ts +2 -0
  177. package/dist/dashboard/web/floating-scrollbars.d.ts.map +1 -0
  178. package/dist/dashboard/web/floating-scrollbars.js +264 -0
  179. package/dist/dashboard/web/floating-scrollbars.js.map +1 -0
  180. package/dist/dashboard/web/groups-api.d.ts +36 -0
  181. package/dist/dashboard/web/groups-api.d.ts.map +1 -0
  182. package/dist/dashboard/web/groups-api.js +44 -0
  183. package/dist/dashboard/web/groups-api.js.map +1 -0
  184. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  185. package/dist/dashboard/web/groups-page.js +746 -15
  186. package/dist/dashboard/web/groups-page.js.map +1 -1
  187. package/dist/dashboard/web/groups.d.ts +51 -18
  188. package/dist/dashboard/web/groups.d.ts.map +1 -1
  189. package/dist/dashboard/web/groups.js +145 -1096
  190. package/dist/dashboard/web/groups.js.map +1 -1
  191. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  192. package/dist/dashboard/web/i18n.js +545 -255
  193. package/dist/dashboard/web/i18n.js.map +1 -1
  194. package/dist/dashboard/web/insights-page.d.ts.map +1 -1
  195. package/dist/dashboard/web/insights-page.js +1047 -14
  196. package/dist/dashboard/web/insights-page.js.map +1 -1
  197. package/dist/dashboard/web/insights.d.ts +177 -7
  198. package/dist/dashboard/web/insights.d.ts.map +1 -1
  199. package/dist/dashboard/web/insights.js +187 -1926
  200. package/dist/dashboard/web/insights.js.map +1 -1
  201. package/dist/dashboard/web/legacy-workflow-page.d.ts +3 -0
  202. package/dist/dashboard/web/legacy-workflow-page.d.ts.map +1 -0
  203. package/dist/dashboard/web/legacy-workflow-page.js +717 -0
  204. package/dist/dashboard/web/legacy-workflow-page.js.map +1 -0
  205. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  206. package/dist/dashboard/web/monitor-room.js +10 -8
  207. package/dist/dashboard/web/monitor-room.js.map +1 -1
  208. package/dist/dashboard/web/monitoring-page.d.ts.map +1 -1
  209. package/dist/dashboard/web/monitoring-page.js +188 -14
  210. package/dist/dashboard/web/monitoring-page.js.map +1 -1
  211. package/dist/dashboard/web/office-page.d.ts.map +1 -1
  212. package/dist/dashboard/web/office-page.js +11 -23
  213. package/dist/dashboard/web/office-page.js.map +1 -1
  214. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  215. package/dist/dashboard/web/overview-page.js +141 -147
  216. package/dist/dashboard/web/overview-page.js.map +1 -1
  217. package/dist/dashboard/web/overview.d.ts +0 -1
  218. package/dist/dashboard/web/overview.d.ts.map +1 -1
  219. package/dist/dashboard/web/overview.js +3 -275
  220. package/dist/dashboard/web/overview.js.map +1 -1
  221. package/dist/dashboard/web/preferences.d.ts +1 -6
  222. package/dist/dashboard/web/preferences.d.ts.map +1 -1
  223. package/dist/dashboard/web/preferences.js +0 -21
  224. package/dist/dashboard/web/preferences.js.map +1 -1
  225. package/dist/dashboard/web/role-batch.d.ts +9 -0
  226. package/dist/dashboard/web/role-batch.d.ts.map +1 -0
  227. package/dist/dashboard/web/role-batch.js +42 -0
  228. package/dist/dashboard/web/role-batch.js.map +1 -0
  229. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  230. package/dist/dashboard/web/roles-page.js +538 -13
  231. package/dist/dashboard/web/roles-page.js.map +1 -1
  232. package/dist/dashboard/web/roles.d.ts +98 -1
  233. package/dist/dashboard/web/roles.d.ts.map +1 -1
  234. package/dist/dashboard/web/roles.js +117 -903
  235. package/dist/dashboard/web/roles.js.map +1 -1
  236. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  237. package/dist/dashboard/web/schedules-page.js +82 -24
  238. package/dist/dashboard/web/schedules-page.js.map +1 -1
  239. package/dist/dashboard/web/sessions-kanban.d.ts +67 -0
  240. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -0
  241. package/dist/dashboard/web/sessions-kanban.js +481 -0
  242. package/dist/dashboard/web/sessions-kanban.js.map +1 -0
  243. package/dist/dashboard/web/sessions-page.d.ts +4 -0
  244. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  245. package/dist/dashboard/web/sessions-page.js +1633 -18
  246. package/dist/dashboard/web/sessions-page.js.map +1 -1
  247. package/dist/dashboard/web/sessions.d.ts +45 -4
  248. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  249. package/dist/dashboard/web/sessions.js +53 -2535
  250. package/dist/dashboard/web/sessions.js.map +1 -1
  251. package/dist/dashboard/web/settings-page.d.ts +0 -2
  252. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  253. package/dist/dashboard/web/settings-page.js +127 -117
  254. package/dist/dashboard/web/settings-page.js.map +1 -1
  255. package/dist/dashboard/web/skills-page.d.ts +10 -1
  256. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  257. package/dist/dashboard/web/skills-page.js +235 -93
  258. package/dist/dashboard/web/skills-page.js.map +1 -1
  259. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  260. package/dist/dashboard/web/team-federation-page.js +446 -28
  261. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  262. package/dist/dashboard/web/team-federation.d.ts +91 -2
  263. package/dist/dashboard/web/team-federation.d.ts.map +1 -1
  264. package/dist/dashboard/web/team-federation.js +87 -474
  265. package/dist/dashboard/web/team-federation.js.map +1 -1
  266. package/dist/dashboard/web/theme-menu.d.ts +1 -0
  267. package/dist/dashboard/web/theme-menu.d.ts.map +1 -1
  268. package/dist/dashboard/web/theme-menu.js +79 -20
  269. package/dist/dashboard/web/theme-menu.js.map +1 -1
  270. package/dist/dashboard/web/ui.d.ts +1 -2
  271. package/dist/dashboard/web/ui.d.ts.map +1 -1
  272. package/dist/dashboard/web/ui.js +8 -20
  273. package/dist/dashboard/web/ui.js.map +1 -1
  274. package/dist/dashboard/web/v3-api.d.ts +16 -0
  275. package/dist/dashboard/web/v3-api.d.ts.map +1 -0
  276. package/dist/dashboard/web/v3-api.js +21 -0
  277. package/dist/dashboard/web/v3-api.js.map +1 -0
  278. package/dist/dashboard/web/v3-components.d.ts +2 -0
  279. package/dist/dashboard/web/v3-components.d.ts.map +1 -0
  280. package/dist/dashboard/web/v3-components.js +290 -0
  281. package/dist/dashboard/web/v3-components.js.map +1 -0
  282. package/dist/dashboard/web/v3-model.d.ts +80 -0
  283. package/dist/dashboard/web/v3-model.d.ts.map +1 -0
  284. package/dist/dashboard/web/v3-model.js +240 -0
  285. package/dist/dashboard/web/v3-model.js.map +1 -0
  286. package/dist/dashboard/web/v3-page.d.ts.map +1 -1
  287. package/dist/dashboard/web/v3-page.js +2 -22
  288. package/dist/dashboard/web/v3-page.js.map +1 -1
  289. package/dist/dashboard/web/v3-terminal.d.ts +14 -0
  290. package/dist/dashboard/web/v3-terminal.d.ts.map +1 -1
  291. package/dist/dashboard/web/v3-terminal.js +31 -19
  292. package/dist/dashboard/web/v3-terminal.js.map +1 -1
  293. package/dist/dashboard/web/webhook-logs-page.d.ts +16 -0
  294. package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -0
  295. package/dist/dashboard/web/webhook-logs-page.js +195 -0
  296. package/dist/dashboard/web/webhook-logs-page.js.map +1 -0
  297. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  298. package/dist/dashboard/web/whiteboards-page.js +18 -23
  299. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  300. package/dist/dashboard/web/workflow-version-switch.d.ts +8 -0
  301. package/dist/dashboard/web/workflow-version-switch.d.ts.map +1 -0
  302. package/dist/dashboard/web/workflow-version-switch.js +9 -0
  303. package/dist/dashboard/web/workflow-version-switch.js.map +1 -0
  304. package/dist/dashboard/web/workflows.d.ts +272 -1
  305. package/dist/dashboard/web/workflows.d.ts.map +1 -1
  306. package/dist/dashboard/web/workflows.js +211 -1397
  307. package/dist/dashboard/web/workflows.js.map +1 -1
  308. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  309. package/dist/dashboard/webhook-routes.js +92 -39
  310. package/dist/dashboard/webhook-routes.js.map +1 -1
  311. package/dist/dashboard-web/app.js +1 -65
  312. package/dist/dashboard-web/brand-logo.png +0 -0
  313. package/dist/dashboard-web/chunks/bot-defaults-page-HSQ6HR3R.js +1 -0
  314. package/dist/dashboard-web/chunks/chunk-6TPODDDC.js +1 -0
  315. package/dist/dashboard-web/chunks/chunk-73EJP2MO.js +1 -0
  316. package/dist/dashboard-web/chunks/chunk-BRTZFELS.js +1 -0
  317. package/dist/dashboard-web/chunks/chunk-EVK77ASG.js +1 -0
  318. package/dist/dashboard-web/chunks/chunk-R3FXBFQH.js +1 -0
  319. package/dist/dashboard-web/chunks/chunk-VRLL7SNU.js +1 -0
  320. package/dist/dashboard-web/chunks/chunk-XCPSHD3G.js +37 -0
  321. package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
  322. package/dist/dashboard-web/chunks/chunk-YVRWYGXR.js +1 -0
  323. package/dist/dashboard-web/chunks/chunk-ZJING6FY.js +66 -0
  324. package/dist/dashboard-web/chunks/connectors-page-ODUA5UCW.js +1 -0
  325. package/dist/dashboard-web/chunks/groups-page-OYX5M6NK.js +8 -0
  326. package/dist/dashboard-web/chunks/insights-page-YCQO6OCR.js +22 -0
  327. package/dist/dashboard-web/chunks/legacy-workflow-page-XJAUNONP.js +2 -0
  328. package/dist/dashboard-web/chunks/{monitor-room-RFX4HPS5.js → monitor-room-WTZNYAFV.js} +17 -15
  329. package/dist/dashboard-web/chunks/monitoring-page-R6LJUK7G.js +2 -0
  330. package/dist/dashboard-web/chunks/office-page-4OXK3GNR.js +1 -0
  331. package/dist/dashboard-web/chunks/overview-page-4MVEJY6U.js +1 -0
  332. package/dist/dashboard-web/chunks/roles-page-NQDW27VV.js +1 -0
  333. package/dist/dashboard-web/chunks/schedules-page-JRNKYHOX.js +1 -0
  334. package/dist/dashboard-web/chunks/sessions-page-LF7UVI7B.js +3 -0
  335. package/dist/dashboard-web/chunks/settings-page-WGW7KQQS.js +2 -0
  336. package/dist/dashboard-web/chunks/skills-page-AMD4XP3T.js +3 -0
  337. package/dist/dashboard-web/chunks/team-federation-page-SYG3K4GG.js +1 -0
  338. package/dist/dashboard-web/chunks/v3-page-6NFFWNUK.js +94 -0
  339. package/dist/dashboard-web/chunks/whiteboards-page-FBRUK2AS.js +1 -0
  340. package/dist/dashboard-web/favicon.png +0 -0
  341. package/dist/dashboard-web/index.html +5 -69
  342. package/dist/dashboard-web/style.css +18835 -7211
  343. package/dist/dashboard.js +150 -32
  344. package/dist/dashboard.js.map +1 -1
  345. package/dist/global-config.d.ts +2 -2
  346. package/dist/global-config.d.ts.map +1 -1
  347. package/dist/i18n/en.d.ts.map +1 -1
  348. package/dist/i18n/en.js +3 -0
  349. package/dist/i18n/en.js.map +1 -1
  350. package/dist/i18n/zh.d.ts.map +1 -1
  351. package/dist/i18n/zh.js +3 -0
  352. package/dist/i18n/zh.js.map +1 -1
  353. package/dist/im/lark/card-builder.d.ts +7 -5
  354. package/dist/im/lark/card-builder.d.ts.map +1 -1
  355. package/dist/im/lark/card-builder.js +10 -7
  356. package/dist/im/lark/card-builder.js.map +1 -1
  357. package/dist/im/lark/card-handler.d.ts.map +1 -1
  358. package/dist/im/lark/card-handler.js +25 -1
  359. package/dist/im/lark/card-handler.js.map +1 -1
  360. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  361. package/dist/im/lark/event-dispatcher.js +84 -0
  362. package/dist/im/lark/event-dispatcher.js.map +1 -1
  363. package/dist/platform/binding.d.ts +9 -0
  364. package/dist/platform/binding.d.ts.map +1 -1
  365. package/dist/platform/binding.js +14 -0
  366. package/dist/platform/binding.js.map +1 -1
  367. package/dist/platform/tunnel-client.d.ts.map +1 -1
  368. package/dist/platform/tunnel-client.js +9 -0
  369. package/dist/platform/tunnel-client.js.map +1 -1
  370. package/dist/services/bot-config-store.d.ts +1 -1
  371. package/dist/services/bot-config-store.d.ts.map +1 -1
  372. package/dist/services/bot-config-store.js +11 -5
  373. package/dist/services/bot-config-store.js.map +1 -1
  374. package/dist/services/file-bridge-path.d.ts +8 -0
  375. package/dist/services/file-bridge-path.d.ts.map +1 -0
  376. package/dist/services/file-bridge-path.js +75 -0
  377. package/dist/services/file-bridge-path.js.map +1 -0
  378. package/dist/services/grok-paths.d.ts +27 -0
  379. package/dist/services/grok-paths.d.ts.map +1 -0
  380. package/dist/services/grok-paths.js +106 -0
  381. package/dist/services/grok-paths.js.map +1 -0
  382. package/dist/services/grok-transcript.d.ts +67 -0
  383. package/dist/services/grok-transcript.d.ts.map +1 -0
  384. package/dist/services/grok-transcript.js +630 -0
  385. package/dist/services/grok-transcript.js.map +1 -0
  386. package/dist/services/hermes-session-filter.d.ts +28 -0
  387. package/dist/services/hermes-session-filter.d.ts.map +1 -0
  388. package/dist/services/hermes-session-filter.js +53 -0
  389. package/dist/services/hermes-session-filter.js.map +1 -0
  390. package/dist/services/hermes-transcript.d.ts +3 -0
  391. package/dist/services/hermes-transcript.d.ts.map +1 -1
  392. package/dist/services/hermes-transcript.js +20 -2
  393. package/dist/services/hermes-transcript.js.map +1 -1
  394. package/dist/services/resumable-session-discovery.d.ts +3 -0
  395. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  396. package/dist/services/resumable-session-discovery.js +6 -1
  397. package/dist/services/resumable-session-discovery.js.map +1 -1
  398. package/dist/services/skill-registry-store.d.ts +9 -0
  399. package/dist/services/skill-registry-store.d.ts.map +1 -1
  400. package/dist/services/skill-registry-store.js +304 -1
  401. package/dist/services/skill-registry-store.js.map +1 -1
  402. package/dist/services/structured-bridge-clis.d.ts +29 -0
  403. package/dist/services/structured-bridge-clis.d.ts.map +1 -0
  404. package/dist/services/structured-bridge-clis.js +76 -0
  405. package/dist/services/structured-bridge-clis.js.map +1 -0
  406. package/dist/services/trigger-log-store.d.ts +59 -0
  407. package/dist/services/trigger-log-store.d.ts.map +1 -1
  408. package/dist/services/trigger-log-store.js +81 -10
  409. package/dist/services/trigger-log-store.js.map +1 -1
  410. package/dist/services/usage-ledger.d.ts +4 -1
  411. package/dist/services/usage-ledger.d.ts.map +1 -1
  412. package/dist/services/usage-ledger.js +48 -10
  413. package/dist/services/usage-ledger.js.map +1 -1
  414. package/dist/services/webhook-audit.d.ts +10 -0
  415. package/dist/services/webhook-audit.d.ts.map +1 -0
  416. package/dist/services/webhook-audit.js +116 -0
  417. package/dist/services/webhook-audit.js.map +1 -0
  418. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  419. package/dist/setup/bot-config-editor.js +2 -0
  420. package/dist/setup/bot-config-editor.js.map +1 -1
  421. package/dist/setup/ensure-tmux.d.ts +26 -12
  422. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  423. package/dist/setup/ensure-tmux.js +85 -18
  424. package/dist/setup/ensure-tmux.js.map +1 -1
  425. package/dist/skills/definitions.d.ts.map +1 -1
  426. package/dist/skills/definitions.js +14 -1
  427. package/dist/skills/definitions.js.map +1 -1
  428. package/dist/skills/injection-mode.d.ts +16 -0
  429. package/dist/skills/injection-mode.d.ts.map +1 -1
  430. package/dist/skills/injection-mode.js +25 -0
  431. package/dist/skills/injection-mode.js.map +1 -1
  432. package/dist/types.d.ts +9 -0
  433. package/dist/types.d.ts.map +1 -1
  434. package/dist/utils/global-install.d.ts +22 -0
  435. package/dist/utils/global-install.d.ts.map +1 -0
  436. package/dist/utils/global-install.js +106 -0
  437. package/dist/utils/global-install.js.map +1 -0
  438. package/dist/utils/install-diagnostics.d.ts +1 -1
  439. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  440. package/dist/utils/install-diagnostics.js +6 -4
  441. package/dist/utils/install-diagnostics.js.map +1 -1
  442. package/dist/utils/install-info.d.ts +2 -0
  443. package/dist/utils/install-info.d.ts.map +1 -1
  444. package/dist/utils/install-info.js +10 -4
  445. package/dist/utils/install-info.js.map +1 -1
  446. package/dist/worker.js +293 -172
  447. package/dist/worker.js.map +1 -1
  448. package/dist/workflows/events/payloads.d.ts +10 -10
  449. package/dist/workflows/events/schema.d.ts +28 -28
  450. package/package.json +8 -7
  451. package/dist/dashboard/web/connectors.d.ts +0 -2
  452. package/dist/dashboard/web/connectors.d.ts.map +0 -1
  453. package/dist/dashboard/web/connectors.js +0 -2
  454. package/dist/dashboard/web/connectors.js.map +0 -1
  455. package/dist/dashboard/web/office.d.ts +0 -2
  456. package/dist/dashboard/web/office.d.ts.map +0 -1
  457. package/dist/dashboard/web/office.js +0 -2
  458. package/dist/dashboard/web/office.js.map +0 -1
  459. package/dist/dashboard/web/schedules.d.ts +0 -2
  460. package/dist/dashboard/web/schedules.d.ts.map +0 -1
  461. package/dist/dashboard/web/schedules.js +0 -2
  462. package/dist/dashboard/web/schedules.js.map +0 -1
  463. package/dist/dashboard/web/settings.d.ts +0 -2
  464. package/dist/dashboard/web/settings.d.ts.map +0 -1
  465. package/dist/dashboard/web/settings.js +0 -2
  466. package/dist/dashboard/web/settings.js.map +0 -1
  467. package/dist/dashboard/web/skills.d.ts +0 -2
  468. package/dist/dashboard/web/skills.d.ts.map +0 -1
  469. package/dist/dashboard/web/skills.js +0 -2
  470. package/dist/dashboard/web/skills.js.map +0 -1
  471. package/dist/dashboard/web/skin-intro.d.ts +0 -4
  472. package/dist/dashboard/web/skin-intro.d.ts.map +0 -1
  473. package/dist/dashboard/web/skin-intro.js +0 -49
  474. package/dist/dashboard/web/skin-intro.js.map +0 -1
  475. package/dist/dashboard/web/v3.d.ts +0 -3
  476. package/dist/dashboard/web/v3.d.ts.map +0 -1
  477. package/dist/dashboard/web/v3.js +0 -539
  478. package/dist/dashboard/web/v3.js.map +0 -1
  479. package/dist/dashboard/web/whiteboards.d.ts +0 -2
  480. package/dist/dashboard/web/whiteboards.d.ts.map +0 -1
  481. package/dist/dashboard/web/whiteboards.js +0 -2
  482. package/dist/dashboard/web/whiteboards.js.map +0 -1
  483. package/dist/dashboard-web/chunks/bot-defaults-page-OCIHPGEU.js +0 -438
  484. package/dist/dashboard-web/chunks/chunk-3N2PPQVD.js +0 -1
  485. package/dist/dashboard-web/chunks/chunk-3UJYRR2Q.js +0 -37
  486. package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +0 -1
  487. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  488. package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +0 -54
  489. package/dist/dashboard-web/chunks/chunk-LXIK7OUJ.js +0 -1
  490. package/dist/dashboard-web/chunks/chunk-PE6W3C2C.js +0 -1
  491. package/dist/dashboard-web/chunks/chunk-QW54DWU3.js +0 -325
  492. package/dist/dashboard-web/chunks/connectors-IGETYWCP.js +0 -1
  493. package/dist/dashboard-web/chunks/groups-page-MYBS4ZYJ.js +0 -207
  494. package/dist/dashboard-web/chunks/insights-page-UPA32V2S.js +0 -309
  495. package/dist/dashboard-web/chunks/monitoring-page-7T3BESZ2.js +0 -1
  496. package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +0 -1
  497. package/dist/dashboard-web/chunks/overview-page-LJSBCQW4.js +0 -1
  498. package/dist/dashboard-web/chunks/roles-page-ESURTVQH.js +0 -98
  499. package/dist/dashboard-web/chunks/schedules-LML2ISOT.js +0 -1
  500. package/dist/dashboard-web/chunks/sessions-YB73XOTU.js +0 -1
  501. package/dist/dashboard-web/chunks/sessions-page-MCO5QR7Z.js +0 -1
  502. package/dist/dashboard-web/chunks/settings-page-OSDKNVS3.js +0 -2
  503. package/dist/dashboard-web/chunks/skills-page-PXBZJ6P6.js +0 -2
  504. package/dist/dashboard-web/chunks/team-federation-page-TTPVHFPX.js +0 -10
  505. package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +0 -149
  506. package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +0 -1
  507. package/dist/dashboard-web/chunks/workflows-FCPM4S4D.js +0 -184
  508. package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
  509. package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
  510. package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
  511. package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
  512. package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
  513. package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
  514. package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
  515. package/dist/dashboard-web/skins/zzz-pattern.svg +0 -25
@@ -1,24 +1,1057 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
3
4
  import { useT } from './react-hooks.js';
4
5
  import { mountReactPage } from './react-mount.js';
5
- import { initialInsightTab, renderDetailShell, renderTabBar, TIME_WINDOWS, wireInsightsPage, } from './insights.js';
6
- function Html(props) {
7
- return _jsx("span", { style: { display: 'contents' }, dangerouslySetInnerHTML: { __html: props.html } });
6
+ import { botDisplayName, loadNameMaps, relTime, t } from './ui.js';
7
+ import { DropdownMenu, LoadingState, RefreshIconButton } from './dashboard-components.js';
8
+ import { BAD_RESULTS, CONVO_ROLES, CONVO_TAGS, FLOW_PHASES, INSIGHT_FILTERS, INSIGHT_TABS, SESS_SORT_KEYS, SESS_SORTS, SEVERITY_RANK, SPAN_TAGS, TIME_WINDOWS, agentMsOf, aggregateRecords, buildInsightScope, buildInsightsHash, cleanPromptText, cliIdOf, cliCounts, fetchInsightConversation, fetchInsightDetail, fetchInsightsSummary, fetchTurnPrompt, filterRecords, fmtInt, fmtMs, focusSets, groupConvo, idText, initialInsightTab, intentPhrase, intentText, intentTextOf, median, newConvoState, okReports, parseInsightsHash, phaseClass, phaseLabel, phaseSlug, projectOf, projectOptions, promptMarkdownHtml, reportNeedsReview, resultLabel, safeStatus, sessionMetaLine, sessionTitle, severityLabel, sortRecordsBy, spanFailed, statusIcon, structLabel, suggestionTitle, tagLabel, toRecord, turnAdvice, turnHeadline, turnMainPhase, turnSenderKind, } from './insights.js';
9
+ const CLI_FILTER_ALL = '__all__';
10
+ function clamp(n, min, max) {
11
+ return Math.min(Math.max(n, min), max);
12
+ }
13
+ function paletteAnchorFromButton(button) {
14
+ const rect = button.getBoundingClientRect();
15
+ const margin = 12;
16
+ const width = Math.min(420, Math.max(280, window.innerWidth - margin * 2));
17
+ const left = clamp(Math.round(rect.left + rect.width / 2 - width / 2), margin, Math.max(margin, window.innerWidth - width - margin));
18
+ const naturalTop = Math.round(rect.bottom + 8);
19
+ const top = clamp(naturalTop, margin, Math.max(margin, window.innerHeight - 132));
20
+ const maxHeight = Math.max(140, Math.floor(window.innerHeight - top - margin));
21
+ return { top, left, width, maxHeight };
22
+ }
23
+ function toggleSet(set, value) {
24
+ const next = new Set(set);
25
+ if (next.has(value))
26
+ next.delete(value);
27
+ else
28
+ next.add(value);
29
+ return next;
30
+ }
31
+ function addToSet(set, value) {
32
+ const next = new Set(set);
33
+ next.add(value);
34
+ return next;
35
+ }
36
+ function MarkdownBody({ text, className = 'md-body' }) {
37
+ return _jsx("div", { className: className, dangerouslySetInnerHTML: { __html: promptMarkdownHtml(text) } });
38
+ }
39
+ function Tooltip({ tip }) {
40
+ return (_jsx("div", { id: "insight-tip", className: "ins-tip", role: "tooltip", hidden: !tip.visible, style: { left: tip.x, top: tip.y }, children: tip.text }));
41
+ }
42
+ function MetricCard({ label, value, sub }) {
43
+ return (_jsxs("div", { className: "card", children: [_jsx("div", { className: "cv", children: value }), _jsx("div", { className: "cl", children: label }), sub ? _jsx("div", { className: "cs", children: sub }) : null] }));
44
+ }
45
+ function DisclosureNote() {
46
+ return _jsxs("p", { className: "insight-disclosure", children: ["\u2696\uFE0E ", t('insights.disclosure')] });
47
+ }
48
+ function PhaseMixBar({ phase }) {
49
+ const entries = Object.entries(phase ?? {}).filter(([, v]) => v.count > 0 || v.ms > 0);
50
+ if (!entries.length)
51
+ return null;
52
+ return (_jsx("div", { className: "mph", children: entries.map(([ph, v]) => {
53
+ const weight = Math.max(1, v.ms || v.count);
54
+ return (_jsx("i", { className: phaseClass(ph), style: { flex: weight }, title: `${phaseLabel(ph)} · ${v.count} · ${fmtMs(v.ms)}` }, ph));
55
+ }) }));
56
+ }
57
+ function InsightTabs({ active, onChange }) {
58
+ return (_jsx("div", { className: "insight-tabs", role: "tablist", children: INSIGHT_TABS.map(tb => (_jsx("button", { type: "button", className: `itab${tb.key === active ? ' on' : ''}`, role: "tab", "aria-selected": tb.key === active, onClick: () => onChange(tb.key), children: t(tb.label) }, tb.key))) }));
59
+ }
60
+ function OverviewContent({ data }) {
61
+ const a = data.agg;
62
+ const rw = a.readWriteRatio === null ? '-' : a.readWriteRatio.toFixed(1);
63
+ const topTools = data.topFailedTools.slice(0, 5);
64
+ const topSuggestions = [...data.suggestions]
65
+ .sort((x, y) => SEVERITY_RANK[x.severity] - SEVERITY_RANK[y.severity] || y.count - x.count)
66
+ .slice(0, 6);
67
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cards insights-metrics", children: [_jsx(MetricCard, { label: t('insights.metricSessions'), value: fmtInt(data.totalCount), sub: t('insights.metricAnalyzed', { count: data.analyzedCount }) }), _jsx(MetricCard, { label: t('insights.metricSpans'), value: fmtInt(a.totalSpans), sub: t('insights.metricSafe') }), _jsx(MetricCard, { label: t('insights.metricFailed'), value: fmtInt(a.failedSpans), sub: topTools[0] ? `${topTools[0].tool} ×${topTools[0].count}` : '' }), _jsx(MetricCard, { label: t('insights.metricSlow'), value: fmtInt(a.slowSpans) }), _jsx(MetricCard, { label: t('insights.metricRw'), value: rw, sub: t('insights.metricCompactions', { count: a.compactions }) })] }), _jsxs("div", { className: "insights-overview-grid", children: [_jsxs("section", { className: "block recblock", children: [_jsx("h3", { children: t('insights.recommendations') }), _jsx("div", { className: "reclist", children: topSuggestions.length ? topSuggestions.map(item => (_jsxs("div", { className: `rec ${item.severity}`, children: [_jsxs("div", { className: "rectop", children: [_jsx("b", { children: suggestionTitle(item) }), _jsx("span", { children: severityLabel(item.severity) })] }), _jsx("div", { className: "recev", children: t('insights.seenInSessions', { count: item.count }) })] }, item.id))) : _jsx("p", { className: "mut", children: t('insights.noRecommendations') }) })] }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.toolFailures') }), _jsx(HBarList, { rows: topTools.map(tt => ({ key: tt.tool, label: tt.tool, count: tt.count })), empty: t('insights.noFailures') })] })] }), _jsx(DisclosureNote, {})] }));
68
+ }
69
+ function HBarList({ rows, empty, renderValue, onRowClick }) {
70
+ if (!rows.length)
71
+ return _jsx("div", { className: "hbars", children: _jsx("p", { className: "mut", children: empty }) });
72
+ const max = Math.max(1, ...rows.map(r => r.count));
73
+ return (_jsx("div", { className: "hbars", children: rows.map(row => {
74
+ const pct = Math.max(row.count ? 4 : 0, Math.round((row.count / max) * 100));
75
+ const content = (_jsxs(_Fragment, { children: [_jsx("div", { className: "hblabel", title: row.label, children: row.label }), _jsx("div", { className: "hbtrack", children: _jsx("div", { className: "hbfill", style: { width: `${pct}%` } }) }), _jsxs("div", { className: "hbval", children: [renderValue ? renderValue(row) : fmtInt(row.count), row.sub] })] }));
76
+ return onRowClick ? (_jsx("button", { type: "button", className: "hbrow hbrow-click", onClick: () => onRowClick(row), children: content }, row.key)) : (_jsx("div", { className: "hbrow", children: content }, row.key));
77
+ }) }));
78
+ }
79
+ function Trend({ records }) {
80
+ const days = 28;
81
+ const dayMs = 86400000;
82
+ const now = Date.now();
83
+ const counts = new Array(days).fill(0);
84
+ for (const rec of records) {
85
+ const ts = Number(rec.session.lastMessageAt ?? 0);
86
+ if (!ts)
87
+ continue;
88
+ const age = Math.floor((now - ts) / dayMs);
89
+ if (age >= 0 && age < days)
90
+ counts[days - 1 - age] += 1;
91
+ }
92
+ const total = counts.reduce((a, b) => a + b, 0);
93
+ if (!total)
94
+ return null;
95
+ const max = Math.max(1, ...counts);
96
+ return (_jsxs("section", { className: "block trend-block", children: [_jsxs("div", { className: "ihist-head", children: [_jsx("h3", { children: t('insights.distTrend') }), _jsxs("span", { className: "mut", children: [t('insights.distTrendSub'), " \u00B7 ", total] })] }), _jsx("div", { className: "trend", children: counts.map((c, j) => {
97
+ const d = new Date(now - (days - 1 - j) * dayMs);
98
+ const label = `${d.getMonth() + 1}-${d.getDate()} · ${c}`;
99
+ return _jsx("i", { className: "trendbar", style: { height: `${c ? Math.max(8, Math.round((c / max) * 100)) : 2}%` }, title: label }, j);
100
+ }) })] }));
101
+ }
102
+ function Histogram({ title, values, bins, fmtMedian = fmtInt, sortKey, onSort }) {
103
+ const counts = bins.map(b => ({ label: b.label, count: values.filter(b.test).length }));
104
+ const max = Math.max(1, ...counts.map(c => c.count));
105
+ const total = Math.max(1, values.length);
106
+ return (_jsxs("section", { className: "block ihist", children: [_jsxs("div", { className: "ihist-head", children: [_jsx("h3", { children: title }), _jsx("span", { className: "mut", children: t('insights.distMedian', { v: fmtMedian(median(values)) }) }), sortKey ? _jsxs("button", { type: "button", className: "ihist-jump", onClick: () => onSort?.(sortKey), children: [t('insights.viewSessions'), " \u203A"] }) : null] }), _jsx("div", { className: "hbars", children: counts.map(c => {
107
+ const pct = Math.max(c.count ? 4 : 0, Math.round((c.count / max) * 100));
108
+ const share = Math.round((c.count / total) * 100);
109
+ return (_jsxs("div", { className: "hbrow", children: [_jsx("div", { className: "hblabel", children: c.label }), _jsx("div", { className: "hbtrack", children: _jsx("div", { className: "hbfill", style: { width: `${pct}%` } }) }), _jsxs("div", { className: "hbval", children: [fmtInt(c.count), _jsxs("small", { children: [share, "%"] })] })] }, c.label));
110
+ }) })] }));
111
+ }
112
+ function Distribution({ records, onSort }) {
113
+ const reports = okReports(records);
114
+ if (!reports.length)
115
+ return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
116
+ const churn = reports.map(r => (r.hot?.files ?? []).reduce((s, f) => s + (f.added ?? 0) + (f.removed ?? 0), 0));
117
+ const spans = reports.map(r => r.agg.totalSpans);
118
+ const failed = reports.map(r => r.agg.failedSpans);
119
+ const slow = reports.map(r => r.agg.slowSpans);
120
+ const agentMin = reports.map(r => agentMsOf(r) / 60000);
121
+ const rw = reports.map(r => r.agg.readWriteRatio).filter((v) => v !== null && Number.isFinite(v));
122
+ return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.distHint') }), _jsxs("div", { className: "ihist-grid", children: [_jsx(Trend, { records: records }), _jsx(Histogram, { title: t('insights.distSpans'), values: spans, onSort: onSort, sortKey: "spans", bins: [
123
+ { label: '0-10', test: v => v <= 10 },
124
+ { label: '11-50', test: v => v > 10 && v <= 50 },
125
+ { label: '51-200', test: v => v > 50 && v <= 200 },
126
+ { label: '201-500', test: v => v > 200 && v <= 500 },
127
+ { label: '500+', test: v => v > 500 },
128
+ ] }), _jsx(Histogram, { title: t('insights.distFailed'), values: failed, onSort: onSort, sortKey: "fails", bins: [
129
+ { label: '0', test: v => v === 0 },
130
+ { label: '1-2', test: v => v >= 1 && v <= 2 },
131
+ { label: '3-5', test: v => v >= 3 && v <= 5 },
132
+ { label: '6-10', test: v => v >= 6 && v <= 10 },
133
+ { label: '10+', test: v => v > 10 },
134
+ ] }), _jsx(Histogram, { title: t('insights.distSlow'), values: slow, onSort: onSort, sortKey: "slow", bins: [
135
+ { label: '0', test: v => v === 0 },
136
+ { label: '1-2', test: v => v >= 1 && v <= 2 },
137
+ { label: '3-5', test: v => v >= 3 && v <= 5 },
138
+ { label: '5+', test: v => v > 5 },
139
+ ] }), _jsx(Histogram, { title: t('insights.distAgentTime'), values: agentMin, onSort: onSort, sortKey: "agent", fmtMedian: n => `${Math.round(n)}m`, bins: [
140
+ { label: '<1m', test: v => v < 1 },
141
+ { label: '1-5m', test: v => v >= 1 && v < 5 },
142
+ { label: '5-30m', test: v => v >= 5 && v < 30 },
143
+ { label: '30m-2h', test: v => v >= 30 && v < 120 },
144
+ { label: '2h+', test: v => v >= 120 },
145
+ ] }), rw.length ? (_jsx(Histogram, { title: t('insights.distRw'), values: rw, fmtMedian: n => n.toFixed(1), bins: [
146
+ { label: '0', test: v => v === 0 },
147
+ { label: '0-1', test: v => v > 0 && v < 1 },
148
+ { label: '1-3', test: v => v >= 1 && v < 3 },
149
+ { label: '3+', test: v => v >= 3 },
150
+ ] })) : null, _jsx(Histogram, { title: t('insights.distChurn'), values: churn, bins: [
151
+ { label: '0', test: v => v === 0 },
152
+ { label: '1-100', test: v => v > 0 && v <= 100 },
153
+ { label: '100-1k', test: v => v > 100 && v <= 1000 },
154
+ { label: '1k-10k', test: v => v > 1000 && v <= 10000 },
155
+ { label: '10k+', test: v => v > 10000 },
156
+ ] })] }), _jsx(DisclosureNote, {})] }));
157
+ }
158
+ function Flow({ records, onSession }) {
159
+ const ok = records.filter(r => r.report && r.report.status === 'ok' && r.report.agg);
160
+ if (!ok.length)
161
+ return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
162
+ const tot = {};
163
+ for (const p of FLOW_PHASES)
164
+ tot[p] = { count: 0, ms: 0 };
165
+ for (const rec of ok)
166
+ for (const p of FLOW_PHASES) {
167
+ const v = rec.report.agg.phase?.[p];
168
+ if (v) {
169
+ tot[p].count += v.count;
170
+ tot[p].ms += v.ms;
171
+ }
172
+ }
173
+ const totCount = FLOW_PHASES.reduce((s, p) => s + tot[p].count, 0) || 1;
174
+ const totMs = FLOW_PHASES.reduce((s, p) => s + tot[p].ms, 0) || 1;
175
+ const rhythm = [...ok].sort((a, b) => agentMsOf(b.report) - agentMsOf(a.report)).slice(0, 40);
176
+ return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.flowHint') }), _jsxs("section", { className: "block flow-pipe-block", children: [_jsx("h3", { children: t('insights.flowPipeline') }), _jsx("p", { className: "mut flow-sub", children: t('insights.flowPipeSub') }), _jsx("div", { className: "flow-pipe", children: FLOW_PHASES.map((p, i) => {
177
+ const v = tot[p];
178
+ const cPct = Math.round((v.count / totCount) * 100);
179
+ const tPct = Math.round((v.ms / totMs) * 100);
180
+ return (_jsxs("span", { style: { display: 'contents' }, children: [_jsxs("div", { className: "flow-node", children: [_jsx("i", { className: `flow-dot ${phaseClass(p)}` }), _jsx("strong", { children: phaseLabel(p) }), _jsxs("span", { className: "flow-n", children: [fmtInt(v.count), _jsxs("em", { children: [" \u00B7 ", cPct, "%"] })] }), _jsxs("span", { className: "flow-t", children: [fmtMs(v.ms), _jsxs("em", { children: [" \u00B7 ", tPct, "%"] })] })] }), i < FLOW_PHASES.length - 1 ? _jsx("span", { className: "flow-arrow", children: "\u2192" }) : null] }, p));
181
+ }) })] }), _jsxs("div", { className: "insights-overview-grid", children: [_jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.flowShares') }), _jsxs("div", { className: "flow-bhead", children: [_jsx("span", {}), _jsx("span", { children: t('insights.flowActShare') }), _jsx("span", { children: t('insights.flowTimeShare') })] }), _jsx("div", { className: "flow-bars", children: FLOW_PHASES.map(p => {
182
+ const v = tot[p];
183
+ const cPct = (v.count / totCount) * 100;
184
+ const tPct = (v.ms / totMs) * 100;
185
+ return (_jsxs("div", { className: "flow-brow", children: [_jsxs("span", { className: "flow-blabel", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }), _jsxs("span", { className: "flow-btrack", children: [_jsx("span", { className: `flow-bfill ${phaseClass(p)}`, style: { width: `${Math.max(2, cPct).toFixed(1)}%` } }), _jsxs("em", { children: [Math.round(cPct), "%"] })] }), _jsxs("span", { className: "flow-btrack", children: [_jsx("span", { className: `flow-bfill ${phaseClass(p)}`, style: { width: `${Math.max(2, tPct).toFixed(1)}%` } }), _jsxs("em", { children: [Math.round(tPct), "%"] })] })] }, p));
186
+ }) })] }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.flowRhythm') }), _jsx("div", { className: "flow-rhythm", children: rhythm.map(rec => (_jsxs("button", { type: "button", className: "flow-sess", onClick: () => onSession(String(rec.session.sessionId)), children: [_jsx("span", { className: "flow-stitle", children: sessionTitle(rec.session) }), _jsx(PhaseMixBar, { phase: rec.report.agg.phase }), _jsx("span", { className: "flow-stime", children: fmtMs(agentMsOf(rec.report)) })] }, String(rec.session.sessionId)))) }), _jsx("div", { className: "rl-legend", children: FLOW_PHASES.map(p => _jsxs("span", { className: "rl-item", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }, p)) })] })] }), _jsx(DisclosureNote, {})] }));
187
+ }
188
+ function Hotspots({ records, openHot, onToggleHot, onProject, onSession }) {
189
+ const reports = records.filter(r => !!r.report && r.report.status === 'ok');
190
+ if (!reports.length)
191
+ return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
192
+ const fileAgg = new Map();
193
+ const cmdAgg = new Map();
194
+ const errAgg = new Map();
195
+ const bump = (m, key, label, sid, title) => {
196
+ let h = m.get(key);
197
+ if (!h) {
198
+ h = { key, label, sessions: [], reads: 0, edits: 0, runs: 0, fails: 0, count: 0 };
199
+ m.set(key, h);
200
+ }
201
+ if (!h.sessions.some(s => s.id === sid))
202
+ h.sessions.push({ id: sid, title });
203
+ return h;
204
+ };
205
+ for (const rec of reports) {
206
+ const sid = String(rec.session.sessionId);
207
+ const title = sessionTitle(rec.session);
208
+ const hot = rec.report.hot;
209
+ for (const f of hot?.files ?? []) {
210
+ const h = bump(fileAgg, `file:${f.path}`, f.path, sid, title);
211
+ h.reads += f.reads;
212
+ h.edits += f.edits;
213
+ }
214
+ for (const c of hot?.cmds ?? []) {
215
+ const h = bump(cmdAgg, `cmd:${c.cmd}`, c.cmd, sid, title);
216
+ h.runs += c.runs;
217
+ h.fails += c.fails;
218
+ }
219
+ for (const e of hot?.errs ?? []) {
220
+ const h = bump(errAgg, `err:${e.tool} ${e.result}`, `${e.tool} · ${resultLabel(e.result)}`, sid, title);
221
+ h.count += e.count;
222
+ }
223
+ }
224
+ const recur = (a, b) => b.sessions.length - a.sessions.length;
225
+ const files = [...fileAgg.values()].sort((a, b) => recur(a, b) || (b.edits + b.reads) - (a.edits + a.reads)).slice(0, 12);
226
+ const cmds = [...cmdAgg.values()].sort((a, b) => recur(a, b) || b.fails - a.fails || b.runs - a.runs).slice(0, 12);
227
+ const errs = [...errAgg.values()].sort((a, b) => recur(a, b) || b.count - a.count).slice(0, 10);
228
+ const projMap = new Map();
229
+ for (const rec of reports) {
230
+ const p = projectOf(rec);
231
+ if (!p)
232
+ continue;
233
+ const e = projMap.get(p) ?? { sessions: 0, fails: 0 };
234
+ e.sessions += 1;
235
+ e.fails += rec.report.agg.failedSpans;
236
+ projMap.set(p, e);
237
+ }
238
+ const projects = [...projMap.entries()].map(([id, v]) => ({ id, ...v })).sort((a, b) => b.fails - a.fails || b.sessions - a.sessions).slice(0, 10);
239
+ const projRows = projects.map(x => ({ key: x.id, label: x.id, count: x.sessions, sub: _jsxs("small", { children: [x.fails, " ", t('insights.hotFailsCol')] }) }));
240
+ const slowSessions = [...reports].filter(r => r.report.agg.slowSpans > 0).sort((a, b) => b.report.agg.slowSpans - a.report.agg.slowSpans).slice(0, 8);
241
+ return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.hotHint') }), _jsxs("div", { className: "hot-grid", children: [_jsx(HotBlock, { title: t('insights.hotFiles'), rows: files, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => `${t('insights.readsShort')}${h.reads} · ${t('insights.editsShort')}${h.edits}` }), _jsx(HotBlock, { title: t('insights.hotCommands'), rows: cmds, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => _jsxs(_Fragment, { children: [h.runs, "\u00D7", h.fails ? _jsxs(_Fragment, { children: [" \u00B7 ", _jsxs("span", { className: "bad", children: [h.fails, " ", t('insights.hotFailsCol')] })] }) : null] }) }), _jsx(HotBlock, { title: t('insights.hotErrors'), rows: errs, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => `${h.count}×` }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.hotProjects') }), _jsx(HBarList, { rows: projRows, empty: "-", onRowClick: row => onProject(row.key), renderValue: row => fmtInt(row.count) })] }), _jsxs("section", { className: "block hot-sessions", children: [_jsx("h3", { children: t('insights.hotSlowSessions') }), _jsx("div", { className: "slist", children: slowSessions.length ? slowSessions.map(rec => {
242
+ const a = rec.report.agg;
243
+ return (_jsxs("button", { type: "button", className: "srow", onClick: () => onSession(String(rec.session.sessionId)), children: [_jsxs("div", { className: "srmain", children: [_jsx("strong", { children: sessionTitle(rec.session) }), _jsxs("small", { children: [botDisplayName(rec.session), " \u00B7 ", String(rec.session.cliId ?? '-')] })] }), _jsxs("div", { className: "srstats", children: [_jsxs("b", { children: [t('insights.hotSlowCol'), _jsx("em", { children: fmtInt(a.slowSpans) })] }), _jsxs("b", { children: ["span", _jsx("em", { children: fmtInt(a.totalSpans) })] })] })] }, String(rec.session.sessionId)));
244
+ }) : _jsx("p", { className: "mut", children: "-" }) })] })] })] }));
245
+ }
246
+ function HotBlock({ title, rows, openHot, onToggle, onSession, meta }) {
247
+ return (_jsxs("section", { className: "block", children: [_jsx("h3", { children: title }), _jsx("div", { className: "hotlist", children: rows.length ? rows.map(h => {
248
+ const open = openHot.has(h.key);
249
+ return (_jsxs("div", { className: `hotitem${open ? ' open' : ''}`, children: [_jsxs("button", { type: "button", className: "hotrow", "aria-expanded": open, onClick: () => onToggle(h.key), children: [_jsx("span", { className: "hotlabel", title: h.label, children: h.label }), _jsx("span", { className: "hotmeta", children: meta(h) }), _jsxs("span", { className: "hotses", children: [h.sessions.length, " ", t('insights.hotSessionsCol')] })] }), open ? (_jsx("div", { className: "hot-sess", children: h.sessions.map(s => _jsx("button", { type: "button", className: "hot-sesslink", onClick: () => onSession(s.id), children: s.title || s.id }, s.id)) })) : null] }, h.key));
250
+ }) : _jsx("p", { className: "mut", children: "-" }) })] }));
251
+ }
252
+ function SortBar({ sort, layout, onSort, onLayout }) {
253
+ const sortOptions = SESS_SORTS.map(s => ({ value: s.key, label: t(s.label) }));
254
+ const sortLabel = sortOptions.find(option => option.value === sort)?.label ?? t('insights.sortLabel');
255
+ return (_jsxs(_Fragment, { children: [_jsx(DropdownMenu, { id: "insight-session-sort", className: "insight-sort-menu", ariaLabel: t('insights.sortLabel'), label: sortLabel, value: sort, options: sortOptions, onChange: onSort }), _jsx("div", { className: "insight-layout-switch", role: "group", "aria-label": `${t('insights.layoutCard')} / ${t('insights.layoutTable')}`, children: [
256
+ ['card', 'insights.layoutCard'],
257
+ ['table', 'insights.layoutTable'],
258
+ ].map(([k, lbl]) => (_jsx("button", { type: "button", className: layout === k ? 'active' : '', "aria-pressed": layout === k, onClick: () => onLayout(k), children: t(lbl) }, k))) })] }));
259
+ }
260
+ function SessionRows({ records, selectedId, wide, onSelect, emptyHint }) {
261
+ if (!records.length) {
262
+ return (_jsxs("div", { className: "insight-empty", children: [t('insights.empty'), emptyHint ? _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "mut", children: emptyHint })] }) : null] }));
263
+ }
264
+ const stat = (label, val, bad = false) => _jsxs("b", { className: bad ? 'bad' : undefined, children: [label, _jsx("em", { children: val })] });
265
+ return (_jsx("div", { className: `slist${wide ? ' wide' : ''}`, children: records.map(rec => {
266
+ const s = rec.session;
267
+ const r = rec.report;
268
+ const ok = r?.status === 'ok';
269
+ const agg = r?.agg;
270
+ const reads = agg?.phase?.research?.count ?? 0;
271
+ const edits = agg?.phase?.edit?.count ?? 0;
272
+ return (_jsxs("button", { type: "button", className: `srow${s.sessionId === selectedId ? ' on' : ''}${reportNeedsReview(r) ? ' review' : ''}`, onClick: () => onSelect(String(s.sessionId)), children: [_jsxs("div", { className: "srmain", children: [_jsx("strong", { children: sessionTitle(s) }), _jsx("small", { children: sessionMetaLine(rec) }), ok ? _jsx(PhaseMixBar, { phase: r.agg.phase }) : null] }), ok ? (_jsxs("div", { className: "srstats", children: [stat(t('insights.spansShort'), fmtInt(agg.totalSpans)), stat(t('insights.failedShort'), fmtInt(agg.failedSpans), agg.failedSpans > 0), stat(t('insights.slowShort'), fmtInt(agg.slowSpans)), wide ? _jsxs(_Fragment, { children: [stat(t('insights.readsShort'), fmtInt(reads)), stat(t('insights.editsShort'), fmtInt(edits)), stat(t('insights.durShort'), fmtMs(agentMsOf(r)))] }) : null, stat(t('insights.rwShort'), agg.readWriteRatio !== null ? agg.readWriteRatio.toFixed(1) : '-')] })) : _jsx("div", { className: "srmsg", children: safeStatus(r, rec.error) })] }, String(s.sessionId)));
273
+ }) }));
274
+ }
275
+ function SessionTable({ records, selectedId, onSelect, emptyHint }) {
276
+ if (!records.length) {
277
+ return (_jsxs("div", { className: "insight-empty", children: [t('insights.empty'), emptyHint ? _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "mut", children: emptyHint })] }) : null] }));
278
+ }
279
+ return (_jsxs("div", { className: "stable", children: [_jsxs("div", { className: "strow sthead", children: [_jsx("span", { className: "stc-title", children: t('insights.colTitle') }), _jsx("span", { className: "stc-proj", children: t('insights.colProject') }), _jsx("span", { className: "stc-num", children: t('insights.spansShort') }), _jsx("span", { className: "stc-num", children: t('insights.failedShort') }), _jsx("span", { className: "stc-num", children: t('insights.slowShort') }), _jsx("span", { className: "stc-num", children: t('insights.rwShort') }), _jsx("span", { className: "stc-num", children: t('insights.durShort') }), _jsx("span", { className: "stc-num", children: t('insights.colTime') })] }), records.map(rec => {
280
+ const s = rec.session;
281
+ const r = rec.report;
282
+ const ok = r?.status === 'ok';
283
+ const agg = r?.agg;
284
+ return (_jsxs("button", { type: "button", className: `strow${s.sessionId === selectedId ? ' on' : ''}${reportNeedsReview(r) ? ' review' : ''}${ok ? '' : ' nostat'}`, onClick: () => onSelect(String(s.sessionId)), children: [_jsxs("span", { className: "stc-title", children: [_jsx("strong", { children: sessionTitle(s) }), _jsxs("small", { children: [botDisplayName(s), " \u00B7 ", String(s.cliId ?? '-')] })] }), _jsx("span", { className: "stc-proj", children: s.workingDir ? projectOf(rec) : '-' }), ok ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "stc-num", children: fmtInt(agg.totalSpans) }), _jsx("span", { className: `stc-num${agg.failedSpans ? ' bad' : ''}`, children: fmtInt(agg.failedSpans) }), _jsx("span", { className: "stc-num", children: fmtInt(agg.slowSpans) }), _jsx("span", { className: "stc-num", children: agg.readWriteRatio !== null ? agg.readWriteRatio.toFixed(1) : '-' }), _jsx("span", { className: "stc-num", children: fmtMs(agentMsOf(r)) })] })) : _jsx("span", { className: "stc-msg", children: safeStatus(r, rec.error) }), _jsx("span", { className: "stc-num stc-time", children: relTime(s.lastMessageAt ?? s.spawnedAt ?? 0) })] }, String(s.sessionId)));
285
+ })] }));
286
+ }
287
+ function TextPreview({ label, preview }) {
288
+ if (!preview?.text)
289
+ return null;
290
+ return (_jsxs("div", { className: "span-io", children: [_jsx("span", { className: "span-io-label", children: label }), _jsxs("pre", { className: "span-io-text", children: [preview.text, preview.truncated ? '\n…' : ''] })] }));
291
+ }
292
+ function OpGlyph({ span, current }) {
293
+ const title = `${intentText(span)}${span.intent?.subject ? ` ${span.intent.subject}` : ''}${span.result ? ` → ${resultLabel(span.result.category)}` : ''} · ${fmtMs(span.durationMs)}`;
294
+ return _jsx("i", { className: `op ph-${phaseSlug(span.phase)}${spanFailed(span) ? ' bad' : ''}${current ? ' cur' : ''}`, title: title });
295
+ }
296
+ function SpanDetail({ spans, idx }) {
297
+ const s = spans[idx];
298
+ if (!s)
299
+ return null;
300
+ const d = s.detail;
301
+ const ev = d?.evidence ?? s.evidence;
302
+ const kv = [
303
+ [t('insights.dPhase'), phaseLabel(s.phase)],
304
+ [t('insights.dStart'), fmtMs(s.relStartMs)],
305
+ [t('insights.dDur'), fmtMs(s.durationMs)],
306
+ ];
307
+ const intent = intentPhrase(s.intent);
308
+ if (intent)
309
+ kv.push([t('insights.dIntent'), intent]);
310
+ if (s.result)
311
+ kv.push([t('insights.dResult'), `${resultLabel(s.result.category)}${s.result.exitCode !== undefined ? ` · exit ${s.result.exitCode}` : ''}`]);
312
+ if (s.inputSummary)
313
+ kv.push([t('insights.dIn'), structLabel(s.inputSummary)]);
314
+ if (s.outputSummary)
315
+ kv.push([t('insights.dOut'), structLabel(s.outputSummary)]);
316
+ const tags = (s.tags ?? []).filter(tg => tg !== 'normal');
317
+ if (tags.length)
318
+ kv.push([t('insights.dTags'), tags.map(tagLabel).join('、')]);
319
+ const prev = d?.context?.previousIntent ? intentPhrase(d.context.previousIntent) : '';
320
+ const next = d?.context?.nextIntent ? intentPhrase(d.context.nextIntent) : '';
321
+ const sibs = spans.map((sp, i) => ({ sp, i })).filter(x => x.sp.turnIndex === s.turnIndex).sort((a, b) => (a.sp.relStartMs ?? 0) - (b.sp.relStartMs ?? 0));
322
+ return (_jsxs("div", { className: "spandetail", children: [_jsx("dl", { className: "span-kv", children: kv.map(([k, v]) => _jsxs("div", { children: [_jsx("dt", { children: k }), _jsx("dd", { children: v })] }, k)) }), _jsx(TextPreview, { label: t('insights.dCommand'), preview: ev?.command }), _jsx(TextPreview, { label: t('insights.dCmdOutput'), preview: ev?.output }), prev || next ? _jsxs("div", { className: "span-flank", children: [prev ? _jsxs("span", { className: "sf-prev", children: ["\u2191 ", prev] }) : null, next ? _jsxs("span", { className: "sf-next", children: ["\u2193 ", next] }) : null] }) : null, _jsxs("div", { className: "span-ctx", children: [_jsx("span", { className: "span-ctx-label", children: t('insights.dTurnContext', { turn: s.turnIndex }) }), _jsx("div", { className: "opstrip", children: sibs.map(x => _jsx(OpGlyph, { span: x.sp, current: x.i === idx }, x.i)) })] })] }));
323
+ }
324
+ function SpanRow({ spans, idx, hot, open, detailable = true, onToggle, scrollTarget, onScrolled }) {
325
+ const ref = useRef(null);
326
+ const s = spans[idx];
327
+ useEffect(() => {
328
+ if (!ref.current || scrollTarget?.kind !== 'span' || scrollTarget.index !== idx)
329
+ return;
330
+ ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
331
+ onScrolled?.();
332
+ }, [idx, onScrolled, scrollTarget]);
333
+ if (!s)
334
+ return null;
335
+ const subject = s.intent?.subject ? _jsx("code", { className: "span-subj", children: s.intent.subject }) : null;
336
+ const res = s.result;
337
+ const tags = (s.tags ?? []).filter(tg => tg !== 'normal' && tg !== 'diagnostic');
338
+ const toggle = () => detailable && onToggle?.(idx);
339
+ return (_jsxs("div", { ref: ref, className: `spanrow ph-${phaseSlug(s.phase)}${s.status === 'error' ? ' error' : ''}${hot ? ' hot' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: `sprow-line${detailable ? ' clickable' : ''}`, role: detailable ? 'button' : undefined, tabIndex: detailable ? 0 : undefined, "aria-expanded": detailable ? open : undefined, onClick: toggle, onKeyDown: event => {
340
+ if (!detailable)
341
+ return;
342
+ if (event.key === 'Enter' || event.key === ' ') {
343
+ event.preventDefault();
344
+ toggle();
345
+ }
346
+ }, children: [_jsxs("span", { className: "span-turn", title: `${t('insights.dStart')} ${fmtMs(s.relStartMs)}`, children: ["#", String(s.turnIndex ?? 0)] }), _jsx("span", { className: `spanst ${s.status}`, children: statusIcon(s.status) }), _jsx("b", { className: "span-what", children: intentText(s) }), subject, res && BAD_RESULTS.has(res.category) ? _jsxs("span", { className: "span-res rc-bad", children: [resultLabel(res.category), res.exitCode !== undefined ? ` · exit ${res.exitCode}` : ''] }) : null, _jsx("span", { className: "span-tags", children: tags.map(tg => _jsx("span", { className: `span-tag tg-${tg}`, children: tagLabel(tg) }, tg)) }), _jsx("span", { className: "span-dur", children: fmtMs(s.durationMs) }), detailable ? _jsx("span", { className: "span-detail-btn", "aria-hidden": "true", children: open ? t('insights.dCollapse') : t('insights.dDetail') }) : null] }), detailable && open ? _jsx(SpanDetail, { spans: spans, idx: idx }) : null] }));
347
+ }
348
+ function WorkSummary({ report }) {
349
+ const ws = report.workSummary;
350
+ if (!ws || (!ws.fileChanges?.length && !ws.commandsRun?.length))
351
+ return null;
352
+ const files = ws.fileChanges ?? [];
353
+ const cmds = ws.commandsRun ?? [];
354
+ return (_jsxs("div", { className: "worksum", children: [_jsxs("section", { className: "ws-panel", children: [_jsx("h4", { children: t('insights.wsFiles', { n: files.length }) }), _jsx("div", { className: "ws-list", children: files.length ? files.map((f, i) => (_jsxs("div", { className: "ws-row", children: [_jsx("code", { className: "ws-path", title: f.path, children: f.path }), _jsx("span", { className: "ws-meta", children: t('insights.wsReads', { n: f.reads }) }), (f.added || f.removed) ? _jsxs("span", { className: "ws-stat", children: [_jsxs("span", { className: "ws-add", children: ["+", f.added ?? 0] }), _jsxs("span", { className: "ws-del", children: ["-", f.removed ?? 0] })] }) : f.edits ? _jsx("span", { className: "ws-stat ws-stat-edits", children: t('insights.wsEdits', { n: f.edits }) }) : null] }, `${f.path}:${i}`))) : _jsx("p", { className: "mut", children: t('insights.wsNoFiles') }) })] }), _jsxs("section", { className: "ws-panel", children: [_jsx("h4", { children: t('insights.wsCmds', { n: cmds.length }) }), _jsx("div", { className: "ws-list", children: cmds.length ? cmds.map((c, i) => {
355
+ const bad = c.failures > 0;
356
+ return (_jsxs("div", { className: `ws-row${bad ? ' bad' : ''}`, children: [_jsxs("code", { className: "ws-cmd", title: c.command.text, children: [c.command.text, c.command.truncated ? '…' : ''] }), _jsxs("span", { className: "ws-meta", children: [c.count > 1 ? _jsxs("span", { className: "ws-x", children: ["\u00D7", c.count] }) : null, bad ? _jsx("span", { className: "ws-fail", children: t('insights.wsFail', { n: c.failures }) }) : null] })] }, `${c.command.text}:${i}`));
357
+ }) : _jsx("p", { className: "mut", children: t('insights.wsNoCmds') }) })] })] }));
358
+ }
359
+ function Evidence({ report, focus, view, onSpanFilter, onToggleSpan, scrollTarget, onScrolled }) {
360
+ const spans = report.spans ?? [];
361
+ if (!spans.length) {
362
+ return _jsxs(_Fragment, { children: [_jsx(WorkSummary, { report: report }), _jsx("p", { className: "mut", children: t('insights.noSpans') })] });
363
+ }
364
+ const order = [...spans.keys()].sort((a, b) => (spans[a].relStartMs ?? 0) - (spans[b].relStartMs ?? 0));
365
+ const counts = new Map();
366
+ for (const i of order)
367
+ for (const tg of spans[i].tags ?? [])
368
+ if (SPAN_TAGS.includes(tg))
369
+ counts.set(tg, (counts.get(tg) ?? 0) + 1);
370
+ const visible = view.spanFilter === 'all' ? order : order.filter(i => spans[i].tags?.includes(view.spanFilter));
371
+ return (_jsxs("div", { className: "evidence", children: [_jsx(WorkSummary, { report: report }), focus.rec ? _jsx("div", { className: "ev-reason", children: idText('why', focus.rec.why) }) : null, _jsxs("div", { className: "spanfilter", children: [_jsxs("button", { type: "button", className: `spanchip${view.spanFilter === 'all' ? ' on' : ''}`, onClick: () => onSpanFilter('all'), children: [t('insights.spanAll'), " ", _jsx("b", { children: order.length })] }), SPAN_TAGS.filter(tg => counts.has(tg)).map(tg => (_jsxs("button", { type: "button", className: `spanchip tg-${tg}${view.spanFilter === tg ? ' on' : ''}`, onClick: () => onSpanFilter(tg), children: [tagLabel(tg), " ", _jsx("b", { children: counts.get(tg) })] }, tg)))] }), _jsx("div", { className: "spantable", children: visible.length ? visible.map(i => (_jsx(SpanRow, { spans: spans, idx: i, hot: focus.spanIdx.has(i), open: view.openSpans.has(i), onToggle: onToggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }, i))) : _jsx("p", { className: "mut", children: t('insights.evNoFlags') }) })] }));
372
+ }
373
+ function PromptSourceChip({ src, fallback }) {
374
+ if (!src?.kind)
375
+ return _jsx("span", { className: "tp-label", children: fallback ?? t('insights.turnPrompt') });
376
+ if (src.kind === 'a2a_agent') {
377
+ const name = src.agentName || src.senderName;
378
+ return _jsxs("span", { className: "tp-label tp-src tp-src-a2a", children: ["\uD83E\uDD1D ", name ? `${name} · a2a` : 'a2a'] });
379
+ }
380
+ if (src.kind === 'system')
381
+ return _jsxs("span", { className: "tp-label tp-src tp-src-system", children: ["\u2699\uFE0F ", t('insights.senderSystem')] });
382
+ return _jsxs("span", { className: "tp-label tp-src tp-src-user", children: ["\uD83D\uDC64 ", src.senderName || fallback || t('insights.turnPrompt')] });
383
+ }
384
+ function PromptMentions({ src }) {
385
+ const mentions = (src?.mentionedNames ?? []).filter(Boolean);
386
+ if (!mentions.length)
387
+ return null;
388
+ return _jsxs("span", { className: "tp-mentions", children: [t('insights.srcMentions'), " ", mentions.map(n => `@${n}`).join(' ')] });
389
+ }
390
+ function TurnEventGlyph({ event }) {
391
+ const bad = event.status === 'error' || (!!event.result && BAD_RESULTS.has(event.result.category));
392
+ const what = intentTextOf(event.intent, String(event.label.params.tool ?? event.kind));
393
+ const title = `${what}${event.intent?.subject ? ` ${event.intent.subject}` : ''}${event.result ? ` → ${resultLabel(event.result.category)}` : ''} · ${fmtMs(event.durationMs)}`;
394
+ return _jsx("i", { className: `op ph-${phaseSlug(event.phase)}${bad ? ' bad' : ''}`, title: title });
395
+ }
396
+ function TurnCard({ report, turn, focus, view, onToggleTurn, onToggleSpan, onTogglePrompt, onTogglePromptRaw, onOpenPrompt, scrollTarget, onScrolled }) {
397
+ const ref = useRef(null);
398
+ const spans = report.spans ?? [];
399
+ const open = view.openTurns.has(turn.turnIndex);
400
+ const hot = focus.turnIdx.has(turn.turnIndex);
401
+ const m = turn.metrics;
402
+ const advice = turn.severity !== 'info' ? turnAdvice(turn.tags) : '';
403
+ const ptext = turn.prompt?.text ? (cleanPromptText(turn.prompt.text) || turn.prompt.text) : '';
404
+ const promptExpanded = view.openPrompts.has(turn.turnIndex);
405
+ const promptRaw = view.rawPrompts.has(turn.turnIndex);
406
+ const promptBody = promptRaw
407
+ ? _jsxs("pre", { className: "tp-raw", children: [ptext, turn.prompt?.truncated ? '\n…' : ''] })
408
+ : _jsx(MarkdownBody, { text: ptext + (turn.prompt?.truncated ? ' …' : '') });
409
+ const mini = `${t('insights.mEdits')}${m.edits} ${t('insights.mRuns')}${m.runs}${m.failures ? ` · ${t('insights.mFailures')}${m.failures}` : ''} · ${fmtMs(m.durationMs)}`;
410
+ useEffect(() => {
411
+ if (!ref.current || scrollTarget?.kind !== 'turn' || scrollTarget.index !== turn.turnIndex)
412
+ return;
413
+ ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
414
+ onScrolled();
415
+ }, [onScrolled, scrollTarget, turn.turnIndex]);
416
+ const pill = (label, val, bad = false) => _jsxs("span", { className: `tm${bad ? ' bad' : ''}`, children: [_jsx("i", { children: label }), _jsx("b", { children: val })] });
417
+ return (_jsxs("div", { ref: ref, className: `turnrow sev-${turn.severity}${hot ? ' hot' : ''}${turn.severity !== 'info' ? ' flagged' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: "turnline", children: [_jsxs("span", { className: "turn-no", children: ["#", String(turn.turnIndex)] }), _jsx("b", { className: "turn-headline", children: turnHeadline(turn.headline) }), _jsx("div", { className: "opstrip turn-strip", children: turn.events.map((e, i) => _jsx(TurnEventGlyph, { event: e }, `${e.spanIndex}:${i}`)) }), _jsx("span", { className: "turn-mini", children: mini }), _jsx("button", { type: "button", className: "turn-expand-btn", "aria-expanded": open, onClick: () => onToggleTurn(turn.turnIndex), children: open ? t('insights.turnCollapse') : t('insights.turnExpand', { count: turn.events.length }) })] }), ptext ? (_jsxs("div", { className: "turn-prompt", children: [_jsx(PromptSourceChip, { src: turn.prompt?.source }), _jsxs("div", { className: "tp-body", children: [_jsx("div", { className: `tp-md${promptExpanded ? ' expanded' : ''}`, children: promptBody }), _jsxs("div", { className: "tp-actions", children: [_jsx(PromptMentions, { src: turn.prompt?.source }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onTogglePrompt(turn.turnIndex), children: promptExpanded ? t('insights.turnPromptCollapse') : t('insights.turnPromptExpand') }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onTogglePromptRaw(turn.turnIndex), children: promptRaw ? t('insights.turnPromptRendered') : t('insights.turnPromptRaw') }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onOpenPrompt(turn.turnIndex), children: t('insights.turnPromptFull') })] })] })] })) : null, advice ? _jsx("div", { className: "turn-advice", children: advice }) : null, open ? (_jsxs("div", { className: "turn-detail", children: [_jsxs("div", { className: "turn-metrics", children: [pill(t('insights.mReads'), String(m.reads)), pill(t('insights.mEdits'), String(m.edits)), pill(t('insights.mRuns'), String(m.runs)), m.failures ? pill(t('insights.mFailures'), String(m.failures), true) : null, pill(t('insights.mDur'), fmtMs(m.durationMs))] }), _jsx("div", { className: "spantable", children: turn.events.map(e => (_jsx(SpanRow, { spans: spans, idx: e.spanIndex, hot: false, open: view.openSpans.has(e.spanIndex), onToggle: onToggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }, e.spanIndex))) })] })) : null] }));
418
+ }
419
+ function TurnEfficiency({ report, focus, view, onLedgerSort, onLedgerSender, onToggleTurn, onToggleSpan, onTogglePrompt, onTogglePromptRaw, onOpenPrompt, scrollTarget, onScrolled }) {
420
+ const spans = report.spans ?? [];
421
+ const allTurns = report.turnTimeline ?? [];
422
+ if (!allTurns.length)
423
+ return _jsx("p", { className: "mut", children: t('insights.noSpans') });
424
+ const recs = report.recommendations ?? [];
425
+ const canGroup = recs.some(r => (r.evidence?.turnIndexes?.length ?? 0) > 0);
426
+ const senderCount = { user: 0, a2a_agent: 0, system: 0 };
427
+ for (const tn of allTurns)
428
+ senderCount[turnSenderKind(tn)] += 1;
429
+ const turns = view.ledgerSender === 'all'
430
+ ? allTurns.filter(tn => turnSenderKind(tn) !== 'system')
431
+ : allTurns.filter(tn => turnSenderKind(tn) === view.ledgerSender);
432
+ const flagged = turns.filter(tn => tn.severity !== 'info').length;
433
+ const card = (tn) => (_jsx(TurnCard, { report: report, turn: tn, focus: focus, view: view, onToggleTurn: onToggleTurn, onToggleSpan: onToggleSpan, onTogglePrompt: onTogglePrompt, onTogglePromptRaw: onTogglePromptRaw, onOpenPrompt: onOpenPrompt, scrollTarget: scrollTarget, onScrolled: onScrolled }, tn.turnIndex));
434
+ const controls = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "spanfilter ledgersender", children: [_jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'all' ? ' on' : ''}`, onClick: () => onLedgerSender('all'), children: [t('insights.spanAll'), " ", _jsx("b", { children: senderCount.user + senderCount.a2a_agent })] }), senderCount.user ? _jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'user' ? ' on' : ''}`, onClick: () => onLedgerSender('user'), children: ["\uD83D\uDC64 ", t('insights.senderHuman'), " ", _jsx("b", { children: senderCount.user })] }) : null, senderCount.a2a_agent ? _jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'a2a_agent' ? ' on' : ''}`, onClick: () => onLedgerSender('a2a_agent'), children: ["\uD83E\uDD1D ", t('insights.senderA2A'), " ", _jsx("b", { children: senderCount.a2a_agent })] }) : null, senderCount.system ? _jsxs("button", { type: "button", className: `spanchip spanchip-sys${view.ledgerSender === 'system' ? ' on' : ''}`, onClick: () => onLedgerSender('system'), children: ["\u2699\uFE0F ", t('insights.senderSystem'), " ", _jsx("b", { children: senderCount.system })] }) : null] }), canGroup ? (_jsxs("div", { className: "spanfilter ledgersort", children: [_jsx("button", { type: "button", className: `spanchip${view.ledgerSort === 'normal' ? ' on' : ''}`, onClick: () => onLedgerSort('normal'), children: t('insights.ledgerNormal') }), _jsx("button", { type: "button", className: `spanchip${view.ledgerSort === 'grouped' ? ' on' : ''}`, onClick: () => onLedgerSort('grouped'), children: t('insights.ledgerGrouped') })] })) : null] }));
435
+ const note = report.meta?.capped ? _jsx("p", { className: "turn-hidden mut", children: t('insights.turnsCapped', { shown: String(report.meta.spansReturned ?? spans.length), total: String(report.meta.spansTotal ?? spans.length) }) }) : null;
436
+ if (!turns.length)
437
+ return _jsxs("div", { className: "turnlist", children: [controls, _jsx("p", { className: "mut", children: t('insights.evNoFlags') })] });
438
+ if (view.ledgerSort === 'grouped' && canGroup) {
439
+ const sortedRecs = [...recs].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
440
+ const byIndex = new Map(turns.map(tn => [tn.turnIndex, tn]));
441
+ const assigned = new Map();
442
+ for (const r of sortedRecs)
443
+ for (const ti of r.evidence?.turnIndexes ?? [])
444
+ if (byIndex.has(ti) && !assigned.has(ti))
445
+ assigned.set(ti, r.id);
446
+ const blocks = [];
447
+ for (const r of sortedRecs) {
448
+ const ts = turns.filter(tn => assigned.get(tn.turnIndex) === r.id).sort((a, b) => a.turnIndex - b.turnIndex);
449
+ if (!ts.length)
450
+ continue;
451
+ blocks.push(_jsxs("div", { className: "turn-group", children: [_jsxs("div", { className: `turn-group-head sev-${r.severity}`, children: [_jsx("b", { children: idText('rec', { id: r.id, params: {} }) }), _jsx("span", { children: t('insights.ledgerGroupCount', { count: ts.length }) })] }), ts.map(card)] }, r.id));
452
+ }
453
+ const other = turns.filter(tn => !assigned.has(tn.turnIndex)).sort((a, b) => a.turnIndex - b.turnIndex);
454
+ if (other.length) {
455
+ blocks.push(_jsxs("div", { className: "turn-group", children: [_jsxs("div", { className: "turn-group-head sev-info", children: [_jsx("b", { children: t('insights.ledgerOther') }), _jsx("span", { children: t('insights.ledgerGroupCount', { count: other.length }) })] }), other.map(card)] }, "other"));
456
+ }
457
+ return _jsxs("div", { className: "turnlist", children: [_jsx("p", { className: "turn-sum mut", children: t('insights.turnSummary', { total: turns.length, flagged }) }), controls, blocks, note] });
458
+ }
459
+ return _jsxs("div", { className: "turnlist", children: [_jsx("p", { className: "turn-sum mut", children: t('insights.turnSummary', { total: turns.length, flagged }) }), controls, [...turns].sort((a, b) => a.turnIndex - b.turnIndex).map(card), note] });
460
+ }
461
+ function ConvoRecBadges({ turnIndex, recByTurn }) {
462
+ const ids = recByTurn.get(turnIndex);
463
+ if (!ids?.length)
464
+ return null;
465
+ return _jsx(_Fragment, { children: ids.map(id => _jsxs("span", { className: "cbub-rec", title: idText('rec', { id, params: {} }), children: ["\uD83D\uDCA1 ", idText('rec', { id, params: {} })] }, id)) });
466
+ }
467
+ function ConvoPrompt({ msg, recByTurn, onOpenPrompt }) {
468
+ const side = msg.role === 'user' ? 'right' : 'left';
469
+ const text = msg.text ? (cleanPromptText(msg.text) || msg.text) : '';
470
+ const sevCls = msg.severity && msg.severity !== 'info' ? ` sev-${msg.severity}` : '';
471
+ return (_jsxs("div", { className: `cbub cbub-${side} role-${msg.role}${sevCls}`, children: [_jsxs("div", { className: "cbub-head", children: [_jsx(PromptSourceChip, { src: msg.source }), _jsx(PromptMentions, { src: msg.source }), _jsxs("span", { className: "cbub-turn", children: ["#", String(msg.turnIndex)] }), _jsx(ConvoRecBadges, { turnIndex: msg.turnIndex, recByTurn: recByTurn })] }), _jsx("div", { className: "cbub-body", children: text ? _jsx(MarkdownBody, { text: text + (msg.truncated ? ' …' : '') }) : _jsx("p", { className: "mut", children: t('insights.replayNoText') }) }), msg.truncated ? _jsx("div", { className: "cbub-foot", children: _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onOpenPrompt(msg.turnIndex), children: t('insights.turnPromptFull') }) }) : null] }));
472
+ }
473
+ function ConvoOpRow({ msg, open, onToggle }) {
474
+ const e = msg.event;
475
+ if (!e)
476
+ return null;
477
+ const bad = e.status === 'error' || (!!e.result && BAD_RESULTS.has(e.result.category));
478
+ const subj = e.intent?.subject ? _jsx("code", { className: "span-subj", children: e.intent.subject }) : null;
479
+ const what = intentTextOf(e.intent, String(e.label?.params?.tool ?? e.kind));
480
+ const ev = e.evidence;
481
+ const expandable = !!(ev?.command?.text || ev?.output?.text);
482
+ const tags = (msg.tags ?? []).filter(tg => tg !== 'normal' && tg !== 'diagnostic');
483
+ const toggle = () => expandable && onToggle(msg.id);
484
+ return (_jsxs("div", { className: `cop${bad ? ' bad' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: `cop-line${expandable ? ' clickable' : ''}`, role: expandable ? 'button' : undefined, tabIndex: expandable ? 0 : undefined, onClick: toggle, onKeyDown: event => {
485
+ if (!expandable)
486
+ return;
487
+ if (event.key === 'Enter' || event.key === ' ') {
488
+ event.preventDefault();
489
+ toggle();
490
+ }
491
+ }, children: [_jsx("i", { className: `op ph-${phaseSlug(e.phase)}${bad ? ' bad' : ''}` }), _jsx("b", { className: "span-what", children: what }), subj, e.result && BAD_RESULTS.has(e.result.category) ? _jsxs("span", { className: "span-res rc-bad", children: [resultLabel(e.result.category), e.result.exitCode !== undefined ? ` · exit ${e.result.exitCode}` : ''] }) : null, _jsx("span", { className: "span-tags", children: tags.map(tg => _jsx("span", { className: `span-tag tg-${tg}`, children: tagLabel(tg) }, tg)) }), _jsx("span", { className: "span-dur", children: fmtMs(e.durationMs) }), expandable ? _jsx("span", { className: "span-detail-btn", "aria-hidden": "true", children: open ? t('insights.dCollapse') : t('insights.dDetail') }) : null] }), open && ev ? _jsxs("div", { className: "spandetail", children: [_jsx(TextPreview, { label: t('insights.dCommand'), preview: ev.command }), _jsx(TextPreview, { label: t('insights.dCmdOutput'), preview: ev.output })] }) : null] }));
492
+ }
493
+ function ConvoOps({ unit, convo, recByTurn, onToggleOp }) {
494
+ const sayMsgs = unit.msgs.filter(m => m.text && !m.event);
495
+ const opMsgs = unit.msgs.filter(m => m.event);
496
+ const worst = unit.msgs.some(m => m.severity === 'bad') ? ' sev-bad' : unit.msgs.some(m => m.severity === 'warn') ? ' sev-warn' : '';
497
+ return (_jsxs("div", { className: `cbub cbub-left role-agent cbub-ops${worst}`, children: [_jsxs("div", { className: "cbub-head", children: [_jsxs("span", { className: "tp-label tp-src tp-src-system", children: ["\uD83E\uDD16 ", t('insights.replayAgent')] }), _jsxs("span", { className: "cbub-turn", children: ["#", String(unit.turnIndex)] }), opMsgs.length ? _jsx("span", { className: "cbub-opcount", children: t('insights.replayOps', { count: opMsgs.length }) }) : null, _jsx(ConvoRecBadges, { turnIndex: unit.turnIndex, recByTurn: recByTurn })] }), sayMsgs.length ? _jsx("div", { className: "cbub-saywrap", children: sayMsgs.map(m => _jsx("div", { className: "cbub-say", children: _jsx(MarkdownBody, { text: m.text }) }, m.id)) }) : null, opMsgs.length ? _jsx("div", { className: "cbub-ops-list", children: opMsgs.map(m => _jsx(ConvoOpRow, { msg: m, open: convo.openOps.has(m.id), onToggle: onToggleOp }, m.id)) }) : null] }));
498
+ }
499
+ function Conversation({ convo, recByTurn, onQuery, onRole, onTag, onLoadMore, onToggleOp, onOpenPrompt }) {
500
+ const [draft, setDraft] = useState(convo.q);
501
+ useEffect(() => setDraft(convo.q), [convo.q]);
502
+ const units = groupConvo(convo.messages);
503
+ return (_jsxs("div", { className: "convo", children: [_jsxs("div", { className: "convo-controls", children: [_jsx("input", { type: "search", className: "convo-search", placeholder: t('insights.replaySearch'), value: draft, onChange: event => setDraft(event.currentTarget.value), onKeyDown: event => {
504
+ if (event.key !== 'Enter')
505
+ return;
506
+ event.preventDefault();
507
+ const next = draft.trim();
508
+ if (next !== convo.q)
509
+ onQuery(next);
510
+ } }), _jsxs("div", { className: "convo-filters", children: [_jsxs("div", { className: "spanfilter convo-rolefilter", children: [_jsx("span", { className: "convo-flabel", children: t('insights.replayBy') }), CONVO_ROLES.map(r => _jsx("button", { type: "button", className: `spanchip${convo.role === r.key ? ' on' : ''}`, onClick: () => onRole(r.key), children: t(`insights.${r.label}`) }, r.key))] }), _jsxs("div", { className: "spanfilter convo-tagfilter", children: [_jsx("span", { className: "convo-flabel", children: t('insights.replayState') }), CONVO_TAGS.map(tg => _jsx("button", { type: "button", className: `spanchip${convo.tag === tg.key ? ' on' : ''}`, onClick: () => onTag(tg.key), children: tg.label.startsWith('tag.') ? tagLabel(tg.label.slice(4)) : t(`insights.${tg.label}`) }, tg.key))] })] })] }), _jsx("div", { className: "convothread", children: !convo.messages.length ? (convo.loading ? _jsx(LoadingState, { label: t('insights.detailLoading'), compact: true }) : _jsx("p", { className: "mut", children: t('insights.replayEmpty') })) : (_jsxs(_Fragment, { children: [units.map((u, i) => u.kind === 'prompt'
511
+ ? _jsx(ConvoPrompt, { msg: u.msg, recByTurn: recByTurn, onOpenPrompt: onOpenPrompt }, u.msg.id)
512
+ : _jsx(ConvoOps, { unit: u, convo: convo, recByTurn: recByTurn, onToggleOp: onToggleOp }, `${u.turnIndex}:${i}`)), convo.hasMore ? (_jsx("div", { className: "convo-more", children: _jsx("button", { type: "button", className: "primary convo-loadmore", disabled: convo.loading, onClick: onLoadMore, children: convo.loading ? t('insights.detailLoading') : t('insights.replayLoadMore', { shown: convo.messages.length, total: convo.total }) }) })) : _jsx("p", { className: "convo-more mut", children: t('insights.replayAllLoaded', { total: convo.total }) })] })) })] }));
513
+ }
514
+ function Recommendations({ report, activeId, onActive }) {
515
+ const recs = report.recommendations ?? [];
516
+ if (!recs.length)
517
+ return _jsx("p", { className: "mut", children: t('insights.noRecommendations') });
518
+ return (_jsx("div", { className: "reclist", children: [...recs].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]).map(r => {
519
+ const active = r.id === activeId;
520
+ const targeted = (r.evidence?.spanIndexes?.length ?? 0) > 0 || (r.evidence?.turnIndexes?.length ?? 0) > 0;
521
+ const impact = idText('impact', r.impact);
522
+ const why = idText('why', r.why);
523
+ return (_jsxs("button", { type: "button", className: `rec ${r.severity}${targeted ? ' rec-clickable' : ''}${active ? ' active' : ''}`, onClick: () => onActive(active ? null : r.id), children: [_jsxs("div", { className: "rectop", children: [_jsx("b", { children: idText('rec', { id: r.id, params: {} }) }), _jsx("span", { children: severityLabel(r.severity) })] }), impact ? _jsx("div", { className: "rec-impact", children: impact }) : null, r.nextActions.length ? _jsx("ul", { className: "rec-actions", children: r.nextActions.map(a => _jsx("li", { children: idText('action', a) }, `${a.id}:${JSON.stringify(a.params)}`)) }) : null, why ? _jsx("div", { className: "rec-why", children: why }) : null, targeted ? _jsx("span", { className: "rec-cta", children: active ? t('insights.diagActive') : t('insights.diagShow') }) : null] }, r.id));
524
+ }) }));
525
+ }
526
+ function Subagents({ report }) {
527
+ const lanes = report.subagents ?? [];
528
+ if (!lanes.length)
529
+ return null;
530
+ const totMs = lanes.reduce((s, l) => s + l.durationMs, 0);
531
+ return (_jsxs("section", { className: "block subagent-block", children: [_jsxs("h3", { children: [t('insights.subagents'), " ", _jsxs("span", { className: "mut", children: ["\u00B7 ", lanes.length, " \u00B7 ", fmtMs(totMs)] })] }), _jsx("p", { className: "mut ins-hint", children: t('insights.subagentsHint') }), _jsx("div", { className: "sublanes", children: lanes.map((l, i) => (_jsxs("div", { className: `sublane${l.failures ? ' bad' : ''}`, children: [_jsxs("div", { className: "sublane-head", children: [_jsx("span", { className: "sublane-type", children: l.agentType }), _jsxs("span", { className: "sublane-task", children: [l.task?.text ?? '', l.task?.truncated ? '…' : ''] })] }), _jsx(PhaseMixBar, { phase: l.phase }), _jsxs("div", { className: "sublane-stats", children: [_jsxs("span", { children: [fmtInt(l.spans), " ", t('insights.spansShort')] }), _jsx("span", { children: fmtMs(l.durationMs) }), l.failures ? _jsxs("span", { className: "bad", children: [fmtInt(l.failures), " ", t('insights.failedShort')] }) : null] })] }, `${l.agentType}:${i}`))) })] }));
532
+ }
533
+ function TurnRail({ report, focus, recByTurn, onJumpTurn, onTip, onTipHide }) {
534
+ const turns = [...(report.turnTimeline ?? [])].sort((a, b) => a.turnIndex - b.turnIndex);
535
+ if (turns.length < 2)
536
+ return null;
537
+ const focused = focus.turnIdx.size > 0;
538
+ const legend = ['research', 'edit', 'run', 'delegate', 'discuss'];
539
+ return (_jsxs("section", { className: "block turnrail-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.turnRail') }), _jsxs("span", { className: "turnrail-legend", children: [legend.map(p => _jsxs("span", { className: "rl-item", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }, p)), _jsxs("span", { className: "rl-item rl-sep", children: [_jsx("i", { className: "rbr-sub" }), t('insights.railSubagent')] })] })] }), _jsx("div", { className: "turnrail", onMouseLeave: onTipHide, children: turns.map(tn => {
540
+ const m = tn.metrics;
541
+ const phase = turnMainPhase(tn);
542
+ const fail = (tn.tags ?? []).includes('failure') || (tn.events ?? []).some(e => e.status === 'error');
543
+ const slow = (tn.tags ?? []).includes('slow');
544
+ const recHit = recByTurn.has(tn.turnIndex);
545
+ const hot = focus.turnIdx.has(tn.turnIndex);
546
+ const cls = ['railnode', phaseClass(phase), hot ? 'hot' : '', (focused && !hot) ? 'dim' : ''].filter(Boolean).join(' ');
547
+ const tip = `#${tn.turnIndex} · ${phaseLabel(phase)} · ${t('insights.mReads')}${m.reads} ${t('insights.mEdits')}${m.edits} ${t('insights.mRuns')}${m.runs}${m.failures ? ` · ${t('insights.mFailures')}${m.failures}` : ''} · ${fmtMs(m.durationMs)}`;
548
+ const subs = (tn.events ?? []).filter(e => e.kind === 'delegate').length;
549
+ return (_jsxs("span", { style: { display: 'contents' }, children: [_jsxs("button", { type: "button", className: cls, onMouseMove: event => onTip(event, tip), onBlur: onTipHide, onClick: () => onJumpTurn(tn), children: [_jsx("span", { children: String(tn.turnIndex) }), fail ? _jsx("i", { className: "rb rb-fail" }) : null, slow ? _jsx("i", { className: "rb rb-slow" }) : null, recHit ? _jsx("i", { className: "rb rb-rec" }) : null] }), subs ? _jsx("span", { className: "railbranch", onMouseMove: event => onTip(event, `#${tn.turnIndex} · ${t('insights.railSubagents', { n: subs })}`), children: Array.from({ length: Math.min(subs, 4) }).map((_, i) => _jsx("i", { className: "rbr-sub" }, i)) }) : null] }, tn.turnIndex));
550
+ }) })] }));
551
+ }
552
+ function WorkGantt({ report, onOpenSpan, onTip, onTipHide }) {
553
+ const timed = (report.spans ?? []).map((s, i) => ({ s, i, start: s.relStartMs ?? 0, dur: Math.max(s.durationMs ?? 0, 0) }))
554
+ .filter(x => Number.isFinite(x.start)).sort((a, b) => a.start - b.start);
555
+ if (timed.length < 2)
556
+ return null;
557
+ const active = Math.max(timed.reduce((sum, x) => sum + x.dur, 0), 1);
558
+ let cursor = 0;
559
+ const realSpan = (timed[timed.length - 1].start + timed[timed.length - 1].dur) - timed[0].start;
560
+ return (_jsxs("section", { className: "block gantt-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.gantt') }), _jsx("span", { className: "gantt-cap", children: t('insights.ganttCaption', { span: timed.length, dur: fmtMs(realSpan), active: fmtMs(active) }) })] }), _jsx("div", { className: "gantt", children: _jsx("div", { className: "gtrack", onMouseLeave: onTipHide, children: timed.map(x => {
561
+ const left = cursor / active * 100;
562
+ const width = Math.max(x.dur / active * 100, 0.3);
563
+ cursor += x.dur;
564
+ const fail = x.s.status === 'error' || (x.s.tags ?? []).includes('failure');
565
+ const slow = (x.s.tags ?? []).includes('slow');
566
+ const cls = ['gbar', phaseClass(x.s.phase), fail ? 'gbar-fail' : '', slow ? 'gbar-slow' : ''].filter(Boolean).join(' ');
567
+ const st = x.s.status === 'error' ? ` · ${tagLabel('failure')}` : '';
568
+ const tip = `#${x.i} · ${x.s.tool} · ${phaseLabel(x.s.phase)} · ${fmtMs(x.s.durationMs)}${st}`;
569
+ return _jsx("button", { type: "button", className: cls, style: { left: `${left.toFixed(3)}%`, width: `${width.toFixed(3)}%` }, onMouseMove: event => onTip(event, tip), onClick: () => onOpenSpan(x.i) }, x.i);
570
+ }) }) })] }));
571
+ }
572
+ function ContextCurve({ report, onTip, onTipHide }) {
573
+ const pts = (report.turnTimeline ?? [])
574
+ .map(tn => (tn.context && Number.isFinite(tn.context.contextTokens)) ? { turn: tn.turnIndex, v: tn.context.contextTokens } : null)
575
+ .filter((p) => p !== null)
576
+ .sort((a, b) => a.turn - b.turn);
577
+ if (pts.length < 2)
578
+ return null;
579
+ const max = Math.max(...pts.map(p => p.v), 1);
580
+ const w = 100;
581
+ const h = 40;
582
+ const xs = (i) => (i / (pts.length - 1)) * w;
583
+ const ys = (v) => h - 1 - (v / max) * (h - 2);
584
+ const line = pts.map((p, i) => `${xs(i).toFixed(2)},${ys(p.v).toFixed(2)}`).join(' ');
585
+ const area = `0,${h} ${line} ${w},${h}`;
586
+ const band = w / pts.length;
587
+ const mid = fmtInt(Math.round(max / 2));
588
+ return (_jsxs("section", { className: "block ctxcurve-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.ctxCurve') }), _jsx("span", { className: "gantt-cap", children: t('insights.ctxCaption', { peak: fmtInt(max), turns: pts.length }) })] }), _jsxs("div", { className: "ctxchart", children: [_jsxs("div", { className: "ctxyaxis", children: [_jsx("span", { children: fmtInt(max) }), _jsx("span", { children: mid }), _jsx("span", { children: "0 tok" })] }), _jsxs("div", { className: "ctxplot", children: [_jsxs("svg", { className: "ctxcurve", viewBox: `0 0 ${w} ${h}`, preserveAspectRatio: "none", role: "img", "aria-label": t('insights.ctxCurve'), onMouseLeave: onTipHide, children: [_jsx("polygon", { className: "ctxarea", points: area }), _jsx("polyline", { className: "ctxline", points: line }), pts.map((p, i) => (_jsx("rect", { className: "ctxhit", x: Math.max(0, xs(i) - band / 2), y: 0, width: band, height: h, onMouseMove: event => onTip(event, `${t('insights.ctxTurn', { n: p.turn })} · ${fmtInt(p.v)} tok`) }, p.turn)))] }), _jsxs("div", { className: "ctxxaxis", children: [_jsx("span", { children: t('insights.ctxTurn', { n: pts[0].turn }) }), _jsx("span", { children: t('insights.ctxTurn', { n: pts[pts.length - 1].turn }) })] })] })] })] }));
589
+ }
590
+ function DetailBody({ report, view, onViewChange, onOpenPrompt, onJumpTurn, onOpenSpan, scrollTarget, onScrolled, onTip, onTipHide, onLoadConvo }) {
591
+ if (report.status !== 'ok')
592
+ return _jsx("p", { className: "mut", children: safeStatus(report) });
593
+ const a = report.agg;
594
+ const focus = focusSets(report, view.activeId);
595
+ const recByTurn = new Map();
596
+ for (const r of report.recommendations ?? [])
597
+ for (const ti of r.evidence?.turnIndexes ?? []) {
598
+ const arr = recByTurn.get(ti) ?? [];
599
+ if (!arr.includes(r.id))
600
+ arr.push(r.id);
601
+ recByTurn.set(ti, arr);
602
+ }
603
+ const meta = [
604
+ report.meta?.asOf ? t('sessions.insightAsOf', { asOf: String(report.meta.asOf) }) : '',
605
+ report.meta?.partial ? t('sessions.insightPartial') : '',
606
+ report.meta?.capped ? t('sessions.insightCapped', { shown: String(report.meta.spansReturned ?? report.spans?.length ?? 0), total: String(report.meta.spansTotal ?? report.spans?.length ?? 0) }) : '',
607
+ ].filter(Boolean).join(' · ');
608
+ const spanCount = report.spans?.length ?? 0;
609
+ const turnTotal = report.turnTimeline?.length ?? 0;
610
+ const setTab = (tab) => {
611
+ onViewChange({ tab });
612
+ if (tab === 'convo' && !view.convo.messages.length && !view.convo.loading)
613
+ onLoadConvo(true);
614
+ };
615
+ const toggleSpan = (idx) => onViewChange({ openSpans: toggleSet(view.openSpans, idx) });
616
+ const toggleTurn = (idx) => onViewChange({ openTurns: toggleSet(view.openTurns, idx) });
617
+ const togglePrompt = (idx) => onViewChange({ openPrompts: toggleSet(view.openPrompts, idx) });
618
+ const togglePromptRaw = (idx) => onViewChange({ rawPrompts: toggleSet(view.rawPrompts, idx) });
619
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cards insight-detail-metrics", children: [_jsx(MetricCard, { label: t('insights.metricSpans'), value: fmtInt(a.totalSpans) }), _jsx(MetricCard, { label: t('insights.metricFailed'), value: fmtInt(a.failedSpans) }), _jsx(MetricCard, { label: t('insights.metricSlow'), value: fmtInt(a.slowSpans) }), _jsx(MetricCard, { label: t('insights.metricRw'), value: a.readWriteRatio === null ? '-' : a.readWriteRatio.toFixed(1) })] }), meta ? _jsx("p", { className: "insight-meta", children: meta }) : null, _jsxs("section", { className: "block recblock", children: [_jsx("h3", { children: t('insights.recommendations') }), _jsx(Recommendations, { report: report, activeId: view.activeId, onActive: activeId => onViewChange({ activeId }) })] }), _jsx(TurnRail, { report: report, focus: focus, recByTurn: recByTurn, onJumpTurn: onJumpTurn, onTip: onTip, onTipHide: onTipHide }), _jsx(WorkGantt, { report: report, onOpenSpan: onOpenSpan, onTip: onTip, onTipHide: onTipHide }), _jsx(Subagents, { report: report }), _jsx(ContextCurve, { report: report, onTip: onTip, onTipHide: onTipHide }), _jsxs("div", { className: "detailtabs", children: [_jsxs("div", { className: "detailtabbar", role: "tablist", "aria-label": t('insights.detailTabs'), children: [_jsxs("button", { type: "button", role: "tab", className: view.tab === 'spans' ? 'on' : '', onClick: () => setTab('spans'), children: [t('insights.trace'), " ", _jsx("b", { children: spanCount })] }), _jsxs("button", { type: "button", role: "tab", className: view.tab === 'ledger' ? 'on' : '', onClick: () => setTab('ledger'), children: [t('insights.ledger'), " ", _jsx("b", { children: turnTotal })] }), _jsx("button", { type: "button", role: "tab", className: view.tab === 'convo' ? 'on' : '', onClick: () => setTab('convo'), children: t('insights.replay') })] }), _jsxs("div", { className: "detailtabbody", children: [_jsx("div", { className: "insight-tab-panel", "data-panel": "spans", hidden: view.tab !== 'spans', children: _jsx(Evidence, { report: report, focus: focus, view: view, onSpanFilter: spanFilter => onViewChange({ spanFilter }), onToggleSpan: toggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }) }), _jsx("div", { className: "insight-tab-panel", "data-panel": "ledger", hidden: view.tab !== 'ledger', children: _jsx(TurnEfficiency, { report: report, focus: focus, view: view, onLedgerSort: ledgerSort => onViewChange({ ledgerSort }), onLedgerSender: ledgerSender => onViewChange({ ledgerSender }), onToggleTurn: toggleTurn, onToggleSpan: toggleSpan, onTogglePrompt: togglePrompt, onTogglePromptRaw: togglePromptRaw, onOpenPrompt: onOpenPrompt, scrollTarget: scrollTarget, onScrolled: onScrolled }) }), _jsx("div", { className: "insight-tab-panel", "data-panel": "convo", hidden: view.tab !== 'convo', children: _jsx(Conversation, { convo: view.convo, recByTurn: recByTurn, onQuery: q => onLoadConvo(true, { q }), onRole: role => { if (role !== view.convo.role)
620
+ onLoadConvo(true, { role }); }, onTag: tag => { if (tag !== view.convo.tag)
621
+ onLoadConvo(true, { tag }); }, onLoadMore: () => onLoadConvo(false), onToggleOp: id => onViewChange({ convo: { ...view.convo, openOps: toggleSet(view.convo.openOps, id) } }), onOpenPrompt: onOpenPrompt }) })] })] })] }));
622
+ }
623
+ function PromptModal({ modal, onClose, onToggleRaw }) {
624
+ if (modal.turnIndex === null)
625
+ return null;
626
+ const prompt = modal.prompt;
627
+ const src = prompt?.source;
628
+ const cleaned = prompt?.text ? (cleanPromptText(prompt.text) || prompt.text) : '';
629
+ const trunc = prompt?.truncated;
630
+ const body = prompt
631
+ ? modal.raw
632
+ ? _jsxs("pre", { className: "tp-raw modal-raw", children: [cleaned, trunc ? '\n…' : ''] })
633
+ : _jsx(MarkdownBody, { className: "md-body modal-md", text: cleaned + (trunc ? ' …' : '') })
634
+ : _jsx(LoadingState, { label: t('insights.detailLoading'), compact: true });
635
+ return (_jsxs("div", { id: "insight-modal", className: "insight-modal", children: [_jsx("div", { className: "modal-backdrop", onClick: onClose }), _jsx("div", { className: "modal-panel", role: "dialog", "aria-modal": "true", "aria-label": t('insights.turnPrompt'), children: prompt ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "modal-head", children: [_jsxs("div", { className: "modal-who", children: [_jsx(PromptSourceChip, { src: src }), _jsx(PromptMentions, { src: src }), _jsxs("span", { className: "modal-turnno", children: ["#", modal.turnIndex] })] }), _jsxs("div", { className: "modal-acts", children: [_jsx("button", { type: "button", className: "tp-toggle", onClick: onToggleRaw, children: modal.raw ? t('insights.turnPromptRendered') : t('insights.turnPromptRaw') }), _jsx("button", { type: "button", className: "modal-close", onClick: onClose, "aria-label": t('insights.modalClose'), children: "\u00D7" })] })] }), _jsxs("div", { className: "modal-body", children: [body, trunc ? _jsx("p", { className: "modal-trunc mut", children: t('insights.promptTruncated') }) : null] })] })) : _jsx("div", { className: "modal-body", children: body }) })] }));
636
+ }
637
+ function CommandPalette({ palette, items, onClose, onInput, onChoose }) {
638
+ if (!palette.open)
639
+ return null;
640
+ const anchored = !!palette.anchor;
641
+ const panelStyle = anchored
642
+ ? {
643
+ top: palette.anchor.top,
644
+ left: palette.anchor.left,
645
+ width: palette.anchor.width,
646
+ '--palette-panel-max-height': `${palette.anchor.maxHeight}px`,
647
+ '--palette-list-max-height': `${Math.max(80, palette.anchor.maxHeight - 54)}px`,
648
+ }
649
+ : undefined;
650
+ const node = (_jsxs("div", { id: "insight-palette", className: `insights-page insight-palette ${anchored ? 'palette-anchored' : 'palette-centered'}`, children: [_jsx("div", { className: "modal-backdrop", onClick: onClose }), _jsxs("div", { className: `palette-panel${anchored ? ' anchored' : ''}`, role: "dialog", "aria-modal": "false", style: panelStyle, children: [_jsx("input", { type: "search", className: "palette-input", placeholder: t('insights.palettePlaceholder'), value: palette.q, autoFocus: true, onChange: event => onInput(event.currentTarget.value) }), _jsx("div", { className: "palette-list", children: items.length ? items.map((it, i) => (_jsxs("button", { type: "button", className: `palette-item${i === palette.idx ? ' on' : ''}`, onClick: () => onChoose(it.type, it.key), children: [_jsx("span", { className: "pal-label", children: it.label }), _jsx("span", { className: "pal-sub", children: it.sub })] }, `${it.type}:${it.key}`))) : _jsx("p", { className: "mut palette-empty", children: t('insights.paletteEmpty') }) })] })] }));
651
+ return createPortal(node, document.body);
652
+ }
653
+ function readInitialState() {
654
+ const hp = parseInsightsHash();
655
+ return {
656
+ tab: initialInsightTab(),
657
+ filter: INSIGHT_FILTERS.includes(hp.filter) ? hp.filter : 'all',
658
+ cli: new Set((hp.cli ?? '').split(',').filter(Boolean)),
659
+ q: hp.q ?? '',
660
+ sess: hp.sess ?? null,
661
+ project: hp.project ?? '',
662
+ time: hp.time ?? 'all',
663
+ showNoise: hp.noise === '1',
664
+ sort: SESS_SORT_KEYS.includes(hp.sort) ? hp.sort : 'recent',
665
+ layout: hp.layout === 'table' ? 'table' : 'card',
666
+ };
8
667
  }
9
- // Render-once scaffold: React owns the translated page shell and unmount
10
- // lifecycle; wireInsightsPage owns the tab/detail/modal controller until the
11
- // insight workbench is split into smaller reactive components.
12
668
  function InsightsPage() {
13
669
  const tr = useT();
14
- const rootRef = useRef(null);
15
- const tab = initialInsightTab();
670
+ const initial = useRef(readInitialState()).current;
671
+ const selectedIdRef = useRef(null);
672
+ const initialSessRef = useRef(initial.sess);
673
+ const detailReqRef = useRef(0);
674
+ const convoReqRef = useRef(0);
675
+ const modalReqRef = useRef(0);
676
+ const [namesVersion, setNamesVersion] = useState(0);
677
+ const [overviewData, setOverviewData] = useState(null);
678
+ const [records, setRecords] = useState([]);
679
+ const [statusText, setStatusText] = useState('');
680
+ const [refreshing, setRefreshing] = useState(false);
681
+ const [tab, setTab] = useState(initial.tab);
682
+ const [filter, setFilter] = useState(initial.filter);
683
+ const [cliFilter, setCliFilter] = useState(
684
+ // Single-select model: a legacy multi-CLI deep-link (?cli=a,b) collapses to its first CLI
685
+ // so the dropdown label always matches the active filter (was: showed "all" while filtering ≥2).
686
+ initial.cli.size > 1 ? new Set([[...initial.cli][0]]) : initial.cli);
687
+ const [q, setQ] = useState(initial.q);
688
+ const [project, setProject] = useState(initial.project);
689
+ const [timeWin, setTimeWin] = useState(initial.time);
690
+ const [showNoise, setShowNoise] = useState(initial.showNoise);
691
+ const [sessSort, setSessSort] = useState(initial.sort);
692
+ const [sessLayout, setSessLayout] = useState(initial.layout);
693
+ const [selectedId, setSelectedId] = useState(null);
694
+ const [detailReport, setDetailReport] = useState(null);
695
+ const [detailLoading, setDetailLoading] = useState(false);
696
+ const [detailError, setDetailError] = useState('');
697
+ const [detailView, setDetailView] = useState({
698
+ activeId: null,
699
+ tab: 'spans',
700
+ spanFilter: 'all',
701
+ openSpans: new Set(),
702
+ openTurns: new Set(),
703
+ ledgerSort: 'normal',
704
+ openPrompts: new Set(),
705
+ rawPrompts: new Set(),
706
+ ledgerSender: 'all',
707
+ convo: newConvoState(),
708
+ });
709
+ const [openHot, setOpenHot] = useState(new Set());
710
+ const [palette, setPalette] = useState({ open: false, q: '', idx: 0 });
711
+ const [modal, setModal] = useState({ turnIndex: null, raw: false, prompt: null, loading: false });
712
+ const [scrollTarget, setScrollTarget] = useState(null);
713
+ const [tip, setTip] = useState({ text: '', x: 0, y: 0, visible: false });
714
+ const scope = useMemo(() => buildInsightScope(timeWin, project, showNoise), [project, showNoise, timeWin]);
715
+ const cliBase = useMemo(() => filterRecords(records, filter, q, new Set(), scope), [filter, q, records, scope, namesVersion]);
716
+ const presentCliKey = useMemo(() => [...new Set(cliBase.map(cliIdOf))].sort().join(','), [cliBase]);
717
+ const projectBase = useMemo(() => filterRecords(records, filter, q, cliFilter, { ...scope, project: undefined }), [cliFilter, filter, q, records, scope, namesVersion]);
718
+ const projectOpts = useMemo(() => projectOptions(projectBase), [projectBase]);
719
+ const projectDropdownOptions = useMemo(() => [
720
+ { value: '', label: tr('insights.projectAll') },
721
+ ...projectOpts.map(o => ({ value: o.id, label: `${o.id} (${o.count})` })),
722
+ ], [projectOpts, tr]);
723
+ const timeDropdownOptions = useMemo(() => TIME_WINDOWS.map(w => ({ value: w.key, label: tr(w.label) })), [tr]);
724
+ const reviewDropdownOptions = useMemo(() => INSIGHT_FILTERS.map(f => ({
725
+ value: f,
726
+ label: f === 'all'
727
+ ? tr('insights.filterAllSessions')
728
+ : f === 'review'
729
+ ? tr('insights.filterReview')
730
+ : f === 'failed'
731
+ ? tr('insights.filterFailed')
732
+ : tr('insights.filterSlow'),
733
+ })), [tr]);
734
+ const cliDropdownOptions = useMemo(() => [
735
+ { value: CLI_FILTER_ALL, label: `${tr('insights.filterAllCli')} (${cliBase.length})` },
736
+ ...cliCounts(cliBase).map(c => ({ value: c.id, label: `${c.id} (${c.count})` })),
737
+ ], [cliBase, tr]);
738
+ const projectLabel = projectDropdownOptions.find(option => option.value === project)?.label ?? tr('insights.projectAll');
739
+ const timeLabel = timeDropdownOptions.find(option => option.value === timeWin)?.label ?? tr('insights.timeAll');
740
+ const reviewLabel = reviewDropdownOptions.find(option => option.value === filter)?.label ?? tr('insights.filterAllSessions');
741
+ const cliFilterValue = cliFilter.size === 1 ? [...cliFilter][0] : CLI_FILTER_ALL;
742
+ const cliFilterLabel = cliDropdownOptions.find(option => option.value === cliFilterValue)?.label ?? tr('insights.filterAllCli');
743
+ const rows = useMemo(() => sortRecordsBy(filterRecords(records, filter, q, cliFilter, scope), sessSort), [cliFilter, filter, q, records, scope, sessSort, namesVersion]);
744
+ const overview = useMemo(() => overviewData ? aggregateRecords(rows) : null, [overviewData, rows]);
745
+ const selectedRecord = useMemo(() => selectedId ? rows.find(r => r.session.sessionId === selectedId) ?? records.find(r => r.session.sessionId === selectedId) : undefined, [records, rows, selectedId]);
746
+ const paletteItems = useMemo(() => {
747
+ const ql = palette.q.trim().toLowerCase();
748
+ const tabs = INSIGHT_TABS
749
+ .map(tb => ({ type: 'tab', key: tb.key, label: t(tb.label), sub: t('insights.paletteTabs') }))
750
+ .filter(it => !ql || it.label.toLowerCase().includes(ql));
751
+ const sess = records
752
+ .filter(r => {
753
+ const s = r.session;
754
+ return !ql || `${sessionTitle(s)} ${botDisplayName(s)} ${s.cliId ?? ''}`.toLowerCase().includes(ql);
755
+ })
756
+ .slice(0, 20)
757
+ .map(r => ({ type: 'session', key: String(r.session.sessionId), label: sessionTitle(r.session), sub: `${botDisplayName(r.session)} · ${r.session.cliId ?? '-'}` }));
758
+ return [...tabs, ...sess];
759
+ }, [palette.q, records, namesVersion]);
760
+ const showTip = useCallback((event, text) => {
761
+ const pad = 14;
762
+ setTip({ text, x: event.clientX + pad, y: event.clientY + pad, visible: true });
763
+ }, []);
764
+ const hideTip = useCallback(() => setTip(v => ({ ...v, visible: false })), []);
765
+ const patchDetailView = useCallback((patch) => {
766
+ setDetailView(prev => ({ ...prev, ...patch }));
767
+ }, []);
768
+ const closeModal = useCallback(() => {
769
+ setModal({ turnIndex: null, raw: false, prompt: null, loading: false });
770
+ }, []);
771
+ const resetDetailState = useCallback(() => {
772
+ setDetailReport(null);
773
+ setDetailError('');
774
+ setDetailLoading(false);
775
+ setDetailView({
776
+ activeId: null,
777
+ tab: 'spans',
778
+ spanFilter: 'all',
779
+ openSpans: new Set(),
780
+ openTurns: new Set(),
781
+ ledgerSort: 'normal',
782
+ openPrompts: new Set(),
783
+ rawPrompts: new Set(),
784
+ ledgerSender: 'all',
785
+ convo: newConvoState(),
786
+ });
787
+ setScrollTarget(null);
788
+ closeModal();
789
+ }, [closeModal]);
790
+ const selectSession = useCallback((sessionId) => {
791
+ selectedIdRef.current = sessionId;
792
+ setSelectedId(sessionId);
793
+ resetDetailState();
794
+ setDetailLoading(true);
795
+ const req = ++detailReqRef.current;
796
+ void (async () => {
797
+ try {
798
+ const report = await fetchInsightDetail(sessionId);
799
+ if (req !== detailReqRef.current || selectedIdRef.current !== sessionId)
800
+ return;
801
+ setDetailReport(report);
802
+ }
803
+ catch (e) {
804
+ if (req !== detailReqRef.current || selectedIdRef.current !== sessionId)
805
+ return;
806
+ setDetailError(String(e));
807
+ }
808
+ finally {
809
+ if (req === detailReqRef.current && selectedIdRef.current === sessionId)
810
+ setDetailLoading(false);
811
+ }
812
+ })();
813
+ }, [resetDetailState]);
814
+ const loadConvo = useCallback((reset, overrides = {}) => {
815
+ const sid = selectedIdRef.current;
816
+ if (!sid || detailView.convo.loading)
817
+ return;
818
+ const nextQ = overrides.q ?? detailView.convo.q;
819
+ const nextRole = overrides.role ?? detailView.convo.role;
820
+ const nextTag = overrides.tag ?? detailView.convo.tag;
821
+ const offset = reset ? 0 : detailView.convo.nextOffset;
822
+ setDetailView(prev => ({
823
+ ...prev,
824
+ convo: {
825
+ ...prev.convo,
826
+ ...overrides,
827
+ messages: reset ? [] : prev.convo.messages,
828
+ nextOffset: reset ? 0 : prev.convo.nextOffset,
829
+ hasMore: reset ? false : prev.convo.hasMore,
830
+ loading: true,
831
+ },
832
+ }));
833
+ const req = ++convoReqRef.current;
834
+ void (async () => {
835
+ try {
836
+ const c = await fetchInsightConversation(sid, { offset, limit: 40, q: nextQ, role: nextRole, tag: nextTag });
837
+ if (req !== convoReqRef.current || selectedIdRef.current !== sid)
838
+ return;
839
+ setDetailView(prev => ({
840
+ ...prev,
841
+ convo: {
842
+ ...prev.convo,
843
+ messages: reset ? c.messages : [...prev.convo.messages, ...c.messages],
844
+ total: c.total,
845
+ hasMore: c.hasMore,
846
+ nextOffset: c.nextOffset,
847
+ loading: false,
848
+ },
849
+ }));
850
+ }
851
+ catch {
852
+ if (req !== convoReqRef.current || selectedIdRef.current !== sid)
853
+ return;
854
+ setDetailView(prev => ({ ...prev, convo: { ...prev.convo, loading: false } }));
855
+ }
856
+ })();
857
+ }, [detailView.convo]);
858
+ const refresh = useCallback(async () => {
859
+ setRefreshing(true);
860
+ setStatusText(tr('insights.loading'));
861
+ try {
862
+ const overviewNext = await fetchInsightsSummary();
863
+ setOverviewData(overviewNext);
864
+ setRecords(overviewNext.sessions.map(toRecord));
865
+ setStatusText(tr('insights.loaded', { count: overviewNext.meta.analyzedSessions }));
866
+ }
867
+ catch (e) {
868
+ setOverviewData(null);
869
+ setRecords([]);
870
+ setStatusText(`${tr('insights.unavailable')}: ${String(e)}`);
871
+ }
872
+ finally {
873
+ setRefreshing(false);
874
+ }
875
+ }, [tr]);
876
+ const openPromptModal = useCallback((turnIndex) => {
877
+ const sid = selectedIdRef.current;
878
+ if (!sid)
879
+ return;
880
+ const req = ++modalReqRef.current;
881
+ setModal({ turnIndex, raw: false, prompt: null, loading: true });
882
+ void (async () => {
883
+ try {
884
+ const prompt = await fetchTurnPrompt(sid, turnIndex);
885
+ if (req !== modalReqRef.current || selectedIdRef.current !== sid)
886
+ return;
887
+ setModal({ turnIndex, raw: false, prompt, loading: false });
888
+ }
889
+ catch {
890
+ if (req !== modalReqRef.current || selectedIdRef.current !== sid)
891
+ return;
892
+ setModal({ turnIndex, raw: false, prompt: { text: tr('insights.unavailable'), truncated: false }, loading: false });
893
+ }
894
+ })();
895
+ }, [tr]);
896
+ const jumpToSession = useCallback((sessionId) => {
897
+ setTab('sessions');
898
+ selectSession(sessionId);
899
+ }, [selectSession]);
900
+ const openSpanFromVisual = useCallback((idx) => {
901
+ patchDetailView({ tab: 'spans', spanFilter: 'all', openSpans: addToSet(detailView.openSpans, idx) });
902
+ setScrollTarget({ kind: 'span', index: idx });
903
+ }, [detailView.openSpans, patchDetailView]);
904
+ const jumpTurnFromRail = useCallback((turn) => {
905
+ patchDetailView({
906
+ tab: 'ledger',
907
+ ledgerSort: 'normal',
908
+ ledgerSender: turnSenderKind(turn) === 'system' ? 'system' : detailView.ledgerSender,
909
+ openTurns: addToSet(detailView.openTurns, turn.turnIndex),
910
+ });
911
+ setScrollTarget({ kind: 'turn', index: turn.turnIndex });
912
+ }, [detailView.ledgerSender, detailView.openTurns, patchDetailView]);
913
+ const choosePalette = useCallback((type, key) => {
914
+ setPalette({ open: false, q: '', idx: 0 });
915
+ if (type === 'tab') {
916
+ setTab(key);
917
+ selectedIdRef.current = null;
918
+ setSelectedId(null);
919
+ return;
920
+ }
921
+ jumpToSession(key);
922
+ }, [jumpToSession]);
16
923
  useEffect(() => {
17
- if (!rootRef.current)
18
- return undefined;
19
- return wireInsightsPage(rootRef.current);
924
+ void loadNameMaps().then(() => setNamesVersion(v => v + 1));
20
925
  }, []);
21
- return (_jsxs("section", { className: "page insights-page", ref: rootRef, children: [_jsxs("div", { className: "page-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "eyebrow", children: tr('nav.insights') }), _jsx("h1", { children: tr('insights.title') }), _jsx("p", { children: tr('insights.subtitle') })] }), _jsxs("div", { className: "insight-head-acts", children: [_jsx("button", { type: "button", id: "insight-palette-open", className: "ins-clear", children: tr('insights.paletteOpen') }), _jsx("button", { type: "button", id: "insight-refresh", className: "primary", children: tr('insights.refresh') })] })] }), _jsxs("form", { id: "insight-filters", className: "filters insights-filters", children: [_jsx("input", { type: "search", name: "q", placeholder: tr('insights.search') }), _jsx("select", { id: "insight-project", className: "ins-select", "aria-label": tr('insights.projectAll') }), _jsx("select", { id: "insight-time", className: "ins-select", "aria-label": tr('insights.timeAll'), children: TIME_WINDOWS.map(w => _jsx("option", { value: w.key, children: tr(w.label) }, w.key)) }), _jsxs("div", { className: "segmented", role: "group", "aria-label": tr('insights.filter'), children: [_jsx("button", { type: "button", "data-filter": "all", children: tr('insights.filterAll') }), _jsx("button", { type: "button", "data-filter": "review", children: tr('insights.filterReview') }), _jsx("button", { type: "button", "data-filter": "failed", children: tr('insights.filterFailed') }), _jsx("button", { type: "button", "data-filter": "slow", children: tr('insights.filterSlow') })] }), _jsx("div", { id: "insight-cli-filter", className: "spanfilter cli-filter", role: "group", "aria-label": tr('insights.filter') }), _jsxs("label", { className: "ins-toggle", children: [_jsx("input", { type: "checkbox", id: "insight-noise" }), " ", tr('insights.showAll')] }), _jsx("button", { type: "button", id: "insight-clear", className: "ins-clear", children: tr('insights.clear') })] }), _jsx("div", { id: "insight-tabbar", children: _jsx(Html, { html: renderTabBar(tab) }) }), _jsx("div", { id: "insight-status", className: "insight-page-status" }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "overview", children: _jsx("div", { id: "insight-overview" }) }), _jsxs("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "sessions", hidden: true, children: [_jsxs("div", { id: "insight-list-view", children: [_jsxs("div", { className: "insight-list-head", children: [_jsx("span", { id: "insight-list-subtitle" }), _jsx("div", { className: "sesssort", id: "insight-sort" })] }), _jsx("div", { id: "insight-list" })] }), _jsxs("div", { id: "insight-detail-view", hidden: true, children: [_jsxs("button", { type: "button", id: "insight-back", className: "ins-back", children: ["\u2190 ", tr('insights.backToList')] }), _jsx("div", { id: "insight-detail", children: _jsx(Html, { html: renderDetailShell(undefined) }) })] })] }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "flow", hidden: true, children: _jsx("div", { id: "insight-flow" }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "dist", hidden: true, children: _jsx("div", { id: "insight-dist" }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "hot", hidden: true, children: _jsx("div", { id: "insight-hot" }) }), _jsx("div", { id: "insight-modal", className: "insight-modal", hidden: true }), _jsx("div", { id: "insight-palette", className: "insight-palette", hidden: true }), _jsx("div", { id: "insight-tip", className: "ins-tip", role: "tooltip", hidden: true })] }));
926
+ useEffect(() => {
927
+ void refresh();
928
+ }, [refresh]);
929
+ useEffect(() => {
930
+ selectedIdRef.current = selectedId;
931
+ }, [selectedId]);
932
+ useEffect(() => {
933
+ const present = new Set(presentCliKey.split(',').filter(Boolean));
934
+ setCliFilter(prev => {
935
+ let changed = false;
936
+ const next = new Set();
937
+ for (const id of prev) {
938
+ if (present.has(id))
939
+ next.add(id);
940
+ else
941
+ changed = true;
942
+ }
943
+ return changed ? next : prev;
944
+ });
945
+ }, [presentCliKey]);
946
+ useEffect(() => {
947
+ if (project && !projectOpts.some(o => o.id === project))
948
+ setProject('');
949
+ }, [project, projectOpts]);
950
+ useEffect(() => {
951
+ if (selectedId && records.length && !records.some(r => r.session.sessionId === selectedId)) {
952
+ selectedIdRef.current = null;
953
+ setSelectedId(null);
954
+ resetDetailState();
955
+ }
956
+ }, [records, resetDetailState, selectedId]);
957
+ useEffect(() => {
958
+ const want = initialSessRef.current;
959
+ if (!want || selectedId || !records.some(r => r.session.sessionId === want))
960
+ return;
961
+ initialSessRef.current = null;
962
+ selectSession(want);
963
+ }, [records, selectSession, selectedId]);
964
+ useEffect(() => {
965
+ if (detailView.tab === 'convo' && !detailView.convo.messages.length && !detailView.convo.loading)
966
+ loadConvo(true);
967
+ }, [detailView.convo.loading, detailView.convo.messages.length, detailView.tab, loadConvo]);
968
+ useEffect(() => {
969
+ const p = {};
970
+ if (tab !== 'overview')
971
+ p.tab = tab;
972
+ if (filter !== 'all')
973
+ p.filter = filter;
974
+ if (q.trim())
975
+ p.q = q.trim();
976
+ if (project)
977
+ p.project = project;
978
+ if (timeWin !== 'all')
979
+ p.time = timeWin;
980
+ if (cliFilter.size)
981
+ p.cli = [...cliFilter].join(',');
982
+ if (sessSort !== 'recent')
983
+ p.sort = sessSort;
984
+ if (sessLayout !== 'card')
985
+ p.layout = sessLayout;
986
+ if (showNoise)
987
+ p.noise = '1';
988
+ if (selectedId)
989
+ p.sess = selectedId;
990
+ try {
991
+ history.replaceState(null, '', buildInsightsHash(p));
992
+ }
993
+ catch { /* ignore */ }
994
+ }, [cliFilter, filter, project, q, selectedId, sessLayout, sessSort, showNoise, tab, timeWin]);
995
+ useEffect(() => {
996
+ const active = palette.open || modal.turnIndex !== null;
997
+ document.body.classList.toggle('insight-modal-open', active);
998
+ return () => document.body.classList.remove('insight-modal-open');
999
+ }, [modal.turnIndex, palette.open]);
1000
+ useEffect(() => {
1001
+ const onKey = (event) => {
1002
+ if ((event.key === 'k' || event.key === 'K') && (event.metaKey || event.ctrlKey)) {
1003
+ event.preventDefault();
1004
+ setPalette(p => p.open ? { open: false, q: '', idx: 0 } : { open: true, q: '', idx: 0, anchor: undefined });
1005
+ return;
1006
+ }
1007
+ if (palette.open) {
1008
+ if (event.isComposing || event.keyCode === 229)
1009
+ return;
1010
+ if (event.key === 'Escape') {
1011
+ event.preventDefault();
1012
+ setPalette({ open: false, q: '', idx: 0 });
1013
+ }
1014
+ else if (event.key === 'ArrowDown') {
1015
+ event.preventDefault();
1016
+ setPalette(p => ({ ...p, idx: Math.min(Math.max(0, paletteItems.length - 1), p.idx + 1) }));
1017
+ }
1018
+ else if (event.key === 'ArrowUp') {
1019
+ event.preventDefault();
1020
+ setPalette(p => ({ ...p, idx: Math.max(0, p.idx - 1) }));
1021
+ }
1022
+ else if (event.key === 'Enter') {
1023
+ event.preventDefault();
1024
+ const it = paletteItems[palette.idx];
1025
+ if (it)
1026
+ choosePalette(it.type, it.key);
1027
+ }
1028
+ return;
1029
+ }
1030
+ if (event.key === 'Escape' && modal.turnIndex !== null)
1031
+ closeModal();
1032
+ };
1033
+ document.addEventListener('keydown', onKey);
1034
+ return () => document.removeEventListener('keydown', onKey);
1035
+ }, [choosePalette, closeModal, modal.turnIndex, palette.idx, palette.open, paletteItems]);
1036
+ const clearFilters = () => {
1037
+ setQ('');
1038
+ setFilter('all');
1039
+ setCliFilter(new Set());
1040
+ setProject('');
1041
+ setTimeWin('all');
1042
+ setShowNoise(false);
1043
+ };
1044
+ const emptyHint = !rows.length && !showNoise ? t('insights.emptyAnalyzableHint') : undefined;
1045
+ return (_jsxs("section", { className: "page insights-page", children: [_jsxs("div", { className: "page-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "eyebrow", children: tr('nav.insights') }), _jsxs("div", { className: "insight-title-line", children: [_jsx("h1", { children: tr('insights.title') }), _jsx(InsightTabs, { active: tab, onChange: next => setTab(next) }), _jsx("div", { id: "insight-status", className: "insight-page-status", children: statusText })] })] }), _jsx("div", { className: "page-heading-actions insight-heading-actions", children: _jsx("button", { type: "button", id: "insight-palette-open", className: "insight-palette-trigger", onClick: event => {
1046
+ setPalette({
1047
+ open: true,
1048
+ q: '',
1049
+ idx: 0,
1050
+ anchor: paletteAnchorFromButton(event.currentTarget),
1051
+ });
1052
+ }, children: tr('insights.paletteOpen') }) })] }), _jsxs("form", { id: "insight-filters", className: "filters dashboard-toolbar insights-filters", onSubmit: event => event.preventDefault(), children: [_jsx("input", { type: "search", name: "q", placeholder: tr('insights.search'), value: q, onChange: event => setQ(event.currentTarget.value) }), _jsx(DropdownMenu, { id: "insight-project", className: "insight-filter-menu", ariaLabel: tr('insights.projectAll'), label: projectLabel, value: project, options: projectDropdownOptions, onChange: setProject }), _jsx(DropdownMenu, { id: "insight-time", className: "insight-filter-menu", ariaLabel: tr('insights.timeAll'), label: timeLabel, value: timeWin, options: timeDropdownOptions, onChange: setTimeWin }), _jsx(DropdownMenu, { id: "insight-review-filter", className: "insight-filter-menu insight-review-menu", ariaLabel: tr('insights.filter'), label: reviewLabel, value: filter, options: reviewDropdownOptions, onChange: setFilter }), _jsx(DropdownMenu, { id: "insight-cli-filter", className: "insight-filter-menu insight-cli-menu", ariaLabel: tr('insights.filter'), label: cliFilterLabel, value: cliFilterValue, options: cliDropdownOptions, onChange: value => setCliFilter(value === CLI_FILTER_ALL ? new Set() : new Set([value])) }), _jsxs("label", { className: "filter-toggle ins-toggle", children: [_jsx("input", { type: "checkbox", id: "insight-noise", checked: showNoise, onChange: event => setShowNoise(event.currentTarget.checked) }), _jsx("span", { className: "filter-toggle-label", children: tr('insights.showAll') }), _jsx("span", { className: "filter-toggle-switch", "aria-hidden": "true" })] }), _jsx("button", { type: "button", id: "insight-clear", className: "ins-clear", onClick: clearFilters, children: tr('insights.clear') }), _jsx("div", { className: "insight-head-acts", children: _jsx(RefreshIconButton, { id: "insight-refresh", label: tr('insights.refresh'), busy: refreshing, disabled: refreshing, onClick: () => void refresh() }) })] }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "overview", hidden: tab !== 'overview', children: _jsx("div", { id: "insight-overview", children: overview ? _jsx(OverviewContent, { data: overview }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "sessions", hidden: tab !== 'sessions', children: !selectedId ? (_jsxs("div", { id: "insight-list-view", children: [_jsxs("div", { className: "insight-list-head", children: [_jsx("span", { id: "insight-list-subtitle", children: tr('insights.listCount', { shown: rows.length, total: records.length }) }), _jsx("div", { className: "sesssort", id: "insight-sort", children: _jsx(SortBar, { sort: sessSort, layout: sessLayout, onSort: setSessSort, onLayout: setSessLayout }) })] }), _jsx("div", { id: "insight-list", children: sessLayout === 'table'
1053
+ ? _jsx(SessionTable, { records: rows, selectedId: selectedId, onSelect: selectSession, emptyHint: emptyHint })
1054
+ : _jsx(SessionRows, { records: rows, selectedId: selectedId, wide: true, onSelect: selectSession, emptyHint: emptyHint }) })] })) : (_jsxs("div", { id: "insight-detail-view", children: [_jsxs("button", { type: "button", id: "insight-back", className: "ins-back", onClick: () => { selectedIdRef.current = null; setSelectedId(null); resetDetailState(); }, children: ["\u2190 ", tr('insights.backToList')] }), _jsx("section", { id: "insight-detail", className: "insight-detail", children: selectedRecord ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "shead", children: [_jsx("h2", { children: sessionTitle(selectedRecord.session) }), _jsxs("div", { className: "smeta", children: [botDisplayName(selectedRecord.session), " \u00B7 ", String(selectedRecord.session.cliId ?? '-'), " \u00B7 ", _jsx("code", { children: String(selectedRecord.session.sessionId ?? '') })] })] }), _jsx("div", { id: "insight-detail-body", children: detailLoading ? _jsx(LoadingState, { label: tr('insights.detailLoading'), compact: true }) : detailError ? _jsx("p", { className: "mut", children: detailError }) : detailReport ? (_jsx(DetailBody, { report: detailReport, view: detailView, onViewChange: patchDetailView, onOpenPrompt: openPromptModal, onJumpTurn: jumpTurnFromRail, onOpenSpan: openSpanFromVisual, scrollTarget: scrollTarget, onScrolled: () => setScrollTarget(null), onTip: showTip, onTipHide: hideTip, onLoadConvo: loadConvo })) : _jsx("p", { className: "mut", children: tr('insights.selectSession') }) })] })) : _jsx("p", { className: "mut", children: tr('insights.selectSession') }) })] })) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "flow", hidden: tab !== 'flow', children: _jsx("div", { id: "insight-flow", children: overviewData ? _jsx(Flow, { records: rows, onSession: jumpToSession }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "dist", hidden: tab !== 'dist', children: _jsx("div", { id: "insight-dist", children: overviewData ? _jsx(Distribution, { records: rows, onSort: sort => { setSessSort(sort); setTab('sessions'); selectedIdRef.current = null; setSelectedId(null); } }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "hot", hidden: tab !== 'hot', children: _jsx("div", { id: "insight-hot", children: overviewData ? (_jsx(Hotspots, { records: rows, openHot: openHot, onToggleHot: key => setOpenHot(prev => toggleSet(prev, key)), onProject: p => { setProject(p); setTab('sessions'); selectedIdRef.current = null; setSelectedId(null); }, onSession: jumpToSession })) : null }) }), _jsx(PromptModal, { modal: modal, onClose: closeModal, onToggleRaw: () => setModal(m => ({ ...m, raw: !m.raw })) }), _jsx(CommandPalette, { palette: palette, items: paletteItems, onClose: () => setPalette({ open: false, q: '', idx: 0 }), onInput: next => setPalette(prev => ({ ...prev, open: true, q: next, idx: 0 })), onChoose: choosePalette }), _jsx(Tooltip, { tip: tip })] }));
22
1055
  }
23
1056
  export function renderInsightsPage(root) {
24
1057
  return mountReactPage(root, _jsx(InsightsPage, {}));