@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
package/app/node/page.tsx CHANGED
@@ -6,6 +6,8 @@ import Link from 'next/link';
6
6
  import { Suspense } from 'react';
7
7
  import { TaskChatPanel } from '../components/TaskChatPanel';
8
8
  import { timeAgo } from '../components/utils';
9
+ import { AliasAvatar } from '../components/AliasAvatar';
10
+ import { SESSION_STATUS_TEXT_CLASS } from '../lib/status';
9
11
 
10
12
  interface SessionDetail {
11
13
  resume_id: string;
@@ -145,20 +147,29 @@ function NodeDetailContent() {
145
147
  );
146
148
  }
147
149
 
148
- const statusColor = session?.status === 'working' ? 'text-green-400' :
149
- session?.status === 'idle' ? 'text-blue-400' : 'text-gray-500';
150
+ /* Round 92: shared 5-state palette (was working / idle / other,
151
+ collapsed blocked + error into gray). */
152
+ const statusColor = SESSION_STATUS_TEXT_CLASS[session?.status || ''] || SESSION_STATUS_TEXT_CLASS.offline;
150
153
 
151
154
  return (
152
155
  <div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
153
- {/* Header */}
154
- <div className="flex items-center gap-4 mb-6">
155
- <Link href="/nodes" className="text-gray-500 hover:text-gray-300 text-sm lg:ml-0 ml-10">&larr; Nodes</Link>
156
- <div className="flex items-center gap-3">
157
- <span className={`inline-block w-3 h-3 rounded-full ${
158
- sse > 0 ? (session?.status === 'working' ? 'bg-green-500 animate-pulse' : 'bg-emerald-400') : 'bg-gray-500'
159
- }`} />
160
- <h1 className="text-2xl font-bold text-white">{alias}</h1>
161
- <span className={`text-sm ${statusColor}`}>{session?.status || 'unknown'}</span>
156
+ {/* Header — round 40: avatar joins the alias for cross-page hue
157
+ consistency; agent type lives in a subtitle so users know what
158
+ kind of node they're looking at. */}
159
+ <div className="flex items-center gap-3 mb-6 lg:ml-0 ml-10">
160
+ <Link href="/nodes" className="text-gray-500 hover:text-gray-300 text-sm shrink-0">&larr; Nodes</Link>
161
+ <AliasAvatar alias={alias} size={32} />
162
+ <div className="min-w-0 flex-1">
163
+ <div className="flex items-center gap-2">
164
+ <h1 className="text-xl sm:text-2xl font-bold text-white truncate">{alias}</h1>
165
+ <span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${
166
+ sse > 0 ? (session?.status === 'working' ? 'bg-green-500 animate-pulse' : 'bg-emerald-400') : 'bg-gray-500'
167
+ }`} />
168
+ <span className={`text-[10px] uppercase tracking-wide font-semibold ${statusColor}`}>{session?.status || 'unknown'}</span>
169
+ </div>
170
+ {session?.agent && (
171
+ <div className="text-xs text-gray-500 truncate">{session.agent}{session.server ? <> <span className="text-gray-700 mx-1">·</span> {session.server}</> : null}</div>
172
+ )}
162
173
  </div>
163
174
  </div>
164
175
 
@@ -190,14 +201,22 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
190
201
 
191
202
  return (
192
203
  <div className="bg-[#0d0d1a] border border-[#2a2a4a] rounded-xl overflow-hidden h-[calc(100vh-140px)] flex flex-col">
193
- {/* Tab bar */}
204
+ {/* Tab bar — round 40: emoji icons (💬 📋 📊) replaced with stroke
205
+ SVG to drop the "AI generated" tell. */}
194
206
  <div className="flex border-b border-[#2a2a4a] bg-[#0a0a15] shrink-0">
195
- {(['chat', 'events', 'info'] as const).map(t => (
196
- <button key={t} onClick={() => setTab(t)}
197
- className={`px-5 py-3 text-sm font-medium border-b-2 transition-colors ${
198
- tab === t ? 'border-cyan-400 text-cyan-300' : 'border-transparent text-gray-500 hover:text-gray-300'
207
+ {([
208
+ { id: 'chat', label: 'Chat', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },
209
+ { id: 'events', label: 'Events', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },
210
+ { id: 'info', label: 'Info', icon: 'M12 21a9 9 0 100-18 9 9 0 000 18z M12 8h.01 M11 12h1v4h1' },
211
+ ] as const).map(t => (
212
+ <button key={t.id} onClick={() => setTab(t.id)}
213
+ className={`flex items-center gap-2 px-5 py-3 text-sm font-medium border-b-2 transition-colors ${
214
+ tab === t.id ? 'border-cyan-400 text-cyan-300' : 'border-transparent text-gray-500 hover:text-gray-300'
199
215
  }`}>
200
- {t === 'chat' ? '💬 Chat' : t === 'events' ? '📋 Events' : '📊 Info'}
216
+ <svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
217
+ <path d={t.icon} />
218
+ </svg>
219
+ <span>{t.label}</span>
201
220
  </button>
202
221
  ))}
203
222
  </div>
@@ -299,13 +318,16 @@ function NodeFullPanel({ alias, session, sse, sendMsg, setSendMsg, sending, send
299
318
  );
300
319
  }
301
320
 
302
- const EVENT_COLORS: Record<string, { dot: string; line: string; icon: string }> = {
303
- delivered: { dot: 'bg-blue-400', line: 'bg-blue-500/30', icon: '📬' },
304
- acked: { dot: 'bg-yellow-400', line: 'bg-yellow-500/30', icon: '✅' },
305
- running: { dot: 'bg-green-400', line: 'bg-green-500/30', icon: '⚡' },
306
- replied: { dot: 'bg-purple-400', line: 'bg-purple-500/30', icon: '💬' },
307
- failed: { dot: 'bg-red-400', line: 'bg-red-500/30', icon: '' },
308
- created: { dot: 'bg-gray-400', line: 'bg-gray-500/30', icon: '📝' },
321
+ /** Round 59: emoji icons replaced with stroke SVG paths to drop the
322
+ * "AI generated" tell. Each path is sized to fit inside the 18px
323
+ * colored event dot. */
324
+ const EVENT_COLORS: Record<string, { dot: string; line: string; path: string }> = {
325
+ delivered: { dot: 'bg-blue-400', line: 'bg-blue-500/30', path: 'M12 5v14 M5 12l7 7 7-7' }, // down arrow
326
+ acked: { dot: 'bg-yellow-400', line: 'bg-yellow-500/30', path: 'M20 6 9 17l-5-5' }, // check
327
+ running: { dot: 'bg-green-400', line: 'bg-green-500/30', path: 'M13 2L4 14h6l-1 8 10-12h-6l1-8z' }, // bolt
328
+ replied: { dot: 'bg-purple-400', line: 'bg-purple-500/30', path: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' }, // chat bubble
329
+ failed: { dot: 'bg-red-400', line: 'bg-red-500/30', path: 'M18 6 6 18 M6 6l12 12' }, // X
330
+ created: { dot: 'bg-gray-400', line: 'bg-gray-500/30', path: 'M12 5v14 M5 12h14' }, // plus
309
331
  };
310
332
 
311
333
  function EventsTimeline({ events, loading }: { events: Array<{ id: number; event_type: string; from_status: string; to_status: string; detail: string; created_at: string; task_id?: string }>; loading: boolean }) {
@@ -334,9 +356,12 @@ function EventsTimeline({ events, loading }: { events: Array<{ id: number; event
334
356
  const cfg = EVENT_COLORS[e.to_status] || EVENT_COLORS.created;
335
357
  return (
336
358
  <div key={e.id} className="relative pb-4 last:pb-0">
337
- {/* Dot on line */}
338
- <div className={`absolute -left-6 top-0.5 w-[18px] h-[18px] rounded-full ${cfg.dot} flex items-center justify-center text-[9px] z-10 border-2 border-[#111128]`}>
339
- {cfg.icon.length <= 2 ? cfg.icon : ''}
359
+ {/* Dot on line — round 59: emoji icons replaced with
360
+ a 10px stroke SVG path matching event type. */}
361
+ <div className={`absolute -left-6 top-0.5 w-[18px] h-[18px] rounded-full ${cfg.dot} flex items-center justify-center z-10 border-2 border-[#111128]`}>
362
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#0a0a15" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
363
+ <path d={cfg.path} />
364
+ </svg>
340
365
  </div>
341
366
  {/* Content */}
342
367
  <div className="ml-2">
@@ -5,17 +5,18 @@ import Link from 'next/link';
5
5
  import { timeAgo } from '../components/utils';
6
6
  import { useSessions, useHealth } from '../lib/hooks';
7
7
  import { TaskChatPanel } from '../components/TaskChatPanel';
8
- import { EmptyState } from '../components/EmptyState';
8
+ import { EmptyState, NodesEmptyState } from '../components/EmptyState';
9
9
  import { AliasAvatar } from '../components/AliasAvatar';
10
10
  import type { Session } from '../components/types';
11
+ import { SESSION_STATUS_CHIP_CLASS as STATUS_COLORS } from '../lib/status';
11
12
 
12
- const STATUS_COLORS: Record<string, string> = {
13
- working: 'bg-green-500/10 text-green-300 border-green-500/20',
14
- idle: 'bg-blue-500/10 text-blue-300 border-blue-500/20',
15
- blocked: 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20',
16
- error: 'bg-red-500/10 text-red-300 border-red-500/20',
17
- offline: 'bg-gray-500/10 text-gray-500 border-gray-500/20',
18
- };
13
+ /** Round 81: shorten long server hostnames (Alibaba `iZ…oyZ` style)
14
+ * for table display. Returns the original string unchanged when ≤12
15
+ * chars. Full value should stay in `title=` for hover + screen-readers. */
16
+ function shortServer(server: string | null | undefined): string {
17
+ if (!server) return '—';
18
+ return server.length > 12 ? `${server.slice(0, 8)}…` : server;
19
+ }
19
20
 
20
21
  type ViewMode = 'list' | 'grid';
21
22
  type SessionRow = Session & { online: boolean };
@@ -121,6 +122,12 @@ export default function NodesPage() {
121
122
  );
122
123
  })()}
123
124
 
125
+ {/* Round 74: hide the filter+view chrome when there are no nodes
126
+ anywhere — these controls have nothing to act on, and they only
127
+ push the onboarding CTA further down. When at least one session
128
+ exists, the chrome is back even if the current filter happens
129
+ to hide everything (so users can clear filters). */}
130
+ {sessions.length > 0 && (
124
131
  <div className="flex flex-wrap gap-3 mb-6">
125
132
  <input
126
133
  type="text"
@@ -142,7 +149,11 @@ export default function NodesPage() {
142
149
  <option value="blocked">Blocked</option>
143
150
  <option value="error">Error</option>
144
151
  </select>
145
- <div className="ml-auto flex rounded-lg border border-[#2a2a4a] bg-[#111128] p-1 text-sm">
152
+ {/* Round 80: List/Grid toggle hidden on mobile — Grid uses
153
+ grid-cols-1 below `md` and List degrades to the same single
154
+ column, so the toggle has no visual effect under sm and only
155
+ steals a row of vertical space. */}
156
+ <div className="hidden sm:flex ml-auto rounded-lg border border-[#2a2a4a] bg-[#111128] p-1 text-sm">
146
157
  {(['list', 'grid'] as const).map(mode => (
147
158
  <button
148
159
  key={mode}
@@ -159,17 +170,27 @@ export default function NodesPage() {
159
170
  ))}
160
171
  </div>
161
172
  </div>
173
+ )}
162
174
 
163
175
  {loading ? (
164
176
  <div className="animate-pulse space-y-2">
165
177
  {[1,2,3,4].map(i => <div key={i} className="h-16 bg-gray-800/20 rounded-lg" />)}
166
178
  </div>
167
179
  ) : filtered.length === 0 ? (
168
- <EmptyState
169
- variant="nodes"
170
- title="No nodes match your filters"
171
- sub="Try clearing search or status filters, or wait for an agent to register."
172
- />
180
+ sessions.length === 0 ? (
181
+ /* Round 73: first-run case — no agents anywhere. Use the same
182
+ onboarding empty state as the Overview (NodesEmptyState
183
+ includes the `npx init` quickstart command). Previously this
184
+ was the "No nodes match your filters" copy, which was wrong
185
+ for new users who have no filters applied. */
186
+ <NodesEmptyState />
187
+ ) : (
188
+ <EmptyState
189
+ variant="nodes"
190
+ title="No nodes match your filters"
191
+ sub="Try clearing search or status filters, or wait for an agent to register."
192
+ />
193
+ )
173
194
  ) : viewMode === 'grid' ? (
174
195
  <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
175
196
  {filtered.map(s => {
@@ -199,7 +220,7 @@ export default function NodesPage() {
199
220
  </span>
200
221
  </div>
201
222
  <div className="mt-0.5 truncate text-xs text-gray-500">
202
- {(s.agent || '—')}<span className="text-gray-700 mx-1.5">·</span>{(s.server || '')}
223
+ {(s.agent || '—')}<span className="text-gray-700 mx-1.5">·</span><span title={s.server || ''}>{shortServer(s.server)}</span>
203
224
  </div>
204
225
  </Link>
205
226
  <div className="flex gap-1.5 shrink-0">
@@ -268,12 +289,14 @@ export default function NodesPage() {
268
289
  </div>
269
290
  ) : (
270
291
  <div className="space-y-2">
292
+ {/* Round 94: AGENT + SERVER merged into one `agent · server`
293
+ cell; the freed 2 columns expand CURRENT TASK so long task
294
+ content lines wrap less. Total still 12: 1+2+2+4+1+2. */}
271
295
  <div className="hidden sm:grid sm:grid-cols-12 gap-2 px-4 py-2 text-xs text-gray-600 uppercase">
272
296
  <div className="col-span-1">Status</div>
273
297
  <div className="col-span-2">Alias</div>
274
- <div className="col-span-2">Agent</div>
275
- <div className="col-span-2">Server</div>
276
- <div className="col-span-2">Current Task</div>
298
+ <div className="col-span-2">Agent · Server</div>
299
+ <div className="col-span-4">Current Task</div>
277
300
  <div className="col-span-1">Updated</div>
278
301
  <div className="col-span-2 text-right">Actions</div>
279
302
  </div>
@@ -298,9 +321,10 @@ export default function NodesPage() {
298
321
  <span className="truncate text-sm font-medium text-white">{s.alias}</span>
299
322
  </Link>
300
323
  </div>
301
- <div className="col-span-2 truncate text-xs text-gray-400">{s.agent || '--'}</div>
302
- <div className="col-span-2 truncate text-xs text-gray-400">{s.server || '--'}</div>
303
- <div className="col-span-2 truncate text-xs text-gray-500">{s.task || '--'}</div>
324
+ <div className="col-span-2 truncate text-xs text-gray-400" title={s.server || ''}>
325
+ {s.agent || '--'}<span className="text-gray-700 mx-1.5">·</span>{shortServer(s.server)}
326
+ </div>
327
+ <div className="col-span-4 truncate text-xs text-gray-500" title={s.task || ''}>{s.task || '--'}</div>
304
328
  <div className="col-span-1 text-xs text-gray-500">{timeAgo(s.last_seen_at || s.updated_at)}</div>
305
329
  <div className="col-span-2 flex justify-end gap-1.5">
306
330
  <button type="button" onClick={() => setChatAlias(s.alias)}
package/app/page.tsx CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
4
  import Link from 'next/link';
5
- import { formatUptime } from './components/utils';
5
+ import { formatUptime, previewContent } from './components/utils';
6
6
  import { StatsBar } from './components/StatsBar';
7
7
  import { BroadcastBar } from './components/BroadcastBar';
8
8
  import { TopoGraph } from './components/TopoGraph';
@@ -11,6 +11,7 @@ import { InboxPanel } from './components/InboxPanel';
11
11
  import { LoadingSkeleton } from './components/LoadingSkeleton';
12
12
  import { NodesEmptyState as EmptyState } from './components/EmptyState';
13
13
  import { AliasAvatar } from './components/AliasAvatar';
14
+ import { STATUS_DOT_HEX, STATUS_CHIP_CLASS } from './lib/status';
14
15
  import { UserBar } from './components/UserBar';
15
16
  import { CommandCenter, useCommandCenter } from './components/CommandCenter';
16
17
  import { DispatchPanel } from './components/DispatchPanel';
@@ -41,6 +42,7 @@ export default function Dashboard() {
41
42
  const cmd = useCommandCenter();
42
43
  const [showDispatch, setShowDispatch] = useState(false);
43
44
  const [inbox, setInbox] = useState<InboxMessage[]>([]);
45
+ const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');
44
46
  const { mutate } = useSWRConfig();
45
47
 
46
48
  // SSE: instant revalidation on CommHub events
@@ -114,18 +116,27 @@ export default function Dashboard() {
114
116
  return bWorking - aWorking;
115
117
  });
116
118
 
119
+ /** Round 70: when the fleet is empty, the Overview reorders to lead with
120
+ * the "Spin up your first agent" CTA and hides the Quick Navigation /
121
+ * Nav rail / Broadcast bar that would otherwise occupy prime real estate
122
+ * with zeros and dead-end links. Computed once, used as a gate below. */
123
+ const fleetEmpty = sessions.length === 0 && !sessError;
124
+
117
125
  return (
118
126
  <div className="min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono">
119
127
  <div className="lg:ml-0 ml-10">
120
128
  <StatsBar online={online} working={working} total={total} version={version} uptime={uptime} />
121
129
  </div>
122
130
 
123
- {/* Dispatch + User Bar */}
131
+ {/* Dispatch + User Bar — Dispatch hidden when fleet empty (nothing to
132
+ dispatch to); UserBar still useful (account/sign-out menu). */}
124
133
  <div className="flex items-center gap-3 mb-4">
125
- <button onClick={() => setShowDispatch(true)}
126
- className="px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0">
127
- Dispatch
128
- </button>
134
+ {!fleetEmpty && (
135
+ <button onClick={() => setShowDispatch(true)}
136
+ className="px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0">
137
+ ⚡ Dispatch
138
+ </button>
139
+ )}
129
140
  <div className="flex-1"><UserBar /></div>
130
141
  </div>
131
142
 
@@ -168,18 +179,15 @@ export default function Dashboard() {
168
179
  <Link href="/tasks" prefetch={false} className="text-xs text-cyan-400 hover:text-cyan-300">View all &rarr;</Link>
169
180
  </div>
170
181
  <div className="flex flex-wrap gap-2">
171
- {[
172
- { key: 'running', color: 'bg-green-500/10 text-green-300 border-green-500/20' },
173
- { key: 'delivered', color: 'bg-blue-500/10 text-blue-300 border-blue-500/20' },
174
- { key: 'acked', color: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20' },
175
- { key: 'replied', color: 'bg-purple-500/10 text-purple-300 border-purple-500/20' },
176
- { key: 'failed', color: 'bg-red-500/10 text-red-300 border-red-500/20' },
177
- { key: 'cancelled', color: 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20' },
178
- { key: 'expired', color: 'bg-orange-500/10 text-orange-300 border-orange-500/20' },
179
- { key: 'closed', color: 'bg-gray-500/10 text-gray-500 border-gray-500/20' },
180
- ].filter(({ key }) => taskStats[key])
181
- .map(({ key, color }) => (
182
- <Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${color} hover:opacity-80 transition-opacity`}>
182
+ {/* Round 69: order is "hot first" — active flow before terminal
183
+ states intentionally different from the lifecycle order on
184
+ /tasks. Colors come from shared STATUS_CHIP_CLASS so a
185
+ palette tweak in app/lib/status.ts updates here too.
186
+ 'created' added in r69 (was missing — enum-coverage bug). */}
187
+ {(['running', 'delivered', 'acked', 'replied', 'created', 'failed', 'cancelled', 'expired', 'closed'] as const)
188
+ .filter((key) => taskStats[key])
189
+ .map((key) => (
190
+ <Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${STATUS_CHIP_CLASS[key]} hover:opacity-80 transition-opacity`}>
183
191
  {key}: {taskStats[key]}
184
192
  </Link>
185
193
  ))}
@@ -191,7 +199,11 @@ export default function Dashboard() {
191
199
  (1) Top: live stat cards (carry data, drill-in on click)
192
200
  (2) Bottom: pure nav rail (no number, icon + label)
193
201
  Round 24 — wrap both in a labelled block so the rhythm reads as
194
- "here are the main jumps" instead of two disconnected strips. */}
202
+ "here are the main jumps" instead of two disconnected strips.
203
+ Round 70 — entire Quick Nav + Nav rail + Broadcast + Recent
204
+ Activity block is hidden when the fleet is empty so the
205
+ onboarding CTA gets the page above the fold. */}
206
+ {!fleetEmpty && <>
195
207
  <div className="text-[10px] uppercase tracking-[0.12em] text-gray-600 mb-2">Quick navigation</div>
196
208
  <section className="mb-3 grid grid-cols-3 gap-2 sm:gap-3">
197
209
  {(() => {
@@ -312,23 +324,25 @@ export default function Dashboard() {
312
324
  <div className="space-y-2 max-h-40 overflow-y-auto">
313
325
  {tasks.slice(0, 5).map((t: { task_id: string; from_name: string; to_name: string; status: string; content: string; created_at: string }) => (
314
326
  <div key={t.task_id} className="flex items-center gap-2 text-xs">
315
- <span className={`w-1.5 h-1.5 rounded-full shrink-0 ${
316
- t.status === 'replied' ? 'bg-purple-400' : t.status === 'running' ? 'bg-green-400' : t.status === 'failed' ? 'bg-red-400' : 'bg-blue-400'
317
- }`} />
327
+ <span
328
+ className="w-1.5 h-1.5 rounded-full shrink-0"
329
+ style={{ backgroundColor: STATUS_DOT_HEX[t.status] || '#6b7280' }}
330
+ />
318
331
  {t.from_name && <AliasAvatar alias={t.from_name} size={14} />}
319
332
  <span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.from_name || '?'}</span>
320
333
  <span className="text-gray-600">&rarr;</span>
321
334
  {t.to_name && <AliasAvatar alias={t.to_name} size={14} />}
322
335
  <span className="text-gray-300 shrink-0 max-w-[20%] truncate">{t.to_name || '?'}</span>
323
- <span className="text-gray-500 truncate flex-1">{t.content?.slice(0, 40) || '--'}</span>
336
+ <span className="text-gray-500 truncate flex-1" title={t.content || ''}>{previewContent(t.content).slice(0, 60)}</span>
324
337
  <span className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] border ${
325
- t.status === 'replied' ? 'text-purple-300 border-purple-500/20' : t.status === 'failed' ? 'text-red-300 border-red-500/20' : 'text-gray-500 border-gray-700/30'
338
+ STATUS_CHIP_CLASS[t.status] || 'text-gray-500 border-gray-700/30'
326
339
  }`}>{t.status}</span>
327
340
  </div>
328
341
  ))}
329
342
  </div>
330
343
  </section>
331
344
  )}
345
+ </>}
332
346
 
333
347
  {sessError && (
334
348
  <div className="bg-red-900/20 border border-red-800/40 text-red-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-center justify-between" role="alert">
@@ -358,14 +372,71 @@ export default function Dashboard() {
358
372
  {showTopo && sessions.length > 0 && <TopoGraph sessions={sessions} sseSessions={sseSessions} />}
359
373
 
360
374
  {sessions.length === 0 && !sessError ? (
361
- <EmptyState hint={sessHint} />
362
- ) : (
363
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4">
364
- {sortedSessions.map(s => (
365
- <AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />
366
- ))}
367
- </div>
368
- )}
375
+ <EmptyState
376
+ hint={sessHint}
377
+ taskHistoryCount={Object.values(taskStats).reduce((a, b) => a + b, 0)}
378
+ />
379
+ ) : (() => {
380
+ const counts = {
381
+ all: sortedSessions.length,
382
+ working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,
383
+ idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,
384
+ offline: sortedSessions.filter(s => !isOnline(s)).length,
385
+ };
386
+ const filtered = sortedSessions.filter(s => {
387
+ if (agentFilter === 'all') return true;
388
+ if (agentFilter === 'offline') return !isOnline(s);
389
+ if (agentFilter === 'working') return isOnline(s) && s.status === 'working';
390
+ if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';
391
+ return true;
392
+ });
393
+ // Filter chip color keyed to status (round 34): working=green, idle=cyan,
394
+ // offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.
395
+ const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [
396
+ { key: 'all', label: 'All' },
397
+ { key: 'working', label: 'Working', dot: '#4ade80' },
398
+ { key: 'idle', label: 'Idle', dot: '#22d3ee' },
399
+ { key: 'offline', label: 'Offline', dot: '#6b7280' },
400
+ ];
401
+ return (
402
+ <>
403
+ <div className="mb-3 flex flex-wrap items-center gap-1">
404
+ {chips.map(c => (
405
+ <button
406
+ key={c.key}
407
+ type="button"
408
+ onClick={() => setAgentFilter(c.key)}
409
+ disabled={counts[c.key] === 0 && c.key !== 'all'}
410
+ className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${
411
+ agentFilter === c.key
412
+ ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'
413
+ : 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'
414
+ }`}
415
+ >
416
+ {c.dot && <span aria-hidden className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.dot }} />}
417
+ <span>{c.label}</span>
418
+ <span className={`text-[10px] tabular-nums ${agentFilter === c.key ? 'text-cyan-400' : 'text-gray-600'}`}>{counts[c.key]}</span>
419
+ </button>
420
+ ))}
421
+ </div>
422
+ {/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`
423
+ which kept lg (1024-1279px) at only 2 columns even though
424
+ each AgentCard is fine ≥260px wide. With the sidebar (208px),
425
+ main area at lg is ~816px so 3 cols at ~272px each fits.
426
+ xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}
427
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4">
428
+ {filtered.map(s => (
429
+ <AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />
430
+ ))}
431
+ </div>
432
+ {filtered.length === 0 && (
433
+ <div className="mt-4 text-center text-xs text-gray-600">
434
+ No agents match "{agentFilter}" — <button onClick={() => setAgentFilter('all')} className="underline hover:text-gray-400">Show all</button>
435
+ </div>
436
+ )}
437
+ </>
438
+ );
439
+ })()}
369
440
 
370
441
  <InboxPanel messages={inbox} />
371
442