@sleep2agi/agent-network-dashboard 0.6.3-preview.51 → 0.6.3-preview.52

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 (230) hide show
  1. package/.next/BUILD_COMMIT +1 -1
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +46 -46
  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.js.nft.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 +2 -2
  17. package/.next/server/app/_not-found.rsc +13 -13
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -8
  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.js.nft.json +1 -1
  25. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app/admin.html +2 -2
  27. package/.next/server/app/admin.rsc +15 -15
  28. package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
  29. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  30. package/.next/server/app/admin.segments/_index.segment.rsc +8 -8
  31. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  32. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  33. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  34. package/.next/server/app/index.html +2 -2
  35. package/.next/server/app/index.rsc +15 -15
  36. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  37. package/.next/server/app/index.segments/_full.segment.rsc +15 -15
  38. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  39. package/.next/server/app/index.segments/_index.segment.rsc +8 -8
  40. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/server/app/login/page.js.nft.json +1 -1
  42. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  43. package/.next/server/app/login.html +2 -2
  44. package/.next/server/app/login.rsc +15 -15
  45. package/.next/server/app/login.segments/_full.segment.rsc +15 -15
  46. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  47. package/.next/server/app/login.segments/_index.segment.rsc +8 -8
  48. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  49. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  51. package/.next/server/app/logs/page.js.nft.json +1 -1
  52. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/logs.html +2 -2
  54. package/.next/server/app/logs.rsc +15 -15
  55. package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
  56. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  57. package/.next/server/app/logs.segments/_index.segment.rsc +8 -8
  58. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  59. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  60. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  61. package/.next/server/app/messages/page.js.nft.json +1 -1
  62. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app/messages.html +2 -2
  64. package/.next/server/app/messages.rsc +15 -15
  65. package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
  66. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  67. package/.next/server/app/messages.segments/_index.segment.rsc +8 -8
  68. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  69. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  71. package/.next/server/app/node/page.js.nft.json +1 -1
  72. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/node.html +2 -2
  74. package/.next/server/app/node.rsc +15 -15
  75. package/.next/server/app/node.segments/_full.segment.rsc +15 -15
  76. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/node.segments/_index.segment.rsc +8 -8
  78. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  81. package/.next/server/app/nodes/[alias]/page.js.nft.json +1 -1
  82. package/.next/server/app/nodes/[alias]/page_client-reference-manifest.js +1 -1
  83. package/.next/server/app/nodes/page.js.nft.json +1 -1
  84. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/nodes.html +2 -2
  86. package/.next/server/app/nodes.rsc +15 -15
  87. package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
  88. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  89. package/.next/server/app/nodes.segments/_index.segment.rsc +8 -8
  90. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  92. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  93. package/.next/server/app/page.js.nft.json +1 -1
  94. package/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/providers/page.js.nft.json +1 -1
  96. package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
  97. package/.next/server/app/providers.html +2 -2
  98. package/.next/server/app/providers.rsc +15 -15
  99. package/.next/server/app/providers.segments/_full.segment.rsc +15 -15
  100. package/.next/server/app/providers.segments/_head.segment.rsc +4 -4
  101. package/.next/server/app/providers.segments/_index.segment.rsc +8 -8
  102. package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
  103. package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +4 -4
  104. package/.next/server/app/providers.segments/providers.segment.rsc +3 -3
  105. package/.next/server/app/server-logs/page.js.nft.json +1 -1
  106. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  107. package/.next/server/app/server-logs.html +2 -2
  108. package/.next/server/app/server-logs.rsc +15 -15
  109. package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
  110. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  111. package/.next/server/app/server-logs.segments/_index.segment.rsc +8 -8
  112. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  113. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  114. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  115. package/.next/server/app/servers/page.js.nft.json +1 -1
  116. package/.next/server/app/servers/page_client-reference-manifest.js +1 -1
  117. package/.next/server/app/servers.html +2 -2
  118. package/.next/server/app/servers.rsc +15 -15
  119. package/.next/server/app/servers.segments/_full.segment.rsc +15 -15
  120. package/.next/server/app/servers.segments/_head.segment.rsc +4 -4
  121. package/.next/server/app/servers.segments/_index.segment.rsc +8 -8
  122. package/.next/server/app/servers.segments/_tree.segment.rsc +2 -2
  123. package/.next/server/app/servers.segments/servers/__PAGE__.segment.rsc +4 -4
  124. package/.next/server/app/servers.segments/servers.segment.rsc +3 -3
  125. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  126. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  127. package/.next/server/app/settings/networks.html +2 -2
  128. package/.next/server/app/settings/networks.rsc +15 -15
  129. package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
  130. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  131. package/.next/server/app/settings/networks.segments/_index.segment.rsc +8 -8
  132. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  133. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  134. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  135. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  136. package/.next/server/app/settings/page.js.nft.json +1 -1
  137. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  138. package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
  139. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/settings/tokens.html +2 -2
  141. package/.next/server/app/settings/tokens.rsc +15 -15
  142. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
  143. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +8 -8
  145. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  148. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  149. package/.next/server/app/settings.html +2 -2
  150. package/.next/server/app/settings.rsc +15 -15
  151. package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
  152. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  153. package/.next/server/app/settings.segments/_index.segment.rsc +8 -8
  154. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  155. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  156. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  157. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  158. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  159. package/.next/server/app/tasks/page.js.nft.json +1 -1
  160. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  161. package/.next/server/app/tasks.html +2 -2
  162. package/.next/server/app/tasks.rsc +15 -15
  163. package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
  164. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  165. package/.next/server/app/tasks.segments/_index.segment.rsc +8 -8
  166. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  167. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  168. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  169. package/.next/server/chunks/ssr/[root-of-the-server]__06w5i08._.js +1 -1
  170. package/.next/server/chunks/ssr/[root-of-the-server]__06w5i08._.js.map +1 -1
  171. package/.next/server/chunks/ssr/[root-of-the-server]__0dcez4x._.js +11 -0
  172. package/.next/server/chunks/ssr/[root-of-the-server]__0dcez4x._.js.map +1 -0
  173. package/.next/server/chunks/ssr/{[root-of-the-server]__0jgqpfv._.js → [root-of-the-server]__0gyqilt._.js} +2 -2
  174. package/.next/server/chunks/ssr/[root-of-the-server]__0gyqilt._.js.map +1 -0
  175. package/.next/server/chunks/ssr/[root-of-the-server]__0j-def.._.js +11 -0
  176. package/.next/server/chunks/ssr/[root-of-the-server]__0j-def.._.js.map +1 -0
  177. package/.next/server/chunks/ssr/{[root-of-the-server]__11wln8.._.js → [root-of-the-server]__0oavw6j._.js} +2 -2
  178. package/.next/server/chunks/ssr/[root-of-the-server]__0oavw6j._.js.map +1 -0
  179. package/.next/server/chunks/ssr/[root-of-the-server]__0rct4.k._.js +3 -0
  180. package/.next/server/chunks/ssr/{[root-of-the-server]__02o~-py._.js.map → [root-of-the-server]__0rct4.k._.js.map} +1 -1
  181. package/.next/server/chunks/ssr/dash-stageb-wt_app_05u4_h~._.js +2 -2
  182. package/.next/server/chunks/ssr/dash-stageb-wt_app_05u4_h~._.js.map +1 -1
  183. package/.next/server/chunks/ssr/dash-stageb-wt_app_0qn7hvc._.js +1 -1
  184. package/.next/server/chunks/ssr/dash-stageb-wt_app_0qn7hvc._.js.map +1 -1
  185. package/.next/server/chunks/ssr/dash-stageb-wt_app_0ss0pj-._.js +1 -1
  186. package/.next/server/chunks/ssr/dash-stageb-wt_app_0ss0pj-._.js.map +1 -1
  187. package/.next/server/chunks/ssr/dash-stageb-wt_app_nodes_NodesView_tsx_0patjj7._.js +2 -2
  188. package/.next/server/chunks/ssr/dash-stageb-wt_app_nodes_NodesView_tsx_0patjj7._.js.map +1 -1
  189. package/.next/server/middleware-build-manifest.js +3 -3
  190. package/.next/server/pages/404.html +2 -2
  191. package/.next/server/pages/500.html +1 -1
  192. package/.next/server/server-reference-manifest.js +1 -1
  193. package/.next/server/server-reference-manifest.json +1 -1
  194. package/.next/static/chunks/0-ot~88nectgp.js +2 -0
  195. package/.next/static/chunks/00adqh_rd04uf.js +9 -0
  196. package/.next/static/chunks/{0advoo2tzdcyi.js → 051p0pw4z~6ag.js} +1 -1
  197. package/.next/static/chunks/{0uk67ty_0h_36.js → 0lf8e-h91s10h.js} +1 -1
  198. package/.next/static/chunks/{0dkk_l872ocgm.js → 0ohx4jpm9~61..js} +1 -1
  199. package/.next/static/chunks/0pg4pce.q5ne_.js +9 -0
  200. package/.next/static/chunks/{0fz7zk.ek.154.js → 0u5y.gmi~jkw6.js} +1 -1
  201. package/.next/static/chunks/0wf03_efeii.w.css +1 -0
  202. package/.next/static/chunks/0ws0p1n62l~ub.js +1 -0
  203. package/.next/static/chunks/{06q7-vhy~1-y3.js → 0~rx9_~1wupcp.js} +2 -2
  204. package/.next/static/chunks/{0z_4d13crd_af.js → 14x094g58kg61.js} +1 -1
  205. package/.next/trace +2 -2
  206. package/.next/trace-build +1 -1
  207. package/app/components/ChatPane.tsx +26 -2
  208. package/app/components/NodeList.tsx +14 -4
  209. package/app/components/TaskChatPanel.tsx +156 -31
  210. package/app/messages/page.tsx +22 -22
  211. package/app/nodes/NodesView.tsx +52 -11
  212. package/package.json +1 -1
  213. package/tests/e2e-9-nodes-team-tag-filter.spec.ts +14 -7
  214. package/tests/e2e-messages-spec-visual-align.spec.ts +244 -0
  215. package/tests/e2e-pin-mute-attr-always-present.spec.ts +171 -0
  216. package/tests/e2e-stage-a-node-list.spec.ts +14 -7
  217. package/tests/e2e-stage-b-chat-pane.spec.ts +74 -10
  218. package/tests/e2e-status-labels.spec.ts +218 -0
  219. package/.next/server/chunks/ssr/[root-of-the-server]__02o~-py._.js +0 -3
  220. package/.next/server/chunks/ssr/[root-of-the-server]__0jgqpfv._.js.map +0 -1
  221. package/.next/server/chunks/ssr/[root-of-the-server]__0w7k~3f._.js +0 -9
  222. package/.next/server/chunks/ssr/[root-of-the-server]__0w7k~3f._.js.map +0 -1
  223. package/.next/server/chunks/ssr/[root-of-the-server]__11wln8.._.js.map +0 -1
  224. package/.next/static/chunks/05~lgfzsv.p~p.js +0 -2
  225. package/.next/static/chunks/092zqb~4un32l.css +0 -1
  226. package/.next/static/chunks/11zz15q37~bz5.js +0 -7
  227. package/.next/static/chunks/17qkc714xy04..js +0 -1
  228. /package/.next/static/{JL_QjNKv1KhsCF68ZK28x → RiXBfeE2zY5tzXRmF7kBD}/_buildManifest.js +0 -0
  229. /package/.next/static/{JL_QjNKv1KhsCF68ZK28x → RiXBfeE2zY5tzXRmF7kBD}/_clientMiddlewareManifest.js +0 -0
  230. /package/.next/static/{JL_QjNKv1KhsCF68ZK28x → RiXBfeE2zY5tzXRmF7kBD}/_ssgManifest.js +0 -0
@@ -315,45 +315,170 @@ function LocalFilePreview({ file, onRemove }: { file: File; onRemove: () => void
315
315
  );
316
316
  }
317
317
 
318
- const STATUS_STEPS = ['created', 'delivered', 'running', 'replied'];
319
- const STATUS_COLORS: Record<string, string> = {
320
- created: 'bg-gray-400', delivered: 'bg-blue-400', running: 'bg-green-400',
321
- replied: 'bg-purple-400', failed: 'bg-red-400', closed: 'bg-gray-500',
318
+ // 通信龙 07-31 GAP 2nd tier: translate hub status codes into human
319
+ // labels. The old form was `● ─ ● ─ ○ ─ ○ delivered` — a colored dot
320
+ // progression + the raw English word. Nobody could tell "delivered"
321
+ // (hub-side mark) from "acked" (node process received) from "replied"
322
+ // (agent actually processed).
323
+ //
324
+ // Two things this rewrite fixes:
325
+ //
326
+ // 1. STATUS_STEPS was `['created','delivered','running','replied']`.
327
+ // Neither `created` nor `running` correspond to hub-emitted terminal
328
+ // statuses (`created` is client-side optimistic, `running` is READ
329
+ // by hub queries but never WRITTEN — grep server/src for `= 'running'`
330
+ // returns zero). Real hub progression is queued → delivered → acked
331
+ // → replied, and the old third dot literally never lit up.
332
+ //
333
+ // 2. `delivered` was labeled as if it meant "message reached the node".
334
+ // Reading hub source (server/src/index.ts:1568 + tools.ts:1080/1232)
335
+ // shows it's set at task-insert time — same instant as delivered_at
336
+ // (which is why delivered_at delay is empirically 0). The label now
337
+ // says "已推送" and the hover explicitly disclaims node reception.
338
+ //
339
+ // Vocabulary — must stay in sync with hub emits:
340
+ // queued / delivered / acked / replied / failed ← the primary 5
341
+ // created ← client-side pre-POST
342
+ // closed / expired / cancelled ← terminal, non-reply
343
+ // running ← reserved (never written today)
344
+ // <anything else> ← fail-loudly (raw + banner)
345
+ const STATUS_STEPS = ['queued', 'delivered', 'acked', 'replied'] as const;
346
+ type StatusInfo = { label: string; hover: string; color: string; icon: React.ReactNode };
347
+ const STATUS_INFO: Record<string, StatusInfo> = {
348
+ created: { label: '发送中', hover: '客户端已建消息,尚未收到 hub 回执。', color: 'text-gray-400', icon: <IconSending /> },
349
+ queued: { label: '排队中', hover: '已入队,尚未推给节点。节点可能离线、或通道拥堵。', color: 'text-gray-400', icon: <IconClock /> },
350
+ delivered: { label: '已推送', hover: 'hub 侧已标记推送。尚未确认节点是否收到(delivered_at 是入队时刻,不代表 SSE 已到达)。', color: 'text-sky-400', icon: <IconCheck /> },
351
+ acked: { label: '已接收', hover: '节点进程已确认收到。未必已处理——对方 agent 可能尚未唤醒。', color: 'text-gray-300', icon: <IconCheckDouble /> },
352
+ replied: { label: '已回复', hover: '对方已针对此消息回复。', color: 'text-emerald-400', icon: <IconCheckDouble filled /> },
353
+ failed: { label: '失败', hover: '发送失败——详细原因见消息下方文本。', color: 'text-red-400', icon: <IconWarn /> },
354
+ closed: { label: '已关闭', hover: '会话已被关闭,不再期待回复。', color: 'text-gray-500', icon: <IconClose /> },
355
+ expired: { label: '已超时', hover: '在期限内未收到回复,任务已超时。', color: 'text-gray-500', icon: <IconClock /> },
356
+ cancelled: { label: '已取消', hover: '任务被主动取消。', color: 'text-gray-500', icon: <IconClose /> },
357
+ running: { label: '处理中', hover: '节点进程正在处理(今日 hub 不写入此值,见组件注释)。', color: 'text-green-400', icon: <IconClock /> },
322
358
  };
323
359
 
324
- function StatusBar({ status }: { status: string }) {
325
- const idx = STATUS_STEPS.indexOf(status);
326
- const isFailed = status === 'failed';
360
+ function StatusBar({ status, result }: { status: string; result?: string }) {
361
+ const info = STATUS_INFO[status];
362
+ const known = !!info;
363
+ // Progression label list for the hover — same string appended after
364
+ // the per-status hover so the reader gets a compact map of the flow.
365
+ // Not per-step timestamps: hub doesn't expose queued_at / acked_at
366
+ // separately from delivered_at, so a per-step timeline would be
367
+ // fabricated data. See feedback_assert_the_fact_not_the_declaration.
368
+ const idx = STATUS_STEPS.indexOf(status as typeof STATUS_STEPS[number]);
369
+ const flow = STATUS_STEPS.map((s, i) => {
370
+ const mark = i < idx || (i === idx && idx >= 0) ? '✓' : '○';
371
+ return `${mark} ${STATUS_INFO[s].label}`;
372
+ }).join(' → ');
373
+ const hoverText = known
374
+ ? `${info.hover}\n\n进度:${flow}`
375
+ : `未知状态「${status}」——请报 bug。UI 词库没有匹配项,可能 hub 加了新 status 但前端没跟上。`;
376
+ const label = known ? info.label : status;
377
+ const color = known ? info.color : 'text-yellow-400';
378
+
379
+ // failed inline reason — hub has no dedicated failure_reason column
380
+ // (task table: task_id/from_*/to_*/status/content/result/... —
381
+ // grep-verified as of 2026-07-31). We show `result` first 40 chars
382
+ // when present. When result is empty, we explicitly say "节点未提供
383
+ // 原因" rather than falling back to a generic "未知错误" — that
384
+ // distinguishes "hub has no error field" from "reason field exists
385
+ // but got a bad value".
386
+ const failedReason = status === 'failed'
387
+ ? ((result && result.trim()) ? result.trim().slice(0, 40) : '节点未提供原因')
388
+ : null;
389
+
327
390
  return (
328
- <div className="flex items-center gap-1 mt-1.5">
329
- {STATUS_STEPS.map((s, i) => (
330
- <div key={s} className="flex items-center gap-1">
331
- <div className={`w-2 h-2 rounded-full transition-all duration-700 ${
332
- isFailed ? (i === 0 ? 'bg-red-400' : 'bg-[var(--border)]')
333
- : i <= idx ? (STATUS_COLORS[s] || 'bg-gray-600') : 'bg-[var(--border)]'
334
- }`} />
335
- {i < STATUS_STEPS.length - 1 && (
336
- <div className={`w-4 h-px transition-colors duration-700 ${
337
- isFailed ? 'bg-red-800' : i < idx ? 'bg-gray-500' : 'bg-[var(--border)]'
338
- }`} />
339
- )}
340
- </div>
341
- ))}
342
- <span className={`text-[10px] ml-1.5 font-medium ${
343
- isFailed ? 'text-red-400' : status === 'replied' ? 'text-purple-400'
344
- : status === 'running' ? 'text-green-400' : 'text-[var(--fg-muted)]'
345
- }`}>{status}</span>
346
- {(status === 'running' || status === 'delivered') && (
347
- <span className="ml-1">
348
- <span className="inline-block w-1 h-1 rounded-full bg-green-400 animate-pulse" />
349
- <span className="inline-block w-1 h-1 rounded-full bg-green-400 animate-pulse ml-0.5" style={{ animationDelay: '0.2s' }} />
350
- <span className="inline-block w-1 h-1 rounded-full bg-green-400 animate-pulse ml-0.5" style={{ animationDelay: '0.4s' }} />
391
+ <div
392
+ data-status-key={status}
393
+ data-status-known={known ? 'true' : 'false'}
394
+ data-status-label={label}
395
+ className="inline-flex flex-col gap-0.5 mt-1.5"
396
+ >
397
+ <span
398
+ title={hoverText}
399
+ aria-label={known ? info.label : `未知状态 ${status}`}
400
+ className={`inline-flex items-center gap-1.5 text-[11px] font-medium ${color}`}
401
+ >
402
+ {known ? info.icon : <IconQuestion />}
403
+ <span>{label}</span>
404
+ {(status === 'delivered' || status === 'created') && (
405
+ <span aria-hidden className="inline-flex items-center gap-0.5 ml-0.5">
406
+ <span className="inline-block w-1 h-1 rounded-full bg-current opacity-70 animate-pulse" />
407
+ <span className="inline-block w-1 h-1 rounded-full bg-current opacity-70 animate-pulse" style={{ animationDelay: '0.2s' }} />
408
+ <span className="inline-block w-1 h-1 rounded-full bg-current opacity-70 animate-pulse" style={{ animationDelay: '0.4s' }} />
409
+ </span>
410
+ )}
411
+ </span>
412
+ {failedReason !== null && (
413
+ <span
414
+ data-status-reason={failedReason}
415
+ className="text-[10px] text-red-400/80 max-w-[260px] truncate"
416
+ title={result || undefined}
417
+ >
418
+ {failedReason}
351
419
  </span>
352
420
  )}
353
421
  </div>
354
422
  );
355
423
  }
356
424
 
425
+ // Icon primitives — inline SVG, single-color (currentColor), sized to
426
+ // match the 11px label text. No external icon library — keeps the
427
+ // StatusBar self-contained and avoids a Tailwind purge/tree-shake
428
+ // dance for icons only this component uses.
429
+ function IconClock() {
430
+ return (
431
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
432
+ <circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" />
433
+ </svg>
434
+ );
435
+ }
436
+ function IconCheck() {
437
+ return (
438
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round">
439
+ <path d="M4 12l6 6L20 6" />
440
+ </svg>
441
+ );
442
+ }
443
+ function IconCheckDouble({ filled }: { filled?: boolean } = {}) {
444
+ return (
445
+ <svg width="14" height="12" viewBox="0 0 28 24" fill="none" stroke="currentColor" strokeWidth={filled ? 2.5 : 2} strokeLinecap="round" strokeLinejoin="round">
446
+ <path d="M2 12l6 6L18 6" />
447
+ <path d="M10 12l6 6L26 6" opacity={filled ? 1 : 0.7} />
448
+ </svg>
449
+ );
450
+ }
451
+ function IconWarn() {
452
+ return (
453
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
454
+ <path d="M12 2L2 20h20L12 2z" /><path d="M12 9v5" /><circle cx="12" cy="17.5" r="0.6" fill="currentColor" />
455
+ </svg>
456
+ );
457
+ }
458
+ function IconClose() {
459
+ return (
460
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
461
+ <circle cx="12" cy="12" r="10" /><path d="M8 12h8" />
462
+ </svg>
463
+ );
464
+ }
465
+ function IconSending() {
466
+ return (
467
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
468
+ <path d="M4 12l16-8-6 16-3-7-7-1z" />
469
+ </svg>
470
+ );
471
+ }
472
+ function IconQuestion() {
473
+ return (
474
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
475
+ <circle cx="12" cy="12" r="10" />
476
+ <path d="M9.5 9.5a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 3.5" />
477
+ <circle cx="12" cy="17" r="0.6" fill="currentColor" />
478
+ </svg>
479
+ );
480
+ }
481
+
357
482
  // Full markdown rendering — handles tables, headings, lists, links, code,
358
483
  // blockquotes via react-markdown + GFM. Earlier hand-rolled parser only did
359
484
  // code blocks + bold; tables / headings showed as raw `|---|` text.
@@ -1442,7 +1567,7 @@ export function TaskChatPanel({ alias, onClose, inline, availableNodes, active }
1442
1567
  ))}
1443
1568
  </div>
1444
1569
  <div className="flex flex-wrap items-center justify-between mt-1.5 gap-2 sm:gap-3">
1445
- <StatusBar status={m.status} />
1570
+ <StatusBar status={m.status} result={m.result} />
1446
1571
  <div className="flex items-center gap-2 shrink-0">
1447
1572
  {/* R4: WeChat send-feedback loop — spinner while the
1448
1573
  optimistic bubble is still in flight; tap-to-resend
@@ -146,7 +146,7 @@ export default function MessagesPage() {
146
146
  }, [filtered]);
147
147
 
148
148
  return (
149
- <div className="min-h-screen bg-[#0b0b0d] text-gray-100 p-4 sm:p-6">
149
+ <div data-testid="messages-shell" className="min-h-screen bg-[#141826] text-gray-100 p-4 sm:p-6">
150
150
  {/* #209 R31 (mobile vertical rhythm — same pattern as R28/R30):
151
151
  header row + quick-from chips row both mb-6 → mb-4 sm:mb-6.
152
152
  #209 R33: header restructured to mirror /nodes R32 — title
@@ -253,7 +253,7 @@ export default function MessagesPage() {
253
253
  <button
254
254
  type="button"
255
255
  onClick={() => setSearch('')}
256
- className="shrink-0 rounded-full border border-gray-700 px-3 py-1 text-xs text-gray-500 hover:text-gray-200"
256
+ className="shrink-0 rounded-full border border-gray-700 px-3 py-1 text-[11px] text-[#64748B] hover:text-gray-200"
257
257
  >
258
258
  Clear
259
259
  </button>
@@ -336,9 +336,9 @@ export default function MessagesPage() {
336
336
  Halve it on mobile. */}
337
337
  {gapExceeded && (
338
338
  <div className="my-2 sm:my-4 flex items-center gap-3">
339
- <div className="h-px flex-1 bg-[#26262b]" />
340
- <div className="text-[11px] text-gray-600">{formatDividerLabel(message.created_at)}</div>
341
- <div className="h-px flex-1 bg-[#26262b]" />
339
+ <div className="h-px flex-1 bg-white/[0.06]" />
340
+ <div className="text-[13px] font-medium text-[#94A3B8]">{formatDividerLabel(message.created_at)}</div>
341
+ <div className="h-px flex-1 bg-white/[0.06]" />
342
342
  </div>
343
343
  )}
344
344
 
@@ -348,21 +348,21 @@ export default function MessagesPage() {
348
348
  390px. Desktop unchanged from sm: up. */}
349
349
  {variant === 'broadcast' ? (
350
350
  <div className={samePrev ? 'mt-0.5 sm:mt-1' : 'mt-2 sm:mt-3'}>
351
- <div className="rounded-2xl border border-purple-500/20 bg-purple-500/10 px-3 py-2 sm:px-4 sm:py-3 shadow-sm w-full">
351
+ <div className="rounded-2xl border border-white/[0.06] border-l-2 border-l-[#7DD3FC] bg-[#1E293B] px-3 py-2 sm:px-4 sm:py-3 shadow-sm w-full">
352
352
  <div className="mb-1 sm:mb-2 flex flex-wrap items-center gap-2">
353
353
  <span className={`text-xs px-2 py-0.5 rounded-md border ${TYPE_COLORS[message.type || ''] || 'bg-gray-500/10 text-gray-400 border-gray-500/20'}`}>
354
354
  {message.type || 'unknown'}
355
355
  </span>
356
- <span className="text-sm font-medium text-purple-200">{fromAlias}</span>
357
- {message.to_alias && <span className="text-xs text-purple-200/80">to {message.to_alias}</span>}
358
- {message.priority === 'high' && <span className="text-xs text-red-400">HIGH</span>}
359
- <span className="ml-auto text-xs text-gray-500">{message.created_at ? timeAgo(message.created_at) : '--'}</span>
356
+ <span className="text-sm font-medium text-[#E2E8F0]">{fromAlias}</span>
357
+ {message.to_alias && <span className="text-xs text-[#CBD5E1]">to {message.to_alias}</span>}
358
+ {message.priority === 'high' && <span className="text-xs text-[#EF4444]">HIGH</span>}
359
+ <span className="ml-auto text-[11px] text-[#64748B]">{message.created_at ? timeAgo(message.created_at) : '--'}</span>
360
360
  </div>
361
361
  {/* Round 79: break-words + overflow-wrap:anywhere so
362
362
  long unbroken runs (URLs, ASCII rules like
363
363
  `═══════════════`) wrap instead of pushing the
364
364
  chat bubble past the mobile viewport. */}
365
- <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-gray-200">
365
+ <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-[#E2E8F0]">
366
366
  {renderHighlighted(message.content, search)}
367
367
  </div>
368
368
  {/* #492: broadcast may carry attachments too. */}
@@ -377,23 +377,23 @@ export default function MessagesPage() {
377
377
  <div className="w-8 shrink-0 pt-1">
378
378
  {!samePrev && <AliasAvatar alias={fromAlias} size={32} />}
379
379
  </div>
380
- <div className={`min-w-0 max-w-3xl rounded-2xl border px-3 py-2 sm:px-4 sm:py-3 shadow-sm ${
380
+ <div className={`min-w-0 max-w-[min(640px,65%)] rounded-2xl border border-white/[0.06] px-3 py-2 sm:px-4 sm:py-3 shadow-sm ${
381
381
  variant === 'outgoing'
382
- ? 'border-green-500/20 bg-green-500/10'
383
- : 'border-blue-500/20 bg-blue-500/10'
382
+ ? 'bg-[#1E3A5F]'
383
+ : 'bg-[#1E293B]'
384
384
  }`}>
385
385
  {!samePrev && (
386
386
  <div className="mb-1 sm:mb-2 flex flex-wrap items-center gap-2">
387
387
  <span className={`text-xs px-2 py-0.5 rounded-md border ${TYPE_COLORS[message.type || ''] || 'bg-gray-500/10 text-gray-400 border-gray-500/20'}`}>
388
388
  {message.type || 'unknown'}
389
389
  </span>
390
- <span className={`text-sm font-medium ${variant === 'outgoing' ? 'text-green-300' : 'text-blue-300'}`}>
390
+ <span className={`text-sm font-medium ${variant === 'outgoing' ? 'text-[#F1F5F9]' : 'text-[#E2E8F0]'}`}>
391
391
  {fromAlias}
392
392
  </span>
393
- <span className="text-xs text-gray-600">&rarr;</span>
394
- <span className="text-sm text-gray-300">{message.to_alias || '--'}</span>
395
- {message.priority === 'high' && <span className="text-xs text-red-400">HIGH</span>}
396
- <span className="ml-auto text-xs text-gray-500">{message.created_at ? timeAgo(message.created_at) : '--'}</span>
393
+ <span className="text-xs text-[#64748B]">&rarr;</span>
394
+ <span className="text-sm text-[#CBD5E1]">{message.to_alias || '--'}</span>
395
+ {message.priority === 'high' && <span className="text-xs text-[#EF4444]">HIGH</span>}
396
+ <span className="ml-auto text-[11px] text-[#64748B]">{message.created_at ? timeAgo(message.created_at) : '--'}</span>
397
397
  </div>
398
398
  )}
399
399
 
@@ -401,7 +401,7 @@ export default function MessagesPage() {
401
401
  long unbroken runs (URLs, ASCII rules like
402
402
  `═══════════════`) wrap instead of pushing the
403
403
  chat bubble past the mobile viewport. */}
404
- <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-gray-200">
404
+ <div className="whitespace-pre-wrap break-words [overflow-wrap:anywhere] text-sm leading-snug sm:leading-relaxed text-[#E2E8F0]">
405
405
  {renderHighlighted(message.content, search)}
406
406
  </div>
407
407
 
@@ -417,13 +417,13 @@ export default function MessagesPage() {
417
417
 
418
418
  {/* When in a streak, surface the timestamp inline-right so users still know cadence */}
419
419
  {samePrev && (
420
- <div className="mt-1 text-[10px] text-gray-600 text-right">
420
+ <div className="mt-1 text-[11px] text-[#64748B] text-right">
421
421
  {message.created_at ? timeAgo(message.created_at) : ''}
422
422
  </div>
423
423
  )}
424
424
 
425
425
  {debug && (
426
- <div className="mt-3 border-t border-white/10 pt-3 text-xs text-gray-500 space-y-1">
426
+ <div className="mt-3 border-t border-white/10 pt-3 text-[11px] text-[#64748B] space-y-1">
427
427
  <div>ID: {message.id}</div>
428
428
  {message.task_id && <div>Task ID: {message.task_id}</div>}
429
429
  <div>Created: {message.created_at}</div>
@@ -4,7 +4,6 @@ import { useState, useRef, useEffect, useSyncExternalStore } from 'react';
4
4
  import { timeAgo } from '../components/utils';
5
5
  import { useSessions, useHealth, useNodeLifecycle } from '../lib/hooks';
6
6
  import { useSWRConfig } from 'swr';
7
- import { TaskChatPanel } from '../components/TaskChatPanel';
8
7
  import { CreateNodeWizard } from '../components/CreateNodeWizard';
9
8
  import { NodeLifecycleMenu } from '../components/NodeLifecycleMenu';
10
9
  import { EmptyState, NodesEmptyState } from '../components/EmptyState';
@@ -118,6 +117,10 @@ export function NodesView({ initialAlias = null }: { initialAlias?: string | nul
118
117
  const [chatAlias, setChatAlias] = useState<string | null>(initialAlias);
119
118
  const isDesktop = useIsDesktop();
120
119
  const selectNode = (alias: string) => {
120
+ try {
121
+ const el = document.querySelector('[data-testid="node-list-scroll"]');
122
+ if (el) savedListScrollRef.current = el.scrollTop;
123
+ } catch {}
121
124
  setChatAlias(alias);
122
125
  try { window.history.pushState({}, '', `/nodes/${encodeURIComponent(alias)}`); } catch {}
123
126
  };
@@ -295,6 +298,31 @@ export function NodesView({ initialAlias = null }: { initialAlias?: string | nul
295
298
  // column rendered.
296
299
  const chatOpenDesktop = !!chatAlias && isDesktop && sessions.length > 0;
297
300
 
301
+ // #Mobile two-level nav (Feishu phone form, 通信龙 07-31, breakpoint md
302
+ // per my own ruling — no third media query):
303
+ // < md unselected = conversation LIST fullscreen (not the table)
304
+ // < md selected = chat as a full PAGE with a back arrow
305
+ // manage mode = the old table, reachable via the same toggle
306
+ // Desktop auto-selects; mobile deliberately does NOT (the list IS the
307
+ // first screen — auto-select effect above is gated on isDesktop).
308
+ const mobileListLevel = !isDesktop && !chatAlias && !manageView && sessions.length > 0;
309
+ const mobileChatLevel = !isDesktop && !!chatAlias;
310
+
311
+ // #Mobile back restores the list scroll position (判据: enter from row
312
+ // ~50, come back, offset ≤200px — snapping to top is the annoying bug).
313
+ // Saved at selection time; restored when the list level remounts.
314
+ // Desktop is exempt: its rail never unmounts while chatting, and the
315
+ // user may have scrolled it meanwhile.
316
+ const savedListScrollRef = useRef(0);
317
+ useEffect(() => {
318
+ if (isDesktop || chatAlias) return;
319
+ const id = requestAnimationFrame(() => {
320
+ const el = document.querySelector('[data-testid="node-list-scroll"]');
321
+ if (el) el.scrollTop = savedListScrollRef.current;
322
+ });
323
+ return () => cancelAnimationFrame(id);
324
+ }, [isDesktop, chatAlias]);
325
+
298
326
  return (
299
327
  <div
300
328
  ref={rootRef}
@@ -310,10 +338,10 @@ export function NodesView({ initialAlias = null }: { initialAlias?: string | nul
310
338
  #Stage C: a selected node FORCES the rail on (even for dense-view
311
339
  users with showRail=false) — in the list+chat layout the rail is
312
340
  the only way to switch nodes, so it must be there. */}
313
- {(showRail || chatOpenDesktop) && sessions.length > 0 && (
341
+ {(mobileListLevel || (isDesktop && (showRail || chatOpenDesktop) && sessions.length > 0)) && (
314
342
  <aside
315
343
  data-testid="node-list-rail"
316
- className="hidden sm:flex sm:flex-col w-[280px] shrink-0 h-full min-h-0 bg-[#0F1320]"
344
+ className="flex flex-col w-full md:w-[280px] shrink-0 h-full min-h-0 bg-[#0F1320]"
317
345
  >
318
346
  <NodeList
319
347
  sessions={railSessions}
@@ -339,8 +367,20 @@ export function NodesView({ initialAlias = null }: { initialAlias?: string | nul
339
367
  {/* #Stage C: middle column (header + filters + table/grid) renders
340
368
  only when NO node is selected on desktop — see chatOpenDesktop
341
369
  note above. Mobile keeps it always (chat is an overlay there). */}
342
- {!chatOpenDesktop && (
343
- <div className="flex-1 min-w-0 max-w-full h-full overflow-y-auto overflow-x-hidden p-4 sm:p-6">
370
+ {(isDesktop ? !chatOpenDesktop : (manageView && !chatAlias)) && (
371
+ <div className="flex-1 min-w-0 max-w-full h-full overflow-y-auto overflow-x-hidden p-4 sm:p-6 pb-[calc(5rem+env(safe-area-inset-bottom))] lg:pb-6">
372
+ {/* #Mobile manage mode: way back to the conversation list — the rail
373
+ (which hosts the toggle) is hidden at this level on phones. */}
374
+ {!isDesktop && manageView && (
375
+ <button
376
+ type="button"
377
+ onClick={toggleManageView}
378
+ data-testid="manage-view-toggle-mobile"
379
+ className="md:hidden mb-3 rounded-lg border border-[rgba(255,255,255,0.08)] bg-[#0F1320] px-3 py-1.5 text-[12px] text-gray-400 hover:text-gray-200"
380
+ >
381
+ {t('≡ 返回对话视图', '≡ Back to conversations')}
382
+ </button>
383
+ )}
344
384
  {/* #209 R31 (mobile vertical rhythm — goal "大幅提升移动端体验",
345
385
  extending R28's Overview pattern + R30's /tasks pattern):
346
386
  this header row + the status-bar wrapper + the filter row
@@ -760,18 +800,19 @@ export function NodesView({ initialAlias = null }: { initialAlias?: string | nul
760
800
  </div>
761
801
  )}
762
802
 
763
- {/* #Stage B: below md the chat keeps the pre-B behavior — a
764
- full-screen overlay push (TaskChatPanel's own fixed shell).
765
- On md+ the persistent right pane below renders instead; the
766
- two are mutually exclusive via isDesktop so only ONE
767
- TaskChatPanel is ever mounted (no double SSE / mark-read). */}
768
- {chatAlias && !isDesktop && <TaskChatPanel alias={chatAlias} onClose={closeChat} />}
803
+ {/* (#Stage B's mobile overlay is gone — < md chat is now a full
804
+ PAGE, rendered as ChatPane variant="page" below. isDesktop
805
+ still guarantees only ONE chat body is ever mounted.) */}
769
806
  </div>
770
807
  )}
771
808
  {/* #Stage B: persistent right chat column (md+) — see ChatPane.tsx
772
809
  (ChatPopover minus the floating shell; TaskChatPanel inline body).
773
810
  Switching nodes swaps `alias` in place (no remount). */}
774
811
  {chatAlias && isDesktop && <ChatPane alias={chatAlias} onClose={closeChat} />}
812
+ {/* #Mobile two-level nav: chat is a full PAGE (not the old overlay).
813
+ Back arrow and browser back both land on the list (closeChat /
814
+ popstate) — 判据: 行为一致. */}
815
+ {mobileChatLevel && <ChatPane alias={chatAlias!} variant="page" onBack={closeChat} onClose={closeChat} />}
775
816
  </div>
776
817
  );
777
818
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.6.3-preview.51",
3
+ "version": "0.6.3-preview.52",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "main": "apps/desktop/electron/main.cjs",
6
6
  "scripts": {
@@ -80,16 +80,23 @@ const HUB_HEALTH_EMPTY = {
80
80
  sse_sessions: {},
81
81
  };
82
82
 
83
+ // Login rate limit is 10/15min/IP (app/lib/rate-limit.ts): with 4 spec
84
+ // files in one combined run, per-test logins blow the budget and late
85
+ // tests land on the sign-in page (false red). Login once per worker.
86
+ let cachedSessionCookie: string | null = null;
83
87
  async function login(context: Page['context'] extends () => infer C ? C : never) {
84
- const res = await context.request.post(`${BASE}/api/auth/login`, {
85
- data: { password: process.env.ANET_DASHBOARD_PASSWORD || 'admin123' },
86
- });
87
- const setCookie = res.headers()['set-cookie'] || '';
88
- const m = setCookie.match(/anet_dashboard_session=([^;]+)/);
89
- if (m) {
88
+ if (!cachedSessionCookie) {
89
+ const res = await context.request.post(`${BASE}/api/auth/login`, {
90
+ data: { password: process.env.ANET_DASHBOARD_PASSWORD || 'admin123' },
91
+ });
92
+ const setCookie = res.headers()['set-cookie'] || '';
93
+ const m = setCookie.match(/anet_dashboard_session=([^;]+)/);
94
+ if (m) cachedSessionCookie = decodeURIComponent(m[1]);
95
+ }
96
+ if (cachedSessionCookie) {
90
97
  await context.addCookies([{
91
98
  name: 'anet_dashboard_session',
92
- value: decodeURIComponent(m[1]),
99
+ value: cachedSessionCookie,
93
100
  domain: new URL(BASE).hostname,
94
101
  path: '/',
95
102
  }]);