@sleep2agi/agent-network-dashboard 0.5.3 → 0.5.4
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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -0
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +35 -35
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +29 -0
- package/.next/routes-manifest.json +12 -0
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +2 -2
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +15 -15
- package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/hub/upload/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/hub/upload/route/build-manifest.json +9 -0
- package/.next/server/app/api/hub/upload/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/hub/upload/route.js +7 -0
- package/.next/server/app/api/hub/upload/route.js.map +5 -0
- package/.next/server/app/api/hub/upload/route.js.nft.json +1 -0
- package/.next/server/app/api/hub/upload/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +15 -15
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +15 -15
- package/.next/server/app/login.segments/_full.segment.rsc +15 -15
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +15 -15
- package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/manifest.webmanifest/route/app-paths-manifest.json +3 -0
- package/.next/server/app/manifest.webmanifest/route/build-manifest.json +9 -0
- package/.next/server/app/manifest.webmanifest/route/server-reference-manifest.json +4 -0
- package/.next/server/app/manifest.webmanifest/route.js +8 -0
- package/.next/server/app/manifest.webmanifest/route.js.map +5 -0
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -0
- package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +3 -0
- package/.next/server/app/manifest.webmanifest.body +1 -0
- package/.next/server/app/manifest.webmanifest.meta +1 -0
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +15 -15
- package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +15 -15
- package/.next/server/app/node.segments/_full.segment.rsc +15 -15
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +15 -15
- package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +15 -15
- package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +15 -15
- package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +15 -15
- package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +2 -0
- package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js +18 -0
- package/.next/server/chunks/00jm_next_dist_0ju_ux9._.js.map +1 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js +3 -0
- package/.next/server/chunks/0ykm__next-internal_server_app_manifest_webmanifest_route_actions_0m16rb8.js.map +1 -0
- package/.next/server/chunks/{[externals]__11vad82._.js → [externals]__036g1.i._.js} +2 -2
- package/.next/server/chunks/[externals]__036g1.i._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-8_f0o._.js.map +1 -0
- package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js → [root-of-the-server]__0jndzts._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__08uyvdq._.js.map → [root-of-the-server]__0jndzts._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js.map +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js +1 -1
- package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +4 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -4
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/middleware.js +2 -2
- package/.next/server/middleware.js.nft.json +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{07~8sx3_5uiox.js → 04wjx7vbxusw5.js} +4 -1
- package/.next/static/chunks/089t1exs6apb8.js +4 -0
- package/.next/static/chunks/{0ph1in3421~o-.js → 09e8kxo30n5cf.js} +1 -1
- package/.next/static/chunks/{0v4-5tng.uh.7.js → 0k-c1chkvf78s.js} +2 -2
- package/.next/static/chunks/0v2~nlpk-cx6v.css +2 -0
- package/.next/static/chunks/0xsye-9kffdi0.js +1 -0
- package/.next/static/chunks/0~rx_~akeylmq.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/hub/send/route.ts +11 -2
- package/app/api/hub/servers/route.ts +3 -29
- package/app/api/hub/upload/route.ts +83 -0
- package/app/components/AppShell.tsx +3 -1
- package/app/components/MobileNav.tsx +45 -0
- package/app/components/TaskChatPanel.tsx +130 -6
- package/app/components/TopoGraph.tsx +6686 -248
- package/app/globals.css +897 -7
- package/app/layout.tsx +6 -0
- package/app/lib/serverDedupe.ts +122 -0
- package/app/lib/vendorIdentity.ts +74 -56
- package/app/manifest.ts +23 -0
- package/package.json +4 -4
- package/public/vendors/claude.svg +7 -8
- package/public/vendors/minimax.svg +8 -9
- package/public/vendors/openai.svg +8 -10
- package/scripts/p157-rc2-dedup-test.mjs +110 -0
- package/scripts/topo-a11y-titles-catalog-test.mjs +132 -0
- package/scripts/topo-active-chrome-hover-text-test.mjs +107 -0
- package/scripts/topo-active-links-chip-halo-layers-test.mjs +81 -0
- package/scripts/topo-alias-chat-brightness-test.mjs +79 -0
- package/scripts/topo-alias-text-halo-layers-test.mjs +98 -0
- package/scripts/topo-animation-temporal-modes-catalog-test.mjs +144 -0
- package/scripts/topo-avatar-chat-gate-test.mjs +77 -0
- package/scripts/topo-avatar-drop-shadow-test.mjs +86 -0
- package/scripts/topo-avatar-fallback-hover-test.mjs +104 -0
- package/scripts/topo-avatar-fallback-rotate-test.mjs +92 -0
- package/scripts/topo-avatar-rotate-test.mjs +85 -0
- package/scripts/topo-avatar-scale-test.mjs +89 -0
- package/scripts/topo-badge-chat-gate-test.mjs +74 -0
- package/scripts/topo-brand-drop-shadow-test.mjs +71 -0
- package/scripts/topo-brand-logo-breath-test.mjs +102 -0
- package/scripts/topo-brand-logo-halo-layers-test.mjs +85 -0
- package/scripts/topo-brand-logo-hover-brightness-test.mjs +105 -0
- package/scripts/topo-brand-logo-hover-rotate-test.mjs +93 -0
- package/scripts/topo-brand-logo-hover-test.mjs +85 -0
- package/scripts/topo-canvas-desc-a11y-test.mjs +87 -0
- package/scripts/topo-canvas-entrance-animation-test.mjs +117 -0
- package/scripts/topo-canvas-scan-beam-diagonal-test.mjs +148 -0
- package/scripts/topo-canvas-scan-beam-test.mjs +109 -0
- package/scripts/topo-canvas-scan-beam-vertical-test.mjs +123 -0
- package/scripts/topo-card-chat-brightness-test.mjs +86 -0
- package/scripts/topo-chat-ring-breath-curve-test.mjs +114 -0
- package/scripts/topo-chat-ring-brightness-test.mjs +80 -0
- package/scripts/topo-chat-ring-halo-layers-test.mjs +100 -0
- package/scripts/topo-chat-ring-r-breath-test.mjs +121 -0
- package/scripts/topo-chat-ring-status-halo-test.mjs +106 -0
- package/scripts/topo-chat-ring-sw-breath-test.mjs +123 -0
- package/scripts/topo-chip-pin-halo-test.mjs +85 -0
- package/scripts/topo-chip-row-digit-ls-test.mjs +135 -0
- package/scripts/topo-chip-row-member-alias-lit-test.mjs +154 -0
- package/scripts/topo-chip-row-tier-glow-brightness-test.mjs +99 -0
- package/scripts/topo-chip-row-unit-hover-tracking-test.mjs +124 -0
- package/scripts/topo-chrome-control-halo-layers-test.mjs +22 -0
- package/scripts/topo-chrome-fullscreen-breath-test.mjs +121 -0
- package/scripts/topo-chrome-layout-trailer-breath-test.mjs +86 -0
- package/scripts/topo-chrome-nodesize-trailer-breath-test.mjs +86 -0
- package/scripts/topo-chrome-reset-breath-test.mjs +115 -0
- package/scripts/topo-chrome-strip-entrance-animation-test.mjs +115 -0
- package/scripts/topo-chrome-wrapper-halo-test.mjs +83 -0
- package/scripts/topo-chrome-zoom-wrapper-breath-test.mjs +85 -0
- package/scripts/topo-click-ripple-glow-test.mjs +86 -0
- package/scripts/topo-click-ripple-halo-layers-test.mjs +79 -0
- package/scripts/topo-click-ripple-sw-test.mjs +110 -0
- package/scripts/topo-cluster-count-attr-test.mjs +80 -0
- package/scripts/topo-crescent-breath-test.mjs +104 -0
- package/scripts/topo-crescent-envelope-breath-test.mjs +103 -0
- package/scripts/topo-crescent-recede-test.mjs +111 -0
- package/scripts/topo-dense-alias-chat-halo-test.mjs +73 -0
- package/scripts/topo-dense-alias-halo-layers-test.mjs +80 -0
- package/scripts/topo-dual-axis-surfaces-catalog-test.mjs +94 -0
- package/scripts/topo-edge-badge-circle-brightness-test.mjs +82 -0
- package/scripts/topo-edge-badge-circle-hot-pulse-test.mjs +100 -0
- package/scripts/topo-edge-badge-digit-halo-layers-test.mjs +107 -0
- package/scripts/topo-edge-badge-endpoint-gate-test.mjs +94 -0
- package/scripts/topo-edge-badge-halo-layers-test.mjs +85 -0
- package/scripts/topo-edge-badge-hot-pulse-test.mjs +92 -0
- package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
- package/scripts/topo-edge-badge-text-brightness-test.mjs +83 -0
- package/scripts/topo-edge-chat-gate-test.mjs +71 -0
- package/scripts/topo-edge-flow-rail-halo-layers-test.mjs +89 -0
- package/scripts/topo-edge-particle-brightness-test.mjs +82 -0
- package/scripts/topo-edge-particle-halo-layers-test.mjs +91 -0
- package/scripts/topo-edge-pill-glow-test.mjs +67 -0
- package/scripts/topo-edge-pin-halo-test.mjs +99 -0
- package/scripts/topo-edge-visible-brightness-test.mjs +84 -0
- package/scripts/topo-edge-visible-halo-layers-test.mjs +87 -0
- package/scripts/topo-endpoint-ring-brightness-test.mjs +83 -0
- package/scripts/topo-endpoint-ring-flow-halo-test.mjs +107 -0
- package/scripts/topo-endpoint-ring-halo-layers-test.mjs +100 -0
- package/scripts/topo-filter-pill-glow-test.mjs +90 -0
- package/scripts/topo-filter-pill-halo-layers-test.mjs +27 -0
- package/scripts/topo-flow-arrow-brightness-test.mjs +82 -0
- package/scripts/topo-flow-rail-brightness-test.mjs +80 -0
- package/scripts/topo-fullscreen-attr-test.mjs +73 -0
- package/scripts/topo-fullscreen-brightness-test.mjs +84 -0
- package/scripts/topo-fullscreen-icon-rotate-test.mjs +93 -0
- package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
- package/scripts/topo-group-box-brightness-test.mjs +84 -0
- package/scripts/topo-group-box-halo-layers-test.mjs +91 -0
- package/scripts/topo-group-chat-gate-test.mjs +77 -0
- package/scripts/topo-group-label-brightness-test.mjs +84 -0
- package/scripts/topo-group-label-halo-layers-test.mjs +78 -0
- package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
- package/scripts/topo-group-label-member-alias-hover-test.mjs +125 -0
- package/scripts/topo-group-pill-glow-test.mjs +76 -0
- package/scripts/topo-group-tint-brightness-test.mjs +82 -0
- package/scripts/topo-h2-dual-axis-breath-test.mjs +92 -0
- package/scripts/topo-h2-triple-axis-breath-test.mjs +142 -0
- package/scripts/topo-halo-chat-gate-test.mjs +72 -0
- package/scripts/topo-hover-detail-halo-test.mjs +76 -0
- package/scripts/topo-hub-a11y-title-test.mjs +95 -0
- package/scripts/topo-hub-core-brightness-test.mjs +82 -0
- package/scripts/topo-hub-core-halo-layers-test.mjs +81 -0
- package/scripts/topo-hub-digit-brightness-test.mjs +79 -0
- package/scripts/topo-hub-digit-halo-layers-test.mjs +76 -0
- package/scripts/topo-hub-digit-ls-test.mjs +119 -0
- package/scripts/topo-hub-halo-brightness-test.mjs +80 -0
- package/scripts/topo-hub-halo-glow-test.mjs +96 -0
- package/scripts/topo-hub-halo-halo-layers-test.mjs +76 -0
- package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
- package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
- package/scripts/topo-hub-highlight-halo-layers-test.mjs +78 -0
- package/scripts/topo-hub-highlight-r-test.mjs +112 -0
- package/scripts/topo-hub-hover-ring-brightness-test.mjs +79 -0
- package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
- package/scripts/topo-hub-hover-ring-halo-layers-test.mjs +71 -0
- package/scripts/topo-hub-spoke-brightness-test.mjs +77 -0
- package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
- package/scripts/topo-hub-spoke-halo-layers-test.mjs +97 -0
- package/scripts/topo-hub-spoke-self-filter-test.mjs +119 -0
- package/scripts/topo-kicker-breath-test.mjs +100 -0
- package/scripts/topo-kicker-dual-axis-breath-test.mjs +81 -0
- package/scripts/topo-kicker-halo-layers-test.mjs +82 -0
- package/scripts/topo-kicker-triple-axis-breath-test.mjs +124 -0
- package/scripts/topo-label-card-brightness-test.mjs +81 -0
- package/scripts/topo-layout-hover-fw-test.mjs +98 -0
- package/scripts/topo-layout-toggle-brightness-test.mjs +94 -0
- package/scripts/topo-layout-toggle-halo-layers-test.mjs +95 -0
- package/scripts/topo-legend-count-brightness-test.mjs +80 -0
- package/scripts/topo-legend-count-halo-layers-test.mjs +79 -0
- package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
- package/scripts/topo-legend-label-fw-test.mjs +107 -0
- package/scripts/topo-legend-panel-title-breath-test.mjs +86 -0
- package/scripts/topo-legend-pin-ring-brightness-test.mjs +82 -0
- package/scripts/topo-legend-pin-ring-halo-layers-test.mjs +71 -0
- package/scripts/topo-legend-row-count-brightness-test.mjs +85 -0
- package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
- package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
- package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
- package/scripts/topo-legend-tint-brightness-test.mjs +83 -0
- package/scripts/topo-legend-trio-halo-layers-test.mjs +22 -0
- package/scripts/topo-live-lanes.mjs +48 -0
- package/scripts/topo-minimap-container-halo-test.mjs +82 -0
- package/scripts/topo-minimap-dot-chat-gate-test.mjs +81 -0
- package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
- package/scripts/topo-minimap-viewport-brightness-test.mjs +84 -0
- package/scripts/topo-minimap-viewport-halo-layers-test.mjs +24 -0
- package/scripts/topo-more-footer-brightness-test.mjs +94 -0
- package/scripts/topo-node-alias-brightness-test.mjs +84 -0
- package/scripts/topo-node-avatar-halo-layers-test.mjs +25 -0
- package/scripts/topo-node-hover-ring-halo-layers-test.mjs +70 -0
- package/scripts/topo-node-label-card-halo-test.mjs +76 -0
- package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
- package/scripts/topo-nodesize-brightness-test.mjs +82 -0
- package/scripts/topo-nodesize-halo-layers-test.mjs +89 -0
- package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
- package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
- package/scripts/topo-overlap-test.mjs +22 -8
- package/scripts/topo-panel-count-halo-layers-test.mjs +91 -0
- package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
- package/scripts/topo-panel-rect-halo-test.mjs +90 -0
- package/scripts/topo-panel-row-brightness-test.mjs +116 -0
- package/scripts/topo-panel-title-brightness-test.mjs +98 -0
- package/scripts/topo-panel-title-glow-test.mjs +111 -0
- package/scripts/topo-panel-titles-dual-axis-breath-test.mjs +94 -0
- package/scripts/topo-panel-titles-halo-layers-test.mjs +23 -0
- package/scripts/topo-panel-titles-triple-axis-breath-test.mjs +161 -0
- package/scripts/topo-pill-x-rotate-test.mjs +96 -0
- package/scripts/topo-pip-brightness-test.mjs +85 -0
- package/scripts/topo-pressure-bar-halo-layers-test.mjs +19 -0
- package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
- package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
- package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
- package/scripts/topo-r717-scan-beam-pair-pattern-test.mjs +100 -0
- package/scripts/topo-r717-triple-axis-pair-pattern-test.mjs +113 -0
- package/scripts/topo-r717-triple-axis-tier-pattern-test.mjs +117 -0
- package/scripts/topo-recent-count-brightness-test.mjs +84 -0
- package/scripts/topo-recent-more-fw-test.mjs +126 -0
- package/scripts/topo-recent-more-halo-layers-test.mjs +90 -0
- package/scripts/topo-recent-panel-hot-pulse-test.mjs +105 -0
- package/scripts/topo-recent-panel-title-breath-test.mjs +91 -0
- package/scripts/topo-recent-pip-halo-layers-test.mjs +82 -0
- package/scripts/topo-recent-row-chat-gate-test.mjs +75 -0
- package/scripts/topo-recent-row-content-lift-test.mjs +140 -0
- package/scripts/topo-recent-row-fw-test.mjs +115 -0
- package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
- package/scripts/topo-recent-row-text-halo-layers-test.mjs +67 -0
- package/scripts/topo-recent-tint-brightness-test.mjs +80 -0
- package/scripts/topo-recent-ts-brightness-test.mjs +86 -0
- package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
- package/scripts/topo-reset-brightness-test.mjs +83 -0
- package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
- package/scripts/topo-respiratory-axis-count-stats-test.mjs +154 -0
- package/scripts/topo-respiratory-patterns-catalog-test.mjs +112 -0
- package/scripts/topo-respiratory-rolodex-test.mjs +83 -0
- package/scripts/topo-respiratory-tiers-catalog-test.mjs +119 -0
- package/scripts/topo-respiratory-triple-axis-surfaces-catalog-test.mjs +127 -0
- package/scripts/topo-runtime-badge-brightness-test.mjs +78 -0
- package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
- package/scripts/topo-runtime-badge-halo-layers-test.mjs +87 -0
- package/scripts/topo-runtime-badge-rotate-test.mjs +85 -0
- package/scripts/topo-section-title-breath-test.mjs +83 -0
- package/scripts/topo-section-title-halo-layers-test.mjs +88 -0
- package/scripts/topo-spoke-chat-gate-test.mjs +72 -0
- package/scripts/topo-starfield-hue-test.mjs +109 -0
- package/scripts/topo-status-pin-pill-halo-layers-test.mjs +17 -0
- package/scripts/topo-status-ring-brightness-test.mjs +84 -0
- package/scripts/topo-status-ring-chat-gate-test.mjs +72 -0
- package/scripts/topo-status-ring-halo-layers-test.mjs +105 -0
- package/scripts/topo-status-ring-status-halo-test.mjs +110 -0
- package/scripts/topo-sub-text-chat-brightness-test.mjs +81 -0
- package/scripts/topo-svg-title-a11y-r731-test.mjs +93 -0
- package/scripts/topo-svg-title-a11y-test.mjs +88 -0
- package/scripts/topo-title-block-entrance-animation-test.mjs +127 -0
- package/scripts/topo-title-block-envelope-breath-test.mjs +87 -0
- package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
- package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
- package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
- package/scripts/topo-tree-diag.mjs +95 -0
- package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
- package/scripts/topo-vendor-chip-halo-layers-test.mjs +18 -0
- package/scripts/topo-vendor-chip-pin-halo-test.mjs +88 -0
- package/scripts/topo-vendor-count-suffix-halo-layers-test.mjs +79 -0
- package/scripts/topo-vendor-distribution-wrapper-halo-test.mjs +93 -0
- package/scripts/topo-vendor-letter-halo-layers-test.mjs +93 -0
- package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
- package/scripts/topo-watermark-breath-test.mjs +100 -0
- package/scripts/topo-watermark-dual-axis-breath-test.mjs +88 -0
- package/scripts/topo-watermark-envelope-breath-test.mjs +88 -0
- package/scripts/topo-watermark-recede-test.mjs +114 -0
- package/scripts/topo-watermark-triple-axis-breath-test.mjs +129 -0
- package/scripts/topo-working-online-chip-halo-layers-test.mjs +94 -0
- package/scripts/topo-zoom-buttons-brightness-test.mjs +94 -0
- package/scripts/topo-zoom-in-out-halo-layers-test.mjs +97 -0
- package/scripts/topo-zoom-level-breath-test.mjs +87 -0
- package/scripts/topo-zoom-level-brightness-test.mjs +83 -0
- package/scripts/topo-zoom-level-color-test.mjs +105 -0
- package/scripts/topo-zoom-level-dual-axis-breath-test.mjs +83 -0
- package/scripts/topo-zoom-level-halo-layers-test.mjs +78 -0
- package/scripts/topo-zoom-level-triple-axis-breath-test.mjs +148 -0
- package/.next/static/chunks/0fuba20p57-zo.js +0 -1
- package/.next/static/chunks/0m.1mvl~t.avc.css +0 -2
- package/.next/static/chunks/10cj-86disers.js +0 -1
- package/.next/static/chunks/158h2g7f-nxbt.js +0 -4
- /package/.next/server/chunks/{[externals]__11vad82._.js.map → 0ykm__next-internal_server_app_api_hub_upload_route_actions_03_eqnf.js.map} +0 -0
- /package/.next/static/{tQ1EtcEiS7ikgAoAyZWm7 → xq88BMF7fMUHWh10yaKTn}/_buildManifest.js +0 -0
- /package/.next/static/{tQ1EtcEiS7ikgAoAyZWm7 → xq88BMF7fMUHWh10yaKTn}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{tQ1EtcEiS7ikgAoAyZWm7 → xq88BMF7fMUHWh10yaKTn}/_ssgManifest.js +0 -0
package/app/layout.tsx
CHANGED
|
@@ -13,6 +13,12 @@ const geistMono = Geist_Mono({
|
|
|
13
13
|
export const metadata: Metadata = {
|
|
14
14
|
title: "Agent Network Dashboard",
|
|
15
15
|
description: "Real-time monitoring dashboard for Agent Network nodes via CommHub",
|
|
16
|
+
manifest: "/manifest.webmanifest",
|
|
17
|
+
appleWebApp: {
|
|
18
|
+
capable: true,
|
|
19
|
+
statusBarStyle: "black-translucent",
|
|
20
|
+
title: "Agent Network",
|
|
21
|
+
},
|
|
16
22
|
icons: { icon: '/favicon.svg' },
|
|
17
23
|
openGraph: {
|
|
18
24
|
title: "Agent Network Dashboard",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #157 RC#2 — host telemetry row dedup for the Servers panel.
|
|
3
|
+
*
|
|
4
|
+
* The CommHub /api/servers endpoint keys host telemetry by (hostname,
|
|
5
|
+
* ip): a machine whose agents report over more than one interface
|
|
6
|
+
* yields one row per IP. Live data caught `iZrj93pr2rcf5r2y9uo1oyZ`
|
|
7
|
+
* twice — once at the Docker-bridge IP (carrying cpu/mem telemetry)
|
|
8
|
+
* and once at loopback (telemetry null) — so the panel showed the same
|
|
9
|
+
* physical host as two cards with split agent counts.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from the /api/hub/servers route handler so the merge can
|
|
12
|
+
* be unit-tested against a mock fixture independently of the proxy.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface HubServerRow {
|
|
16
|
+
hostname: string;
|
|
17
|
+
ip?: string | null;
|
|
18
|
+
agent_count?: number;
|
|
19
|
+
cpu_load_1min?: number | null;
|
|
20
|
+
cpu_cores?: number | null;
|
|
21
|
+
mem_used_gb?: number | null;
|
|
22
|
+
mem_total_gb?: number | null;
|
|
23
|
+
mem_avail_gb?: number | null;
|
|
24
|
+
disk_used_gb?: number | null;
|
|
25
|
+
disk_total_gb?: number | null;
|
|
26
|
+
cpu_history?: number[];
|
|
27
|
+
mem_history?: number[];
|
|
28
|
+
agents?: unknown[];
|
|
29
|
+
last_seen?: string | null;
|
|
30
|
+
status?: 'online' | 'offline';
|
|
31
|
+
note?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse a hub timestamp to epoch ms. CommHub reports last_seen as
|
|
36
|
+
* "YYYY-MM-DD HH:MM:SS" (space-separated, no timezone) — treat as UTC
|
|
37
|
+
* so all clients agree regardless of local zone. ISO 8601 with an
|
|
38
|
+
* explicit offset also parses cleanly (the +'Z' is only appended when
|
|
39
|
+
* no zone is present). Returns NaN for missing / unparseable input.
|
|
40
|
+
*/
|
|
41
|
+
export function parseHubTime(ts: string | null | undefined): number {
|
|
42
|
+
if (!ts) return NaN;
|
|
43
|
+
const iso = ts.includes('T') ? ts : ts.replace(' ', 'T');
|
|
44
|
+
const withTz = /[zZ]|[+-]\d{2}:?\d{2}$/.test(iso) ? iso : iso + 'Z';
|
|
45
|
+
return Date.parse(withTz);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Collapse rows that share a hostname into one card.
|
|
50
|
+
*
|
|
51
|
+
* Dedup keys on hostname, not IP: a hostname is the machine identity,
|
|
52
|
+
* whereas an IP can be shared across hosts behind NAT (two distinct
|
|
53
|
+
* hostnames can surface as the same public IP) or differ per interface
|
|
54
|
+
* on one host. Merge rule for a multi-row hostname:
|
|
55
|
+
* - agent_count = sum across rows (one host's total fan-out)
|
|
56
|
+
* - telemetry = freshest-non-null per field — walk rows newest
|
|
57
|
+
* last_seen first, take the first row that actually
|
|
58
|
+
* reports each field, so a stale-but-telemetried row
|
|
59
|
+
* still contributes cpu/mem when the freshest row's
|
|
60
|
+
* values are null
|
|
61
|
+
* - last_seen = newest across rows
|
|
62
|
+
* - agents[] = union, dedup by alias
|
|
63
|
+
*
|
|
64
|
+
* Sentinel hostnames that are not real identities — empty or the
|
|
65
|
+
* literal "unknown" the hub emits when an agent never resolved its
|
|
66
|
+
* host — are passed through untouched: merging two distinct unknown
|
|
67
|
+
* machines into one card would be worse than showing both.
|
|
68
|
+
*/
|
|
69
|
+
export function dedupeByHostname(rows: HubServerRow[]): HubServerRow[] {
|
|
70
|
+
const groups = new Map<string, HubServerRow[]>();
|
|
71
|
+
const passthrough: HubServerRow[] = [];
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
const h = row.hostname;
|
|
74
|
+
if (!h || h === 'unknown') { passthrough.push(row); continue; }
|
|
75
|
+
const bucket = groups.get(h) ?? [];
|
|
76
|
+
bucket.push(row);
|
|
77
|
+
groups.set(h, bucket);
|
|
78
|
+
}
|
|
79
|
+
const merged: HubServerRow[] = [];
|
|
80
|
+
for (const bucket of groups.values()) {
|
|
81
|
+
if (bucket.length === 1) { merged.push(bucket[0]); continue; }
|
|
82
|
+
// Freshest last_seen first. NaN (missing timestamp) sorts last.
|
|
83
|
+
const sorted = [...bucket].sort((a, b) => {
|
|
84
|
+
const ta = parseHubTime(a.last_seen), tb = parseHubTime(b.last_seen);
|
|
85
|
+
return (Number.isNaN(tb) ? -Infinity : tb) - (Number.isNaN(ta) ? -Infinity : ta);
|
|
86
|
+
});
|
|
87
|
+
const coalesce = <K extends keyof HubServerRow>(key: K): HubServerRow[K] | undefined => {
|
|
88
|
+
for (const r of sorted) {
|
|
89
|
+
const v = r[key];
|
|
90
|
+
if (v != null) return v;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
};
|
|
94
|
+
const agentMap = new Map<string, unknown>();
|
|
95
|
+
for (const r of sorted) {
|
|
96
|
+
if (!Array.isArray(r.agents)) continue;
|
|
97
|
+
for (const a of r.agents) {
|
|
98
|
+
const key = (a as { alias?: string })?.alias ?? JSON.stringify(a);
|
|
99
|
+
if (!agentMap.has(key)) agentMap.set(key, a);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
merged.push({
|
|
103
|
+
hostname: sorted[0].hostname,
|
|
104
|
+
ip: coalesce('ip') ?? null,
|
|
105
|
+
agent_count: bucket.reduce((sum, r) => sum + (r.agent_count ?? 0), 0),
|
|
106
|
+
cpu_load_1min: coalesce('cpu_load_1min') ?? null,
|
|
107
|
+
cpu_cores: coalesce('cpu_cores') ?? null,
|
|
108
|
+
mem_used_gb: coalesce('mem_used_gb') ?? null,
|
|
109
|
+
mem_total_gb: coalesce('mem_total_gb') ?? null,
|
|
110
|
+
mem_avail_gb: coalesce('mem_avail_gb') ?? null,
|
|
111
|
+
disk_used_gb: coalesce('disk_used_gb') ?? null,
|
|
112
|
+
disk_total_gb: coalesce('disk_total_gb') ?? null,
|
|
113
|
+
cpu_history: coalesce('cpu_history'),
|
|
114
|
+
mem_history: coalesce('mem_history'),
|
|
115
|
+
agents: agentMap.size > 0 ? [...agentMap.values()] : undefined,
|
|
116
|
+
last_seen: coalesce('last_seen') ?? null,
|
|
117
|
+
status: coalesce('status'),
|
|
118
|
+
note: coalesce('note'),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return [...merged, ...passthrough];
|
|
122
|
+
}
|
|
@@ -33,70 +33,88 @@ const UNKNOWN_VENDOR: VendorIdentity = {
|
|
|
33
33
|
logo: null,
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
// Vendor identities, named so both the model-prefix rules and the
|
|
37
|
+
// runtime fallback map below can reference the same object.
|
|
38
|
+
const INTERN_VENDOR: VendorIdentity = {
|
|
39
|
+
id: 'intern',
|
|
40
|
+
label: '书生 · 上海 AI 实验室',
|
|
41
|
+
mono: { bg: 'hsl(28 38% 24%)', ring: 'hsl(32 45% 52%)', text: 'hsl(34 60% 82%)' },
|
|
42
|
+
initial: '书',
|
|
43
|
+
// #79 shipped this asset — reuse it as the 书生 vendor logo.
|
|
44
|
+
logo: '/intern_avatar.png',
|
|
45
|
+
};
|
|
46
|
+
const MINIMAX_VENDOR: VendorIdentity = {
|
|
47
|
+
id: 'minimax',
|
|
48
|
+
label: 'MiniMax',
|
|
49
|
+
mono: { bg: 'hsl(18 50% 26%)', ring: 'hsl(18 65% 52%)', text: 'hsl(20 80% 82%)' },
|
|
50
|
+
initial: 'M',
|
|
51
|
+
// Official MiniMax mark (simple-icons set, CC0) on a warm-red tinted
|
|
52
|
+
// badge. Vincent-authorized 2026-05-21 to use real vendor marks.
|
|
53
|
+
logo: '/vendors/minimax.svg',
|
|
54
|
+
};
|
|
55
|
+
const ANTHROPIC_VENDOR: VendorIdentity = {
|
|
56
|
+
id: 'anthropic',
|
|
57
|
+
label: 'Anthropic',
|
|
58
|
+
mono: { bg: 'hsl(16 32% 26%)', ring: 'hsl(16 48% 54%)', text: 'hsl(18 60% 84%)' },
|
|
59
|
+
initial: 'A',
|
|
60
|
+
// Official Anthropic mark (simple-icons set, CC0) on a warm-orange
|
|
61
|
+
// tinted badge. Vincent-authorized 2026-05-21 to use real vendor marks.
|
|
62
|
+
logo: '/vendors/claude.svg',
|
|
63
|
+
};
|
|
64
|
+
const OPENAI_VENDOR: VendorIdentity = {
|
|
65
|
+
id: 'openai',
|
|
66
|
+
label: 'OpenAI',
|
|
67
|
+
mono: { bg: 'hsl(165 26% 22%)', ring: 'hsl(165 40% 44%)', text: 'hsl(165 45% 80%)' },
|
|
68
|
+
initial: 'O',
|
|
69
|
+
// Official OpenAI mark (simple-icons set, CC0) on a teal tinted
|
|
70
|
+
// badge. Vincent-authorized 2026-05-21 to use real vendor marks.
|
|
71
|
+
logo: '/vendors/openai.svg',
|
|
72
|
+
};
|
|
73
|
+
|
|
36
74
|
// Ordered prefix rules — first match wins. `test` runs against a lowercased
|
|
37
75
|
// model id. Keep the most specific prefixes first.
|
|
38
76
|
const VENDOR_RULES: Array<{ test: (m: string) => boolean; vendor: VendorIdentity }> = [
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
id: 'intern',
|
|
43
|
-
label: '书生 · 上海 AI 实验室',
|
|
44
|
-
mono: { bg: 'hsl(28 38% 24%)', ring: 'hsl(32 45% 52%)', text: 'hsl(34 60% 82%)' },
|
|
45
|
-
initial: '书',
|
|
46
|
-
// #79 shipped this asset — reuse it as the 书生 vendor logo.
|
|
47
|
-
logo: '/intern_avatar.png',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
test: (m) => m.startsWith('minimax'),
|
|
52
|
-
vendor: {
|
|
53
|
-
id: 'minimax',
|
|
54
|
-
label: 'MiniMax',
|
|
55
|
-
mono: { bg: 'hsl(18 50% 26%)', ring: 'hsl(18 65% 52%)', text: 'hsl(20 80% 82%)' },
|
|
56
|
-
initial: 'M',
|
|
57
|
-
// P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of
|
|
58
|
-
// the MiniMax trademark; geometric min/max zigzag in their warm-
|
|
59
|
-
// red palette. Replaces plain-letter "M" fallback.
|
|
60
|
-
logo: '/vendors/minimax.svg',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
test: (m) => m.startsWith('claude'),
|
|
65
|
-
vendor: {
|
|
66
|
-
id: 'anthropic',
|
|
67
|
-
label: 'Anthropic',
|
|
68
|
-
mono: { bg: 'hsl(16 32% 26%)', ring: 'hsl(16 48% 54%)', text: 'hsl(18 60% 84%)' },
|
|
69
|
-
initial: 'A',
|
|
70
|
-
// P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of
|
|
71
|
-
// the Anthropic trademark; 4-pointed sparkle in their warm-orange
|
|
72
|
-
// palette evokes AI/Claude without imitating the official mark.
|
|
73
|
-
// Real Anthropic logo still pending Vincent-direct asset OK.
|
|
74
|
-
logo: '/vendors/claude.svg',
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
+
{ test: (m) => m.startsWith('intern'), vendor: INTERN_VENDOR },
|
|
78
|
+
{ test: (m) => m.startsWith('minimax'), vendor: MINIMAX_VENDOR },
|
|
79
|
+
{ test: (m) => m.startsWith('claude'), vendor: ANTHROPIC_VENDOR },
|
|
77
80
|
{
|
|
78
81
|
test: (m) => m.startsWith('gpt') || m.startsWith('codex') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'),
|
|
79
|
-
vendor:
|
|
80
|
-
id: 'openai',
|
|
81
|
-
label: 'OpenAI',
|
|
82
|
-
mono: { bg: 'hsl(165 26% 22%)', ring: 'hsl(165 40% 44%)', text: 'hsl(165 45% 80%)' },
|
|
83
|
-
initial: 'O',
|
|
84
|
-
// P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of
|
|
85
|
-
// the OpenAI trademark; hexagonal frame + center dot in their
|
|
86
|
-
// teal palette evokes geometric AI lattice without imitating the
|
|
87
|
-
// knot. Real OpenAI logo still pending Vincent-direct asset OK.
|
|
88
|
-
logo: '/vendors/openai.svg',
|
|
89
|
-
},
|
|
82
|
+
vendor: OPENAI_VENDOR,
|
|
90
83
|
},
|
|
91
84
|
];
|
|
92
85
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
/* Runtime → vendor fallback. Used only when the `model` field can't
|
|
87
|
+
* resolve a vendor (null or unmatched).
|
|
88
|
+
*
|
|
89
|
+
* Why this exists: live hub /api/status probe (2026-05-21) showed 62%
|
|
90
|
+
* of sessions report `model: null` — including ALL claude-code-cli
|
|
91
|
+
* nodes — so the avatar fell back to the dark "unknown vendor"
|
|
92
|
+
* monogram for most of the fleet. The `runtime` field, by contrast,
|
|
93
|
+
* is reliably populated. A claude-code-cli / claude-agent-sdk node is
|
|
94
|
+
* an Anthropic shell; codex-sdk is OpenAI's. Deriving the vendor from
|
|
95
|
+
* runtime when the model is silent lights up the vendor logo for the
|
|
96
|
+
* whole fleet instead of leaving it grey. The model still wins when
|
|
97
|
+
* present — runtime is strictly the fallback. */
|
|
98
|
+
const RUNTIME_VENDOR: Record<string, VendorIdentity> = {
|
|
99
|
+
'claude-code-cli': ANTHROPIC_VENDOR,
|
|
100
|
+
'claude-agent-sdk': ANTHROPIC_VENDOR,
|
|
101
|
+
'codex-sdk': OPENAI_VENDOR,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Resolve a node's vendor identity. Model id wins; when the model is
|
|
105
|
+
* null or unmatched, fall back to the execution runtime. Both silent
|
|
106
|
+
* → UNKNOWN. */
|
|
107
|
+
export function vendorForModel(
|
|
108
|
+
model: string | null | undefined,
|
|
109
|
+
runtime?: string | null | undefined,
|
|
110
|
+
): VendorIdentity {
|
|
111
|
+
if (model) {
|
|
112
|
+
const m = model.toLowerCase();
|
|
113
|
+
for (const rule of VENDOR_RULES) {
|
|
114
|
+
if (rule.test(m)) return rule.vendor;
|
|
115
|
+
}
|
|
99
116
|
}
|
|
117
|
+
if (runtime && RUNTIME_VENDOR[runtime]) return RUNTIME_VENDOR[runtime];
|
|
100
118
|
return UNKNOWN_VENDOR;
|
|
101
119
|
}
|
|
102
120
|
|
|
@@ -146,7 +164,7 @@ export function runtimeIdentity(runtime: string | null | undefined): RuntimeIden
|
|
|
146
164
|
/** Compact one-line identity for hover / detail surfaces:
|
|
147
165
|
* "Anthropic · claude-opus-4 · Claude Code CLI". Pieces with no data drop. */
|
|
148
166
|
export function identityLine(model: string | null | undefined, runtime: string | null | undefined): string {
|
|
149
|
-
const v = vendorForModel(model);
|
|
167
|
+
const v = vendorForModel(model, runtime);
|
|
150
168
|
const r = runtimeIdentity(runtime);
|
|
151
169
|
const parts: string[] = [];
|
|
152
170
|
if (v.id !== 'unknown') parts.push(v.label);
|
package/app/manifest.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next';
|
|
2
|
+
|
|
3
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
4
|
+
return {
|
|
5
|
+
name: 'Agent Network Dashboard',
|
|
6
|
+
short_name: 'AgentNet',
|
|
7
|
+
description: 'Mobile command surface for Agent Network and CommHub',
|
|
8
|
+
start_url: '/',
|
|
9
|
+
scope: '/',
|
|
10
|
+
display: 'standalone',
|
|
11
|
+
background_color: '#0a0a1a',
|
|
12
|
+
theme_color: '#0a0a1a',
|
|
13
|
+
orientation: 'portrait',
|
|
14
|
+
icons: [
|
|
15
|
+
{
|
|
16
|
+
src: '/favicon.svg',
|
|
17
|
+
sizes: 'any',
|
|
18
|
+
type: 'image/svg+xml',
|
|
19
|
+
purpose: 'any',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/agent-network-dashboard",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Agent Network Dashboard
|
|
3
|
+
"version": "0.5.4",
|
|
4
|
+
"description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "next dev",
|
|
7
7
|
"build": "next build",
|
|
8
8
|
"start": "next start",
|
|
9
9
|
"lint": "eslint",
|
|
10
|
-
"prepublishOnly": "[ -f .next/BUILD_ID ] || (echo 'prepublishOnly: .next/BUILD_ID missing
|
|
10
|
+
"prepublishOnly": "[ -f .next/BUILD_ID ] || (echo 'prepublishOnly: .next/BUILD_ID missing — run npm run build first (see commit 05c1ebf body for R224 chunk-500 root cause)' >&2 && exit 1)"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
13
|
"agent-network-dashboard": "./bin/start.js"
|
|
@@ -44,4 +44,4 @@
|
|
|
44
44
|
"tailwindcss": "^4",
|
|
45
45
|
"typescript": "^5"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
-
<!-- Anthropic
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<circle cx="32" cy="32" r="
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
<path d="M 0 -18 L 4 -4 L 18 0 L 4 4 L 0 18 L -4 4 L -18 0 L -4 -4 Z" />
|
|
2
|
+
<!-- Anthropic vendor mark — official Anthropic logo (simple-icons set, CC0).
|
|
3
|
+
Vincent-authorized 2026-05-21 to use real vendor marks. Tinted circular
|
|
4
|
+
badge frames the official mark for the dashboard node-avatar context. -->
|
|
5
|
+
<circle cx="32" cy="32" r="32" fill="hsl(16 32% 22%)"/>
|
|
6
|
+
<circle cx="32" cy="32" r="30" fill="none" stroke="hsl(16 48% 54%)" stroke-width="2"/>
|
|
7
|
+
<g transform="translate(16 16) scale(1.333)" fill="hsl(20 80% 82%)">
|
|
8
|
+
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/>
|
|
10
9
|
</g>
|
|
11
10
|
</svg>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
-
<!-- MiniMax vendor mark
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<circle cx="32" cy="32" r="
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
stroke-linecap="round" stroke-linejoin="round" />
|
|
2
|
+
<!-- MiniMax vendor mark — official MiniMax logo (simple-icons set, CC0).
|
|
3
|
+
Vincent-authorized 2026-05-21 to use real vendor marks. Tinted circular
|
|
4
|
+
badge frames the official mark for the dashboard node-avatar context. -->
|
|
5
|
+
<circle cx="32" cy="32" r="32" fill="hsl(18 50% 22%)"/>
|
|
6
|
+
<circle cx="32" cy="32" r="30" fill="none" stroke="hsl(18 65% 52%)" stroke-width="2"/>
|
|
7
|
+
<g transform="translate(16 16) scale(1.333)" fill="hsl(20 80% 84%)">
|
|
8
|
+
<path d="M11.43 3.92a.86.86 0 1 0-1.718 0v14.236a1.999 1.999 0 0 1-3.997 0V9.022a.86.86 0 1 0-1.718 0v3.87a1.999 1.999 0 0 1-3.997 0V11.49a.57.57 0 0 1 1.139 0v1.404a.86.86 0 0 0 1.719 0V9.022a1.999 1.999 0 0 1 3.997 0v9.134a.86.86 0 0 0 1.719 0V3.92a1.998 1.998 0 1 1 3.996 0v11.788a.57.57 0 1 1-1.139 0zm10.572 3.105a2 2 0 0 0-1.999 1.997v7.63a.86.86 0 0 1-1.718 0V3.923a1.999 1.999 0 0 0-3.997 0v16.16a.86.86 0 0 1-1.719 0V18.08a.57.57 0 1 0-1.138 0v2a1.998 1.998 0 0 0 3.996 0V3.92a.86.86 0 0 1 1.719 0v12.73a1.999 1.999 0 0 0 3.996 0V9.023a.86.86 0 1 1 1.72 0v6.686a.57.57 0 0 0 1.138 0V9.022a2 2 0 0 0-1.998-1.997"/>
|
|
9
|
+
</g>
|
|
11
10
|
</svg>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
-
<!-- OpenAI
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
fill="none" stroke="hsl(165 50% 78%)" stroke-width="3" stroke-linejoin="round" />
|
|
11
|
-
<circle cx="32" cy="32" r="4" fill="hsl(165 50% 78%)" />
|
|
2
|
+
<!-- OpenAI vendor mark — official OpenAI logo (simple-icons set, CC0).
|
|
3
|
+
Vincent-authorized 2026-05-21 to use real vendor marks. Tinted circular
|
|
4
|
+
badge frames the official mark for the dashboard node-avatar context. -->
|
|
5
|
+
<circle cx="32" cy="32" r="32" fill="hsl(165 26% 20%)"/>
|
|
6
|
+
<circle cx="32" cy="32" r="30" fill="none" stroke="hsl(165 40% 44%)" stroke-width="2"/>
|
|
7
|
+
<g transform="translate(16 16) scale(1.333)" fill="hsl(165 50% 82%)">
|
|
8
|
+
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/>
|
|
9
|
+
</g>
|
|
12
10
|
</svg>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* #157 RC#2 verification — Servers panel hostname dedup.
|
|
2
|
+
*
|
|
3
|
+
* Bug (Vincent 5560 + live re-probe 2026-05-20): the hub /api/servers
|
|
4
|
+
* endpoint keys host telemetry by (hostname, ip), so a machine whose
|
|
5
|
+
* agents report over more than one interface surfaces as multiple
|
|
6
|
+
* rows. `iZrj93pr2rcf5r2y9uo1oyZ` appeared twice — Docker-bridge IP
|
|
7
|
+
* (telemetry-bearing) + loopback (telemetry null) — splitting its
|
|
8
|
+
* agent count across two cards.
|
|
9
|
+
*
|
|
10
|
+
* Fix: `dedupeByHostname` in app/lib/serverDedupe.ts, applied in the
|
|
11
|
+
* /api/hub/servers proxy before normalizeServer.
|
|
12
|
+
*
|
|
13
|
+
* Two legs:
|
|
14
|
+
* A. mock fixture — compile serverDedupe.ts, run a crafted payload
|
|
15
|
+
* that exercises every branch (multi-row merge, telemetry
|
|
16
|
+
* coalesce, NAT-shared IP NOT merged, `unknown` passthrough,
|
|
17
|
+
* agents[] union).
|
|
18
|
+
* B. real payload — GET the running dashboard proxy and assert no
|
|
19
|
+
* hostname appears twice + counts are summed.
|
|
20
|
+
*/
|
|
21
|
+
import { execSync } from 'node:child_process';
|
|
22
|
+
import { readFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
23
|
+
import { pathToFileURL } from 'node:url';
|
|
24
|
+
|
|
25
|
+
let failed = 0;
|
|
26
|
+
const ok = (name, cond, detail = '') => {
|
|
27
|
+
if (cond) { console.log(` ✅ ${name}`); }
|
|
28
|
+
else { console.log(` ❌ ${name}${detail ? ' — ' + detail : ''}`); failed++; }
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ── Leg A: mock fixture against the real dedup module ──────────────
|
|
32
|
+
console.log('Leg A — mock fixture (compiled serverDedupe.ts):');
|
|
33
|
+
const OUT = '/tmp/p157-rc2-dedupe';
|
|
34
|
+
mkdirSync(OUT, { recursive: true });
|
|
35
|
+
execSync(
|
|
36
|
+
`npx tsc app/lib/serverDedupe.ts --outDir ${OUT} --module es2022 --target es2022 --moduleResolution bundler --skipLibCheck`,
|
|
37
|
+
{ stdio: 'inherit' },
|
|
38
|
+
);
|
|
39
|
+
renameSync(`${OUT}/serverDedupe.js`, `${OUT}/serverDedupe.mjs`);
|
|
40
|
+
const { dedupeByHostname } = await import(pathToFileURL(`${OUT}/serverDedupe.mjs`).href);
|
|
41
|
+
|
|
42
|
+
const fixture = [
|
|
43
|
+
// same hostname, two interfaces — bridge row fresh + telemetried,
|
|
44
|
+
// loopback row staler + telemetry null
|
|
45
|
+
{ hostname: 'host-A', ip: '172.17.0.2', agent_count: 29, last_seen: '2026-05-20 04:41:46',
|
|
46
|
+
cpu_load_1min: 1.49, cpu_cores: 8, mem_used_gb: 22.6, mem_total_gb: 32,
|
|
47
|
+
agents: [{ alias: 'a1' }, { alias: 'a2' }] },
|
|
48
|
+
{ hostname: 'host-A', ip: '127.0.0.1', agent_count: 38, last_seen: '2026-05-20 04:41:43',
|
|
49
|
+
cpu_load_1min: null, mem_used_gb: null,
|
|
50
|
+
agents: [{ alias: 'a2' }, { alias: 'a3' }] },
|
|
51
|
+
// NAT-shared public IP, distinct hostnames — must NOT merge
|
|
52
|
+
{ hostname: 'host-B', ip: '223.167.142.73', agent_count: 5, last_seen: '2026-05-18 02:34:16' },
|
|
53
|
+
{ hostname: 'host-C', ip: '223.167.142.73', agent_count: 2, last_seen: '2026-05-13 16:44:41' },
|
|
54
|
+
// sentinel hostname — two distinct unknown machines, pass through both
|
|
55
|
+
{ hostname: 'unknown', ip: '127.0.0.1', agent_count: 2, last_seen: '2026-05-16 08:40:14' },
|
|
56
|
+
{ hostname: 'unknown', ip: '10.0.0.9', agent_count: 1, last_seen: '2026-05-16 09:00:00' },
|
|
57
|
+
];
|
|
58
|
+
const out = dedupeByHostname(fixture);
|
|
59
|
+
const byHost = (h) => out.filter(r => r.hostname === h);
|
|
60
|
+
const hostA = byHost('host-A')[0];
|
|
61
|
+
|
|
62
|
+
ok('host-A collapses 2 rows → 1', byHost('host-A').length === 1, `got ${byHost('host-A').length}`);
|
|
63
|
+
ok('host-A agent_count summed 29+38=67', hostA?.agent_count === 67, `got ${hostA?.agent_count}`);
|
|
64
|
+
ok('host-A telemetry coalesced from bridge row (cpu 1.49)', hostA?.cpu_load_1min === 1.49, `got ${hostA?.cpu_load_1min}`);
|
|
65
|
+
ok('host-A mem coalesced (22.6)', hostA?.mem_used_gb === 22.6, `got ${hostA?.mem_used_gb}`);
|
|
66
|
+
ok('host-A last_seen = freshest (04:41:46)', hostA?.last_seen === '2026-05-20 04:41:46', `got ${hostA?.last_seen}`);
|
|
67
|
+
ok('host-A agents[] unioned + dedup by alias (a1,a2,a3)',
|
|
68
|
+
hostA?.agents?.length === 3 &&
|
|
69
|
+
new Set(hostA.agents.map(a => a.alias)).size === 3,
|
|
70
|
+
`got ${JSON.stringify(hostA?.agents)}`);
|
|
71
|
+
ok('host-B + host-C NOT merged despite shared IP', byHost('host-B').length === 1 && byHost('host-C').length === 1);
|
|
72
|
+
ok('two `unknown` rows both pass through (not merged)', byHost('unknown').length === 2, `got ${byHost('unknown').length}`);
|
|
73
|
+
ok('total rows: 1 (A) + 1 (B) + 1 (C) + 2 (unknown) = 5', out.length === 5, `got ${out.length}`);
|
|
74
|
+
|
|
75
|
+
// idempotency — running dedup on already-deduped output is a no-op
|
|
76
|
+
const twice = dedupeByHostname(out);
|
|
77
|
+
ok('idempotent — dedup(dedup(x)) === dedup(x)', twice.length === out.length);
|
|
78
|
+
|
|
79
|
+
// ── Leg B: real payload through the running dashboard proxy ─────────
|
|
80
|
+
console.log('\nLeg B — real payload (live /api/hub/servers proxy):');
|
|
81
|
+
let TOKEN;
|
|
82
|
+
try {
|
|
83
|
+
TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
84
|
+
} catch {
|
|
85
|
+
console.log(' ⚠️ no anet config — skipping live leg');
|
|
86
|
+
}
|
|
87
|
+
if (TOKEN) {
|
|
88
|
+
const res = await fetch('http://127.0.0.1:3000/api/hub/servers', {
|
|
89
|
+
headers: { cookie: `anet_dashboard_session=v3:${TOKEN}` },
|
|
90
|
+
});
|
|
91
|
+
ok('proxy returns 200', res.status === 200, `status ${res.status}`);
|
|
92
|
+
const body = await res.json();
|
|
93
|
+
const servers = body.servers ?? [];
|
|
94
|
+
ok('proxy returns servers[]', Array.isArray(servers) && servers.length > 0, `got ${servers.length}`);
|
|
95
|
+
const counts = {};
|
|
96
|
+
for (const s of servers) counts[s.hostname] = (counts[s.hostname] ?? 0) + 1;
|
|
97
|
+
const dups = Object.entries(counts).filter(([h, n]) => n > 1 && h !== 'unknown' && h !== '');
|
|
98
|
+
ok('no real hostname appears twice', dups.length === 0, `dups: ${JSON.stringify(dups)}`);
|
|
99
|
+
const izrj = servers.find(s => s.hostname?.startsWith('iZrj93pr'));
|
|
100
|
+
if (izrj) {
|
|
101
|
+
ok('iZrj93pr… host present as single card', true);
|
|
102
|
+
ok('iZrj93pr… has telemetry after coalesce (cpu non-null)', izrj.cpu_load_1min != null, `cpu ${izrj.cpu_load_1min}`);
|
|
103
|
+
console.log(` ℹ️ iZrj93pr… merged: agent_count=${izrj.agent_count}, cpu=${izrj.cpu_load_1min}, ip=${izrj.ip}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(' ℹ️ iZrj93pr… not in current live payload (hub state changed) — structural dup check still authoritative');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`\n${failed === 0 ? '✅ ALL GREEN' : `❌ ${failed} FAILED`}`);
|
|
110
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Round 732 — a11y titles catalog on canvas root. 7TH orthogonal
|
|
2
|
+
* meta-doc axis. The introspection layer crosses families:
|
|
3
|
+
*
|
|
4
|
+
* Breath family (6 meta-docs):
|
|
5
|
+
* R710 data-topo-respiratory-rolodex (cadences)
|
|
6
|
+
* R716 data-topo-respiratory-dual-axis-surfaces (axes)
|
|
7
|
+
* R717 data-topo-respiratory-patterns (patterns)
|
|
8
|
+
* R720 data-topo-respiratory-tiers (tiers)
|
|
9
|
+
* R723 data-topo-respiratory-triple-axis-surfaces (triple-axis)
|
|
10
|
+
* R729 data-topo-respiratory-axis-count-stats (stats)
|
|
11
|
+
*
|
|
12
|
+
* A11y sub-family (1 meta-doc):
|
|
13
|
+
* R732 data-topo-a11y-titles (accessible names) ← this round
|
|
14
|
+
*
|
|
15
|
+
* Each entry maps surface → DOM selector → accessible name. The test
|
|
16
|
+
* resolves each selector at runtime and verifies the element's direct
|
|
17
|
+
* <title> child textContent matches the catalog's accessible_name.
|
|
18
|
+
*
|
|
19
|
+
* Assertions:
|
|
20
|
+
* - attr present on root <svg>
|
|
21
|
+
* - JSON parses to Array of 5 entries (R730: 3, R731: 2)
|
|
22
|
+
* - Each entry has {surface, selector, accessible_name} shape with
|
|
23
|
+
* non-empty string values
|
|
24
|
+
* - Each accessible_name follows canonical "<name> · <role>" format
|
|
25
|
+
* - Each selector resolves at runtime to an element (or null IF
|
|
26
|
+
* conditionally rendered, treated as pass for the cross-check)
|
|
27
|
+
* - When element exists, its direct <title> child's textContent
|
|
28
|
+
* equals catalog's accessible_name (STRICT cross-check)
|
|
29
|
+
* - All 5 surfaces are present in the expected anchor set
|
|
30
|
+
*/
|
|
31
|
+
import { chromium } from 'playwright';
|
|
32
|
+
import { readFileSync } from 'node:fs';
|
|
33
|
+
|
|
34
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
35
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
36
|
+
|
|
37
|
+
const browser = await chromium.launch({ headless: true });
|
|
38
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
|
|
39
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
40
|
+
await ctx.addInitScript(() => {
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
43
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
44
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
45
|
+
} catch {}
|
|
46
|
+
});
|
|
47
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
48
|
+
const r = await route.fetch();
|
|
49
|
+
const b = await r.json();
|
|
50
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
51
|
+
const mk = (alias) => ({
|
|
52
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'claude-code-cli',
|
|
53
|
+
network_id: nid, project_dir: null,
|
|
54
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
55
|
+
});
|
|
56
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('a·1'), mk('a·2')] } });
|
|
57
|
+
});
|
|
58
|
+
await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
|
|
59
|
+
await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
|
|
60
|
+
const page = await ctx.newPage();
|
|
61
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
|
|
62
|
+
await page.waitForSelector('[data-topo-canvas-aria]', { timeout: 15000, state: 'attached' });
|
|
63
|
+
await page.waitForTimeout(300);
|
|
64
|
+
|
|
65
|
+
const result = await page.evaluate(() => {
|
|
66
|
+
const svg = document.querySelector('[data-topo-canvas-aria]');
|
|
67
|
+
const catalogAttr = svg?.getAttribute('data-topo-a11y-titles') ?? null;
|
|
68
|
+
let catalog = null;
|
|
69
|
+
try { catalog = JSON.parse(catalogAttr ?? ''); } catch {}
|
|
70
|
+
|
|
71
|
+
/* Direct child <title> only — skip nested titles in mask defs etc. */
|
|
72
|
+
const directTitleText = (el) => {
|
|
73
|
+
if (!el) return null;
|
|
74
|
+
for (const child of el.children) {
|
|
75
|
+
if (child.tagName.toLowerCase() === 'title') return child.textContent;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const crossChecks = Array.isArray(catalog)
|
|
81
|
+
? catalog.map(entry => {
|
|
82
|
+
const el = document.querySelector(entry.selector);
|
|
83
|
+
return {
|
|
84
|
+
surface: entry.surface,
|
|
85
|
+
element_present: !!el,
|
|
86
|
+
actual_title: directTitleText(el),
|
|
87
|
+
matches: !el || directTitleText(el) === entry.accessible_name,
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
: [];
|
|
91
|
+
|
|
92
|
+
return { catalogAttr, catalog, crossChecks };
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await browser.close();
|
|
96
|
+
|
|
97
|
+
const { catalog, crossChecks } = result;
|
|
98
|
+
|
|
99
|
+
const validShape = Array.isArray(catalog) && catalog.every(e =>
|
|
100
|
+
typeof e?.surface === 'string' && e.surface.length > 0
|
|
101
|
+
&& typeof e?.selector === 'string' && e.selector.length > 0
|
|
102
|
+
&& typeof e?.accessible_name === 'string' && e.accessible_name.length > 0
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/* R733 relaxed canonical-format from "exactly 2 parts" to ">=2 parts"
|
|
106
|
+
* to admit the new hub-highlight entry's 3-part name ("hub · network
|
|
107
|
+
* center · idle indicator"). All earlier entries still match. */
|
|
108
|
+
const canonicalFormat = (s) => typeof s === 'string' && /^[^·]+( · [^·]+)+$/.test(s);
|
|
109
|
+
const allCanonical = Array.isArray(catalog) && catalog.every(e => canonicalFormat(e.accessible_name));
|
|
110
|
+
|
|
111
|
+
const expectedSurfaces = ['canvas scan beam', 'canvas scan beam diagonal', 'canvas scan beam vertical', 'canvas-corner crescent', 'hub-highlight disc', 'legend panel title', 'recent panel title', 'title-block brand logo', 'watermark text'];
|
|
112
|
+
const actualSurfaces = Array.isArray(catalog) ? catalog.map(e => e.surface).sort() : [];
|
|
113
|
+
const surfacesMatch = JSON.stringify(actualSurfaces) === JSON.stringify(expectedSurfaces);
|
|
114
|
+
|
|
115
|
+
const allCrossChecks = crossChecks.every(c => c.matches);
|
|
116
|
+
|
|
117
|
+
const results = {
|
|
118
|
+
attr_present: !!result.catalogAttr,
|
|
119
|
+
json_parses: catalog !== null,
|
|
120
|
+
is_array: Array.isArray(catalog),
|
|
121
|
+
has_9_entries: Array.isArray(catalog) && catalog.length === 9,
|
|
122
|
+
shape_valid: validShape,
|
|
123
|
+
all_accessible_names_canonical: allCanonical,
|
|
124
|
+
surfaces_match_expected_set: surfacesMatch,
|
|
125
|
+
all_cross_checks_pass: allCrossChecks,
|
|
126
|
+
};
|
|
127
|
+
const ok = Object.values(results).every(Boolean);
|
|
128
|
+
console.log(`${ok ? '✅' : '❌'} R732 a11y titles catalog (7th meta-doc — pentagon→hexagon→HEPTAGON, cross-family):`,
|
|
129
|
+
JSON.stringify(results, null, 2),
|
|
130
|
+
`\n catalog: ${JSON.stringify(catalog)}`,
|
|
131
|
+
`\n cross-checks: ${JSON.stringify(crossChecks)}`);
|
|
132
|
+
process.exit(ok ? 0 : 1);
|