@agenticmail/enterprise 0.5.319 → 0.5.321

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 (330) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/CODE_OF_CONDUCT.md +31 -0
  3. package/README.md +118 -38
  4. package/SECURITY.md +42 -0
  5. package/dist/agent-heartbeat-3FWNHZFX.js +510 -0
  6. package/dist/agent-heartbeat-4RWHZR7H.js +510 -0
  7. package/dist/agent-heartbeat-6ZGB5ILY.js +510 -0
  8. package/dist/agent-heartbeat-BIVHLKFM.js +510 -0
  9. package/dist/agent-heartbeat-HRKVFK2T.js +510 -0
  10. package/dist/agent-heartbeat-JC5GWVXD.js +510 -0
  11. package/dist/agent-heartbeat-K6A4HMHB.js +510 -0
  12. package/dist/agent-heartbeat-LCDXWFVB.js +510 -0
  13. package/dist/agent-heartbeat-P7HZCZAQ.js +510 -0
  14. package/dist/agent-heartbeat-PUIRSNIO.js +510 -0
  15. package/dist/agent-heartbeat-SN5ILQ6Y.js +510 -0
  16. package/dist/agent-heartbeat-TW5YTDYC.js +510 -0
  17. package/dist/agent-heartbeat-Z2QQXROL.js +510 -0
  18. package/dist/agent-notify-OEQBCZLN.js +43 -0
  19. package/dist/{agent-tools-263HM5QU.js → agent-tools-3W7XLUYA.js} +1 -1
  20. package/dist/agent-tools-4QK7LLNP.js +9 -0
  21. package/dist/agent-tools-54VZGT6L.js +9 -0
  22. package/dist/{agent-tools-AT4D276V.js → agent-tools-AYYDPO27.js} +7 -7
  23. package/dist/{agent-tools-MSTAPX2I.js → agent-tools-F2X47FKF.js} +7 -7
  24. package/dist/{agent-tools-FA26SY5O.js → agent-tools-O6W3QAZL.js} +11 -6
  25. package/dist/agent-tools-OAWVZBMW.js +9 -0
  26. package/dist/agent-tools-QCCU74PN.js +13949 -0
  27. package/dist/chunk-2LHUARN6.js +4929 -0
  28. package/dist/chunk-2WVCNCYC.js +5087 -0
  29. package/dist/{chunk-6PWDS7KY.js → chunk-3FM6YQUK.js} +20 -20
  30. package/dist/chunk-3UAFHUEC.js +212 -0
  31. package/dist/{chunk-WJO57PMO.js → chunk-46GOWZT4.js} +20 -20
  32. package/dist/{chunk-BNRE7TSX.js → chunk-5KYJAUZV.js} +3 -3
  33. package/dist/chunk-6C5PKREN.js +467 -0
  34. package/dist/{chunk-447MTPZF.js → chunk-6ZMLNEHB.js} +3 -3
  35. package/dist/chunk-BPZQT5N5.js +25652 -0
  36. package/dist/chunk-BQM7MBPS.js +1380 -0
  37. package/dist/{chunk-ZRFKGPIU.js → chunk-C52OQNNY.js} +20 -20
  38. package/dist/chunk-C7HGQF4Y.js +25652 -0
  39. package/dist/chunk-CAHNZGGK.js +25656 -0
  40. package/dist/{chunk-FL3CH3ET.js → chunk-CK7R6UHE.js} +51 -27
  41. package/dist/chunk-D36RPWB7.js +25652 -0
  42. package/dist/{chunk-36NM2B4C.js → chunk-DJK2UPFH.js} +63 -93
  43. package/dist/chunk-DM7FTF7W.js +4929 -0
  44. package/dist/chunk-DMD24UFZ.js +5101 -0
  45. package/dist/{chunk-36XNMIHA.js → chunk-DXZGPUAF.js} +20 -20
  46. package/dist/chunk-F46WB5IL.js +5087 -0
  47. package/dist/chunk-F5QG5SQH.js +5087 -0
  48. package/dist/{chunk-JGEVQZDR.js → chunk-FLQ5FLHW.js} +13 -16
  49. package/dist/chunk-H7GP733U.js +5087 -0
  50. package/dist/{chunk-OZSQLOV6.js → chunk-HHBXWB5U.js} +415 -19
  51. package/dist/{chunk-D24JY75H.js → chunk-IMXS4N6W.js} +3 -3
  52. package/dist/{chunk-6PVBV6ZP.js → chunk-JNMDD7JY.js} +3 -3
  53. package/dist/chunk-JTV5LA47.js +1519 -0
  54. package/dist/chunk-KV6G7NZX.js +1519 -0
  55. package/dist/chunk-MU5MEBIK.js +1519 -0
  56. package/dist/chunk-NLT5MC7X.js +465 -0
  57. package/dist/{chunk-GTFZZUXX.js → chunk-NVLYIM4J.js} +51 -27
  58. package/dist/{chunk-6G5SXLXC.js → chunk-NZY2BIZH.js} +63 -93
  59. package/dist/chunk-O42L6G67.js +1519 -0
  60. package/dist/chunk-OCNERGGM.js +4891 -0
  61. package/dist/chunk-OJSNHONE.js +1519 -0
  62. package/dist/{chunk-2TAZJWJN.js → chunk-OWL3QVH7.js} +18 -0
  63. package/dist/{chunk-P3HVY2HS.js → chunk-OWTLNV4Q.js} +382 -7
  64. package/dist/chunk-PCNYEP6T.js +4891 -0
  65. package/dist/{chunk-YL3Z5KPR.js → chunk-PI4AQ4Z6.js} +438 -15
  66. package/dist/chunk-PN3EGTCA.js +194 -0
  67. package/dist/chunk-Q37UKNRC.js +1519 -0
  68. package/dist/chunk-QXTC6J7H.js +5087 -0
  69. package/dist/{chunk-SPBQVNDI.js → chunk-RKERL5LZ.js} +25 -21
  70. package/dist/chunk-RVBK2IOX.js +25652 -0
  71. package/dist/chunk-SAKODCZ5.js +4891 -0
  72. package/dist/{chunk-XV4TU65E.js → chunk-SALGFC5L.js} +51 -27
  73. package/dist/chunk-STGWZ2MS.js +1519 -0
  74. package/dist/chunk-UY3ZVQDP.js +25652 -0
  75. package/dist/chunk-V6OSD62M.js +5087 -0
  76. package/dist/chunk-VP6YAHX4.js +1519 -0
  77. package/dist/chunk-WDYJOEAI.js +5087 -0
  78. package/dist/chunk-WEAFQNOS.js +195 -0
  79. package/dist/chunk-XKUSAZGP.js +5087 -0
  80. package/dist/chunk-Z6K5FKAB.js +548 -0
  81. package/dist/chunk-ZGE3XAXY.js +1519 -0
  82. package/dist/chunk-ZGYVXYQQ.js +3296 -0
  83. package/dist/cli-agent-7TB2BWS6.js +2370 -0
  84. package/dist/cli-agent-AKXFFST2.js +2370 -0
  85. package/dist/cli-agent-DZTKLITB.js +2357 -0
  86. package/dist/cli-agent-FOF7PFEP.js +2357 -0
  87. package/dist/cli-agent-H74M2ZYN.js +2357 -0
  88. package/dist/cli-agent-HORWVPHB.js +2370 -0
  89. package/dist/cli-agent-HSZT6SKF.js +2423 -0
  90. package/dist/cli-agent-JLUQ4ZU6.js +2424 -0
  91. package/dist/cli-agent-MVCDH4HV.js +2370 -0
  92. package/dist/cli-agent-NZXOEPJ2.js +2357 -0
  93. package/dist/cli-agent-PADN3QRC.js +2357 -0
  94. package/dist/cli-agent-QAYEX3BE.js +2441 -0
  95. package/dist/cli-agent-QT64DT5J.js +2370 -0
  96. package/dist/cli-agent-TFL2M6UK.js +2424 -0
  97. package/dist/cli-agent-UIKXATTD.js +2357 -0
  98. package/dist/cli-agent-UJN6FYTO.js +2370 -0
  99. package/dist/cli-agent-VIQAYVY4.js +2357 -0
  100. package/dist/cli-agent-WNWFVOFM.js +2370 -0
  101. package/dist/cli-agent-XBQX67VJ.js +2423 -0
  102. package/dist/cli-agent-ZLSC6FF4.js +2357 -0
  103. package/dist/cli-serve-2IL5DTEY.js +153 -0
  104. package/dist/cli-serve-47N5UKKW.js +153 -0
  105. package/dist/cli-serve-4XGZFUV2.js +140 -0
  106. package/dist/cli-serve-6OT3UEAN.js +140 -0
  107. package/dist/cli-serve-7L6EY5UH.js +153 -0
  108. package/dist/cli-serve-BDGOOOKQ.js +260 -0
  109. package/dist/cli-serve-BFNIW2LF.js +153 -0
  110. package/dist/cli-serve-C7MN6U5Q.js +153 -0
  111. package/dist/cli-serve-CR3OY3IM.js +153 -0
  112. package/dist/cli-serve-DAJFRWQ7.js +153 -0
  113. package/dist/cli-serve-FW6FHFW4.js +153 -0
  114. package/dist/cli-serve-GEEOQS77.js +153 -0
  115. package/dist/cli-serve-H562I3ZK.js +153 -0
  116. package/dist/cli-serve-HDQZF4C4.js +153 -0
  117. package/dist/cli-serve-LICAOMEB.js +140 -0
  118. package/dist/cli-serve-LLGYLWFS.js +153 -0
  119. package/dist/cli-serve-N3OISDNB.js +153 -0
  120. package/dist/cli-serve-TIZ27EVR.js +153 -0
  121. package/dist/cli-serve-TUNI2RCN.js +153 -0
  122. package/dist/cli-serve-WNOZMAWD.js +153 -0
  123. package/dist/cli-validate-Z726VJCN.js +150 -0
  124. package/dist/cli.js +4 -4
  125. package/dist/connection-manager-KAWEUWUR.js +9 -0
  126. package/dist/dashboard/app.js +9 -3
  127. package/dist/dashboard/components/knowledge-link.js +15 -0
  128. package/dist/dashboard/components/settings-help.js +4 -2
  129. package/dist/dashboard/docs/agent-deployment.html +33 -1
  130. package/dist/dashboard/docs/settings-network.html +321 -0
  131. package/dist/dashboard/docs/settings-security.html +347 -0
  132. package/dist/dashboard/docs/settings-tool-security.html +176 -0
  133. package/dist/dashboard/docs/settings.html +36 -16
  134. package/dist/dashboard/pages/agent-detail/deployment.js +39 -6
  135. package/dist/dashboard/pages/agent-detail/tools.js +10 -0
  136. package/dist/dashboard/pages/database-access.js +4 -3
  137. package/dist/dashboard/pages/settings.js +174 -37
  138. package/dist/dashboard/pages/task-pipeline.js +400 -843
  139. package/dist/db-adapter-2T56ORSD.js +7 -0
  140. package/dist/db-adapter-IRHOUMVC.js +7 -0
  141. package/dist/index.js +41 -41
  142. package/dist/microsoft-VREAZ7M2.js +3955 -0
  143. package/dist/routes-3MMLQTB6.js +90 -0
  144. package/dist/routes-4ZUIJ4HE.js +90 -0
  145. package/dist/routes-5MXHKKH4.js +90 -0
  146. package/dist/routes-64NJFK3B.js +90 -0
  147. package/dist/routes-6AKQ2LBV.js +90 -0
  148. package/dist/routes-CRRBUDO4.js +90 -0
  149. package/dist/routes-DIAF3MC3.js +90 -0
  150. package/dist/routes-KMUNU6CY.js +90 -0
  151. package/dist/routes-LRRLXIZR.js +90 -0
  152. package/dist/routes-N647AJYG.js +90 -0
  153. package/dist/routes-SSSELAAR.js +90 -0
  154. package/dist/routes-STERVGKJ.js +90 -0
  155. package/dist/routes-ZEZZACZP.js +90 -0
  156. package/dist/runtime-5EQN4GFM.js +45 -0
  157. package/dist/runtime-5LP7PUD4.js +45 -0
  158. package/dist/runtime-6BULDBR3.js +45 -0
  159. package/dist/runtime-6YEENDN3.js +45 -0
  160. package/dist/runtime-7LQFRG3B.js +45 -0
  161. package/dist/runtime-AMXJU2MB.js +45 -0
  162. package/dist/runtime-D6WSE7FG.js +45 -0
  163. package/dist/runtime-EYVN7NFJ.js +45 -0
  164. package/dist/runtime-F6RPWQVW.js +45 -0
  165. package/dist/runtime-FYMJURFC.js +45 -0
  166. package/dist/runtime-JRNBL4O4.js +45 -0
  167. package/dist/runtime-OM2NIBMI.js +45 -0
  168. package/dist/runtime-QWPVD7CY.js +45 -0
  169. package/dist/runtime-YLIIPTE4.js +45 -0
  170. package/dist/runtime-YU6P22CG.js +45 -0
  171. package/dist/screen-unlock-4RPZBHOI.js +118 -0
  172. package/dist/server-AMCSXINC.js +28 -0
  173. package/dist/server-CU6LVQS4.js +28 -0
  174. package/dist/server-DFYGH2CV.js +28 -0
  175. package/dist/server-EELWOC3X.js +28 -0
  176. package/dist/server-EN5E2OWQ.js +28 -0
  177. package/dist/server-GW2HYJYI.js +28 -0
  178. package/dist/server-J25NCRWJ.js +28 -0
  179. package/dist/server-JDGNOTFV.js +28 -0
  180. package/dist/server-NE5HD5DJ.js +28 -0
  181. package/dist/server-NQOT7W77.js +28 -0
  182. package/dist/server-PWE5PQTR.js +28 -0
  183. package/dist/server-Q2Q32H2B.js +28 -0
  184. package/dist/server-Q77ME7TL.js +28 -0
  185. package/dist/server-WLLH4WST.js +28 -0
  186. package/dist/server-WTUJ2O3F.js +28 -0
  187. package/dist/server-X4CJTHHF.js +28 -0
  188. package/dist/server-XK3ILCJC.js +28 -0
  189. package/dist/server-ZRD3NDJE.js +28 -0
  190. package/dist/setup-44VBAO4J.js +20 -0
  191. package/dist/setup-4ONNQBWB.js +20 -0
  192. package/dist/setup-4OSBXSCL.js +20 -0
  193. package/dist/setup-4QFGRBLZ.js +20 -0
  194. package/dist/setup-6766SGAR.js +20 -0
  195. package/dist/setup-AYY24DKM.js +20 -0
  196. package/dist/setup-B34N4HPU.js +20 -0
  197. package/dist/setup-E2YLC2EY.js +20 -0
  198. package/dist/setup-ER6NXTY5.js +20 -0
  199. package/dist/setup-H2AGCBW5.js +20 -0
  200. package/dist/setup-ICOZRKCX.js +20 -0
  201. package/dist/setup-JFTJH7UF.js +20 -0
  202. package/dist/setup-PRFNI6YW.js +20 -0
  203. package/dist/setup-RAHBMYHE.js +20 -0
  204. package/dist/setup-TXPR5UQX.js +20 -0
  205. package/dist/setup-XCJMELVU.js +20 -0
  206. package/dist/setup-XIYEIFVK.js +20 -0
  207. package/dist/setup-Z4PZSHBI.js +20 -0
  208. package/dist/skills-FR7I5V7H.js +16 -0
  209. package/dist/skills-HCVBA6PK.js +16 -0
  210. package/dist/system-prompts-TM7OA32C.js +913 -0
  211. package/dist/task-queue-O7IVZYUO.js +9 -0
  212. package/dist/transport-encryption-2T7PIXKG.js +25 -0
  213. package/logs/cloudflared-error.log +61 -0
  214. package/logs/cloudflared-out.log +0 -0
  215. package/logs/enterprise-error.log +0 -0
  216. package/logs/enterprise-out.log +3 -0
  217. package/logs/fola-error.log +0 -0
  218. package/logs/fola-out.log +0 -0
  219. package/logs/john-error.log +8 -0
  220. package/logs/john-out.log +0 -0
  221. package/package.json +31 -3
  222. package/src/agent-tools/tool-resolver.ts +50 -61
  223. package/src/agent-tools/tools/enterprise-database.ts +5 -5
  224. package/src/agent-tools/tools/local/dependency-manager.ts +2 -2
  225. package/src/agent-tools/tools/microsoft/graph-api.ts +137 -26
  226. package/src/agent-tools/tools/microsoft/outlook-mail.ts +392 -100
  227. package/src/agent-tools/tools/microsoft/teams.ts +267 -48
  228. package/src/auth/routes.ts +4 -4
  229. package/src/cli-agent.ts +108 -8
  230. package/src/cli-serve.ts +140 -0
  231. package/src/dashboard/app.js +9 -3
  232. package/src/dashboard/components/knowledge-link.js +15 -0
  233. package/src/dashboard/components/settings-help.js +4 -2
  234. package/src/dashboard/docs/agent-deployment.html +33 -1
  235. package/src/dashboard/docs/settings-network.html +321 -0
  236. package/src/dashboard/docs/settings-security.html +347 -0
  237. package/src/dashboard/docs/settings-tool-security.html +176 -0
  238. package/src/dashboard/docs/settings.html +36 -16
  239. package/src/dashboard/pages/agent-detail/deployment.js +39 -6
  240. package/src/dashboard/pages/agent-detail/tools.js +10 -0
  241. package/src/dashboard/pages/database-access.js +4 -3
  242. package/src/dashboard/pages/settings.js +174 -37
  243. package/src/dashboard/pages/task-pipeline.js +400 -843
  244. package/src/database-access/agent-tools.ts +78 -63
  245. package/src/database-access/connection-manager.ts +13 -2
  246. package/src/database-access/routes.ts +13 -1
  247. package/src/db/adapter.ts +1 -0
  248. package/src/engine/agent-memory.ts +2 -1
  249. package/src/engine/agent-notify.ts +50 -0
  250. package/src/engine/agent-routes.ts +257 -4
  251. package/src/engine/db-adapter.ts +16 -0
  252. package/src/engine/lifecycle.ts +4 -0
  253. package/src/engine/routes.ts +4 -3
  254. package/src/engine/screen-unlock.ts +136 -0
  255. package/src/engine/skills/database-access.ts +78 -0
  256. package/src/engine/skills/index.ts +3 -2
  257. package/src/engine/skills.ts +2 -0
  258. package/src/engine/task-queue-routes.ts +18 -0
  259. package/src/engine/task-queue.ts +15 -2
  260. package/src/middleware/transport-encryption.ts +1 -4
  261. package/src/runtime/agent-loop.ts +4 -0
  262. package/src/runtime/index.ts +15 -6
  263. package/src/server.ts +14 -1
  264. package/src/system-prompts/google/index.ts +1 -2
  265. package/src/system-prompts/index.ts +1 -1
  266. package/src/system-prompts/microsoft/contacts.ts +34 -0
  267. package/src/system-prompts/microsoft/excel.ts +52 -0
  268. package/src/system-prompts/microsoft/index.ts +31 -0
  269. package/src/system-prompts/microsoft/onedrive.ts +41 -0
  270. package/src/system-prompts/microsoft/onenote.ts +36 -0
  271. package/src/system-prompts/microsoft/outlook-calendar.ts +37 -0
  272. package/src/system-prompts/microsoft/outlook-mail.ts +46 -0
  273. package/src/system-prompts/microsoft/planner.ts +37 -0
  274. package/src/system-prompts/microsoft/powerbi.ts +38 -0
  275. package/src/system-prompts/microsoft/powerpoint.ts +35 -0
  276. package/src/system-prompts/microsoft/sharepoint.ts +44 -0
  277. package/src/system-prompts/microsoft/teams.ts +49 -0
  278. package/src/system-prompts/microsoft/todo.ts +37 -0
  279. package/src/types/hono-env.ts +4 -0
  280. package/.github/CODEOWNERS +0 -23
  281. package/.github/workflows/publish-community-skills.yml +0 -121
  282. package/.github/workflows/validate-community-skills.yml +0 -172
  283. package/agriculture_southwest_nigeria_research.txt +0 -10
  284. package/boa_credit_cards_research.txt +0 -10
  285. package/customer_support_research_feb2026.txt +0 -10
  286. package/dist/agent-tools-LRA7PPXG.js +0 -13922
  287. package/dist/agent-tools-VAU5DOQB.js +0 -13910
  288. package/dist/agent-tools-VWV7OWXU.js +0 -13922
  289. package/dist/chunk-2Z7MWTCX.js +0 -4977
  290. package/dist/chunk-3T4XU3VV.js +0 -5010
  291. package/dist/chunk-445QM4NX.js +0 -5061
  292. package/dist/chunk-5TW3Y7DJ.js +0 -1519
  293. package/dist/chunk-6I7VY3LT.js +0 -5060
  294. package/dist/chunk-6W5EK3UP.js +0 -4977
  295. package/dist/chunk-AQMSHJQT.js +0 -5069
  296. package/dist/chunk-ASSQW7HX.js +0 -5051
  297. package/dist/chunk-CIN27FGC.js +0 -5037
  298. package/dist/chunk-CMXY3NUB.js +0 -4977
  299. package/dist/chunk-DRLMRUDP.js +0 -5052
  300. package/dist/chunk-EHI7Z446.js +0 -1519
  301. package/dist/chunk-FEAILFAQ.js +0 -1519
  302. package/dist/chunk-GA3PYBZL.js +0 -1519
  303. package/dist/chunk-GWX63G5J.js +0 -1519
  304. package/dist/chunk-HHMZ4UY6.js +0 -1519
  305. package/dist/chunk-HVQMNF7E.js +0 -4921
  306. package/dist/chunk-HXM7F3YN.js +0 -1519
  307. package/dist/chunk-K6NGOUXG.js +0 -5060
  308. package/dist/chunk-KPG5WINJ.js +0 -4977
  309. package/dist/chunk-LBCUBYDL.js +0 -1519
  310. package/dist/chunk-LIRQSWLR.js +0 -5014
  311. package/dist/chunk-LRCKO5KE.js +0 -1519
  312. package/dist/chunk-M7XL3DJD.js +0 -5069
  313. package/dist/chunk-MHJULEIQ.js +0 -1519
  314. package/dist/chunk-MJGGW6MC.js +0 -106
  315. package/dist/chunk-MMYBDHDB.js +0 -4921
  316. package/dist/chunk-MQT5FXKD.js +0 -1519
  317. package/dist/chunk-OIMPEQF5.js +0 -4977
  318. package/dist/chunk-OOU7JUYE.js +0 -542
  319. package/dist/chunk-OW4GLBHP.js +0 -1519
  320. package/dist/chunk-Q4K4MMLU.js +0 -4977
  321. package/dist/chunk-RUK4CRPF.js +0 -1519
  322. package/dist/chunk-T7H65XQY.js +0 -1519
  323. package/dist/chunk-TQVFWG57.js +0 -5064
  324. package/dist/chunk-UEPK3IMC.js +0 -1519
  325. package/dist/chunk-VUWTXJH6.js +0 -1519
  326. package/dist/chunk-WCPGGSAD.js +0 -1519
  327. package/dist/chunk-WO63NZOJ.js +0 -1519
  328. package/dist/chunk-YPJDRVUM.js +0 -5064
  329. package/dist/chunk-ZROMH5DL.js +0 -4921
  330. package/src/dashboard/docs/_template.txt +0 -92
@@ -6,17 +6,11 @@ import { useOrgContext } from '../components/org-switcher.js';
6
6
  import { KnowledgeLink } from '../components/knowledge-link.js';
7
7
 
8
8
  // ─── Constants ───────────────────────────────────────────
9
- var NODE_W = 200;
10
- var NODE_H = 52;
11
- var AGENT_W = 130;
12
- var AGENT_H = 40;
13
- var H_GAP = 32; // horizontal gap (left→right flow)
14
- var V_GAP = 12; // vertical gap between lanes
15
- var PAD = 16;
16
-
9
+ var PAGE_SIZES = [25, 50, 100];
17
10
  var STATUS_COLORS = { created: '#6366f1', assigned: '#991b1b', in_progress: '#06b6d4', completed: '#15803d', failed: '#ef4444', cancelled: '#6b7394' };
18
11
  var PRIORITY_COLORS = { urgent: '#ef4444', high: '#991b1b', normal: '#6366f1', low: '#6b7394' };
19
12
  var DELEGATION_COLORS = { delegation: '#6366f1', review: '#991b1b', revision: '#f97316', escalation: '#ef4444', return: '#15803d' };
13
+
20
14
  function sourceBadge(src) {
21
15
  var meta = { telegram: { color: '#0088cc' }, whatsapp: { color: '#25d366' }, email: { color: '#ea4335' }, google_chat: { color: '#1a73e8' }, internal: { color: '#6b7394' }, api: { color: '#8b5cf6' } };
22
16
  var icons = { telegram: E.telegram, whatsapp: E.whatsapp, email: E.email, google_chat: E.google, internal: E.gear, api: E.link };
@@ -25,157 +19,11 @@ function sourceBadge(src) {
25
19
  var label = src ? src.replace(/_/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); }) : 'Unknown';
26
20
  return h('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 9, padding: '1px 5px', borderRadius: 4, background: m.color + '18', color: m.color, fontWeight: 600, whiteSpace: 'nowrap', flexShrink: 0 } }, iconFn(10), ' ', label);
27
21
  }
28
- // Theme-aware: use CSS variables where possible, detect dark/light
29
- function isDark() { try { return window.matchMedia('(prefers-color-scheme: dark)').matches || document.documentElement.classList.contains('dark') || document.body.getAttribute('data-theme') === 'dark'; } catch(e) { return true; } }
30
- var BG = 'var(--bg-canvas, var(--bg-primary, #0a0c14))';
31
- var EDGE_COLOR = 'var(--tp-edge, rgba(128,128,128,0.25))';
32
- var EDGE_HL = 'rgba(99,102,241,0.7)';
33
22
 
34
- // ─── CSS Keyframes (injected once) ──────────────────────
35
- var _injected = false;
36
- function injectCSS() {
37
- if (_injected) return; _injected = true;
38
- var style = document.createElement('style');
39
- style.textContent = `
40
- :root { --tp-bg: #0a0c14; --tp-text: #fff; --tp-text-dim: rgba(255,255,255,0.4); --tp-text-faint: rgba(255,255,255,0.15); --tp-border: rgba(255,255,255,0.08); --tp-card: rgba(255,255,255,0.02); --tp-card-hover: rgba(255,255,255,0.06); --tp-edge: rgba(255,255,255,0.18); --tp-toolbar: rgba(0,0,0,0.3); --tp-metrics: rgba(0,0,0,0.12); }
41
- [data-theme="light"], .light, :root:not(.dark) { --tp-bg: var(--bg-primary, #f8fafc); --tp-text: var(--text-primary, #1e293b); --tp-text-dim: var(--text-muted, #64748b); --tp-text-faint: rgba(0,0,0,0.06); --tp-border: var(--border, rgba(0,0,0,0.08)); --tp-card: rgba(0,0,0,0.02); --tp-card-hover: rgba(0,0,0,0.05); --tp-edge: rgba(0,0,0,0.2); --tp-toolbar: rgba(0,0,0,0.03); --tp-metrics: rgba(0,0,0,0.02); }
42
- @media (prefers-color-scheme: light) { :root:not(.dark) { --tp-bg: var(--bg-primary, #f8fafc); --tp-text: var(--text-primary, #1e293b); --tp-text-dim: var(--text-muted, #64748b); --tp-text-faint: rgba(0,0,0,0.06); --tp-border: var(--border, rgba(0,0,0,0.08)); --tp-card: rgba(0,0,0,0.02); --tp-card-hover: rgba(0,0,0,0.05); --tp-edge: rgba(0,0,0,0.2); --tp-toolbar: rgba(0,0,0,0.03); --tp-metrics: rgba(0,0,0,0.02); } }
43
- @keyframes flowDash { to { stroke-dashoffset: -24; } }
44
- @keyframes flowPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
45
- @keyframes taskPulse { 0%,100% { box-shadow: 0 0 0 0 rgba(6,182,212,0.3); } 50% { box-shadow: 0 0 8px 2px rgba(6,182,212,0.2); } }
46
- .tp-flow-active { animation: flowDash 1.2s linear infinite; }
47
- .tp-node-active { animation: taskPulse 2s ease-in-out infinite; }
48
- .tp-node:hover { transform: scale(1.03); z-index: 10; }
49
- .tp-chain-tag { font-size: 9px; padding: 1px 5px; border-radius: 4px; font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; flex-shrink: 0; }
50
- `;
51
- document.head.appendChild(style);
23
+ function tag(color, text) {
24
+ return h('span', { style: { display: 'inline-block', fontSize: 9, padding: '1px 5px', borderRadius: 4, fontWeight: 600, letterSpacing: '0.02em', whiteSpace: 'nowrap', background: color + '22', color: color } }, text);
52
25
  }
53
26
 
54
- // ─── Layout: Left-to-Right Chain Flow ────────────────────
55
- // Tasks flow horizontally. Each chain = a horizontal row.
56
- // Multiple chains stack vertically. Circular flows curve back.
57
-
58
- function layoutChains(tasks) {
59
- if (!tasks.length) return { nodes: [], edges: [], width: 0, height: 0, chains: [] };
60
-
61
- // Group by chainId — but single-task chains are treated as orphans (no chain)
62
- var chainMap = new Map();
63
- var orphans = [];
64
- var tempChains = new Map();
65
- tasks.forEach(function(t) {
66
- if (t.chainId) {
67
- if (!tempChains.has(t.chainId)) tempChains.set(t.chainId, []);
68
- tempChains.get(t.chainId).push(t);
69
- } else {
70
- orphans.push(t);
71
- }
72
- });
73
- // Only keep chains with 2+ tasks; singles become orphans
74
- tempChains.forEach(function(arr, key) {
75
- if (arr.length > 1) { chainMap.set(key, arr); }
76
- else { orphans = orphans.concat(arr); }
77
- });
78
-
79
- // Sort each chain by chainSeq
80
- chainMap.forEach(function(arr) { arr.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); }); });
81
-
82
- // Also group orphans by agent for a simpler layout
83
- var orphansByAgent = new Map();
84
- orphans.forEach(function(t) {
85
- var key = t.assignedTo || 'unassigned';
86
- if (!orphansByAgent.has(key)) orphansByAgent.set(key, []);
87
- orphansByAgent.get(key).push(t);
88
- });
89
-
90
- var allNodes = [];
91
- var allEdges = [];
92
- var chainInfos = [];
93
- var y = PAD;
94
-
95
- // Layout each chain as a horizontal row
96
- chainMap.forEach(function(chainTasks, chainId) {
97
- var x = PAD;
98
- var rowNodes = [];
99
- var maxH = NODE_H;
100
-
101
- chainTasks.forEach(function(t, i) {
102
- var node = { id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: chainId };
103
- rowNodes.push(node);
104
- allNodes.push(node);
105
-
106
- if (i > 0) {
107
- var prev = rowNodes[i - 1];
108
- var dtype = t.delegationType || 'delegation';
109
- var isReturn = dtype === 'return' || dtype === 'revision';
110
- // Check for circular: does this task go back to an agent already seen?
111
- var seenAgents = chainTasks.slice(0, i).map(function(ct) { return ct.assignedTo; });
112
- var isCircular = seenAgents.indexOf(t.assignedTo) !== -1 && isReturn;
113
-
114
- allEdges.push({
115
- from: prev, to: node,
116
- delegationType: dtype,
117
- isCircular: isCircular,
118
- isActive: prev.task.status === 'in_progress' || t.status === 'in_progress',
119
- });
120
- }
121
-
122
- x += NODE_W + H_GAP;
123
- });
124
-
125
- // Customer context badge on first node
126
- var firstTask = chainTasks[0];
127
- chainInfos.push({
128
- chainId: chainId,
129
- y: y,
130
- taskCount: chainTasks.length,
131
- customer: firstTask.customerContext,
132
- status: chainTasks[chainTasks.length - 1].status,
133
- title: firstTask.title,
134
- });
135
-
136
- y += maxH + V_GAP + 8; // space between chains
137
- });
138
-
139
- // Layout orphans horizontally in a single row (with wrap if too many)
140
- var orphanList = [];
141
- orphansByAgent.forEach(function(agentTasks) { orphanList = orphanList.concat(agentTasks); });
142
- if (orphanList.length > 0) {
143
- var x = PAD;
144
- orphanList.forEach(function(t) {
145
- allNodes.push({ id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: null });
146
- x += NODE_W + H_GAP;
147
- });
148
- y += NODE_H + V_GAP;
149
- }
150
-
151
- var maxX = 0;
152
- allNodes.forEach(function(n) { maxX = Math.max(maxX, n.x + n.w); });
153
-
154
- return { nodes: allNodes, edges: allEdges, width: maxX + PAD, height: y + PAD, chains: chainInfos };
155
- }
156
-
157
- // ─── SVG Edge Paths ──────────────────────────────────────
158
- function horizontalPath(from, to) {
159
- var x1 = from.x + from.w;
160
- var y1 = from.y + from.h / 2;
161
- var x2 = to.x;
162
- var y2 = to.y + to.h / 2;
163
- var midX = x1 + (x2 - x1) * 0.5;
164
- return 'M ' + x1 + ' ' + y1 + ' C ' + midX + ' ' + y1 + ', ' + midX + ' ' + y2 + ', ' + x2 + ' ' + y2;
165
- }
166
-
167
- function circularPath(from, to) {
168
- // Arc back: goes up and curves back left
169
- var x1 = from.x + from.w;
170
- var y1 = from.y + from.h / 2;
171
- var x2 = to.x;
172
- var y2 = to.y + to.h / 2;
173
- var lift = 28;
174
- var topY = Math.min(y1, y2) - lift;
175
- return 'M ' + x1 + ' ' + y1 + ' C ' + (x1 + 50) + ' ' + topY + ', ' + (x2 - 50) + ' ' + topY + ', ' + x2 + ' ' + y2;
176
- }
177
-
178
- // ─── Helpers ─────────────────────────────────────────────
179
27
  function timeAgo(ts) {
180
28
  if (!ts) return '-';
181
29
  var diff = Date.now() - new Date(ts).getTime();
@@ -185,6 +33,7 @@ function timeAgo(ts) {
185
33
  if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
186
34
  return Math.floor(diff / 86400000) + 'd ago';
187
35
  }
36
+
188
37
  function formatDuration(ms) {
189
38
  if (!ms) return '-';
190
39
  var s = Math.floor(ms / 1000);
@@ -193,154 +42,189 @@ function formatDuration(ms) {
193
42
  if (m < 60) return m + 'm ' + (s % 60) + 's';
194
43
  return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
195
44
  }
196
- function tag(color, text) { return h('span', { className: 'tp-chain-tag', style: { background: color + '22', color: color } }, text); }
197
45
 
198
- var toolbarBtnStyle = {
199
- background: 'var(--tp-border)', border: '1px solid rgba(255,255,255,0.12)',
200
- borderRadius: 6, color: 'var(--tp-text)', fontSize: 12, fontWeight: 600, padding: '4px 10px', cursor: 'pointer',
201
- };
202
- function legendDot(color, label) {
203
- return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
204
- h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: color } }),
205
- h('span', { style: { color: 'var(--tp-text-dim)', fontSize: 11 } }, label)
206
- );
46
+ // ─── CSS (injected once) ─────────────────────────────────
47
+ var _injected = false;
48
+ function injectCSS() {
49
+ if (_injected) return; _injected = true;
50
+ var style = document.createElement('style');
51
+ style.textContent = [
52
+ '@keyframes flowPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }',
53
+ '@keyframes taskPulse { 0%,100% { box-shadow: none; } 50% { box-shadow: 0 0 0 2px rgba(6,182,212,0.15); } }',
54
+ '.tp-row { transition: background 0.15s; }',
55
+ '.tp-row:hover { background: var(--bg-secondary) !important; }',
56
+ '.tp-row-active { animation: taskPulse 3s ease-in-out infinite; }',
57
+ '.tp-tab { padding: 6px 14px; font-size: 12px; font-weight: 600; border: none; background: none; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s; white-space: nowrap; }',
58
+ '.tp-tab:hover { color: var(--text-primary); }',
59
+ '.tp-tab-active { color: var(--text-primary); border-bottom-color: #6366f1; }',
60
+ '@keyframes flowDash { to { stroke-dashoffset: -24; } }',
61
+ '.tp-flow-active { animation: flowDash 1.2s linear infinite; }',
62
+ ].join('\n');
63
+ document.head.appendChild(style);
207
64
  }
208
- var _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
209
- var _ul = { paddingLeft: 20, margin: '4px 0 8px' };
210
- var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
211
65
 
212
- // ─── Customer Profile Mini-Card ──────────────────────────
213
- function CustomerBadge(props) {
214
- var c = props.customer;
215
- if (!c) return null;
216
- return h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px', background: 'rgba(99,102,241,0.08)', border: '1px solid rgba(99,102,241,0.2)', borderRadius: 8, fontSize: 11, marginBottom: 4 } },
217
- h('div', { style: { width: 20, height: 20, borderRadius: '50%', background: 'linear-gradient(135deg, #6366f1, #a855f7)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--tp-text)', fontSize: 9, fontWeight: 700, flexShrink: 0 } }, (c.name || '?').charAt(0).toUpperCase()),
218
- h('div', { style: { overflow: 'hidden' } },
219
- h('div', { style: { fontWeight: 600, color: 'var(--tp-text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, c.name || 'Unknown'),
220
- h('div', { style: { color: 'var(--tp-text-dim)', fontSize: 9 } },
221
- c.isNew ? 'New customer' : (c.company || c.email || c.channel || '')
222
- )
66
+ // ─── Stats Cards ─────────────────────────────────────────
67
+ function StatsRow(props) {
68
+ var s = props.stats;
69
+ function card(label, value, color, sub) {
70
+ return h('div', { style: { flex: '1 1 0', minWidth: 100, padding: '12px 16px', background: 'var(--bg-secondary)', borderRadius: 'var(--radius)', border: '1px solid var(--border)' } },
71
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 } }, label),
72
+ h('div', { style: { fontSize: 22, fontWeight: 700, color: color, lineHeight: 1 } }, value),
73
+ sub && h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 4 } }, sub)
74
+ );
75
+ }
76
+ return h('div', { style: { display: 'flex', gap: 10, marginBottom: 16, flexWrap: 'wrap' } },
77
+ card('Active', s.inProgress || 0, '#06b6d4', (s.created || 0) + ' created, ' + (s.assigned || 0) + ' assigned'),
78
+ card('Completed', s.completed || 0, '#15803d', (s.todayCompleted || 0) + ' today'),
79
+ card('Failed', (s.failed || 0) + (s.cancelled || 0), '#ef4444', (s.todayFailed || 0) + ' today'),
80
+ card('Total', s.total || 0, 'var(--text-primary)', s.avgDurationMs > 0 ? 'Avg ' + formatDuration(s.avgDurationMs) : ''),
81
+ (s.totalCost > 0 || s.totalTokens > 0) && card('Usage',
82
+ s.totalTokens > 999999 ? (s.totalTokens / 1000000).toFixed(1) + 'M' : s.totalTokens > 999 ? (s.totalTokens / 1000).toFixed(1) + 'K' : s.totalTokens || 0,
83
+ '#a855f7',
84
+ s.totalCost > 0 ? '$' + s.totalCost.toFixed(2) + ' spent' : 'tokens'
223
85
  )
224
86
  );
225
87
  }
226
88
 
227
- // ─── Task Detail Modal ───────────────────────────────────
228
- // ─── Activity Log Component ──────────────────────────────
229
- var ACTIVITY_PAGE_SIZE = 10;
89
+ // ─── Activity Log ────────────────────────────────────────
230
90
  var ACTIVITY_TYPE_COLORS = {
231
91
  created: '#6366f1', assigned: '#991b1b', started: '#06b6d4', in_progress: '#06b6d4',
232
92
  completed: '#15803d', failed: '#ef4444', cancelled: '#6b7394', delegated: '#a855f7',
233
- compaction: '#8b5cf6', error: '#ef4444',
234
- crash: '#dc2626', recovery: '#f59e0b', note: '#3b82f6',
93
+ compaction: '#8b5cf6', error: '#ef4444', crash: '#dc2626', recovery: '#f59e0b', note: '#3b82f6',
235
94
  };
236
95
 
237
96
  function ActivityLog(props) {
238
97
  var entries = props.entries || [];
239
- var _search = useState(''); var search = _search[0]; var setSearch = _search[1];
240
- var _typeFilter = useState('all'); var typeFilter = _typeFilter[0]; var setTypeFilter = _typeFilter[1];
241
98
  var _page = useState(0); var page = _page[0]; var setPage = _page[1];
242
-
243
- // Get unique types for filter dropdown
244
- var types = [];
245
- var seen = {};
246
- entries.forEach(function(e) { if (e.type && !seen[e.type]) { seen[e.type] = true; types.push(e.type); } });
247
-
248
- // Filter
249
- var filtered = entries.filter(function(e) {
250
- if (typeFilter !== 'all' && e.type !== typeFilter) return false;
251
- if (search) {
252
- var q = search.toLowerCase();
253
- return (e.type || '').toLowerCase().includes(q) || (e.detail || '').toLowerCase().includes(q) || (e.agent || '').toLowerCase().includes(q);
254
- }
255
- return true;
256
- });
257
-
258
- var totalPages = Math.max(1, Math.ceil(filtered.length / ACTIVITY_PAGE_SIZE));
259
- if (page >= totalPages) page = totalPages - 1;
260
- var pageEntries = filtered.slice(page * ACTIVITY_PAGE_SIZE, (page + 1) * ACTIVITY_PAGE_SIZE);
99
+ var perPage = 10;
100
+ var totalPages = Math.max(1, Math.ceil(entries.length / perPage));
101
+ var pageEntries = entries.slice(page * perPage, (page + 1) * perPage);
261
102
 
262
103
  return h('div', { style: { marginBottom: 16 } },
263
- h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, gap: 8, flexWrap: 'wrap' } },
264
- h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)' } }, 'ACTIVITY LOG (' + filtered.length + ')'),
265
- h('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
266
- h('input', {
267
- placeholder: 'Search...', value: search,
268
- onChange: function(e) { setSearch(e.target.value); setPage(0); },
269
- style: { padding: '3px 8px', fontSize: 11, borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: 'var(--text-primary)', width: 120, outline: 'none' }
270
- }),
271
- h('select', {
272
- value: typeFilter,
273
- onChange: function(e) { setTypeFilter(e.target.value); setPage(0); },
274
- style: { padding: '3px 8px', fontSize: 11, borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: 'var(--text-primary)', outline: 'none' }
275
- },
276
- h('option', { value: 'all' }, 'All types'),
277
- types.map(function(t) { return h('option', { key: t, value: t }, t); })
278
- )
279
- )
280
- ),
104
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6 } }, 'ACTIVITY LOG (' + entries.length + ')'),
281
105
  h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
282
106
  pageEntries.map(function(entry, i) {
283
107
  var tc = ACTIVITY_TYPE_COLORS[entry.type] || 'var(--text-muted)';
284
- return h('div', { key: page * ACTIVITY_PAGE_SIZE + i, style: { display: 'flex', gap: 8, padding: '6px 10px', borderBottom: i < pageEntries.length - 1 ? '1px solid var(--border)' : 'none', fontSize: 11, alignItems: 'flex-start' } },
108
+ return h('div', { key: page * perPage + i, style: { display: 'flex', gap: 8, padding: '5px 10px', borderBottom: i < pageEntries.length - 1 ? '1px solid var(--border)' : 'none', fontSize: 11, alignItems: 'flex-start' } },
285
109
  h('span', { style: { color: 'var(--text-muted)', flexShrink: 0, fontFamily: 'var(--font-mono)', fontSize: 10, minWidth: 65 } }, entry.ts ? new Date(entry.ts).toLocaleTimeString() : ''),
286
110
  h('span', { style: { fontWeight: 600, flexShrink: 0, minWidth: 70, color: tc, padding: '0 4px', borderRadius: 4, background: tc + '15' } }, entry.type),
287
111
  h('span', { style: { color: 'var(--text-secondary)', wordBreak: 'break-word' } }, entry.detail)
288
112
  );
289
113
  }),
290
- pageEntries.length === 0 && h('div', { style: { padding: '12px 10px', fontSize: 11, color: 'var(--text-muted)', textAlign: 'center' } }, 'No matching entries')
114
+ pageEntries.length === 0 && h('div', { style: { padding: 12, fontSize: 11, color: 'var(--text-muted)', textAlign: 'center' } }, 'No entries')
291
115
  ),
292
- // Pagination
293
- totalPages > 1 && h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 6, fontSize: 11 } },
294
- h('span', { style: { color: 'var(--text-muted)' } }, 'Page ' + (page + 1) + ' of ' + totalPages),
116
+ totalPages > 1 && h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 4, fontSize: 11 } },
117
+ h('span', { style: { color: 'var(--text-muted)' } }, 'Page ' + (page + 1) + '/' + totalPages),
295
118
  h('div', { style: { display: 'flex', gap: 4 } },
296
- h('button', {
297
- disabled: page === 0,
298
- onClick: function() { setPage(page - 1); },
299
- style: { padding: '2px 8px', fontSize: 11, borderRadius: 4, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: page === 0 ? 'var(--text-muted)' : 'var(--text-primary)', cursor: page === 0 ? 'default' : 'pointer' }
300
- }, 'Prev'),
301
- h('button', {
302
- disabled: page >= totalPages - 1,
303
- onClick: function() { setPage(page + 1); },
304
- style: { padding: '2px 8px', fontSize: 11, borderRadius: 4, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: page >= totalPages - 1 ? 'var(--text-muted)' : 'var(--text-primary)', cursor: page >= totalPages - 1 ? 'default' : 'pointer' }
305
- }, 'Next')
119
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: page === 0, onClick: function() { setPage(page - 1); } }, 'Prev'),
120
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: page >= totalPages - 1, onClick: function() { setPage(page + 1); } }, 'Next')
306
121
  )
307
122
  )
308
123
  );
309
124
  }
310
125
 
126
+ // ─── Chain Flow (inline in detail modal) ─────────────────
127
+ function ChainFlow(props) {
128
+ var chain = props.chain;
129
+ var currentId = props.currentId;
130
+ var agentMap = props.agentMap || {};
131
+ if (!chain || chain.length < 2) return null;
132
+
133
+ var STEP_W = 110;
134
+ var STEP_H = 40;
135
+ var STEP_GAP = 36;
136
+ var totalW = chain.length * STEP_W + (chain.length - 1) * STEP_GAP;
137
+
138
+ // Build steps
139
+ var steps = chain.map(function(ct, i) {
140
+ var nextArrow = i < chain.length - 1 ? (chain[i + 1].delegationType || 'delegation') : null;
141
+ return { task: ct, label: ct.assignedToName || ct.assignedTo, status: ct.status, arrow: nextArrow, duration: ct.actualDurationMs, progress: ct.progress };
142
+ });
143
+
144
+ return h('div', { style: { marginBottom: 16 } },
145
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'DELEGATION CHAIN'),
146
+ h('div', { style: { overflowX: 'auto', padding: '4px 0' } },
147
+ h('div', { style: { position: 'relative', height: STEP_H + 12, minWidth: totalW } },
148
+ // SVG arrows
149
+ h('svg', { width: totalW, height: STEP_H + 12, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
150
+ h('defs', null,
151
+ h('marker', { id: 'chain-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
152
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: 'var(--text-muted)' })
153
+ )
154
+ ),
155
+ steps.map(function(step, i) {
156
+ if (!step.arrow || i >= steps.length - 1) return null;
157
+ var x1 = i * (STEP_W + STEP_GAP) + STEP_W;
158
+ var x2 = (i + 1) * (STEP_W + STEP_GAP);
159
+ var y = 6 + STEP_H / 2;
160
+ var arrowColor = DELEGATION_COLORS[step.arrow] || 'rgba(99,102,241,0.5)';
161
+ var isActive = step.status === 'in_progress';
162
+ return h(Fragment, { key: 'a' + i },
163
+ h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: arrowColor, strokeWidth: 2, markerEnd: 'url(#chain-arr)' }),
164
+ isActive && h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: '#06b6d4', strokeWidth: 2, strokeDasharray: '4 12', className: 'tp-flow-active', style: { opacity: 0.7 } }),
165
+ step.arrow !== 'delegation' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: arrowColor, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, step.arrow)
166
+ );
167
+ })
168
+ ),
169
+ // Step nodes
170
+ steps.map(function(step, i) {
171
+ var x = i * (STEP_W + STEP_GAP);
172
+ var sc = STATUS_COLORS[step.status] || '#6366f1';
173
+ var isMe = step.task.id === currentId;
174
+ var agent = agentMap[step.task.assignedTo];
175
+ return h('div', { key: i, style: {
176
+ position: 'absolute', left: x, top: 6, width: STEP_W, height: STEP_H,
177
+ background: isMe ? sc + '15' : 'var(--bg-secondary)',
178
+ border: '1px solid ' + (isMe ? sc : 'var(--border)'),
179
+ borderRadius: 8, display: 'flex', alignItems: 'center', gap: 6, padding: '0 8px', overflow: 'hidden',
180
+ } },
181
+ agent && agent.avatar
182
+ ? h('img', { src: agent.avatar, style: { width: 18, height: 18, borderRadius: '50%', objectFit: 'cover', flexShrink: 0 } })
183
+ : h('div', { style: { width: 18, height: 18, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, color: sc, flexShrink: 0 } }, step.label.charAt(0).toUpperCase()),
184
+ h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
185
+ h('div', { style: { fontSize: 10, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, step.label),
186
+ h('div', { style: { fontSize: 8, color: 'var(--text-muted)' } }, step.status.replace('_', ' '), step.duration ? ' · ' + formatDuration(step.duration) : '')
187
+ )
188
+ );
189
+ })
190
+ )
191
+ )
192
+ );
193
+ }
194
+
195
+ // ─── Task Detail Modal ───────────────────────────────────
311
196
  function TaskDetail(props) {
312
197
  var task = props.task;
313
198
  var chain = props.chain;
314
199
  var onClose = props.onClose;
315
200
  var onCancel = props.onCancel;
201
+ var agentMap = props.agentMap || {};
316
202
  if (!task) return null;
317
- var statusColor = STATUS_COLORS[task.status] || '#6b7394';
203
+ var sc = STATUS_COLORS[task.status] || '#6b7394';
318
204
 
319
205
  return h('div', { className: 'modal-overlay', onClick: onClose },
320
206
  h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 640, maxHeight: '85vh', overflow: 'auto' } },
321
207
  h('div', { className: 'modal-header' },
322
- h('h2', { style: { fontSize: 16 } }, task.title),
208
+ h('h2', { style: { fontSize: 16, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.title),
323
209
  h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
324
210
  ),
325
211
  h('div', { className: 'modal-body', style: { padding: 20 } },
326
- // Status badges
212
+ // Badges
327
213
  h('div', { style: { display: 'flex', gap: 6, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' } },
328
- h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } }, task.status.replace('_', ' ').toUpperCase()),
329
- h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, task.priority.toUpperCase()),
214
+ h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, fontWeight: 600, background: sc + '22', color: sc, border: '1px solid ' + sc + '44' } }, task.status.replace('_', ' ').toUpperCase()),
215
+ h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, (task.priority || 'normal').toUpperCase()),
330
216
  task.source && sourceBadge(task.source),
331
217
  task.chainId && h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: 'rgba(99,102,241,0.1)', color: '#6366f1', fontFamily: 'var(--font-mono)' } }, 'Chain #' + task.chainId.slice(0, 8)),
332
218
  task.delegationType && tag(DELEGATION_COLORS[task.delegationType] || '#6b7394', task.delegationType)
333
219
  ),
334
220
 
335
- // Customer context
221
+ // Customer
336
222
  task.customerContext && h('div', { style: { padding: 12, background: 'rgba(99,102,241,0.06)', border: '1px solid rgba(99,102,241,0.15)', borderRadius: 'var(--radius)', marginBottom: 16 } },
337
- h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--tp-text-dim)', marginBottom: 8 } }, 'CUSTOMER'),
338
- h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px 16px', fontSize: 13 } },
223
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6 } }, 'CUSTOMER'),
224
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px 16px', fontSize: 13 } },
339
225
  task.customerContext.name && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Name'), h('div', null, task.customerContext.name)),
340
226
  task.customerContext.email && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Email'), h('div', null, task.customerContext.email)),
341
- task.customerContext.company && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Company'), h('div', null, task.customerContext.company)),
342
- task.customerContext.channel && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Channel'), h('div', null, task.customerContext.channel)),
343
- h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Type'), h('div', null, task.customerContext.isNew ? 'New Customer' : 'Returning'))
227
+ task.customerContext.company && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Company'), h('div', null, task.customerContext.company))
344
228
  )
345
229
  ),
346
230
 
@@ -348,13 +232,13 @@ function TaskDetail(props) {
348
232
 
349
233
  // Progress
350
234
  task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
351
- h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, task.progress + '%')),
235
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, (task.progress || 0) + '%')),
352
236
  h('div', { style: { height: 6, background: 'var(--border)', borderRadius: 3, overflow: 'hidden' } },
353
- h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3, transition: 'width 0.3s' } })
237
+ h('div', { style: { height: '100%', width: (task.progress || 0) + '%', background: '#06b6d4', borderRadius: 3, transition: 'width 0.3s' } })
354
238
  )
355
239
  ),
356
240
 
357
- // Grid details
241
+ // Details grid
358
242
  h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 24px', fontSize: 13, marginBottom: 16 } },
359
243
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
360
244
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
@@ -365,35 +249,13 @@ function TaskDetail(props) {
365
249
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Source'), task.source ? sourceBadge(task.source) : h('div', null, '-'))
366
250
  ),
367
251
 
368
- // Task chain timeline (if part of a chain)
369
- chain && chain.length > 1 && h('div', { style: { marginBottom: 16 } },
370
- h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'DELEGATION CHAIN'),
371
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 0, overflow: 'auto', padding: '8px 0' } },
372
- chain.map(function(ct, i) {
373
- var isMe = ct.id === task.id;
374
- var sc = STATUS_COLORS[ct.status] || '#6b7394';
375
- return h(Fragment, { key: ct.id },
376
- i > 0 && h('div', { style: { display: 'flex', alignItems: 'center', flexShrink: 0 } },
377
- h('div', { style: { width: 32, height: 2, background: (DELEGATION_COLORS[ct.delegationType] || '#6366f1') + '66' } }),
378
- h('div', { style: { fontSize: 8, color: 'var(--tp-text-dim)', position: 'relative', top: -8 } }, ct.delegationType || '')
379
- ),
380
- h('div', { style: {
381
- padding: '6px 10px', borderRadius: 8, fontSize: 11, flexShrink: 0,
382
- background: isMe ? sc + '22' : 'var(--tp-card)',
383
- border: '1px solid ' + (isMe ? sc : 'var(--tp-border)'),
384
- fontWeight: isMe ? 700 : 400, color: isMe ? sc : 'var(--tp-text-dim)',
385
- } },
386
- h('div', { style: { fontWeight: 600 } }, ct.assignedToName || ct.assignedTo),
387
- h('div', { style: { fontSize: 9, marginTop: 2, opacity: 0.6 } }, ct.status.replace('_', ' '))
388
- )
389
- );
390
- })
391
- )
392
- ),
252
+ // Chain flow
253
+ chain && chain.length > 1 && h(ChainFlow, { chain: chain, currentId: task.id, agentMap: agentMap }),
393
254
 
394
- // Activity log — paginated with filter + search
255
+ // Activity log
395
256
  task.activityLog && task.activityLog.length > 0 && h(ActivityLog, { entries: task.activityLog }),
396
257
 
258
+ // Error
397
259
  task.error && h('div', { style: { padding: 12, background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 'var(--radius)', marginBottom: 16, fontSize: 13, color: '#ef4444' } }, h('strong', null, 'Error: '), task.error),
398
260
 
399
261
  // Actions
@@ -405,44 +267,6 @@ function TaskDetail(props) {
405
267
  );
406
268
  }
407
269
 
408
- // ─── Metrics Bar ─────────────────────────────────────────
409
- function MetricsBar(props) {
410
- var s = props.stats;
411
- function chip(label, value, color) {
412
- return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', background: 'var(--tp-card)', borderRadius: 6 } },
413
- h('span', { style: { fontSize: 10, color: 'var(--tp-text-dim)' } }, label),
414
- h('span', { style: { fontSize: 11, fontWeight: 700, color: color } }, value)
415
- );
416
- }
417
- var hasActivity = s.total > 0;
418
-
419
- return h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '6px 16px', borderBottom: '1px solid var(--tp-border)', background: 'var(--tp-metrics)', flexShrink: 0, overflowX: 'auto', fontSize: 11 } },
420
- h('span', { style: { fontSize: 9, color: 'var(--tp-text-faint)', fontWeight: 600, letterSpacing: '0.06em', marginRight: 2 } }, 'TODAY'),
421
- chip('Done', s.todayCompleted || 0, '#15803d'),
422
- chip('Active', s.inProgress || 0, '#06b6d4'),
423
- chip('New', s.todayCreated || 0, '#991b1b'),
424
- s.todayFailed > 0 && chip('Failed', s.todayFailed, '#ef4444'),
425
- hasActivity && h('div', { style: { width: 1, height: 14, background: 'var(--tp-border)' } }),
426
- hasActivity && h('span', { style: { fontSize: 9, color: 'var(--tp-text-faint)', fontWeight: 600, letterSpacing: '0.06em' } }, 'ALL'),
427
- hasActivity && chip('Total', s.total, 'rgba(255,255,255,0.6)'),
428
- s.avgDurationMs > 0 && chip('Avg', formatDuration(s.avgDurationMs), '#fff'),
429
- s.totalTokens > 0 && chip('Tokens', s.totalTokens > 999999 ? (s.totalTokens / 1000000).toFixed(1) + 'M' : s.totalTokens > 999 ? (s.totalTokens / 1000).toFixed(1) + 'K' : s.totalTokens, '#a855f7'),
430
- s.totalCost > 0 && chip('Cost', '$' + s.totalCost.toFixed(2), '#15803d'),
431
- s.topAgents && s.topAgents.length > 0 && h(Fragment, null,
432
- h('div', { style: { width: 1, height: 14, background: 'var(--tp-border)' } }),
433
- s.topAgents.slice(0, 3).map(function(a) {
434
- return h('div', { key: a.agent, style: { display: 'flex', alignItems: 'center', gap: 3, padding: '2px 6px', background: 'var(--tp-card)', borderRadius: 6 } },
435
- h('div', { style: { width: 12, height: 12, borderRadius: '50%', background: '#6366f133', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: '#6366f1' } }, (a.name || '?').charAt(0).toUpperCase()),
436
- h('span', { style: { fontSize: 10, color: 'var(--tp-text)', fontWeight: 600 } }, a.name),
437
- h('span', { style: { fontSize: 9, color: 'var(--tp-text-dim)' } }, a.completed + '/' + a.active)
438
- );
439
- })
440
- )
441
- );
442
- }
443
-
444
- // (ChainFlowInline removed — chain flow now renders inline on canvas)
445
-
446
270
  // ─── Main Page ───────────────────────────────────────────
447
271
  export function TaskPipelinePage() {
448
272
  injectCSS();
@@ -450,89 +274,90 @@ export function TaskPipelinePage() {
450
274
  var toast = app.toast;
451
275
  var orgCtx = useOrgContext();
452
276
  var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
453
- var _tasks = useState([]);
454
- var tasks = _tasks[0]; var setTasks = _tasks[1];
277
+
278
+ var _tasks = useState([]); var tasks = _tasks[0]; var setTasks = _tasks[1];
279
+ var _totalCount = useState(0); var totalCount = _totalCount[0]; var setTotalCount = _totalCount[1];
455
280
  var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] });
456
281
  var stats = _stats[0]; var setStats = _stats[1];
457
- var _expandedTaskId = useState(null);
458
- var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
459
- var _loading = useState(true);
460
- var loading = _loading[0]; var setLoading = _loading[1];
461
- var _selectedTask = useState(null);
462
- var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
463
- var _selectedChain = useState(null);
464
- var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
465
- var _hoveredId = useState(null);
466
- var hoveredId = _hoveredId[0]; var setHoveredId = _hoveredId[1];
467
- var _zoom = useState(1);
468
- var zoom = _zoom[0]; var setZoom = _zoom[1];
469
- var _pan = useState({ x: 0, y: 0 });
470
- var pan = _pan[0]; var setPan = _pan[1];
471
- var _dragging = useState(false);
472
- var dragging = _dragging[0]; var setDragging = _dragging[1];
473
- var _dragStart = useState({ x: 0, y: 0 });
474
- var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
475
- var _filter = useState('active');
476
- var filter = _filter[0]; var setFilter = _filter[1];
477
- var _mousePos = useState({ x: 0, y: 0 });
478
- var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
479
- var containerRef = useRef(null);
480
-
481
- var _agents = useState({});
482
- var agentMap = _agents[0]; var setAgentMap = _agents[1];
483
-
484
- var loadData = useCallback(function() {
485
- setLoading(true);
486
- Promise.all([
487
- engineCall('/task-pipeline?limit=200'),
488
- engineCall('/task-pipeline/stats'),
489
- apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).catch(function() { return { agents: [] }; }),
490
- ]).then(function(res) {
491
- var allTasks = res[0]?.tasks || [];
492
- var orgAgents = res[2]?.agents || [];
493
- // Build agent avatar/name map
282
+ var _loading = useState(true); var loading = _loading[0]; var setLoading = _loading[1];
283
+ var _tab = useState('active'); var tab = _tab[0]; var setTab = _tab[1];
284
+ var _page = useState(0); var page = _page[0]; var setPage = _page[1];
285
+ var _pageSize = useState(25); var pageSize = _pageSize[0]; var setPageSize = _pageSize[1];
286
+ var _search = useState(''); var search = _search[0]; var setSearch = _search[1];
287
+ var _sortBy = useState('createdAt'); var sortBy = _sortBy[0]; var setSortBy = _sortBy[1];
288
+ var _sortDir = useState('desc'); var sortDir = _sortDir[0]; var setSortDir = _sortDir[1];
289
+ var _selectedTask = useState(null); var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
290
+ var _selectedChain = useState(null); var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
291
+ var _agentMap = useState({}); var agentMap = _agentMap[0]; var setAgentMap = _agentMap[1];
292
+ var searchTimer = useRef(null);
293
+
294
+ // Load agents for avatar/name resolution
295
+ useEffect(function() {
296
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(function(res) {
297
+ var agents = res?.agents || res || [];
298
+ if (!Array.isArray(agents)) agents = [];
494
299
  var map = {};
495
- orgAgents.forEach(function(a) {
300
+ agents.forEach(function(a) {
496
301
  map[a.id] = { name: a.config?.name || a.name || a.id, avatar: a.config?.identity?.avatar || a.config?.avatar || a.config?.persona?.avatar || null };
497
302
  });
498
303
  setAgentMap(map);
499
- // Filter tasks by org's agents when org is selected
500
- if (orgCtx.selectedOrgId && orgAgents.length > 0) {
501
- var agentIds = {};
502
- orgAgents.forEach(function(a) { agentIds[a.id] = true; });
503
- allTasks = allTasks.filter(function(t) { return agentIds[t.assignedAgent] || agentIds[t.createdBy]; });
504
- } else if (orgCtx.selectedOrgId && orgAgents.length === 0) {
505
- allTasks = []; // No agents in this org = no tasks
506
- }
507
- setTasks(allTasks);
508
- // For client org users, compute stats from filtered tasks instead of global stats
509
- if (orgCtx.isLocked && orgCtx.clientOrgId) {
510
- var todayStart = new Date(); todayStart.setHours(0,0,0,0); var todayMs = todayStart.getTime();
511
- var cs = { created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: allTasks.length, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] };
512
- var durSum = 0, durCount = 0, am = {};
513
- allTasks.forEach(function(t) {
514
- if (t.status === 'created') cs.created++; else if (t.status === 'assigned') cs.assigned++; else if (t.status === 'in_progress') cs.inProgress++; else if (t.status === 'completed') cs.completed++; else if (t.status === 'failed') cs.failed++; else if (t.status === 'cancelled') cs.cancelled++;
515
- var cMs = new Date(t.createdAt).getTime(); if (cMs >= todayMs) cs.todayCreated++;
516
- if (t.completedAt && new Date(t.completedAt).getTime() >= todayMs) { if (t.status === 'completed') cs.todayCompleted++; if (t.status === 'failed') cs.todayFailed++; }
517
- if (t.completedAt && t.createdAt) { var dur = new Date(t.completedAt).getTime() - cMs; if (dur > 0) { durSum += dur; durCount++; } }
518
- cs.totalTokens += t.tokensUsed || 0; cs.totalCost += t.costUsd || 0;
519
- var aid = t.assignedAgent || 'unassigned';
520
- if (!am[aid]) am[aid] = { agent: aid, name: (map[aid] && map[aid].name) || aid, completed: 0, active: 0 };
521
- if (t.status === 'completed') am[aid].completed++; else if (t.status === 'in_progress') am[aid].active++;
304
+ }).catch(function() {});
305
+ }, [effectiveOrgId]);
306
+
307
+ // Load tasks
308
+ var loadTasks = useCallback(function() {
309
+ setLoading(true);
310
+ var limit = pageSize;
311
+ var offset = page * pageSize;
312
+ engineCall('/task-pipeline?limit=' + limit + '&offset=' + offset).then(function(res) {
313
+ var allTasks = res?.tasks || [];
314
+ // Client-side filtering (server should ideally do this)
315
+ var filtered = allTasks;
316
+ // Tab filter
317
+ if (tab === 'active') filtered = filtered.filter(function(t) { return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress'; });
318
+ else if (tab === 'completed') filtered = filtered.filter(function(t) { return t.status === 'completed'; });
319
+ else if (tab === 'failed') filtered = filtered.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
320
+ // Search
321
+ if (search) {
322
+ var q = search.toLowerCase();
323
+ filtered = filtered.filter(function(t) {
324
+ return (t.title || '').toLowerCase().includes(q)
325
+ || (t.assignedToName || t.assignedTo || '').toLowerCase().includes(q)
326
+ || (t.createdByName || t.createdBy || '').toLowerCase().includes(q)
327
+ || (t.description || '').toLowerCase().includes(q)
328
+ || (t.category || '').toLowerCase().includes(q);
522
329
  });
523
- if (durCount > 0) cs.avgDurationMs = durSum / durCount;
524
- cs.topAgents = Object.values(am).sort(function(a,b) { return (b.completed + b.active) - (a.completed + a.active); });
525
- setStats(cs);
526
- } else {
527
- setStats(res[1] || stats);
528
330
  }
331
+ // Sort
332
+ filtered.sort(function(a, b) {
333
+ var va = a[sortBy] || '';
334
+ var vb = b[sortBy] || '';
335
+ if (sortBy === 'createdAt' || sortBy === 'completedAt') {
336
+ va = new Date(va || 0).getTime();
337
+ vb = new Date(vb || 0).getTime();
338
+ }
339
+ if (typeof va === 'string') { va = va.toLowerCase(); vb = (vb || '').toLowerCase(); }
340
+ if (va < vb) return sortDir === 'asc' ? -1 : 1;
341
+ if (va > vb) return sortDir === 'asc' ? 1 : -1;
342
+ return 0;
343
+ });
344
+ setTasks(filtered);
345
+ setTotalCount(allTasks.length); // approximate
529
346
  }).catch(function(err) { console.error('[TaskPipeline]', err); })
530
347
  .finally(function() { setLoading(false); });
348
+ }, [effectiveOrgId, tab, page, pageSize, search, sortBy, sortDir]);
349
+
350
+ var loadStats = useCallback(function() {
351
+ engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
531
352
  }, [effectiveOrgId]);
532
353
 
533
- // SSE
534
354
  useEffect(function() {
535
- loadData();
355
+ loadTasks();
356
+ loadStats();
357
+ }, [loadTasks, loadStats]);
358
+
359
+ // SSE for real-time updates
360
+ useEffect(function() {
536
361
  var baseUrl = window.__ENGINE_BASE || '/api/engine';
537
362
  var es;
538
363
  try {
@@ -540,505 +365,238 @@ export function TaskPipelinePage() {
540
365
  es.onmessage = function(e) {
541
366
  try {
542
367
  var event = JSON.parse(e.data);
543
- if (event.type === 'init') {
544
- if (event.tasks) setTasks(function(prev) {
545
- var map = new Map();
546
- prev.forEach(function(t) { map.set(t.id, t); });
547
- event.tasks.forEach(function(t) { map.set(t.id, t); });
548
- return Array.from(map.values()).sort(function(a, b) { return new Date(b.createdAt) - new Date(a.createdAt); });
549
- });
550
- if (event.stats) setStats(event.stats);
551
- } else if (event.task) {
368
+ if (event.task) {
369
+ var matchesTab = function(status) {
370
+ if (tab === 'all') return true;
371
+ if (tab === 'active') return status === 'created' || status === 'assigned' || status === 'in_progress';
372
+ if (tab === 'completed') return status === 'completed';
373
+ if (tab === 'failed') return status === 'failed' || status === 'cancelled';
374
+ return true;
375
+ };
552
376
  setTasks(function(prev) {
553
377
  var idx = prev.findIndex(function(t) { return t.id === event.task.id; });
554
- if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
555
- return [event.task].concat(prev);
378
+ if (idx >= 0) {
379
+ // Task exists in list — update it, then remove if it no longer matches tab
380
+ if (!matchesTab(event.task.status)) {
381
+ // Status changed and no longer belongs in this tab — remove it
382
+ return prev.filter(function(t) { return t.id !== event.task.id; });
383
+ }
384
+ var next = prev.slice();
385
+ next[idx] = Object.assign({}, next[idx], event.task);
386
+ return next;
387
+ }
388
+ // New task — add if it matches current tab
389
+ if (matchesTab(event.task.status)) return [event.task].concat(prev);
390
+ return prev;
556
391
  });
557
- setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
558
- // Refresh stats on every task event for real-time metrics (skip for client org stats are computed locally)
559
- if (!orgCtx.isLocked) engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
392
+ // Update selected task if it's open
393
+ setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? Object.assign({}, prev, event.task) : prev; });
394
+ // Refresh stats
395
+ loadStats();
560
396
  }
397
+ if (event.type === 'init' && event.stats) setStats(event.stats);
561
398
  } catch (err) {}
562
399
  };
563
400
  } catch (err) {}
564
401
  return function() { if (es) es.close(); };
565
- }, []);
402
+ }, [tab]);
566
403
 
567
- useEffect(function() {
568
- if (orgCtx.isLocked) return; // Client org stats computed locally from filtered tasks
569
- var iv = setInterval(function() {
570
- engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
571
- }, 15000);
572
- return function() { clearInterval(iv); };
573
- }, []);
404
+ // (Tab filtering is handled inline in SSE handler + loadTasks)
574
405
 
575
406
  var cancelTask = useCallback(function(taskId) {
576
407
  engineCall('/task-pipeline/' + taskId + '/cancel', { method: 'POST' }).then(function() {
577
408
  toast('Task cancelled', 'success');
578
409
  setSelectedTask(null);
579
- loadData();
410
+ loadTasks();
580
411
  }).catch(function(err) { toast(err.message || 'Failed', 'error'); });
581
- }, []);
412
+ }, [loadTasks]);
582
413
 
583
- function openTaskDetail(t) {
414
+ function openTask(t) {
584
415
  setSelectedTask(t);
585
416
  if (t.chainId) {
586
- var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
587
- chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
588
- setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
417
+ engineCall('/task-pipeline/chain/' + t.chainId).then(function(res) {
418
+ var chain = res?.chain || [];
419
+ chain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
420
+ setSelectedChain(chain.length > 1 ? chain : null);
421
+ }).catch(function() { setSelectedChain(null); });
589
422
  } else {
590
423
  setSelectedChain(null);
591
424
  }
592
425
  }
593
426
 
594
- // Toggle inline chain flowchart (single click on node)
595
- function toggleExpand(t) {
596
- if (expandedTaskId === t.id) {
597
- setExpandedTaskId(null);
598
- setSelectedChain(null);
599
- } else {
600
- setExpandedTaskId(t.id);
601
- if (t.chainId) {
602
- var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
603
- chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
604
- setSelectedChain(chainTasks.length > 0 ? chainTasks : [t]);
605
- } else {
606
- setSelectedChain([t]);
607
- }
608
- }
427
+ function handleSort(col) {
428
+ if (sortBy === col) setSortDir(sortDir === 'asc' ? 'desc' : 'asc');
429
+ else { setSortBy(col); setSortDir('desc'); }
609
430
  }
610
431
 
611
- // Filter
612
- var filtered = tasks.filter(function(t) {
613
- if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
614
- if (filter === 'completed') return t.status === 'completed';
615
- if (filter === 'failed') return t.status === 'failed' || t.status === 'cancelled';
616
- return true;
617
- });
618
-
619
- // Layout
620
- var layout = layoutChains(filtered);
621
- var nodes = layout.nodes;
622
- var edges = layout.edges;
623
- var treeW = layout.width;
624
- var treeH = layout.height;
625
- var chainInfos = layout.chains;
626
-
627
- // Zoom/Pan handlers
628
- var handleWheel = useCallback(function(e) {
629
- e.preventDefault();
630
- setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
631
- }, []);
632
- var handleMouseDown = useCallback(function(e) {
633
- if (e.button !== 0 || e.target.closest('.tp-node')) return;
634
- setDragging(true);
635
- setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
636
- }, [pan]);
637
- var handleMouseMove = useCallback(function(e) {
638
- if (!dragging) return;
639
- setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
640
- }, [dragging, dragStart]);
641
- var handleMouseUp = useCallback(function() { setDragging(false); }, []);
642
- useEffect(function() {
643
- if (dragging) {
644
- window.addEventListener('mousemove', handleMouseMove);
645
- window.addEventListener('mouseup', handleMouseUp);
646
- return function() { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); };
647
- }
648
- }, [dragging, handleMouseMove, handleMouseUp]);
649
-
650
- var fitToView = useCallback(function() {
651
- // No auto-zoom — keep nodes at full size, use scroll instead
652
- setZoom(1);
653
- setPan({ x: 0, y: 0 });
654
- }, []);
655
- useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
656
-
657
- var scrollLeft = useCallback(function() {
658
- if (containerRef.current) containerRef.current.scrollBy({ left: -400, behavior: 'smooth' });
659
- }, []);
660
- var scrollRight = useCallback(function() {
661
- if (containerRef.current) containerRef.current.scrollBy({ left: 400, behavior: 'smooth' });
662
- }, []);
663
-
664
- // Highlight connected chain on hover
665
- var hoveredChainId = null;
666
- if (hoveredId) {
667
- var hn = nodes.find(function(n) { return n.id === hoveredId; });
668
- if (hn) hoveredChainId = hn.chainId;
432
+ function sortIcon(col) {
433
+ if (sortBy !== col) return h('span', { style: { opacity: 0.2, fontSize: 10 } }, '\u2195');
434
+ return h('span', { style: { fontSize: 10 } }, sortDir === 'asc' ? '\u2191' : '\u2193');
669
435
  }
670
436
 
671
- var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
672
-
673
- // ─── Toolbar ─────────────────────────────────────────
674
- var toolbar = h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderBottom: '1px solid var(--tp-border)', background: 'var(--tp-toolbar)', flexShrink: 0, flexWrap: 'wrap' } },
675
- h('div', { style: { fontWeight: 700, fontSize: 14, color: 'var(--tp-text)', display: 'flex', alignItems: 'center', gap: 6 } },
676
- I.workflow(), 'Task Pipeline',
677
- h(KnowledgeLink, { page: 'task-pipeline' }),
678
- h(HelpButton, { label: 'Task Pipeline' },
679
- h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
680
- h('h4', { style: _h4 }, 'Features'),
681
- h('ul', { style: _ul },
682
- h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
683
- h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
684
- h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
685
- h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
686
- h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
687
- h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
437
+ var handleSearch = function(e) {
438
+ var val = e.target.value;
439
+ setSearch(val);
440
+ setPage(0);
441
+ };
442
+
443
+ var totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
444
+
445
+ // Tab counts
446
+ var tabCounts = { active: (stats.inProgress || 0) + (stats.created || 0) + (stats.assigned || 0), completed: stats.completed || 0, failed: (stats.failed || 0) + (stats.cancelled || 0), all: stats.total || 0 };
447
+
448
+ return h(Fragment, null, h(orgCtx.Switcher),
449
+ h('div', { style: { padding: 0 } },
450
+ // Header
451
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, flexWrap: 'wrap' } },
452
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
453
+ I.workflow(),
454
+ h('h1', { style: { fontSize: 20, fontWeight: 700, margin: 0 } }, 'Task Pipeline'),
455
+ h(KnowledgeLink, { page: 'task-pipeline' }),
456
+ h(HelpButton, { label: 'Task Pipeline' },
457
+ h('p', null, 'View and manage all agent tasks. Tasks update in real-time via SSE.'),
458
+ h('ul', { style: { paddingLeft: 20, margin: '8px 0' } },
459
+ h('li', null, 'Click any row to see full details, chain flow, and activity log'),
460
+ h('li', null, 'Use tabs to filter by status'),
461
+ h('li', null, 'Search by title, agent, or description'),
462
+ h('li', null, 'Sort by clicking column headers')
463
+ )
464
+ )
688
465
  ),
689
- h('h4', { style: _h4 }, 'Interactions'),
690
- h('ul', { style: _ul },
691
- h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
692
- h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
693
- h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
694
- h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
466
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, marginLeft: 8 } },
467
+ h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#15803d', animation: 'flowPulse 2s infinite' } }),
468
+ h('span', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Live')
695
469
  ),
696
- h('div', { style: _tip }, h('strong', null, 'Tip: '), 'Each task has a unique chain ID linking all delegation steps. Even circular review loops (agent A \u2192 B \u2192 A) are tracked.')
697
- )
698
- ),
699
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
700
- h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#15803d', animation: 'flowPulse 2s infinite' } }),
701
- h('span', { style: { color: 'var(--tp-text-dim)', fontSize: 11 } }, 'Live')
702
- ),
703
- h('div', { style: { color: 'var(--tp-text-dim)', fontSize: 11 } },
704
- (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
705
- ),
706
- h('div', { style: { flex: 1 } }),
707
- // Filters
708
- ['active', 'all', 'completed', 'failed'].map(function(f) {
709
- return h('button', { key: f, onClick: function() { setFilter(f); }, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, background: filter === f ? 'rgba(99,102,241,0.3)' : toolbarBtnStyle.background }) }, f.charAt(0).toUpperCase() + f.slice(1));
710
- }),
711
- h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
712
- legendDot(STATUS_COLORS.in_progress, 'Active'),
713
- legendDot(STATUS_COLORS.completed, 'Done'),
714
- legendDot(STATUS_COLORS.failed, 'Failed'),
715
- h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
716
- h('button', { onClick: scrollLeft, style: toolbarBtnStyle, title: 'Scroll left' }, '\u2190'),
717
- h('button', { onClick: scrollRight, style: toolbarBtnStyle, title: 'Scroll right' }, '\u2192'),
718
- h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
719
- );
720
-
721
- if (loading) return h(Fragment, null, h(orgCtx.Switcher), h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...'));
722
-
723
- if (nodes.length === 0) return h(Fragment, null, h(orgCtx.Switcher), h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--tp-bg)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
724
- toolbar,
725
- h(MetricsBar, { stats: stats }),
726
- h('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' } },
727
- h('div', { style: { width: 48, height: 48, borderRadius: 12, background: 'rgba(99,102,241,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16, color: '#6366f1' } }, I.workflow()),
728
- h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: 'var(--tp-text)' } }, 'No Tasks in Pipeline'),
729
- h('div', { style: { color: 'var(--tp-text-dim)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
730
- )
731
- ));
732
-
733
- // Build expanded chain for inline flowchart
734
- var expandedChain = null;
735
- if (expandedTaskId) {
736
- var et = tasks.find(function(t) { return t.id === expandedTaskId; });
737
- if (et && et.chainId) {
738
- expandedChain = tasks.filter(function(ct) { return ct.chainId === et.chainId; });
739
- expandedChain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
740
- } else if (et) {
741
- expandedChain = [et];
742
- }
743
- }
744
-
745
- return h(Fragment, null, h(orgCtx.Switcher), h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--tp-bg)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
746
- toolbar,
747
- // Metrics bar
748
- h(MetricsBar, { stats: stats }),
749
- // Canvas — native scroll, no zoom transform
750
- h('div', {
751
- ref: containerRef,
752
- style: { flex: 1, overflow: 'auto', position: 'relative' },
753
- },
754
- h('div', { style: { position: 'relative', width: treeW + PAD * 2, minHeight: treeH + PAD * 2 } },
470
+ h('div', { style: { flex: 1 } }),
471
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: function() { loadTasks(); loadStats(); } }, 'Refresh')
472
+ ),
755
473
 
756
- // Chain labels (left side)
757
- chainInfos.map(function(ci, i) {
758
- return h('div', { key: ci.chainId, style: { position: 'absolute', left: 4, top: ci.y - 2, fontSize: 9, color: 'var(--tp-text-faint)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', maxWidth: PAD - 8, overflow: 'hidden' } },
759
- ci.customer && h(CustomerBadge, { customer: ci.customer })
760
- );
474
+ // Stats
475
+ h(StatsRow, { stats: stats }),
476
+
477
+ // Tabs + Search
478
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 0, borderBottom: '1px solid var(--border)', marginBottom: 0 } },
479
+ ['active', 'completed', 'failed', 'all'].map(function(t) {
480
+ var label = t.charAt(0).toUpperCase() + t.slice(1);
481
+ var count = tabCounts[t] || 0;
482
+ return h('button', {
483
+ key: t,
484
+ className: 'tp-tab' + (tab === t ? ' tp-tab-active' : ''),
485
+ onClick: function() { setTab(t); setPage(0); }
486
+ }, label + ' (' + count + ')');
761
487
  }),
488
+ h('div', { style: { flex: 1 } }),
489
+ h('input', {
490
+ type: 'text', placeholder: 'Search tasks...', value: search,
491
+ onChange: handleSearch,
492
+ style: { padding: '5px 10px', fontSize: 12, borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: 'var(--text-primary)', width: 200, outline: 'none' }
493
+ })
494
+ ),
762
495
 
763
- // SVG edges with animated flow
764
- h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
765
- h('defs', null,
766
- h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
767
- h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
768
- ),
769
- h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
770
- h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
771
- ),
772
- // Animated glow filter
773
- h('filter', { id: 'tp-glow' },
774
- h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
775
- h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
776
- )
777
- ),
778
- edges.map(function(e, i) {
779
- var fromId = e.from.id;
780
- var toId = e.to.id;
781
- var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
782
- var dim = hoveredChainId && !isHl;
783
- var dType = e.delegationType || 'delegation';
784
- var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
785
- var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
786
-
787
- return h(Fragment, { key: i },
788
- // Base path
789
- h('path', {
790
- d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
791
- markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
792
- style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
793
- }),
794
- // Animated flow dash overlay for active tasks
795
- e.isActive && h('path', {
796
- d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
797
- strokeDasharray: '6 18',
798
- className: 'tp-flow-active',
799
- filter: 'url(#tp-glow)',
800
- style: { opacity: dim ? 0.1 : 0.7 },
801
- }),
802
- // Delegation type label on edge
803
- !dim && dType !== 'delegation' && h('text', {
804
- x: (e.from.x + e.from.w + e.to.x) / 2,
805
- y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
806
- fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
807
- fontSize: 8, textAnchor: 'middle', fontWeight: 600,
808
- }, dType)
809
- );
810
- })
496
+ // Table
497
+ h('div', { style: { border: '1px solid var(--border)', borderTop: 'none', borderRadius: '0 0 var(--radius) var(--radius)', overflow: 'hidden' } },
498
+ // Header
499
+ h('div', { style: { display: 'grid', gridTemplateColumns: '2fr 1fr 100px 80px 90px 80px 60px', gap: 0, padding: '8px 12px', background: 'var(--bg-secondary)', borderBottom: '1px solid var(--border)', fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', userSelect: 'none' } },
500
+ h('div', { style: { cursor: 'pointer' }, onClick: function() { handleSort('title'); } }, 'Task ', sortIcon('title')),
501
+ h('div', { style: { cursor: 'pointer' }, onClick: function() { handleSort('assignedToName'); } }, 'Agent ', sortIcon('assignedToName')),
502
+ h('div', { style: { cursor: 'pointer' }, onClick: function() { handleSort('status'); } }, 'Status ', sortIcon('status')),
503
+ h('div', null, 'Priority'),
504
+ h('div', { style: { cursor: 'pointer' }, onClick: function() { handleSort('createdAt'); } }, 'Created ', sortIcon('createdAt')),
505
+ h('div', null, 'Duration'),
506
+ h('div', null, 'Source')
811
507
  ),
812
508
 
813
- // Task nodes
814
- nodes.map(function(node) {
815
- var t = node.task;
816
- var sc = STATUS_COLORS[t.status] || '#6b7394';
817
- var isHovered = hoveredId === node.id;
818
- var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
819
- var dim = hoveredChainId && !isChainHl;
820
- var isActive = t.status === 'in_progress';
821
-
822
- var isExpanded = expandedTaskId === node.id;
823
- return h('div', {
824
- key: node.id,
825
- className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
826
- onClick: function() { toggleExpand(t); },
827
- onDoubleClick: function() { openTaskDetail(t); },
828
- onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
829
- onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
830
- onMouseLeave: function() { setHoveredId(null); },
831
- style: {
832
- position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
833
- background: isHovered ? 'var(--tp-card-hover)' : 'var(--tp-card)',
834
- border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'var(--tp-border)'),
835
- borderRadius: 6, padding: '5px 8px', cursor: 'pointer', overflow: 'hidden',
836
- transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
837
- backdropFilter: 'blur(6px)',
838
- display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
839
- },
840
- },
841
- // Agent + title row
842
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
843
- (agentMap[t.assignedTo] && agentMap[t.assignedTo].avatar)
844
- ? h('img', { src: agentMap[t.assignedTo].avatar, style: { width: 16, height: 16, borderRadius: '50%', border: '1px solid ' + sc + '66', objectFit: 'cover', flexShrink: 0 } })
845
- : h('div', { style: { width: 16, height: 16, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: sc, flexShrink: 0, border: '1px solid ' + sc + '44' } },
846
- (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
847
- ),
848
- h('span', { style: { fontSize: 10, fontWeight: 600, color: 'var(--tp-text)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
849
- ),
850
- // Status + agent name + time
851
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'nowrap', overflow: 'hidden' } },
852
- tag(sc, t.status.replace('_', ' ')),
853
- h('span', { style: { fontSize: 9, color: 'var(--tp-text-dim)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 } }, t.assignedToName || t.assignedTo),
854
- t.source && sourceBadge(t.source),
855
- t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
856
- h('span', { style: { fontSize: 9, color: 'var(--tp-text-dim)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
857
- ),
858
- // Progress bar
859
- isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'var(--tp-border)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
860
- h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
861
- )
862
- );
863
- }),
864
-
865
- // ── Expanded chain flow (rendered ON the canvas below clicked node) ──
866
- expandedChain && expandedChain.length > 0 && (function() {
867
- // Find the clicked node position to anchor below it
868
- var anchor = nodes.find(function(n) { return n.id === expandedTaskId; });
869
- if (!anchor) return null;
870
- var flowY = anchor.y + anchor.h + 20;
871
- var flowX = anchor.x;
872
- var STEP_W = 100;
873
- var STEP_H = 36;
874
- var STEP_GAP = 32;
875
- var ARROW_W = STEP_GAP;
876
-
877
- // Build person-centric flow steps: createdBy → assignedTo for each chain task, then final status
878
- var steps = [];
879
- expandedChain.forEach(function(ct, i) {
880
- if (i === 0 && ct.createdBy && ct.createdBy !== 'system') {
881
- steps.push({ label: ct.createdByName || ct.createdBy, type: 'person', isHuman: ct.createdBy.indexOf('agent') === -1 && ct.createdBy !== 'system', status: null, arrow: ct.delegationType || 'assigned' });
882
- } else if (i === 0 && ct.createdBy === 'system') {
883
- steps.push({ label: 'System', type: 'system', isHuman: false, status: null, arrow: 'assigned' });
884
- }
885
- var nextArrow = i < expandedChain.length - 1 ? (expandedChain[i + 1].delegationType || 'delegation') : null;
886
- steps.push({ label: ct.assignedToName || ct.assignedTo, type: 'agent', isHuman: false, status: ct.status, taskId: ct.id, arrow: nextArrow, duration: ct.actualDurationMs, progress: ct.progress });
887
- });
888
- // Add final status node + ensure arrow from last agent to terminal
889
- var lastTask = expandedChain[expandedChain.length - 1];
890
- var isDone = lastTask.status === 'completed' || lastTask.status === 'failed' || lastTask.status === 'cancelled';
891
- if (isDone) {
892
- // Set arrow on the last non-terminal step so the connector draws
893
- if (steps.length > 0 && !steps[steps.length - 1].arrow) {
894
- steps[steps.length - 1].arrow = lastTask.status;
895
- }
896
- steps.push({ label: lastTask.status === 'completed' ? 'Completed!' : lastTask.status === 'failed' ? 'Failed' : 'Cancelled', type: 'terminal', isHuman: false, status: lastTask.status, arrow: null });
897
- }
898
-
899
- var totalW = steps.length * STEP_W + (steps.length - 1) * STEP_GAP;
900
-
901
- var maxFlowW = Math.max(totalW + 40, 400);
902
- var containerW = (containerRef.current ? containerRef.current.getBoundingClientRect().width / zoom : 800) - flowX;
903
- if (maxFlowW > containerW) maxFlowW = Math.max(containerW, 320);
904
-
905
- return h('div', { style: { position: 'absolute', left: flowX, top: flowY, pointerEvents: 'auto', maxWidth: maxFlowW, zIndex: 20 } },
906
- // Background card
907
- h('div', { style: { background: 'var(--bg-primary, rgba(10,12,20,0.95))', border: '1px solid rgba(99,102,241,0.2)', borderRadius: 8, padding: '8px 10px 8px', backdropFilter: 'blur(8px)', overflowX: 'auto', overflowY: 'hidden' } },
908
- // Header
909
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 } },
910
- h('span', { style: { fontSize: 9, fontWeight: 600, color: 'var(--tp-text-dim)', letterSpacing: '0.06em' } }, 'TASK FLOW'),
911
- expandedChain[0].chainId && h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.2)', fontFamily: 'var(--font-mono)' } }, '#' + expandedChain[0].chainId.slice(0, 8)),
912
- h('div', { style: { flex: 1 } }),
913
- h('button', { className: 'tp-node', onClick: function() { setExpandedTaskId(null); }, style: { background: 'none', border: 'none', color: 'var(--tp-text-dim)', cursor: 'pointer', fontSize: 14, padding: '0 2px' } }, '\u00D7')
914
- ),
915
- // Flow
916
- h('div', { style: { position: 'relative', height: STEP_H + 8, minWidth: totalW } },
917
- // SVG arrows
918
- h('svg', { width: totalW, height: STEP_H + 8, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
919
- h('defs', null,
920
- h('marker', { id: 'fc-arr', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
921
- h('polygon', { points: '0 0, 8 3, 0 6', fill: 'var(--tp-text-dim, rgba(99,102,241,0.6))' })
922
- ),
923
- h('marker', { id: 'fc-arr-active', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
924
- h('polygon', { points: '0 0, 8 3, 0 6', fill: STATUS_COLORS.in_progress })
509
+ // Rows
510
+ loading && tasks.length === 0
511
+ ? h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'Loading...')
512
+ : tasks.length === 0
513
+ ? h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } },
514
+ h('div', { style: { fontSize: 14, fontWeight: 600, marginBottom: 4 } }, 'No tasks found'),
515
+ h('div', { style: { fontSize: 12 } }, search ? 'Try a different search term' : 'Tasks will appear here as agents are assigned work')
516
+ )
517
+ : tasks.map(function(t) {
518
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
519
+ var agent = agentMap[t.assignedTo];
520
+ var isActive = t.status === 'in_progress';
521
+ return h('div', {
522
+ key: t.id,
523
+ className: 'tp-row' + (isActive ? ' tp-row-active' : ''),
524
+ onClick: function() { openTask(t); },
525
+ style: { display: 'grid', gridTemplateColumns: '2fr 1fr 100px 80px 90px 80px 60px', gap: 0, padding: '10px 12px', borderBottom: '1px solid var(--border)', cursor: 'pointer', alignItems: 'center', fontSize: 12 }
526
+ },
527
+ // Task title + chain indicator
528
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, overflow: 'hidden' } },
529
+ t.chainId && h('div', { style: { width: 3, height: 20, borderRadius: 2, background: '#6366f1', flexShrink: 0 } }),
530
+ h('div', { style: { overflow: 'hidden' } },
531
+ h('div', { style: { fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
532
+ (t.category || t.delegationType) && h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 1, display: 'flex', gap: 4, alignItems: 'center' } },
533
+ t.category,
534
+ t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType)
535
+ )
925
536
  )
926
537
  ),
927
- steps.map(function(step, i) {
928
- if (!step.arrow || i >= steps.length - 1) return null;
929
- var x1 = i * (STEP_W + STEP_GAP) + STEP_W;
930
- var x2 = (i + 1) * (STEP_W + STEP_GAP);
931
- var y = 4 + STEP_H / 2;
932
- var arrowColor = DELEGATION_COLORS[step.arrow] || 'rgba(99,102,241,0.5)';
933
- var nextStep = steps[i + 1];
934
- var isFlowActive = step.status === 'in_progress' || (nextStep && nextStep.status === 'in_progress');
935
- var midX = x1 + (x2 - x1) * 0.5;
936
- var d = 'M ' + x1 + ' ' + y + ' C ' + midX + ' ' + y + ', ' + midX + ' ' + y + ', ' + x2 + ' ' + y;
937
- return h(Fragment, { key: 'a' + i },
938
- h('path', { d: d, stroke: arrowColor, strokeWidth: 2, fill: 'none', markerEnd: 'url(#fc-arr)' }),
939
- isFlowActive && h('path', { d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none', strokeDasharray: '4 12', className: 'tp-flow-active', style: { opacity: 0.7 }, markerEnd: 'url(#fc-arr-active)' }),
940
- step.arrow !== 'assigned' && step.arrow !== 'delegation' && step.arrow !== 'completed' && step.arrow !== 'failed' && step.arrow !== 'cancelled' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: arrowColor, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, step.arrow)
941
- );
942
- })
943
- ),
944
- // Step nodes
945
- steps.map(function(step, i) {
946
- var x = i * (STEP_W + STEP_GAP);
947
- var sc = step.type === 'terminal'
948
- ? (STATUS_COLORS[step.status] || '#15803d')
949
- : step.type === 'person' || step.isHuman
950
- ? '#991b1b'
951
- : step.status ? (STATUS_COLORS[step.status] || '#6366f1') : '#6366f1';
952
- var isTerminal = step.type === 'terminal';
953
- var isMe = step.taskId === expandedTaskId;
954
-
955
- return h('div', {
956
- key: i,
957
- className: 'tp-node',
958
- onClick: function(e) { e.stopPropagation(); if (step.taskId) { var ct = expandedChain.find(function(c) { return c.id === step.taskId; }); if (ct) openTaskDetail(ct); } else if (expandedChain.length > 0) { openTaskDetail(expandedChain[0]); } },
959
- style: {
960
- position: 'absolute', left: x, top: 4, width: isTerminal ? 'auto' : STEP_W, minWidth: isTerminal ? 90 : undefined, height: STEP_H,
961
- background: isTerminal ? sc + '15' : isMe ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
962
- border: '1px solid ' + (isTerminal ? sc + '44' : isMe ? sc : 'rgba(255,255,255,0.1)'),
963
- borderRadius: isTerminal ? 18 : 6,
964
- display: 'flex', alignItems: 'center', gap: 6, padding: '0 8px',
965
- cursor: 'pointer',
966
- },
967
- },
968
- // Avatar
969
- (function() {
970
- // Check if we have an agent avatar for this step
971
- var stepAgent = step.taskId && expandedChain.find(function(c) { return c.id === step.taskId; });
972
- var agentAvatar = stepAgent && agentMap[stepAgent.assignedTo] && agentMap[stepAgent.assignedTo].avatar;
973
- if (!isTerminal && agentAvatar) {
974
- return h('img', { src: agentAvatar, style: { width: 18, height: 18, borderRadius: '50%', border: '1px solid ' + sc + '44', objectFit: 'cover', flexShrink: 0 } });
975
- }
976
- return h('div', { style: {
977
- width: 18, height: 18, borderRadius: '50%', flexShrink: 0,
978
- background: isTerminal ? sc + '33' : step.isHuman || step.type === 'person' ? 'linear-gradient(135deg, #991b1b, #f97316)' : step.type === 'system' ? 'var(--tp-card)' : 'linear-gradient(135deg, #6366f1, #8b5cf6)',
979
- display: 'flex', alignItems: 'center', justifyContent: 'center',
980
- fontSize: isTerminal ? 10 : 8, fontWeight: 700,
981
- color: isTerminal ? sc : '#fff',
982
- border: '1px solid ' + (isTerminal ? sc + '44' : 'transparent'),
983
- } },
984
- isTerminal ? (step.status === 'completed' ? '\u2714' : '\u2716') : step.label.charAt(0).toUpperCase()
985
- );
986
- })(),
987
- // Info
988
- h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
989
- h('div', { style: { fontSize: 10, fontWeight: 600, color: isTerminal ? sc : 'var(--tp-text)', whiteSpace: 'nowrap', overflow: isTerminal ? 'visible' : 'hidden', textOverflow: isTerminal ? 'unset' : 'ellipsis' } }, step.label),
990
- !isTerminal && h('div', { style: { fontSize: 8, color: 'var(--tp-text-dim)', marginTop: 1 } },
991
- step.type === 'person' || step.isHuman ? 'Human' : step.type === 'system' ? 'System' : 'Agent',
992
- step.duration ? ' \u00B7 ' + formatDuration(step.duration) : '',
993
- step.status === 'in_progress' && step.progress > 0 ? ' \u00B7 ' + step.progress + '%' : ''
994
- )
538
+ // Agent
539
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, overflow: 'hidden' } },
540
+ agent && agent.avatar
541
+ ? h('img', { src: agent.avatar, style: { width: 20, height: 20, borderRadius: '50%', objectFit: 'cover', flexShrink: 0 } })
542
+ : h('div', { style: { width: 20, height: 20, borderRadius: '50%', background: sc + '22', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, color: sc, flexShrink: 0 } },
543
+ (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
544
+ ),
545
+ h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 11 } }, t.assignedToName || (agent && agent.name) || t.assignedTo || '-')
546
+ ),
547
+ // Status
548
+ h('div', null,
549
+ h('span', { style: { padding: '2px 8px', borderRadius: 8, fontSize: 10, fontWeight: 600, background: sc + '22', color: sc } }, t.status.replace('_', ' ')),
550
+ isActive && t.progress > 0 && h('div', { style: { height: 3, background: 'var(--border)', borderRadius: 2, marginTop: 4, overflow: 'hidden' } },
551
+ h('div', { style: { height: '100%', width: t.progress + '%', background: '#06b6d4', borderRadius: 2 } })
995
552
  )
996
- );
997
- })
998
- )
999
- )
1000
- );
1001
- })()
1002
- )
1003
- ),
1004
-
1005
- // Hover tooltip
1006
- hoveredNode && hoveredNode.task && h('div', { style: {
1007
- position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
1008
- background: 'var(--bg-secondary)', backdropFilter: 'blur(12px)',
1009
- border: '1px solid var(--tp-border)', borderRadius: 10, boxShadow: '0 8px 32px rgba(0,0,0,0.2)',
1010
- padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
1011
- }},
1012
- hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
1013
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 } },
1014
- (agentMap[hoveredNode.task.assignedTo] && agentMap[hoveredNode.task.assignedTo].avatar)
1015
- ? h('img', { src: agentMap[hoveredNode.task.assignedTo].avatar, style: { width: 22, height: 22, borderRadius: '50%', border: '1.5px solid ' + (STATUS_COLORS[hoveredNode.task.status] || '#6366f1'), objectFit: 'cover', flexShrink: 0 } })
1016
- : null,
1017
- h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--tp-text)' } }, hoveredNode.task.title)
553
+ ),
554
+ // Priority
555
+ h('div', null,
556
+ h('span', { style: { fontSize: 10, fontWeight: 600, color: PRIORITY_COLORS[t.priority] || '#6366f1' } }, (t.priority || 'normal'))
557
+ ),
558
+ // Created
559
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, timeAgo(t.createdAt)),
560
+ // Duration
561
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, formatDuration(t.actualDurationMs)),
562
+ // Source
563
+ h('div', null, t.source ? sourceBadge(t.source) : h('span', { style: { fontSize: 10, color: 'var(--text-muted)' } }, '-'))
564
+ );
565
+ })
1018
566
  ),
1019
- h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
1020
- h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'var(--tp-text-dim)' } }, 'Agent'), h('span', { style: { fontWeight: 600 } }, hoveredNode.task.assignedToName || '-')),
1021
- h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'var(--tp-text-dim)' } }, 'Status'), h('span', { style: { color: STATUS_COLORS[hoveredNode.task.status] } }, hoveredNode.task.status.replace('_', ' '))),
1022
- hoveredNode.task.chainId && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'var(--tp-text-dim)' } }, 'Chain Step'), h('span', null, '#' + ((hoveredNode.task.chainSeq || 0) + 1))),
1023
- hoveredNode.task.delegationType && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'var(--tp-text-dim)' } }, 'Type'), h('span', { style: { color: DELEGATION_COLORS[hoveredNode.task.delegationType] } }, hoveredNode.task.delegationType)),
1024
- hoveredNode.task.progress > 0 && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'var(--tp-text-dim)' } }, 'Progress'), h('span', { style: { color: STATUS_COLORS.in_progress } }, hoveredNode.task.progress + '%'))
567
+
568
+ // Pagination
569
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', fontSize: 12 } },
570
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, color: 'var(--text-muted)' } },
571
+ h('span', null, 'Showing ' + tasks.length + ' of ' + (stats.total || totalCount) + ' tasks'),
572
+ h('select', {
573
+ value: pageSize,
574
+ onChange: function(e) { setPageSize(parseInt(e.target.value)); setPage(0); },
575
+ style: { padding: '3px 6px', fontSize: 11, borderRadius: 4, border: '1px solid var(--border)', background: 'var(--bg-secondary)', color: 'var(--text-primary)' }
576
+ },
577
+ PAGE_SIZES.map(function(s) { return h('option', { key: s, value: s }, s + ' per page'); })
578
+ )
579
+ ),
580
+ h('div', { style: { display: 'flex', gap: 4, alignItems: 'center' } },
581
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: page === 0, onClick: function() { setPage(0); } }, 'First'),
582
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: page === 0, onClick: function() { setPage(page - 1); } }, 'Prev'),
583
+ h('span', { style: { padding: '0 8px', color: 'var(--text-muted)' } }, 'Page ' + (page + 1)),
584
+ h('button', { className: 'btn btn-ghost btn-sm', disabled: tasks.length < pageSize, onClick: function() { setPage(page + 1); } }, 'Next')
585
+ )
1025
586
  )
1026
587
  ),
1027
588
 
1028
- // Detail modal (double-click)
1029
- selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
1030
- ));
589
+ // Detail modal
590
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask, agentMap: agentMap })
591
+ );
1031
592
  }
1032
593
 
1033
594
  // ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
1034
595
  export function AgentTaskPipeline(props) {
1035
596
  var agentId = props.agentId;
1036
- var _tasks = useState([]);
1037
- var tasks = _tasks[0]; var setTasks = _tasks[1];
1038
- var _loading = useState(true);
1039
- var loading = _loading[0]; var setLoading = _loading[1];
1040
- var _selectedTask = useState(null);
1041
- var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
597
+ var _tasks = useState([]); var tasks = _tasks[0]; var setTasks = _tasks[1];
598
+ var _loading = useState(true); var loading = _loading[0]; var setLoading = _loading[1];
599
+ var _selectedTask = useState(null); var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
1042
600
  var app = useApp();
1043
601
  var toast = app.toast;
1044
602
 
@@ -1084,8 +642,7 @@ export function AgentTaskPipeline(props) {
1084
642
  function renderTaskRow(t) {
1085
643
  var sc = STATUS_COLORS[t.status] || '#6b7394';
1086
644
  return h('div', {
1087
- key: t.id,
1088
- onClick: function() { setSelectedTask(t); },
645
+ key: t.id, onClick: function() { setSelectedTask(t); },
1089
646
  style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
1090
647
  onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
1091
648
  onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
@@ -1122,6 +679,6 @@ export function AgentTaskPipeline(props) {
1122
679
  h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
1123
680
  h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
1124
681
  ),
1125
- selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
682
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask, agentMap: {} })
1126
683
  );
1127
684
  }