@sleep2agi/agent-network-dashboard 0.4.5-preview.9 → 0.4.5-preview.91

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 (319) hide show
  1. package/.mcp.json +11 -0
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +48 -48
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +4 -4
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page/next-font-manifest.json +1 -1
  25. package/.next/server/app/admin/page.js.nft.json +1 -1
  26. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  27. package/.next/server/app/admin.html +4 -4
  28. package/.next/server/app/admin.rsc +15 -15
  29. package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
  30. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  31. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  32. package/.next/server/app/admin.segments/_tree.segment.rsc +3 -3
  33. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  35. package/.next/server/app/index.html +4 -4
  36. package/.next/server/app/index.rsc +15 -15
  37. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  38. package/.next/server/app/index.segments/_full.segment.rsc +15 -15
  39. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  40. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  41. package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  42. package/.next/server/app/login/page/next-font-manifest.json +1 -1
  43. package/.next/server/app/login/page.js.nft.json +1 -1
  44. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  45. package/.next/server/app/login.html +2 -2
  46. package/.next/server/app/login.rsc +15 -15
  47. package/.next/server/app/login.segments/_full.segment.rsc +15 -15
  48. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  49. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  50. package/.next/server/app/login.segments/_tree.segment.rsc +3 -3
  51. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  53. package/.next/server/app/logs/page/next-font-manifest.json +1 -1
  54. package/.next/server/app/logs/page.js.nft.json +1 -1
  55. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/logs.html +4 -4
  57. package/.next/server/app/logs.rsc +15 -15
  58. package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
  59. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  60. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  61. package/.next/server/app/logs.segments/_tree.segment.rsc +3 -3
  62. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  63. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  64. package/.next/server/app/messages/page/next-font-manifest.json +1 -1
  65. package/.next/server/app/messages/page.js.nft.json +1 -1
  66. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  67. package/.next/server/app/messages.html +4 -4
  68. package/.next/server/app/messages.rsc +15 -15
  69. package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
  70. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  71. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  72. package/.next/server/app/messages.segments/_tree.segment.rsc +3 -3
  73. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  74. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  75. package/.next/server/app/node/page/next-font-manifest.json +1 -1
  76. package/.next/server/app/node/page.js.nft.json +1 -1
  77. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  78. package/.next/server/app/node.html +4 -4
  79. package/.next/server/app/node.rsc +15 -15
  80. package/.next/server/app/node.segments/_full.segment.rsc +15 -15
  81. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  83. package/.next/server/app/node.segments/_tree.segment.rsc +3 -3
  84. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  85. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  86. package/.next/server/app/nodes/page/next-font-manifest.json +1 -1
  87. package/.next/server/app/nodes/page.js.nft.json +1 -1
  88. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  89. package/.next/server/app/nodes.html +4 -4
  90. package/.next/server/app/nodes.rsc +15 -15
  91. package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
  92. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  93. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  94. package/.next/server/app/nodes.segments/_tree.segment.rsc +3 -3
  95. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  96. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  97. package/.next/server/app/page/next-font-manifest.json +1 -1
  98. package/.next/server/app/page.js.nft.json +1 -1
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
  101. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app/server-logs.html +4 -4
  103. package/.next/server/app/server-logs.rsc +15 -15
  104. package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
  105. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  106. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  107. package/.next/server/app/server-logs.segments/_tree.segment.rsc +3 -3
  108. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  109. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  110. package/.next/server/app/settings/networks/page/next-font-manifest.json +1 -1
  111. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  112. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/networks.html +4 -4
  114. package/.next/server/app/settings/networks.rsc +15 -15
  115. package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
  116. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  118. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +3 -3
  119. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  120. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  121. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  122. package/.next/server/app/settings/page/next-font-manifest.json +1 -1
  123. package/.next/server/app/settings/page.js.nft.json +1 -1
  124. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  125. package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
  126. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  127. package/.next/server/app/settings/tokens.html +4 -4
  128. package/.next/server/app/settings/tokens.rsc +15 -15
  129. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
  130. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  131. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  132. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +3 -3
  133. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  134. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  135. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  136. package/.next/server/app/settings.html +4 -4
  137. package/.next/server/app/settings.rsc +15 -15
  138. package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
  139. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  140. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  141. package/.next/server/app/settings.segments/_tree.segment.rsc +3 -3
  142. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  143. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  144. package/.next/server/app/tasks/[id]/page/next-font-manifest.json +1 -1
  145. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  146. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  147. package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
  148. package/.next/server/app/tasks/page.js.nft.json +1 -1
  149. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  150. package/.next/server/app/tasks.html +4 -4
  151. package/.next/server/app/tasks.rsc +15 -15
  152. package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
  153. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  154. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  155. package/.next/server/app/tasks.segments/_tree.segment.rsc +3 -3
  156. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  157. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  158. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +3 -0
  159. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +1 -0
  160. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +3 -0
  161. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +1 -0
  162. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +3 -0
  163. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +1 -0
  164. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +1 -1
  165. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +1 -1
  166. package/.next/server/chunks/ssr/_0nne7mz._.js +3 -0
  167. package/.next/server/chunks/ssr/_0nne7mz._.js.map +1 -0
  168. package/.next/server/chunks/ssr/app_0.zbtn5._.js +9 -0
  169. package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +1 -0
  170. package/.next/server/chunks/ssr/app_01y-~mp._.js +3 -0
  171. package/.next/server/chunks/ssr/app_01y-~mp._.js.map +1 -0
  172. package/.next/server/chunks/ssr/app_04n78pr._.js +3 -0
  173. package/.next/server/chunks/ssr/app_04n78pr._.js.map +1 -0
  174. package/.next/server/chunks/ssr/app_0_agk0r._.js +3 -0
  175. package/.next/server/chunks/ssr/app_0_agk0r._.js.map +1 -0
  176. package/.next/server/chunks/ssr/app_0aenxef._.js +3 -0
  177. package/.next/server/chunks/ssr/app_0aenxef._.js.map +1 -0
  178. package/.next/server/chunks/ssr/app_0aum_g5._.js +3 -0
  179. package/.next/server/chunks/ssr/app_0aum_g5._.js.map +1 -0
  180. package/.next/server/chunks/ssr/app_0emvkn2._.js +3 -0
  181. package/.next/server/chunks/ssr/app_0emvkn2._.js.map +1 -0
  182. package/.next/server/chunks/ssr/app_0esuljp._.js +3 -0
  183. package/.next/server/chunks/ssr/app_0esuljp._.js.map +1 -0
  184. package/.next/server/chunks/ssr/app_0izi28j._.js +1 -1
  185. package/.next/server/chunks/ssr/app_0izi28j._.js.map +1 -1
  186. package/.next/server/chunks/ssr/app_components_08ys5y3._.js +5 -5
  187. package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +1 -1
  188. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js +1 -1
  189. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +1 -1
  190. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +1 -1
  191. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +1 -1
  192. package/.next/server/middleware-build-manifest.js +3 -3
  193. package/.next/server/next-font-manifest.js +1 -1
  194. package/.next/server/next-font-manifest.json +14 -14
  195. package/.next/server/pages/404.html +4 -4
  196. package/.next/server/pages/500.html +1 -1
  197. package/.next/server/server-reference-manifest.js +1 -1
  198. package/.next/server/server-reference-manifest.json +1 -1
  199. package/.next/static/chunks/0-hlcuoemhe7w.js +7 -0
  200. package/.next/static/chunks/02_zpo7taex9o.js +7 -0
  201. package/.next/static/chunks/03b6jkic-1r9u.css +2 -0
  202. package/.next/static/chunks/06fliypy0.f0r.js +1 -0
  203. package/.next/static/chunks/06n3d~fbbtagk.js +1 -0
  204. package/.next/static/chunks/07eba1lz2u3p4.js +1 -0
  205. package/.next/static/chunks/0av2~0.96~d~g.js +1 -0
  206. package/.next/static/chunks/{07okhs5561y5y.js → 0c1bp~g7.8ru..js} +1 -1
  207. package/.next/static/chunks/0g9aof0ob_fcj.js +1 -0
  208. package/.next/static/chunks/0gtbwcilfu6b7.js +1 -0
  209. package/.next/static/chunks/0s8jo6jypzkx5.js +1 -0
  210. package/.next/static/chunks/0ubqqhhpsaez4.js +1 -0
  211. package/.next/static/chunks/0vmfu_gz9tp4f.js +1 -0
  212. package/.next/static/chunks/0~qw9kxksr1v2.js +1 -0
  213. package/.next/static/chunks/12gprw3l0kvvf.js +1 -0
  214. package/.next/static/chunks/12mjtyh-j74~k.js +1 -0
  215. package/.next/static/chunks/12yuo353giomx.js +1 -0
  216. package/.next/static/media/4fa387ec64143e14-s.0.qu-9752pffj.woff2 +0 -0
  217. package/.next/static/media/5ce348bf30bf5439-s.0ee55_hj9qcer.woff2 +0 -0
  218. package/.next/static/media/6306c77e7c8268e4-s.0mao5jbfbduzp.woff2 +0 -0
  219. package/.next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2 +0 -0
  220. package/.next/static/media/7d817b4c03b0c5f1-s.0uzt.a6d44yda.woff2 +0 -0
  221. package/.next/static/media/bbc41e54d2fcbd21-s.0mvwgmnhv29no.woff2 +0 -0
  222. package/.next/trace +2 -2
  223. package/.next/trace-build +1 -1
  224. package/app/admin/page.tsx +97 -27
  225. package/app/components/AgentCard.tsx +19 -5
  226. package/app/components/BroadcastBar.tsx +22 -4
  227. package/app/components/DispatchPanel.tsx +9 -5
  228. package/app/components/EmptyState.tsx +127 -12
  229. package/app/components/HealthBanner.tsx +11 -2
  230. package/app/components/HelpOverlay.tsx +50 -10
  231. package/app/components/InboxPanel.tsx +7 -4
  232. package/app/components/Sidebar.tsx +81 -44
  233. package/app/components/StatsBar.tsx +59 -36
  234. package/app/components/TaskChatPanel.tsx +8 -10
  235. package/app/components/TaskDrawer.tsx +72 -28
  236. package/app/components/TopoGraph.tsx +527 -41
  237. package/app/components/utils.ts +13 -0
  238. package/app/globals.css +90 -0
  239. package/app/lib/status.ts +85 -0
  240. package/app/lib/version.ts +7 -0
  241. package/app/login/page.tsx +40 -7
  242. package/app/logs/page.tsx +70 -21
  243. package/app/messages/page.tsx +34 -9
  244. package/app/node/page.tsx +52 -27
  245. package/app/nodes/page.tsx +45 -21
  246. package/app/page.tsx +103 -32
  247. package/app/server-logs/page.tsx +104 -28
  248. package/app/settings/networks/page.tsx +40 -16
  249. package/app/settings/page.tsx +39 -11
  250. package/app/tasks/[id]/page.tsx +2 -13
  251. package/app/tasks/page.tsx +66 -73
  252. package/package.json +1 -1
  253. package/public/intern_avatar.png +0 -0
  254. package/scripts/empty-state-test.mjs +41 -0
  255. package/scripts/fold-only.mjs +47 -0
  256. package/scripts/intern-alias-test.mjs +46 -0
  257. package/scripts/loop-review.mjs +270 -0
  258. package/scripts/network-derive-shot.mjs +16 -0
  259. package/scripts/network-derive-test.mjs +28 -0
  260. package/scripts/topo-brand-shot.mjs +61 -0
  261. package/scripts/topo-cluster-test.mjs +41 -0
  262. package/scripts/topo-controls-shot.mjs +24 -0
  263. package/scripts/topo-group-test.mjs +37 -0
  264. package/scripts/topo-inspect.mjs +56 -0
  265. package/scripts/topo-interaction-test.mjs +80 -0
  266. package/scripts/topo-mock-large.mjs +58 -0
  267. package/scripts/topo-r104-test.mjs +54 -0
  268. package/scripts/topo-shot.mjs +31 -0
  269. package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js +0 -3
  270. package/.next/server/chunks/ssr/[root-of-the-server]__06w_u7t._.js.map +0 -1
  271. package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js +0 -3
  272. package/.next/server/chunks/ssr/[root-of-the-server]__0ehu0jb._.js.map +0 -1
  273. package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js +0 -3
  274. package/.next/server/chunks/ssr/[root-of-the-server]__0nrckjd._.js.map +0 -1
  275. package/.next/server/chunks/ssr/_13_v7-3._.js +0 -3
  276. package/.next/server/chunks/ssr/_13_v7-3._.js.map +0 -1
  277. package/.next/server/chunks/ssr/app_0.0wpa~._.js +0 -3
  278. package/.next/server/chunks/ssr/app_0.0wpa~._.js.map +0 -1
  279. package/.next/server/chunks/ssr/app_0ap_oiq._.js +0 -3
  280. package/.next/server/chunks/ssr/app_0ap_oiq._.js.map +0 -1
  281. package/.next/server/chunks/ssr/app_0dk5wao._.js +0 -3
  282. package/.next/server/chunks/ssr/app_0dk5wao._.js.map +0 -1
  283. package/.next/server/chunks/ssr/app_0et5yua._.js +0 -3
  284. package/.next/server/chunks/ssr/app_0et5yua._.js.map +0 -1
  285. package/.next/server/chunks/ssr/app_10b5blc._.js +0 -9
  286. package/.next/server/chunks/ssr/app_10b5blc._.js.map +0 -1
  287. package/.next/server/chunks/ssr/app_11vl967._.js +0 -3
  288. package/.next/server/chunks/ssr/app_11vl967._.js.map +0 -1
  289. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js +0 -3
  290. package/.next/server/chunks/ssr/app_login_page_tsx_0z71ad3._.js.map +0 -1
  291. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js +0 -3
  292. package/.next/server/chunks/ssr/app_page_tsx_0es_sk2._.js.map +0 -1
  293. package/.next/static/chunks/0-.hjh--kcdiy.js +0 -1
  294. package/.next/static/chunks/02afyxgpty3uf.js +0 -1
  295. package/.next/static/chunks/02flrewxlf_m_.js +0 -1
  296. package/.next/static/chunks/051gslfjka1kj.js +0 -1
  297. package/.next/static/chunks/07oobuohrpigy.js +0 -1
  298. package/.next/static/chunks/09al3qm76q7nd.js +0 -1
  299. package/.next/static/chunks/0e7c40p~wq665.js +0 -1
  300. package/.next/static/chunks/0g7m7zk.v7g6h.js +0 -1
  301. package/.next/static/chunks/0h.ajsk_uxigl.js +0 -1
  302. package/.next/static/chunks/0msa~bub38ytf.css +0 -2
  303. package/.next/static/chunks/0ogeals5ufhho.js +0 -1
  304. package/.next/static/chunks/10ir.g.n~hre0.js +0 -7
  305. package/.next/static/chunks/10njj7kn9ylkf.js +0 -1
  306. package/.next/static/chunks/12dcb86elsl-e.js +0 -7
  307. package/.next/static/chunks/13.dst7liziwo.js +0 -1
  308. package/.next/static/chunks/132e__998_q9r.js +0 -1
  309. package/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
  310. package/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
  311. package/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
  312. package/public/file.svg +0 -1
  313. package/public/globe.svg +0 -1
  314. package/public/next.svg +0 -1
  315. package/public/vercel.svg +0 -1
  316. package/public/window.svg +0 -1
  317. /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_buildManifest.js +0 -0
  318. /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_clientMiddlewareManifest.js +0 -0
  319. /package/.next/static/{stSZtBF9uGYc3L399m0h1 → u-HIIfy-O0UVQ02APphkX}/_ssgManifest.js +0 -0
@@ -25,6 +25,8 @@ interface SidebarNetwork {
25
25
  network_id: string;
26
26
  network_name: string;
27
27
  role?: string;
28
+ /** Round 107: live agent count, derived from /api/status sessions. */
29
+ agentCount?: number;
28
30
  }
29
31
 
30
32
  const ROLE_ICON: Record<string, string> = { owner: '⭐', admin: '🔧', member: '👤', viewer: '👁' };
@@ -35,7 +37,26 @@ export function Sidebar() {
35
37
  const [mobileOpen, setMobileOpen] = useState(false);
36
38
  const { networkId, setNetworkId } = useNetworkId();
37
39
  const { data: netData } = useSWR<{ networks: SidebarNetwork[] }>('/api/hub/networks', networkFetcher, { refreshInterval: 15000, dedupingInterval: 10000 });
38
- const networks = netData?.networks || [];
40
+ // Round 107 (issue #92): /api/networks can be scope-limited (a hub owner's
41
+ // token may only list "default" even when agents live in other networks).
42
+ // The unfiltered /api/status response carries every visible session with
43
+ // its network_id, so derive networks from there and merge them in — the
44
+ // admin can then switch to any network that actually has agents.
45
+ const { data: statusData } = useSWR<StatusData>(
46
+ '/api/hub/status', statusFetcher, { refreshInterval: 15000, dedupingInterval: 10000 },
47
+ );
48
+ const apiNetworks = netData?.networks || [];
49
+ const networks: SidebarNetwork[] = (() => {
50
+ const agentCount = new Map<string, number>();
51
+ for (const s of statusData?.sessions || []) {
52
+ if (s.network_id) agentCount.set(s.network_id, (agentCount.get(s.network_id) || 0) + 1);
53
+ }
54
+ const known = new Set(apiNetworks.map(n => n.network_id));
55
+ const derived: SidebarNetwork[] = [...agentCount.keys()]
56
+ .filter(id => !known.has(id))
57
+ .map(id => ({ network_id: id, network_name: id }));
58
+ return [...apiNetworks, ...derived].map(n => ({ ...n, agentCount: agentCount.get(n.network_id) || 0 }));
59
+ })();
39
60
 
40
61
  const isActive = (href: string) => {
41
62
  if (href === '/') return pathname === '/';
@@ -51,11 +72,12 @@ export function Sidebar() {
51
72
  href={item.href}
52
73
  prefetch={false}
53
74
  onClick={() => setMobileOpen(false)}
75
+ title={collapsed ? item.label : undefined}
54
76
  className={`relative flex items-center gap-3 px-3 py-3 rounded-lg text-sm transition-colors active:bg-[#1a1a3a] ${
55
77
  isActive(item.href)
56
78
  ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20 anet-nav-active'
57
79
  : 'text-gray-400 hover:text-gray-200 hover:bg-[#1a1a2a]'
58
- } ${collapsed ? 'justify-center' : ''}`}
80
+ } ${collapsed ? 'justify-center px-0' : ''}`}
59
81
  >
60
82
  <svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
61
83
  <path strokeLinecap="round" strokeLinejoin="round" d={item.icon} />
@@ -81,10 +103,11 @@ export function Sidebar() {
81
103
  </svg>
82
104
  </button>
83
105
 
84
- {/* Mobile overlay */}
106
+ {/* Mobile overlay — round 47: fades in via anet-fade-in (150ms ease-out)
107
+ matching the TaskDrawer / TaskChatPanel backdrop. */}
85
108
  {mobileOpen && (
86
109
  <div
87
- className="fixed inset-0 bg-black/50 z-30 lg:hidden"
110
+ className="fixed inset-0 bg-black/50 z-30 lg:hidden anet-fade-in"
88
111
  onClick={() => setMobileOpen(false)}
89
112
  />
90
113
  )}
@@ -93,13 +116,15 @@ export function Sidebar() {
93
116
  but in light/mint we want a distinct surface, so we layer
94
117
  `lg:bg-white` / `lg:dark:bg-[#0d0d1a]` via the theme attribute.
95
118
  The CSS shim in globals.css upgrades sidebar bg to bg-secondary
96
- in light themes so the sidebar reads as its own card. */}
119
+ in light themes so the sidebar reads as its own card.
120
+ Round 47: explicit ease-out curve + slight shadow so the drawer
121
+ edge "leaves a trail" as it slides in. */}
97
122
  <aside data-anet-sidebar="true" className={`
98
123
  fixed top-0 left-0 h-full z-40 bg-[#0d0d1a] border-r border-[#2a2a4a]
99
- transition-all duration-200
124
+ transition-transform duration-200 ease-out
100
125
  ${collapsed ? 'w-16' : 'w-52'}
101
- ${mobileOpen ? 'translate-x-0' : '-translate-x-full'}
102
- lg:translate-x-0 lg:static lg:shrink-0
126
+ ${mobileOpen ? 'translate-x-0 shadow-2xl shadow-black/40 lg:shadow-none' : '-translate-x-full'}
127
+ lg:translate-x-0 lg:static lg:shrink-0 lg:shadow-none
103
128
  `}>
104
129
  {/* Brand header — round 4: 3-node mesh mark matches /login,
105
130
  with an inline live "online" pulse so every page surfaces
@@ -116,6 +141,7 @@ export function Sidebar() {
116
141
  <button
117
142
  key={n.network_id}
118
143
  onClick={() => { setNetworkId(n.network_id); setMobileOpen(false); }}
144
+ title={n.network_id}
119
145
  className={`w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-xs transition-colors text-left ${
120
146
  networkId === n.network_id
121
147
  ? 'bg-cyan-500/10 text-cyan-300'
@@ -123,7 +149,14 @@ export function Sidebar() {
123
149
  }`}
124
150
  >
125
151
  <span>{ROLE_ICON[n.role || 'member'] || '👤'}</span>
126
- <span className="truncate">{n.network_name}</span>
152
+ <span className="truncate flex-1">{n.network_name}</span>
153
+ {/* Round 107 (issue #92): show live agent count so an
154
+ admin can spot which network actually has agents —
155
+ the whole point of the bug was a network with 30
156
+ agents being invisible. */}
157
+ {n.agentCount ? (
158
+ <span className="shrink-0 tabular-nums text-[10px] text-gray-600">{n.agentCount}</span>
159
+ ) : null}
127
160
  </button>
128
161
  ))}
129
162
  </div>
@@ -134,45 +167,49 @@ export function Sidebar() {
134
167
  {nav}
135
168
  </div>
136
169
 
137
- {/* Sign out + collapse */}
170
+ {/* Sign out + collapse — round 27: collapsed-state gets icon-only
171
+ variants so users still have Sign out / Quick search access at
172
+ 56px width, plus title= tooltips. */}
138
173
  <div className="absolute bottom-0 left-0 right-0 border-t border-[#2a2a4a] bg-[#0d0d1a]">
139
- {!collapsed && (
140
- <button
141
- onClick={async () => {
142
- await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
143
- sessionStorage.removeItem('anet_v3_auth');
144
- window.location.assign('/login');
145
- }}
146
- className="w-full flex items-center gap-3 px-5 py-3 text-xs text-gray-500 hover:text-red-400 hover:bg-red-500/5 transition-colors"
147
- >
148
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
149
- <path strokeLinecap="round" strokeLinejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
174
+ <button
175
+ onClick={() => {
176
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true, ctrlKey: true }));
177
+ }}
178
+ title={collapsed ? 'Quick search (⌘K)' : undefined}
179
+ className={`w-full flex items-center text-[11px] text-gray-600 hover:text-gray-400 hover:bg-[#1a1a2a] transition-colors ${
180
+ collapsed ? 'justify-center px-0 py-2.5' : 'justify-between gap-2 px-5 py-2'
181
+ }`}
182
+ aria-label="Open command palette"
183
+ >
184
+ <span className={`flex items-center ${collapsed ? '' : 'gap-2'}`}>
185
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
186
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
150
187
  </svg>
151
- Sign out
152
- </button>
153
- )}
154
- {!collapsed && (
155
- <button
156
- onClick={() => {
157
- // Dispatch a synthetic keypress so the global Cmd+K listener fires
158
- window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true, ctrlKey: true }));
159
- }}
160
- className="w-full flex items-center justify-between gap-2 px-5 py-2 text-[11px] text-gray-600 hover:text-gray-400 hover:bg-[#1a1a2a] transition-colors"
161
- aria-label="Open command palette"
162
- >
163
- <span className="flex items-center gap-2">
164
- <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
165
- <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
166
- </svg>
167
- Quick search
168
- </span>
169
- <kbd className="text-[10px] border border-current rounded px-1 py-0.5 opacity-60 font-mono">⌘K</kbd>
170
- </button>
171
- )}
172
- <div className={`flex items-center gap-2 px-3 py-2 ${collapsed ? 'justify-center' : 'justify-between'}`}>
188
+ {!collapsed && 'Quick search'}
189
+ </span>
190
+ {!collapsed && <kbd className="text-[10px] border border-current rounded px-1 py-0.5 opacity-60 font-mono">⌘K</kbd>}
191
+ </button>
192
+ <button
193
+ onClick={async () => {
194
+ await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
195
+ sessionStorage.removeItem('anet_v3_auth');
196
+ window.location.assign('/login');
197
+ }}
198
+ title={collapsed ? 'Sign out' : undefined}
199
+ className={`w-full flex items-center text-xs text-gray-500 hover:text-red-400 hover:bg-red-500/5 transition-colors ${
200
+ collapsed ? 'justify-center px-0 py-2.5' : 'gap-3 px-5 py-3'
201
+ }`}
202
+ >
203
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
204
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
205
+ </svg>
206
+ {!collapsed && 'Sign out'}
207
+ </button>
208
+ <div className={`flex items-center gap-2 ${collapsed ? 'flex-col px-0 py-2' : 'px-3 py-2 justify-between'}`}>
173
209
  <ThemeSwitcher compact={collapsed} />
174
210
  <button
175
211
  onClick={() => setCollapsed(!collapsed)}
212
+ title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
176
213
  className="hidden lg:flex p-1.5 rounded text-gray-600 hover:text-gray-400 transition-colors"
177
214
  aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
178
215
  >
@@ -188,7 +225,7 @@ export function Sidebar() {
188
225
  }
189
226
 
190
227
  interface StatusData {
191
- sessions?: Array<{ status?: string; alias?: string }>;
228
+ sessions?: Array<{ status?: string; alias?: string; network_id?: string }>;
192
229
  }
193
230
 
194
231
  const statusFetcher = (url: string) =>
@@ -10,9 +10,10 @@ interface StatsBarProps {
10
10
 
11
11
  export function StatsBar({ online, working, total, version, uptime }: StatsBarProps) {
12
12
  const onlinePercent = total > 0 ? Math.round((online / total) * 100) : 0;
13
+ const fleetEmpty = total === 0;
13
14
 
14
15
  return (
15
- <div className="mb-8">
16
+ <div className={fleetEmpty ? 'mb-4' : 'mb-8'}>
16
17
  {/* Title row */}
17
18
  <div className="flex flex-wrap items-center gap-3 mb-4">
18
19
  <h1 className="text-2xl font-bold text-white tracking-tight">Agent Network</h1>
@@ -21,41 +22,63 @@ export function StatsBar({ online, working, total, version, uptime }: StatsBarPr
21
22
  </span>
22
23
  </div>
23
24
 
24
- {/* Stats grid */}
25
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
26
- <StatCard
27
- value={online}
28
- label="Online"
29
- sub={`${onlinePercent}% of fleet`}
30
- color="text-green-400"
31
- accent="from-green-500/20 to-green-500/0"
32
- border="border-green-500/15"
33
- />
34
- <StatCard
35
- value={working}
36
- label="Working"
37
- sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}
38
- color="text-cyan-400"
39
- accent="from-cyan-500/20 to-cyan-500/0"
40
- border="border-cyan-500/15"
41
- />
42
- <StatCard
43
- value={total - online}
44
- label="Offline"
45
- sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}
46
- color="text-gray-400"
47
- accent="from-gray-500/10 to-gray-500/0"
48
- border="border-gray-500/15"
49
- />
50
- <StatCard
51
- value={total}
52
- label="Total"
53
- sub="Registered nodes"
54
- color="text-white"
55
- accent="from-blue-500/15 to-blue-500/0"
56
- border="border-blue-500/15"
57
- />
58
- </div>
25
+ {fleetEmpty ? (
26
+ /* Round 72: thin status strip replaces the 4-card grid when fleet
27
+ is empty. Saves ~280px on mobile (CTA y=650 → ~370) and keeps
28
+ the same data visible in a single inline row. */
29
+ <div className="anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#2a2a4a] py-2">
30
+ <span className="inline-flex items-center gap-1.5">
31
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-gray-600" />
32
+ <span className="text-gray-300 tabular-nums">0</span> online
33
+ </span>
34
+ <span className="text-gray-700">·</span>
35
+ <span className="inline-flex items-center gap-1.5">
36
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-gray-600" />
37
+ <span className="text-gray-300 tabular-nums">0</span> working
38
+ </span>
39
+ <span className="text-gray-700">·</span>
40
+ <span className="inline-flex items-center gap-1.5">
41
+ <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full bg-gray-600" />
42
+ <span className="text-gray-300 tabular-nums">0</span> registered
43
+ </span>
44
+ </div>
45
+ ) : (
46
+ /* Populated state full 4-card grid as before */
47
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
48
+ <StatCard
49
+ value={online}
50
+ label="Online"
51
+ sub={`${onlinePercent}% of fleet`}
52
+ color="text-green-400"
53
+ accent="from-green-500/20 to-green-500/0"
54
+ border="border-green-500/15"
55
+ />
56
+ <StatCard
57
+ value={working}
58
+ label="Working"
59
+ sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}
60
+ color="text-cyan-400"
61
+ accent="from-cyan-500/20 to-cyan-500/0"
62
+ border="border-cyan-500/15"
63
+ />
64
+ <StatCard
65
+ value={total - online}
66
+ label="Offline"
67
+ sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}
68
+ color="text-gray-400"
69
+ accent="from-gray-500/10 to-gray-500/0"
70
+ border="border-gray-500/15"
71
+ />
72
+ <StatCard
73
+ value={total}
74
+ label="Total"
75
+ sub="Registered nodes"
76
+ color="text-white"
77
+ accent="from-blue-500/15 to-blue-500/0"
78
+ border="border-blue-500/15"
79
+ />
80
+ </div>
81
+ )}
59
82
  </div>
60
83
  );
61
84
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useRef, useCallback } from 'react';
4
4
  import { timeAgo } from './utils';
5
+ import { AliasAvatar } from './AliasAvatar';
5
6
 
6
7
  interface ChatTask {
7
8
  task_id: string;
@@ -386,7 +387,10 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
386
387
  {m.result && (
387
388
  <div className="flex justify-start">
388
389
  <div className="max-w-[85%] bg-green-500/8 border border-green-500/15 rounded-2xl rounded-bl-md px-4 py-2.5 shadow-sm">
389
- <div className="text-[10px] text-green-400 font-medium mb-1.5">{m.to_name}</div>
390
+ <div className="flex items-center gap-1.5 mb-1.5">
391
+ {m.to_name && <AliasAvatar alias={m.to_name} size={14} />}
392
+ <span className="text-[10px] text-gray-200 font-medium">{m.to_name}</span>
393
+ </div>
390
394
  <div className="text-[13px] text-gray-200">
391
395
  <MarkdownContent text={m.result} />
392
396
  </div>
@@ -422,7 +426,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
422
426
  {filteredMentions.map(node => (
423
427
  <button key={node} onClick={() => insertMention(node)}
424
428
  className="w-full flex items-center gap-2 px-3 py-2 text-xs text-left hover:bg-cyan-500/10 transition-colors">
425
- <span className="w-2 h-2 rounded-full bg-cyan-400 shrink-0" />
429
+ <AliasAvatar alias={node} size={16} />
426
430
  <span className="text-gray-300">{node}</span>
427
431
  </button>
428
432
  ))}
@@ -471,14 +475,12 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
471
475
 
472
476
  return (
473
477
  <>
474
- <div className="fixed inset-0 bg-black/30 z-40 lg:hidden" onClick={onClose} />
478
+ <div className="fixed inset-0 bg-black/30 z-40 lg:hidden anet-fade-in" onClick={onClose} />
475
479
  <div className="fixed top-0 right-0 h-full w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in">
476
480
  {/* Header */}
477
481
  <div className="flex items-center justify-between px-4 py-3 border-b border-[#2a2a4a] bg-[#0d0d1a]">
478
482
  <div className="flex items-center gap-3">
479
- <div className="w-8 h-8 rounded-full bg-gradient-to-br from-cyan-500/30 to-blue-500/30 flex items-center justify-center text-xs font-bold text-cyan-300">
480
- {alias.slice(0, 2)}
481
- </div>
483
+ <AliasAvatar alias={alias} size={32} />
482
484
  <div>
483
485
  <div className="text-sm font-semibold text-white">{alias}</div>
484
486
  <div className="text-[10px] text-gray-500">{pollingIds.size > 0 ? 'Processing...' : 'Ready'}</div>
@@ -492,10 +494,6 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes }: TaskCh
492
494
  </div>
493
495
  {chatContent}
494
496
  </div>
495
- <style jsx global>{`
496
- @keyframes slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
497
- .animate-slide-in { animation: slide-in 0.2s ease-out; }
498
- `}</style>
499
497
  </>
500
498
  );
501
499
  }
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState } from 'react';
4
4
  import { timeAgo } from './utils';
5
5
  import Link from 'next/link';
6
+ import { AliasAvatar } from './AliasAvatar';
6
7
 
7
8
  interface TaskDetail {
8
9
  task_id: string;
@@ -58,12 +59,20 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
58
59
  })();
59
60
  }, [taskId]);
60
61
 
62
+ /** Timeline steps — same 4 hops every task goes through. `time` is set
63
+ * when the hop has happened; missing time means pending. Round 36 adds
64
+ * `key` for the "current step" highlight + the `done` flag for layout. */
61
65
  const timeline = task ? [
62
- { label: 'Created', time: task.created_at, color: 'bg-gray-400' },
63
- { label: 'Delivered', time: task.delivered_at, color: 'bg-blue-400' },
64
- { label: 'Started', time: task.started_at, color: 'bg-green-400' },
65
- { label: 'Completed', time: task.completed_at, color: 'bg-purple-400' },
66
- ] : [];
66
+ { key: 'created', label: 'Created', time: task.created_at, color: 'bg-gray-400' },
67
+ { key: 'delivered', label: 'Delivered', time: task.delivered_at, color: 'bg-blue-400' },
68
+ { key: 'started', label: 'Started', time: task.started_at, color: 'bg-green-400' },
69
+ { key: 'completed', label: 'Completed', time: task.completed_at, color: 'bg-purple-400'},
70
+ ].map(s => ({ ...s, done: !!s.time })) : [];
71
+
72
+ /** Index of the highest-completed step. The step *after* this index is
73
+ * the "current" one (in-progress). When all are done, currentIdx = -1. */
74
+ const currentStepIdx = timeline.findIndex(s => !s.done);
75
+ const isActive = task && task.status !== 'completed' && task.status !== 'failed' && task.status !== 'expired' && task.status !== 'cancelled';
67
76
 
68
77
  const duration = task?.started_at && task?.completed_at
69
78
  ? Math.round((new Date(task.completed_at).getTime() - new Date(task.started_at).getTime()) / 1000)
@@ -71,7 +80,7 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
71
80
 
72
81
  return (
73
82
  <>
74
- <div className="fixed inset-0 bg-black/30 z-40" onClick={onClose} />
83
+ <div className="fixed inset-0 bg-black/30 z-40 anet-fade-in" onClick={onClose} />
75
84
  <div className="fixed top-0 right-0 h-full w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 overflow-y-auto animate-slide-in">
76
85
  {/* Header */}
77
86
  <div className="flex items-center justify-between px-5 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a] sticky top-0">
@@ -105,29 +114,68 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
105
114
  )}
106
115
  </div>
107
116
 
108
- {/* From → To */}
109
- <div className="flex items-center gap-2 text-sm">
110
- <span className="text-blue-400 font-medium">{task.from_name || '--'}</span>
111
- <span className="text-gray-600">→</span>
112
- <span className="text-cyan-400 font-medium">{task.to_name || '--'}</span>
117
+ {/* From → To — round 41: use AliasAvatar so from/to colours
118
+ match the rest of the app instead of the legacy blue/cyan
119
+ hardcode. */}
120
+ <div className="flex items-center gap-2 text-sm flex-wrap">
121
+ {task.from_name && <AliasAvatar alias={task.from_name} size={18} />}
122
+ <span className="text-gray-200 font-medium">{task.from_name || '--'}</span>
123
+ <span className="text-gray-600">&rarr;</span>
124
+ {task.to_name && <AliasAvatar alias={task.to_name} size={18} />}
125
+ <span className="text-gray-200 font-medium">{task.to_name || '--'}</span>
113
126
  </div>
114
127
 
115
- {/* Timeline */}
128
+ {/* Timeline — round 36 polish: relative timestamps, current-step
129
+ pulse if task is still in flight, full ISO in title=. */}
116
130
  <div className="bg-[#111128] border border-[#2a2a4a] rounded-xl p-4">
117
- <div className="text-xs text-gray-500 uppercase mb-3">Timeline</div>
131
+ <div className="flex items-center justify-between mb-3">
132
+ <div className="text-xs text-gray-500 uppercase tracking-wide">Timeline</div>
133
+ {duration !== null && (
134
+ <div className="text-[10px] text-gray-600">
135
+ {duration < 60 ? `${duration}s` : `${Math.floor(duration/60)}m ${duration%60}s`} runtime
136
+ </div>
137
+ )}
138
+ </div>
118
139
  <div className="space-y-3">
119
- {timeline.map((step, i) => (
120
- <div key={step.label} className="flex items-center gap-3">
121
- <div className="flex flex-col items-center">
122
- <div className={`w-3 h-3 rounded-full ${step.time ? step.color : 'bg-gray-700'}`} />
123
- {i < timeline.length - 1 && <div className={`w-0.5 h-4 mt-1 ${step.time && timeline[i+1]?.time ? 'bg-gray-600' : 'bg-gray-800'}`} />}
140
+ {timeline.map((step, i) => {
141
+ const isCurrent = isActive && i === currentStepIdx;
142
+ const isNextDone = !!timeline[i+1]?.done;
143
+ return (
144
+ <div key={step.key} className="flex items-start gap-3">
145
+ <div className="flex flex-col items-center pt-0.5">
146
+ <span
147
+ className={`relative w-3 h-3 rounded-full shrink-0 ${
148
+ step.done ? step.color : 'bg-gray-700'
149
+ }`}
150
+ >
151
+ {isCurrent && (
152
+ <span
153
+ aria-hidden
154
+ className="absolute -inset-1 rounded-full border border-current opacity-60 anet-current-step-pulse"
155
+ style={{ borderColor: 'currentColor' }}
156
+ />
157
+ )}
158
+ </span>
159
+ {i < timeline.length - 1 && (
160
+ <div className={`w-0.5 h-5 mt-1 ${
161
+ step.done && isNextDone ? 'bg-gray-500' : step.done ? 'bg-gradient-to-b from-gray-500 to-gray-800' : 'bg-gray-800'
162
+ }`} />
163
+ )}
164
+ </div>
165
+ <div className="flex-1 min-w-0">
166
+ <div className={`text-xs font-medium flex items-center gap-2 ${
167
+ step.done ? 'text-gray-200' : isCurrent ? 'text-cyan-300' : 'text-gray-600'
168
+ }`}>
169
+ <span>{step.label}</span>
170
+ {isCurrent && <span className="text-[9px] uppercase tracking-wide text-cyan-400">in&nbsp;progress</span>}
171
+ </div>
172
+ <div className="text-[10px] text-gray-500" title={step.time || undefined}>
173
+ {step.time ? timeAgo(step.time) : isCurrent ? '—' : 'Pending'}
174
+ </div>
175
+ </div>
124
176
  </div>
125
- <div className="flex-1">
126
- <div className={`text-xs font-medium ${step.time ? 'text-gray-300' : 'text-gray-600'}`}>{step.label}</div>
127
- <div className="text-[10px] text-gray-500">{step.time || 'Pending'}</div>
128
- </div>
129
- </div>
130
- ))}
177
+ );
178
+ })}
131
179
  </div>
132
180
  </div>
133
181
 
@@ -172,10 +220,6 @@ export function TaskDrawer({ taskId, onClose }: TaskDrawerProps) {
172
220
  </div>
173
221
  )}
174
222
  </div>
175
- <style jsx global>{`
176
- @keyframes slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
177
- .animate-slide-in { animation: slide-in 0.2s ease-out; }
178
- `}</style>
179
223
  </>
180
224
  );
181
225
  }