@agentmemory/agentmemory 0.9.30 → 0.9.32

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 (334) hide show
  1. package/.env.example +57 -48
  2. package/AGENTS.md +8 -8
  3. package/README.md +256 -819
  4. package/dist/.env.example +57 -48
  5. package/dist/cli.d.mts.map +1 -1
  6. package/dist/cli.mjs +2257 -1027
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/compression-recovery-CZh63Jmz.mjs +5997 -0
  9. package/dist/compression-recovery-CZh63Jmz.mjs.map +1 -0
  10. package/dist/{connect-B4_ca6BD.mjs → connect-BX3njQ3Y.mjs} +897 -276
  11. package/dist/connect-BX3njQ3Y.mjs.map +1 -0
  12. package/dist/hooks/_capture.d.mts +5 -1
  13. package/dist/hooks/_capture.d.mts.map +1 -1
  14. package/dist/hooks/_capture.mjs +141 -26
  15. package/dist/hooks/_capture.mjs.map +1 -1
  16. package/dist/hooks/notification.mjs +20 -6
  17. package/dist/hooks/notification.mjs.map +1 -1
  18. package/dist/hooks/post-commit.mjs +83 -10
  19. package/dist/hooks/post-commit.mjs.map +1 -1
  20. package/dist/hooks/post-tool-failure.mjs +19 -6
  21. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  22. package/dist/hooks/post-tool-use.mjs +19 -6
  23. package/dist/hooks/post-tool-use.mjs.map +1 -1
  24. package/dist/hooks/pre-compact.mjs +104 -22
  25. package/dist/hooks/pre-compact.mjs.map +1 -1
  26. package/dist/hooks/pre-tool-use.mjs +83 -10
  27. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  28. package/dist/hooks/prompt-submit.mjs +12 -4
  29. package/dist/hooks/prompt-submit.mjs.map +1 -1
  30. package/dist/hooks/session-end.mjs +59 -17
  31. package/dist/hooks/session-end.mjs.map +1 -1
  32. package/dist/hooks/session-start.mjs +95 -15
  33. package/dist/hooks/session-start.mjs.map +1 -1
  34. package/dist/hooks/stop.mjs +75 -72
  35. package/dist/hooks/stop.mjs.map +1 -1
  36. package/dist/hooks/subagent-start.mjs +13 -5
  37. package/dist/hooks/subagent-start.mjs.map +1 -1
  38. package/dist/hooks/subagent-stop.mjs +32 -10
  39. package/dist/hooks/subagent-stop.mjs.map +1 -1
  40. package/dist/hooks/task-completed.mjs +21 -6
  41. package/dist/hooks/task-completed.mjs.map +1 -1
  42. package/dist/{image-refs-T_urbwHM.mjs → image-refs-Cyta4Lf9.mjs} +2 -2
  43. package/dist/{image-refs-T_urbwHM.mjs.map → image-refs-Cyta4Lf9.mjs.map} +1 -1
  44. package/dist/index.mjs +16390 -11530
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/integrations/dsh.d.mts +55 -0
  47. package/dist/integrations/dsh.d.mts.map +1 -0
  48. package/dist/integrations/dsh.mjs +1172 -0
  49. package/dist/integrations/dsh.mjs.map +1 -0
  50. package/dist/{keyed-mutex-DchXsyfP.mjs → keyed-mutex-DCUh7PG-.mjs} +58 -9
  51. package/dist/keyed-mutex-DCUh7PG-.mjs.map +1 -0
  52. package/dist/{secret-store-BnY3A50C.mjs → secret-store-iYXRcrO8.mjs} +717 -474
  53. package/dist/secret-store-iYXRcrO8.mjs.map +1 -0
  54. package/dist/session-arguments-zxsbbd8y.mjs +113 -0
  55. package/dist/session-arguments-zxsbbd8y.mjs.map +1 -0
  56. package/dist/{src-Jsq9LCeH.mjs → src-C23nMpda.mjs} +5263 -4667
  57. package/dist/src-C23nMpda.mjs.map +1 -0
  58. package/dist/{standalone-DKCdrf1d.mjs → standalone-BYo5USDb.mjs} +379 -63
  59. package/dist/standalone-BYo5USDb.mjs.map +1 -0
  60. package/dist/standalone.d.mts +1 -1
  61. package/dist/standalone.d.mts.map +1 -1
  62. package/dist/standalone.mjs +864 -81
  63. package/dist/standalone.mjs.map +1 -1
  64. package/dist/viewer/index.html +337 -168
  65. package/integrations/hermes/README.md +94 -0
  66. package/integrations/hermes/__init__.py +670 -0
  67. package/integrations/hermes/plugin.yaml +27 -0
  68. package/integrations/omp/LICENSE +190 -0
  69. package/integrations/omp/README.md +76 -0
  70. package/integrations/omp/capture.ts +1 -0
  71. package/integrations/omp/dist/index.mjs +1477 -0
  72. package/integrations/omp/index.ts +488 -0
  73. package/integrations/omp/package.json +13 -0
  74. package/integrations/omp/security.ts +67 -0
  75. package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +32 -0
  76. package/integrations/omp/skills/agentmemory-agents/SKILL.md +48 -0
  77. package/integrations/omp/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  78. package/integrations/omp/skills/agentmemory-architecture/SKILL.md +37 -0
  79. package/integrations/omp/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  80. package/integrations/omp/skills/agentmemory-config/REFERENCE.md +91 -0
  81. package/integrations/omp/skills/agentmemory-config/SKILL.md +98 -0
  82. package/integrations/omp/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  83. package/integrations/omp/skills/agentmemory-hooks/REFERENCE.md +20 -0
  84. package/integrations/omp/skills/agentmemory-hooks/SKILL.md +60 -0
  85. package/integrations/omp/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  86. package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  87. package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +52 -0
  88. package/integrations/omp/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  89. package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +159 -0
  90. package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +49 -0
  91. package/integrations/omp/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  92. package/integrations/omp/skills/commit-context/EXAMPLES.md +71 -0
  93. package/integrations/omp/skills/commit-context/SKILL.md +64 -0
  94. package/integrations/omp/skills/commit-context/TROUBLESHOOTING.md +70 -0
  95. package/integrations/omp/skills/commit-history/EXAMPLES.md +71 -0
  96. package/integrations/omp/skills/commit-history/SKILL.md +62 -0
  97. package/integrations/omp/skills/commit-history/TROUBLESHOOTING.md +70 -0
  98. package/integrations/omp/skills/forget/EXAMPLES.md +40 -0
  99. package/integrations/omp/skills/forget/SKILL.md +73 -0
  100. package/integrations/omp/skills/forget/TROUBLESHOOTING.md +70 -0
  101. package/integrations/omp/skills/handoff/EXAMPLES.md +57 -0
  102. package/integrations/omp/skills/handoff/SKILL.md +75 -0
  103. package/integrations/omp/skills/handoff/TROUBLESHOOTING.md +70 -0
  104. package/integrations/omp/skills/lesson/SKILL.md +64 -0
  105. package/integrations/omp/skills/lesson/TROUBLESHOOTING.md +70 -0
  106. package/integrations/omp/skills/memory-discipline/SKILL.md +65 -0
  107. package/integrations/omp/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  108. package/integrations/omp/skills/recall/EXAMPLES.md +74 -0
  109. package/integrations/omp/skills/recall/SKILL.md +74 -0
  110. package/integrations/omp/skills/recall/TROUBLESHOOTING.md +70 -0
  111. package/integrations/omp/skills/recap/EXAMPLES.md +62 -0
  112. package/integrations/omp/skills/recap/SKILL.md +68 -0
  113. package/integrations/omp/skills/recap/TROUBLESHOOTING.md +70 -0
  114. package/integrations/omp/skills/remember/EXAMPLES.md +67 -0
  115. package/integrations/omp/skills/remember/SKILL.md +66 -0
  116. package/integrations/omp/skills/remember/TROUBLESHOOTING.md +70 -0
  117. package/integrations/omp/skills/session-history/EXAMPLES.md +59 -0
  118. package/integrations/omp/skills/session-history/SKILL.md +67 -0
  119. package/integrations/omp/skills/session-history/TROUBLESHOOTING.md +70 -0
  120. package/integrations/omp/skills/write-agentmemory-skill/SKILL.md +41 -0
  121. package/integrations/omp/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  122. package/integrations/openclaw/LICENSE +190 -0
  123. package/integrations/openclaw/README.md +98 -0
  124. package/integrations/openclaw/openclaw.plugin.json +71 -0
  125. package/integrations/openclaw/package.json +38 -0
  126. package/integrations/openclaw/plugin.mjs +425 -0
  127. package/integrations/openclaw/plugin.yaml +26 -0
  128. package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +32 -0
  129. package/integrations/openclaw/skills/agentmemory-agents/SKILL.md +48 -0
  130. package/integrations/openclaw/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  131. package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +37 -0
  132. package/integrations/openclaw/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  133. package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +91 -0
  134. package/integrations/openclaw/skills/agentmemory-config/SKILL.md +98 -0
  135. package/integrations/openclaw/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  136. package/integrations/openclaw/skills/agentmemory-hooks/REFERENCE.md +20 -0
  137. package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +60 -0
  138. package/integrations/openclaw/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  139. package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  140. package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +52 -0
  141. package/integrations/openclaw/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  142. package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +159 -0
  143. package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +49 -0
  144. package/integrations/openclaw/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  145. package/integrations/openclaw/skills/commit-context/EXAMPLES.md +71 -0
  146. package/integrations/openclaw/skills/commit-context/SKILL.md +64 -0
  147. package/integrations/openclaw/skills/commit-context/TROUBLESHOOTING.md +70 -0
  148. package/integrations/openclaw/skills/commit-history/EXAMPLES.md +71 -0
  149. package/integrations/openclaw/skills/commit-history/SKILL.md +62 -0
  150. package/integrations/openclaw/skills/commit-history/TROUBLESHOOTING.md +70 -0
  151. package/integrations/openclaw/skills/forget/EXAMPLES.md +40 -0
  152. package/integrations/openclaw/skills/forget/SKILL.md +73 -0
  153. package/integrations/openclaw/skills/forget/TROUBLESHOOTING.md +70 -0
  154. package/integrations/openclaw/skills/handoff/EXAMPLES.md +57 -0
  155. package/integrations/openclaw/skills/handoff/SKILL.md +75 -0
  156. package/integrations/openclaw/skills/handoff/TROUBLESHOOTING.md +70 -0
  157. package/integrations/openclaw/skills/lesson/SKILL.md +64 -0
  158. package/integrations/openclaw/skills/lesson/TROUBLESHOOTING.md +70 -0
  159. package/integrations/openclaw/skills/memory-discipline/SKILL.md +65 -0
  160. package/integrations/openclaw/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  161. package/integrations/openclaw/skills/recall/EXAMPLES.md +74 -0
  162. package/integrations/openclaw/skills/recall/SKILL.md +74 -0
  163. package/integrations/openclaw/skills/recall/TROUBLESHOOTING.md +70 -0
  164. package/integrations/openclaw/skills/recap/EXAMPLES.md +62 -0
  165. package/integrations/openclaw/skills/recap/SKILL.md +68 -0
  166. package/integrations/openclaw/skills/recap/TROUBLESHOOTING.md +70 -0
  167. package/integrations/openclaw/skills/remember/EXAMPLES.md +67 -0
  168. package/integrations/openclaw/skills/remember/SKILL.md +66 -0
  169. package/integrations/openclaw/skills/remember/TROUBLESHOOTING.md +70 -0
  170. package/integrations/openclaw/skills/session-history/EXAMPLES.md +59 -0
  171. package/integrations/openclaw/skills/session-history/SKILL.md +67 -0
  172. package/integrations/openclaw/skills/session-history/TROUBLESHOOTING.md +70 -0
  173. package/integrations/openclaw/skills/write-agentmemory-skill/SKILL.md +41 -0
  174. package/integrations/openclaw/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  175. package/integrations/pi/LICENSE +190 -0
  176. package/integrations/pi/README.md +46 -51
  177. package/integrations/pi/dist/index.mjs +1460 -0
  178. package/integrations/pi/index.ts +192 -175
  179. package/integrations/pi/package.json +17 -7
  180. package/integrations/pi/security.ts +6 -26
  181. package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +32 -0
  182. package/integrations/pi/skills/agentmemory-agents/SKILL.md +48 -0
  183. package/integrations/pi/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  184. package/integrations/pi/skills/agentmemory-architecture/SKILL.md +37 -0
  185. package/integrations/pi/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  186. package/integrations/pi/skills/agentmemory-config/REFERENCE.md +91 -0
  187. package/integrations/pi/skills/agentmemory-config/SKILL.md +98 -0
  188. package/integrations/pi/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  189. package/integrations/pi/skills/agentmemory-hooks/REFERENCE.md +20 -0
  190. package/integrations/pi/skills/agentmemory-hooks/SKILL.md +60 -0
  191. package/integrations/pi/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  192. package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  193. package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +52 -0
  194. package/integrations/pi/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  195. package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +159 -0
  196. package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +49 -0
  197. package/integrations/pi/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  198. package/integrations/pi/skills/commit-context/EXAMPLES.md +71 -0
  199. package/integrations/pi/skills/commit-context/SKILL.md +64 -0
  200. package/integrations/pi/skills/commit-context/TROUBLESHOOTING.md +70 -0
  201. package/integrations/pi/skills/commit-history/EXAMPLES.md +71 -0
  202. package/integrations/pi/skills/commit-history/SKILL.md +62 -0
  203. package/integrations/pi/skills/commit-history/TROUBLESHOOTING.md +70 -0
  204. package/integrations/pi/skills/forget/EXAMPLES.md +40 -0
  205. package/integrations/pi/skills/forget/SKILL.md +73 -0
  206. package/integrations/pi/skills/forget/TROUBLESHOOTING.md +70 -0
  207. package/integrations/pi/skills/handoff/EXAMPLES.md +57 -0
  208. package/integrations/pi/skills/handoff/SKILL.md +75 -0
  209. package/integrations/pi/skills/handoff/TROUBLESHOOTING.md +70 -0
  210. package/integrations/pi/skills/lesson/SKILL.md +64 -0
  211. package/integrations/pi/skills/lesson/TROUBLESHOOTING.md +70 -0
  212. package/integrations/pi/skills/memory-discipline/SKILL.md +65 -0
  213. package/integrations/pi/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  214. package/integrations/pi/skills/recall/EXAMPLES.md +74 -0
  215. package/integrations/pi/skills/recall/SKILL.md +74 -0
  216. package/integrations/pi/skills/recall/TROUBLESHOOTING.md +70 -0
  217. package/integrations/pi/skills/recap/EXAMPLES.md +62 -0
  218. package/integrations/pi/skills/recap/SKILL.md +68 -0
  219. package/integrations/pi/skills/recap/TROUBLESHOOTING.md +70 -0
  220. package/integrations/pi/skills/remember/EXAMPLES.md +67 -0
  221. package/integrations/pi/skills/remember/SKILL.md +66 -0
  222. package/integrations/pi/skills/remember/TROUBLESHOOTING.md +70 -0
  223. package/integrations/pi/skills/session-history/EXAMPLES.md +59 -0
  224. package/integrations/pi/skills/session-history/SKILL.md +67 -0
  225. package/integrations/pi/skills/session-history/TROUBLESHOOTING.md +70 -0
  226. package/integrations/pi/skills/write-agentmemory-skill/SKILL.md +41 -0
  227. package/integrations/pi/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  228. package/package.json +33 -11
  229. package/plugin/.claude-plugin/plugin.json +16 -3
  230. package/plugin/.codex-plugin/plugin.json +4 -3
  231. package/plugin/.devin-plugin/plugin.json +6 -5
  232. package/plugin/.mcp.json +5 -1
  233. package/plugin/README.md +71 -0
  234. package/plugin/cursor/hooks.json +23 -8
  235. package/plugin/hooks/hooks.json +24 -12
  236. package/plugin/hooks.json +67 -0
  237. package/plugin/opencode/README.md +1 -1
  238. package/plugin/opencode/agentmemory-capture.ts +25 -11
  239. package/plugin/opencode/plugin.json +1 -1
  240. package/plugin/plugin.json +1 -1
  241. package/plugin/scripts/_capture.d.mts +5 -1
  242. package/plugin/scripts/_capture.d.mts.map +1 -1
  243. package/plugin/scripts/_capture.mjs +141 -26
  244. package/plugin/scripts/_capture.mjs.map +1 -1
  245. package/plugin/scripts/cursor/run-detached.mjs +786 -0
  246. package/plugin/scripts/cursor/run-detached.mjs.map +1 -0
  247. package/plugin/scripts/cursor/run-hook.d.mts +1 -0
  248. package/plugin/scripts/cursor/run-hook.mjs +704 -0
  249. package/plugin/scripts/cursor/run-hook.mjs.map +1 -0
  250. package/plugin/scripts/notification.mjs +20 -6
  251. package/plugin/scripts/notification.mjs.map +1 -1
  252. package/plugin/scripts/plugin-bridge.mjs +61 -11
  253. package/plugin/scripts/post-tool-failure.mjs +19 -6
  254. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  255. package/plugin/scripts/post-tool-use.mjs +19 -6
  256. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  257. package/plugin/scripts/pre-compact.mjs +104 -22
  258. package/plugin/scripts/pre-compact.mjs.map +1 -1
  259. package/plugin/scripts/pre-tool-use.mjs +83 -10
  260. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  261. package/plugin/scripts/prompt-submit.mjs +12 -4
  262. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  263. package/plugin/scripts/session-end.mjs +59 -17
  264. package/plugin/scripts/session-end.mjs.map +1 -1
  265. package/plugin/scripts/session-start.mjs +95 -15
  266. package/plugin/scripts/session-start.mjs.map +1 -1
  267. package/plugin/scripts/stop.mjs +75 -72
  268. package/plugin/scripts/stop.mjs.map +1 -1
  269. package/plugin/scripts/subagent-start.mjs +13 -5
  270. package/plugin/scripts/subagent-start.mjs.map +1 -1
  271. package/plugin/scripts/subagent-stop.mjs +32 -10
  272. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  273. package/plugin/scripts/task-completed.mjs +21 -6
  274. package/plugin/scripts/task-completed.mjs.map +1 -1
  275. package/plugin/skills/_shared/TROUBLESHOOTING.md +21 -7
  276. package/plugin/skills/agentmemory-agents/REFERENCE.md +8 -7
  277. package/plugin/skills/agentmemory-agents/SKILL.md +19 -5
  278. package/plugin/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  279. package/plugin/skills/agentmemory-architecture/SKILL.md +8 -4
  280. package/plugin/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  281. package/plugin/skills/agentmemory-config/REFERENCE.md +13 -15
  282. package/plugin/skills/agentmemory-config/SKILL.md +34 -10
  283. package/plugin/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  284. package/plugin/skills/agentmemory-hooks/SKILL.md +31 -10
  285. package/plugin/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  286. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +5 -5
  287. package/plugin/skills/agentmemory-mcp-tools/SKILL.md +16 -3
  288. package/plugin/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  289. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +10 -1
  290. package/plugin/skills/agentmemory-rest-api/SKILL.md +12 -6
  291. package/plugin/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  292. package/plugin/skills/commit-context/SKILL.md +1 -1
  293. package/plugin/skills/commit-context/TROUBLESHOOTING.md +70 -0
  294. package/plugin/skills/commit-history/SKILL.md +1 -1
  295. package/plugin/skills/commit-history/TROUBLESHOOTING.md +70 -0
  296. package/plugin/skills/forget/EXAMPLES.md +19 -54
  297. package/plugin/skills/forget/SKILL.md +16 -12
  298. package/plugin/skills/forget/TROUBLESHOOTING.md +70 -0
  299. package/plugin/skills/handoff/EXAMPLES.md +5 -4
  300. package/plugin/skills/handoff/SKILL.md +10 -5
  301. package/plugin/skills/handoff/TROUBLESHOOTING.md +70 -0
  302. package/plugin/skills/lesson/SKILL.md +1 -1
  303. package/plugin/skills/lesson/TROUBLESHOOTING.md +70 -0
  304. package/plugin/skills/memory-discipline/SKILL.md +1 -1
  305. package/plugin/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  306. package/plugin/skills/recall/SKILL.md +18 -7
  307. package/plugin/skills/recall/TROUBLESHOOTING.md +70 -0
  308. package/plugin/skills/recap/EXAMPLES.md +5 -3
  309. package/plugin/skills/recap/SKILL.md +11 -6
  310. package/plugin/skills/recap/TROUBLESHOOTING.md +70 -0
  311. package/plugin/skills/remember/EXAMPLES.md +3 -3
  312. package/plugin/skills/remember/SKILL.md +1 -1
  313. package/plugin/skills/remember/TROUBLESHOOTING.md +70 -0
  314. package/plugin/skills/session-history/EXAMPLES.md +2 -2
  315. package/plugin/skills/session-history/SKILL.md +10 -4
  316. package/plugin/skills/session-history/TROUBLESHOOTING.md +70 -0
  317. package/plugin/skills/write-agentmemory-skill/SKILL.md +2 -1
  318. package/plugin/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  319. package/dist/connect-B4_ca6BD.mjs.map +0 -1
  320. package/dist/docker-compose.yml +0 -73
  321. package/dist/iii-config.docker.yaml +0 -53
  322. package/dist/iii-config.yaml +0 -63
  323. package/dist/keyed-mutex-DchXsyfP.mjs.map +0 -1
  324. package/dist/secret-store-BnY3A50C.mjs.map +0 -1
  325. package/dist/src-Jsq9LCeH.mjs.map +0 -1
  326. package/dist/standalone-DKCdrf1d.mjs.map +0 -1
  327. package/dist/status-IRz7ta8-.mjs +0 -2344
  328. package/dist/status-IRz7ta8-.mjs.map +0 -1
  329. package/docker-compose.yml +0 -73
  330. package/iii-config.docker.yaml +0 -53
  331. package/iii-config.yaml +0 -63
  332. package/plugin/scripts/post-commit.mjs +0 -184
  333. package/plugin/scripts/post-commit.mjs.map +0 -1
  334. /package/plugin/scripts/{post-commit.d.mts → cursor/run-detached.d.mts} +0 -0
@@ -1005,8 +1005,8 @@
1005
1005
  .cb-open { border-color: var(--accent); color: var(--accent); }
1006
1006
  .cb-half-open { border-color: var(--yellow); color: var(--yellow); }
1007
1007
 
1008
- .worker-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; font-family: var(--font-ui); }
1009
- .worker-dot { width: 8px; height: 8px; }
1008
+ .worker-row { display: flex; align-items: center; flex-wrap: wrap; overflow-wrap: anywhere; gap: 8px; padding: 4px 0; font-size: 12px; font-family: var(--font-ui); }
1009
+ .worker-dot { width: 8px; height: 8px; flex-shrink: 0; background: var(--ink-faint); }
1010
1010
  .worker-dot.running { background: var(--green); }
1011
1011
  .worker-dot.stopped { background: var(--accent); }
1012
1012
  .worker-dot.starting { background: var(--yellow); }
@@ -1521,28 +1521,17 @@
1521
1521
  var params = new URLSearchParams(window.location.search);
1522
1522
  var paramPort = params.get('port');
1523
1523
  var locPort = window.location.port;
1524
- var wsProto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1525
- var REST, WS_URL, WS_DIRECT_URL, wsPort;
1524
+ var REST;
1526
1525
  if (paramPort) {
1527
1526
  var resolvedPort = parseInt(paramPort) === 3111 ? '3113' : paramPort;
1528
1527
  REST = window.location.protocol + '//' + window.location.hostname + ':' + resolvedPort;
1529
- wsPort = params.get('wsPort') || String(parseInt(resolvedPort) - 1);
1530
- WS_URL = wsProto + '//' + window.location.hostname + ':' + wsPort;
1531
- WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer';
1532
1528
  } else if (locPort) {
1533
1529
  var resolvedPort = parseInt(locPort) === 3111 ? '3113' : locPort;
1534
1530
  REST = window.location.protocol + '//' + window.location.hostname + ':' + resolvedPort;
1535
- wsPort = params.get('wsPort') || String(parseInt(resolvedPort) - 1);
1536
- WS_URL = wsProto + '//' + window.location.hostname + ':' + wsPort;
1537
- WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer';
1538
1531
  } else {
1539
1532
  REST = window.location.origin;
1540
- wsPort = params.get('wsPort');
1541
- WS_URL = wsPort
1542
- ? wsProto + '//' + window.location.hostname + ':' + wsPort
1543
- : wsProto + '//' + window.location.host;
1544
- WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer';
1545
1533
  }
1534
+ var LIVE_EVENTS_URL = REST + '/agentmemory/viewer/events';
1546
1535
 
1547
1536
  var dateEl = document.getElementById('dateline');
1548
1537
  if (dateEl) dateEl.textContent = new Date().toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' });
@@ -1654,12 +1643,12 @@
1654
1643
  eventLoop: { term: 'Event loop delay', text: 'How far behind the worker is in handling work. Under 20 ms is healthy; a large value means it is busy or blocked.' },
1655
1644
  uptime: { term: 'Uptime', text: 'How long the agentmemory worker has been running since it last started.' },
1656
1645
  health: { term: 'Health status', text: 'Healthy means everything is normal. Degraded means something needs attention but memory still works. Critical means a core part is failing.' },
1657
- stateBackend: { term: 'State store', text: 'Where the engine keeps every record: file (local files on this machine, the default) or redis (a Redis server set by AGENTMEMORY_STATE_BACKEND and AGENTMEMORY_REDIS_URL). ok means a test write and read just succeeded; the number is how long it took.' },
1658
- engineConnection: { term: 'Engine connection', text: 'Whether the agentmemory worker is connected to the iii engine that stores its data and runs its functions.' },
1659
- live: { term: 'Live updates', text: 'The viewer keeps one streaming connection open and updates every page the moment data changes. Nothing is refreshed on a timer.' },
1646
+ stateBackend: { term: 'State store', text: 'Where the state worker keeps every record: file (local files on this machine, the default) or redis (a Redis server set by AGENTMEMORY_STATE_BACKEND and AGENTMEMORY_REDIS_URL). ok means a test write and read just succeeded; the number is how long it took.' },
1647
+ engineConnection: { term: 'Engine connection', text: 'Whether the agentmemory worker is connected to the iii engine that routes calls between workers.' },
1648
+ live: { term: 'Live updates', text: 'The viewer keeps one SSE connection open and updates every page the moment data changes. Nothing is refreshed on a timer.' },
1660
1649
  flags: { term: 'Feature flags', text: 'Optional features that are on or off in this install. Off features leave some pages empty; each one lists the setting that turns it on.' },
1661
1650
  audit: { term: 'Audit log', text: 'A record of every change agentmemory made to stored data: saves, deletes, imports, consolidation runs. Use it to see who changed what and when.' },
1662
- workers: { term: 'Workers', text: 'Processes connected to the iii engine. agentmemory runs as one worker.' },
1651
+ workers: { term: 'Workers', text: 'Application workers provide memory, storage and HTTP. Engine details lists the coordinator and internal runtime services; available means a service is registered, not that an optional feature is enabled.' },
1663
1652
  latency: { term: 'Average latency', text: 'Average time one call of the function took to finish.' },
1664
1653
  failureRate: { term: 'Failure rate', text: 'Share of calls that failed. Functions that need an LLM show as off, not failing, when no LLM provider is set.' },
1665
1654
  replay: { term: 'Replay', text: 'Plays back a past session step by step: prompts, responses and tool calls in the order they happened, with their real timing. Works for any session agentmemory recorded, from any connected agent.' },
@@ -1670,20 +1659,19 @@
1670
1659
  var state = {
1671
1660
  activeTab: 'dashboard',
1672
1661
  dashboard: { loaded: false },
1673
- graph: { loaded: false, loading: false, dirty: false, nodeMap: {}, edgeMap: {}, degrees: {}, rev: 0, localDegree: null, localDegreeRev: -1, stats: null, hidden: {}, search: '', searchOpen: false, searchIndex: 0, selectedId: null, detail: null, queryError: null, disabledInfo: null, warning: '', truncated: false, totalNodes: 0, totalEdges: 0, requestSeq: 0, fetches: 0, patches: 0, lastPatchAt: 0, fresh: {}, simStale: false, rebuildResult: '' },
1662
+ graph: { loaded: false, loading: false, dirty: false, nodeMap: {}, edgeMap: {}, degrees: {}, rev: 0, localDegree: null, localDegreeRev: -1, stats: null, hidden: {}, search: '', searchOpen: false, searchIndex: 0, selectedId: null, detail: null, queryError: null, disabledInfo: null, warning: '', truncated: false, totalNodes: 0, totalEdges: 0, requestSeq: 0, searchRequestSeq: 0, resetSeq: 0, generation: undefined, fetches: 0, patches: 0, lastPatchAt: 0, fresh: {}, simStale: false, rebuildResult: '' },
1674
1663
  memories: { search: '', typeFilter: '', projectFilter: '', agentFilter: '', selectedId: null, nextCursor: null, loadingMore: false, searchIds: null, searchMode: '', searching: false, query: null, facets: null, checked: {}, editing: null, saving: false, pendingId: null, fetches: {}, diffId: null, error: '' },
1675
1664
  sessions: { selectedId: null, autoSelected: false, closed: false, nextCursor: null, loadingMore: false, filters: { q: '', project: '', agent: '', status: '' }, query: null, facets: null, detail: null, pendingObs: null, notice: null, summarizing: '' },
1676
1665
  audit: { opFilter: '', selectedIdx: null, from: '', to: '', q: '', query: null, loadingMore: false, exhausted: {}, fresh: {} },
1677
1666
  activity: { loaded: false, observations: [], sessionCount: 0, typeFilter: '', fileFilter: '', day: '' },
1678
1667
  lessons: { search: '', selectedId: null, projectFilter: '', notice: null, busy: '' },
1679
- actions: { frontier: [], statusFilter: '', search: '', selectedId: null, notice: null, busy: '' },
1668
+ actions: { frontier: [], statusFilter: '', search: '', selectedId: null, notice: null, busy: '', detailLoading: '', detailAttemptedId: '', detailRequestFailedId: '', loadError: false },
1680
1669
  crystals: { search: '', selectedId: null },
1681
1670
  profile: { loaded: false, selectedProject: '', data: null, dataProject: '', userPicked: false, notice: null },
1682
- health: { error: null, checking: false, consolidating: false, reveal: '' },
1671
+ health: { error: null, checking: false, consolidating: false, reveal: '', engineDetailsOpen: false },
1683
1672
  replay: { loaded: false, selectedId: '', timeline: null, cursor: 0, playing: false, speed: 1, timer: null, startAt: 0, offsetAt: 0, loadingTimeline: false, loadError: '', importPath: '', importing: false, importOpen: false, importStatus: null },
1684
1673
  flagsOpen: false,
1685
1674
  freshRows: {},
1686
- ws: null
1687
1675
  };
1688
1676
 
1689
1677
  var store = {
@@ -1714,7 +1702,7 @@
1714
1702
  memory: { path: 'memories?latest=true&limit=100&facets=true', key: 'memories' },
1715
1703
  session: { path: 'sessions?limit=100&facets=true', key: 'sessions' },
1716
1704
  lesson: { path: 'lessons', key: 'lessons' },
1717
- action: { path: 'actions', key: 'actions' },
1705
+ action: { path: 'actions', key: 'actions', allPages: true },
1718
1706
  crystal: { path: 'crystals', key: 'crystals' },
1719
1707
  semantic: { path: 'semantic', key: 'facts', fallbackKey: 'semantic' },
1720
1708
  procedural: { path: 'procedural', key: 'procedures', fallbackKey: 'procedural' },
@@ -2314,6 +2302,20 @@
2314
2302
  store.entities[kind] = bucket;
2315
2303
  }
2316
2304
 
2305
+ async function loadAllPages(spec, apiGet) {
2306
+ var rows = [];
2307
+ var cursor = null;
2308
+ var result;
2309
+ do {
2310
+ var path = spec.path + '?limit=500' + (cursor ? '&cursor=' + encodeURIComponent(cursor) : '');
2311
+ result = await apiGet(path);
2312
+ if (result === null) return null;
2313
+ rows.push.apply(rows, listOr([], result, spec.key, spec.fallbackKey));
2314
+ cursor = result.nextCursor || null;
2315
+ } while (cursor);
2316
+ return Object.assign({}, result, { [spec.key]: rows });
2317
+ }
2318
+
2317
2319
  async function ensureLoaded(kind) {
2318
2320
  if (store.loaded[kind]) return;
2319
2321
  if (store.loading[kind]) return store.loading[kind];
@@ -2323,8 +2325,16 @@
2323
2325
  try {
2324
2326
  await bootGate;
2325
2327
  var seqAtStart = eventSeq;
2326
- var result = await apiGet(spec.path);
2327
- if (result === null) return;
2328
+ var result;
2329
+ if (spec.allPages) {
2330
+ result = await loadAllPages(spec, apiGet);
2331
+ } else {
2332
+ result = await apiGet(spec.path);
2333
+ }
2334
+ if (result === null) {
2335
+ if (kind === 'action') state.actions.loadError = true;
2336
+ return;
2337
+ }
2328
2338
  replaceBucket(kind, listOr([], result, spec.key, spec.fallbackKey));
2329
2339
  if (kind === 'memory') {
2330
2340
  state.memories.nextCursor = result.nextCursor || null;
@@ -2343,6 +2353,9 @@
2343
2353
  }
2344
2354
  replayEventsSince(seqAtStart, kind);
2345
2355
  store.loaded[kind] = true;
2356
+ if (kind === 'action') state.actions.loadError = false;
2357
+ } catch {
2358
+ if (kind === 'action') state.actions.loadError = true;
2346
2359
  } finally {
2347
2360
  store.loading[kind] = null;
2348
2361
  }
@@ -2358,11 +2371,14 @@
2358
2371
  });
2359
2372
  }
2360
2373
 
2361
- async function loadSnapshot() {
2374
+ async function loadSnapshot(retried) {
2375
+ var resetSeq = state.graph.resetSeq;
2362
2376
  var snap = await apiGet('viewer/snapshot');
2377
+ if (resetSeq !== state.graph.resetSeq) return retried ? false : loadSnapshot(true);
2363
2378
  if (!snap) return false;
2364
2379
  var graphBefore = store.graph && store.graph.updatedAt;
2365
2380
  var reconnect = !!store.snapshotAt;
2381
+ noteGraphGeneration(snap.graph);
2366
2382
  applySnapshot(store, snap);
2367
2383
  renderFlagsChip();
2368
2384
  renderFlagsPanel();
@@ -2612,7 +2628,7 @@
2612
2628
 
2613
2629
  html += renderMemoryLayers(store.consolidationStatus, entityList('semantic'), entityList('procedural'));
2614
2630
 
2615
- html += '<details class="card" style="margin-top:16px;"><summary class="card-title" style="cursor:pointer;margin-bottom:0;border-bottom:none;">Function metrics and workers ' + help('functionCalls') + '</summary>';
2631
+ html += '<details class="card" style="margin-top:16px;"><summary class="card-title" style="cursor:pointer;margin-bottom:0;border-bottom:none;">Function metrics ' + help('functionCalls') + '</summary>';
2616
2632
  if (fMetrics.length > 0) {
2617
2633
  var sorted = fMetrics.slice().sort(function(a, b) { return (b.totalCalls || 0) - (a.totalCalls || 0); });
2618
2634
  html += '<div class="table-wrap" style="margin-top:10px;"><table class="metric-table"><tr><th>Function</th><th style="text-align:right">Calls</th><th style="text-align:right">Failed</th><th style="text-align:right">Avg latency ' + help('latency') + '</th></tr>';
@@ -2627,14 +2643,7 @@
2627
2643
  } else {
2628
2644
  html += '<p style="font-size:12px;color:var(--ink-muted);margin-top:10px;">No function has run since the worker started. Calls appear here as agents observe, search and save.</p>';
2629
2645
  }
2630
- var workers = snap.workers || [];
2631
- if (workers.length > 0) {
2632
- html += '<div style="margin-top:12px;font-family:var(--font-ui);font-size:10px;letter-spacing:0.12em;text-transform:uppercase;color:var(--ink-muted);font-weight:700;">Workers ' + help('workers') + '</div>';
2633
- workers.forEach(function(w) {
2634
- var statusClass = w.status === 'running' ? 'running' : w.status === 'starting' ? 'starting' : 'stopped';
2635
- html += '<div class="worker-row"><span class="worker-dot ' + statusClass + '"></span><span style="color:var(--ink);font-weight:600;">' + esc(w.name) + '</span><span class="badge ' + (w.status === 'running' ? 'badge-green' : 'badge-muted') + '">' + esc(w.status) + '</span></div>';
2636
- });
2637
- }
2646
+ html += '<p class="hnote"><a href="#health">Worker status and engine details &rarr;</a></p>';
2638
2647
  html += '</details>';
2639
2648
 
2640
2649
  setViewHtml(el, html);
@@ -2806,8 +2815,54 @@
2806
2815
  return graphNodeList().filter(function(n) { return !deg[n.id]; }).length;
2807
2816
  }
2808
2817
 
2818
+ function clearGraphView() {
2819
+ var g = state.graph;
2820
+ g.resetSeq++;
2821
+ g.requestSeq++;
2822
+ g.searchRequestSeq++;
2823
+ g.selectedId = null;
2824
+ g.detail = null;
2825
+ g.nodeMap = {};
2826
+ g.edgeMap = {};
2827
+ g.degrees = {};
2828
+ g.fresh = {};
2829
+ g.stats = null;
2830
+ g.totalNodes = 0;
2831
+ g.totalEdges = 0;
2832
+ g.truncated = false;
2833
+ g.loading = false;
2834
+ g.loaded = false;
2835
+ g.dirty = false;
2836
+ g.queryError = null;
2837
+ g.disabledInfo = null;
2838
+ g.warning = '';
2839
+ g.searchOpen = false;
2840
+ g.rev++;
2841
+ g.simStale = true;
2842
+ if (graphReloadTimer) clearTimeout(graphReloadTimer);
2843
+ graphReloadTimer = null;
2844
+ graphSim.hoverId = null;
2845
+ graphSim.drag = null;
2846
+ graphSim.nodes = [];
2847
+ graphSim.byId = {};
2848
+ graphSim.edges = [];
2849
+ graphSim.isolatedBox = null;
2850
+ setRouteEntity('graph', '');
2851
+ renderGraphDetail();
2852
+ requestGraphDraw();
2853
+ }
2854
+
2855
+ function noteGraphGeneration(data) {
2856
+ if (!data) return;
2857
+ var g = state.graph;
2858
+ if (data.reset === true && data.source === 'items') store.graph = null;
2859
+ if (data.reset === true || ('generation' in data && g.generation !== undefined && g.generation !== data.generation)) clearGraphView();
2860
+ if ('generation' in data) g.generation = data.generation;
2861
+ }
2862
+
2809
2863
  function setGraphData(result) {
2810
2864
  var g = state.graph;
2865
+ if (!(result.nodes || []).length && !result.truncated && !result.totalNodes && (g.selectedId || g.detail || Object.keys(g.nodeMap).length)) clearGraphView();
2811
2866
  var nodeMap = {};
2812
2867
  var edgeMap = {};
2813
2868
  (result.nodes || []).forEach(function(n) { if (n && n.id) nodeMap[n.id] = n; });
@@ -2841,6 +2896,7 @@
2841
2896
  g.loading = false;
2842
2897
  if (result === null) {
2843
2898
  var stats = await api('graph/stats', { readErrorBody: true });
2899
+ if (seq !== g.requestSeq) return;
2844
2900
  if (stats && stats.error && stats.flag) {
2845
2901
  g.disabledInfo = stats;
2846
2902
  g.queryError = null;
@@ -2865,8 +2921,9 @@
2865
2921
 
2866
2922
  function applyGraphDelta(delta) {
2867
2923
  var g = state.graph;
2868
- if (!g.loaded || !delta) return false;
2924
+ if (!delta || (!g.loaded && !delta.full)) return false;
2869
2925
  if (delta.full) {
2926
+ if (!(delta.nodes || []).length && (g.selectedId || g.detail || Object.keys(g.nodeMap).length)) clearGraphView();
2870
2927
  var keepNodes = {};
2871
2928
  (delta.nodes || []).forEach(function(n) { keepNodes[n.id] = true; });
2872
2929
  Object.keys(g.nodeMap).forEach(function(id) { if (!keepNodes[id] && id !== g.selectedId) delete g.nodeMap[id]; });
@@ -2893,13 +2950,14 @@
2893
2950
  g.lastPatchAt = Date.now();
2894
2951
  Object.keys(touched).forEach(function(id) { g.fresh[id] = Date.now(); });
2895
2952
  if (g.selectedId && touched[g.selectedId] && g.detail && g.detail.id === g.selectedId) g.detail.stale = true;
2953
+ g.loaded = true;
2896
2954
  return true;
2897
2955
  }
2898
2956
 
2899
2957
  function handleGraphChange(data) {
2900
2958
  var g = state.graph;
2901
2959
  if (!data) return;
2902
- if (!g.loaded) { g.dirty = true; return; }
2960
+ noteGraphGeneration(data);
2903
2961
  if (data.source === 'snapshot') {
2904
2962
  if (data.delta && applyGraphDelta(data.delta)) {
2905
2963
  if (graphSim.canvas && state.activeTab === 'graph') { syncGraphSim(false); requestGraphDraw(); setTimeout(requestGraphDraw, 4200); }
@@ -2925,10 +2983,10 @@
2925
2983
 
2926
2984
  async function fetchGraphDetail(id) {
2927
2985
  var g = state.graph;
2928
- g.detail = { id: id, loading: true, data: null, error: '', stale: false };
2986
+ var detail = g.detail = { id: id, loading: true, data: null, error: '', stale: false };
2929
2987
  renderGraphDetail();
2930
2988
  var res = await api('graph/node?id=' + encodeURIComponent(id), { readErrorBody: true });
2931
- if (!g.detail || g.detail.id !== id) return;
2989
+ if (g.detail !== detail) return;
2932
2990
  if (!res || res.error) {
2933
2991
  g.detail = { id: id, loading: false, data: null, error: res && res.error ? res.error : 'request failed', stale: false };
2934
2992
  } else {
@@ -3584,7 +3642,7 @@
3584
3642
  title: 'The knowledge graph is off',
3585
3643
  what: 'Entities and relations agentmemory pulls out of what agents do.',
3586
3644
  why: esc(g.disabledInfo.enableHow || 'Graph extraction is disabled on this server.'),
3587
- steps: [{ label: 'Turn it on and restart:', cmd: 'export GRAPH_EXTRACTION_ENABLED=true\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory' }],
3645
+ steps: [{ label: 'Turn it on and restart:', cmd: 'export GRAPH_EXTRACTION_ENABLED=true\nexport AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory' }],
3588
3646
  link: g.disabledInfo.docsHref ? { href: g.disabledInfo.docsHref, text: 'Knowledge graph docs' } : null
3589
3647
  });
3590
3648
  } else if (g.queryError) {
@@ -3604,7 +3662,7 @@
3604
3662
  steps: [
3605
3663
  { label: 'Build it now from every stored observation (spends LLM tokens when extraction is on):', cmd: curlPost('graph/build', {}) },
3606
3664
  { label: 'Or import a structural code graph made by graphify (no LLM needed):', cmd: curlPost('graph/import-graphify', { cwd: '/path/to/repo' }) },
3607
- { label: 'For typed entities and relations, turn on LLM extraction and restart:', cmd: 'export GRAPH_EXTRACTION_ENABLED=true\nexport ANTHROPIC_API_KEY=sk-ant-...' }
3665
+ { label: 'For typed entities and relations, turn on LLM extraction and restart:', cmd: 'export GRAPH_EXTRACTION_ENABLED=true\nexport AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...' }
3608
3666
  ]
3609
3667
  }) + '<div class="graph-msg-actions"><button type="button" class="btn btn-primary" data-action="rebuild-graph">Build from observations</button></div>';
3610
3668
  }
@@ -3671,7 +3729,7 @@
3671
3729
  html += copyBtn('memory_graph_query { "startNodeId": ' + JSON.stringify(id) + ', "maxDepth": 1 }', 'MCP call').replace('>copy<', '>copy MCP call<');
3672
3730
  html += copyBtn(curlGet('graph/node?id=' + encodeURIComponent(id)), 'curl command').replace('>copy<', '>copy curl<');
3673
3731
  html += '</div>';
3674
- if (d && d.stale) html += '<div class="mem-note">This node changed on the live stream. <button type="button" class="linkish" data-action="graph-refresh-detail" data-node-id="' + esc(id) + '">Reload sources</button></div>';
3732
+ if (d && d.stale) html += '<div class="mem-note">This node changed on the SSE feed. <button type="button" class="linkish" data-action="graph-refresh-detail" data-node-id="' + esc(id) + '">Reload sources</button></div>';
3675
3733
 
3676
3734
  var rels = graphDetailRelations(id);
3677
3735
  var relTotal = data && typeof data.relationsTotal === 'number' ? Math.max(data.relationsTotal, rels.length) : rels.length;
@@ -3761,7 +3819,7 @@
3761
3819
  html += '<div class="go-intro"><strong>Knowledge graph</strong> ' + help('graph') + ' The files, functions, concepts and decisions agents worked with, and how they relate. Click a node to see its relations and the observations it came from.</div>';
3762
3820
  html += '<div class="go-stats"><div><b>' + fmtNum(t.nodes) + '</b><span>nodes ' + help('graphNode') + '</span></div><div><b>' + fmtNum(t.edges) + '</b><span>relations ' + help('graphEdge') + '</span></div><div><b>' + fmtNum(iso) + '</b><span>unlinked ' + help('graphIsolated') + '</span></div></div>';
3763
3821
  if (shown >= 3 && (iso / shown > 0.4 || t.edges < shown / 2)) {
3764
- html += '<div class="mem-note go-sparse"><b>Sparse graph.</b> ' + fmtNum(iso) + ' of ' + fmtNum(shown) + ' nodes have no relations yet. ' + esc(graphLlmNote()) + (graphLlmMode() !== 'llm' ? cmdBlock('export GRAPH_EXTRACTION_ENABLED=true\nexport ANTHROPIC_API_KEY=sk-ant-...') : '') + '</div>';
3822
+ html += '<div class="mem-note go-sparse"><b>Sparse graph.</b> ' + fmtNum(iso) + ' of ' + fmtNum(shown) + ' nodes have no relations yet. ' + esc(graphLlmNote()) + (graphLlmMode() !== 'llm' ? cmdBlock('export GRAPH_EXTRACTION_ENABLED=true\nexport AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...') : '') + '</div>';
3765
3823
  }
3766
3824
  if (g.truncated && shown < t.nodes) html += '<div class="mem-note">Showing the ' + fmtNum(shown) + ' most connected of ' + fmtNum(t.nodes) + ' nodes. Search finds the rest on the server.</div>';
3767
3825
  if (g.warning) html += '<div class="mem-note">' + esc(g.warning) + '</div>';
@@ -3796,7 +3854,7 @@
3796
3854
  html += '<div class="empty-step-label" style="margin-top:8px;">Query it from an agent:</div>' + cmdBlock('memory_graph_query { "query": "checkout" }');
3797
3855
  html += '</div>';
3798
3856
  if (g.rebuildResult) html += '<div class="mem-note">' + esc(g.rebuildResult) + '</div>';
3799
- html += '<div class="go-live">' + (g.patches ? 'Patched ' + fmtNum(g.patches) + ' time' + (g.patches === 1 ? '' : 's') + ' from the live stream, last ' + relTime(new Date(g.lastPatchAt).toISOString()) + '.' : 'Updates arrive on the live stream; nothing refreshes on a timer.') + ' ' + help('live') + '</div>';
3857
+ html += '<div class="go-live">' + (g.patches ? 'Patched ' + fmtNum(g.patches) + ' time' + (g.patches === 1 ? '' : 's') + ' from the SSE feed, last ' + relTime(new Date(g.lastPatchAt).toISOString()) + '.' : 'Updates arrive on the SSE feed; nothing refreshes on a timer.') + ' ' + help('live') + '</div>';
3800
3858
  html += '</div>';
3801
3859
  setViewHtml(host, html);
3802
3860
  }
@@ -4021,9 +4079,11 @@
4021
4079
 
4022
4080
  async function searchGraphRemote(q) {
4023
4081
  var g = state.graph;
4082
+ var seq = ++g.searchRequestSeq;
4024
4083
  var box = document.getElementById('graph-search-results');
4025
4084
  if (box) { box.hidden = false; box.innerHTML = '<div class="gs-empty">Searching all nodes…</div>'; }
4026
4085
  var res = await apiPost('graph/query', { query: q, limit: 50 });
4086
+ if (seq !== g.searchRequestSeq) return;
4027
4087
  if (!res) {
4028
4088
  if (box) box.innerHTML = '<div class="gs-empty">The server search failed.</div>';
4029
4089
  return;
@@ -4097,7 +4157,7 @@
4097
4157
  try {
4098
4158
  var result = await apiPost('graph/build', {});
4099
4159
  g.rebuildResult = result && result.success
4100
- ? 'Rebuilt from ' + (result.sessions || 0) + ' sessions: ' + (result.nodes || 0) + ' nodes and ' + (result.edges || 0) + ' relations extracted. New ones arrive on the live stream.'
4160
+ ? 'Rebuilt from ' + (result.sessions || 0) + ' sessions: ' + (result.nodes || 0) + ' nodes and ' + (result.edges || 0) + ' relations extracted. New ones arrive on the SSE feed.'
4101
4161
  : 'The rebuild failed. Check the server log.';
4102
4162
  } finally {
4103
4163
  graphRebuilding = false;
@@ -4505,7 +4565,7 @@
4505
4565
  var mem = store.entities.memory[id];
4506
4566
  if (!mem) {
4507
4567
  if (m.pendingId === id) {
4508
- setViewHtml(panel, '<div class="detail-panel mem-detail"><div class="mem-detail-head"><span>Saving the new version</span>' + memoryCloseBtn() + '</div><div class="mem-note">Saved. Waiting for the new version to arrive on the live stream…</div>' + skeleton(4) + '</div>');
4568
+ setViewHtml(panel, '<div class="detail-panel mem-detail"><div class="mem-detail-head"><span>Saving the new version</span>' + memoryCloseBtn() + '</div><div class="mem-note">Saved. Waiting for the new version to arrive on the SSE feed…</div>' + skeleton(4) + '</div>');
4509
4569
  return;
4510
4570
  }
4511
4571
  if (!m.fetches[id]) fetchMemoryVersion(id);
@@ -6144,7 +6204,7 @@
6144
6204
  var body = '<h3>Summarize this session?</h3>' +
6145
6205
  '<p>An LLM reads the observations of <b>' + esc(sessionDisplayName(s)) + '</b> (' + fmtNum(s.observationCount || 0) + ') and writes a short summary: a title, what happened and the key decisions. The summary is shown here and in the session list, and later feeds consolidation into facts and procedures. Running it again replaces the old summary.</p>';
6146
6206
  if (!llm) {
6147
- body += '<div class="mem-note err">No LLM provider is configured, so Summarize would do nothing. Set a key and restart agentmemory:</div>' + cmdBlock('export ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory');
6207
+ body += '<div class="mem-note err">No LLM provider is configured, so Summarize would do nothing. Set a key and restart agentmemory:</div>' + cmdBlock('export AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory');
6148
6208
  body += '<div class="modal-actions" style="margin-top:14px;"><button class="btn" data-action="close-modal">Close</button></div>';
6149
6209
  } else {
6150
6210
  body += '<p style="font-size:11px;">Same call from a terminal: <code>' + esc(curlPost('summarize', { sessionId: id }).replace(/\s*\\\n\s*/g, ' ')) + '</code></p>';
@@ -6404,7 +6464,7 @@
6404
6464
  var res = await apiPost('lessons/strengthen', { lessonId: id });
6405
6465
  l.busy = '';
6406
6466
  l.notice = res && res.success !== false
6407
- ? { id: id, html: 'Recorded one more use. Confidence and uses update here from the live stream.' }
6467
+ ? { id: id, html: 'Recorded one more use. Confidence and uses update here from the SSE feed.' }
6408
6468
  : { id: id, err: true, html: 'Strengthen failed' + (res && res.error ? ': ' + esc(res.error) : '. Check that agentmemory is running.') };
6409
6469
  renderLessons();
6410
6470
  }
@@ -6497,12 +6557,15 @@
6497
6557
  function renderActions() {
6498
6558
  var el = document.getElementById('view-actions');
6499
6559
  var a = state.actions;
6500
- if (!store.loaded.action) {
6560
+ var all = entityList('action');
6561
+ if (!store.loaded.action && !all.length) {
6501
6562
  listDetailShell(el, 'actions', '');
6502
- setViewHtml(document.getElementById('actions-list'), skeleton(8));
6563
+ setViewHtml(document.getElementById('actions-list'), a.loadError
6564
+ ? '<div class="empty-state compact"><div class="empty-title">Could not load actions</div><div class="empty-what">Check that agentmemory is running, then try again.</div><button type="button" class="btn" data-action="action-list-retry">Retry</button></div>'
6565
+ : skeleton(8));
6566
+ renderActionDetail({});
6503
6567
  return;
6504
6568
  }
6505
- var all = entityList('action');
6506
6569
  var rows = actionRows();
6507
6570
  var frontierIds = {};
6508
6571
  (a.frontier || []).forEach(function(f) {
@@ -6560,24 +6623,82 @@
6560
6623
  html += '</tbody></table></div>';
6561
6624
  if (all.length < 8) html += usageCard('action');
6562
6625
  }
6626
+ if (a.loadError) {
6627
+ html = '<div class="inline-note"><strong>Could not refresh actions.</strong> Showing previously loaded actions. <button type="button" class="btn" data-action="action-list-retry">Retry</button></div>' + html;
6628
+ }
6563
6629
  setViewHtml(document.getElementById('actions-list'), html);
6630
+ renderActionDetail(frontierIds);
6631
+ }
6632
+
6633
+ function renderActionDetail(frontierIds) {
6634
+ var a = state.actions;
6564
6635
  var detail = document.getElementById('actions-detail');
6565
6636
  var sel = a.selectedId ? store.entities.action[a.selectedId] : null;
6566
6637
  if (a.selectedId && !sel) {
6567
- setViewHtml(detail, '<div id="action-detail" class="detail-panel mem-detail"><div class="mem-detail-head"><span>Action</span><button type="button" class="icon-btn icon-close" data-action="action-close" aria-label="Close action details">close</button></div>' + emptyState({
6638
+ ensureActionById(a.selectedId);
6639
+ var loadingAction = a.detailLoading === a.selectedId;
6640
+ var actionBody = loadingAction ? '<div class="empty-state compact"><div class="empty-title">Loading action…</div></div>' : a.detailRequestFailedId === a.selectedId
6641
+ ? '<div class="empty-state compact"><div class="empty-title">Could not load action</div><div class="empty-what">Check that agentmemory is running, then try again.</div><button type="button" class="btn" data-action="action-retry">Retry</button></div>'
6642
+ : emptyState({
6568
6643
  compact: true,
6569
6644
  icon: '&#128269;',
6570
6645
  title: 'Action not found',
6571
6646
  what: 'Action links anywhere in the viewer open this panel by id.',
6572
6647
  why: 'No action has the id <code>' + esc(a.selectedId) + '</code>. It was removed, or the link comes from another install.',
6573
6648
  steps: [{ label: 'Check from a terminal:', cmd: curlGet('actions/get?actionId=' + encodeURIComponent(a.selectedId)) }]
6574
- }) + '</div>');
6649
+ });
6650
+ setViewHtml(detail, '<div id="action-detail" class="detail-panel mem-detail"><div class="mem-detail-head"><span>Action</span><button type="button" class="icon-btn icon-close" data-action="action-close" aria-label="Close action details">close</button></div>' + actionBody + '</div>');
6575
6651
  } else {
6576
6652
  setViewHtml(detail, sel ? actionDetailHtml(sel, !!frontierIds[sel.id]) : '');
6577
6653
  }
6578
6654
  document.getElementById('actions-layout').classList.toggle('has-detail', !!detail.firstElementChild);
6579
6655
  }
6580
6656
 
6657
+ async function ensureActionById(id) {
6658
+ var a = state.actions;
6659
+ if (!id || store.entities.action[id] || a.detailLoading === id || a.detailAttemptedId === id) return;
6660
+ a.detailAttemptedId = id;
6661
+ a.detailLoading = id;
6662
+ try {
6663
+ var result = await apiGet('actions/get?actionId=' + encodeURIComponent(id));
6664
+ if (result && result.success && result.action && result.action.id) {
6665
+ store.entities.action[result.action.id] = result.action;
6666
+ a.detailRequestFailedId = '';
6667
+ renderActions();
6668
+ } else if (result === null) {
6669
+ a.detailRequestFailedId = id;
6670
+ }
6671
+ } catch (_) {
6672
+ a.detailRequestFailedId = id;
6673
+ } finally {
6674
+ if (a.detailLoading === id) {
6675
+ a.detailLoading = '';
6676
+ if (!store.entities.action[id]) renderActions();
6677
+ }
6678
+ }
6679
+ }
6680
+
6681
+ function retryActionById(id) {
6682
+ var a = state.actions;
6683
+ if (a.detailLoading === id) return;
6684
+ a.detailAttemptedId = '';
6685
+ a.detailRequestFailedId = '';
6686
+ return ensureActionById(id);
6687
+ }
6688
+
6689
+ async function retryActionList() {
6690
+ if (store.loading.action) return store.loading.action;
6691
+ state.actions.loadError = false;
6692
+ store.loaded.action = false;
6693
+ renderActions();
6694
+ await ensureLoaded('action');
6695
+ if (state.actions.selectedId && !store.entities.action[state.actions.selectedId]) {
6696
+ state.actions.detailAttemptedId = '';
6697
+ state.actions.detailRequestFailedId = '';
6698
+ }
6699
+ renderActions();
6700
+ }
6701
+
6581
6702
  async function setActionStatus(id, status) {
6582
6703
  var a = state.actions;
6583
6704
  a.busy = id;
@@ -6585,7 +6706,7 @@
6585
6706
  var res = await apiPost('actions/update', { actionId: id, status: status });
6586
6707
  a.busy = '';
6587
6708
  a.notice = res && res.success !== false
6588
- ? { id: id, html: 'Status set to <b>' + esc(status) + '</b>. Ready markers update from the live stream.' }
6709
+ ? { id: id, html: 'Status set to <b>' + esc(status) + '</b>. Ready markers update from the SSE feed.' }
6589
6710
  : { id: id, err: true, html: 'Update failed' + (res && res.error ? ': ' + esc(res.error) : '. Check that agentmemory is running.') };
6590
6711
  renderActions();
6591
6712
  }
@@ -6615,7 +6736,7 @@
6615
6736
  { label: 'Crystallize every finished action now:', cmd: curlPost('crystals/auto', {}) },
6616
6737
  { label: 'Or from an agent, for specific actions:', cmd: 'memory_crystallize { "actionIds": ["act_..."] }' }
6617
6738
  ] : [
6618
- { label: 'Set an LLM key and restart agentmemory:', cmd: 'export ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory' },
6739
+ { label: 'Set an LLM key and restart agentmemory:', cmd: 'export AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx @agentmemory/agentmemory' },
6619
6740
  { label: 'Then crystallize finished actions:', cmd: curlPost('crystals/auto', {}) }
6620
6741
  ],
6621
6742
  link: { href: 'https://github.com/rohitg00/agentmemory#crystals', text: 'How crystals are built' }
@@ -6679,6 +6800,8 @@
6679
6800
  return true;
6680
6801
  }
6681
6802
  if (action === 'action-close') { state.actions.selectedId = null; setRouteEntity('actions', ''); renderActions(); return true; }
6803
+ if (action === 'action-retry') { retryActionById(state.actions.selectedId); return true; }
6804
+ if (action === 'action-list-retry') { retryActionList(); return true; }
6682
6805
  if (action === 'action-set-status') { setActionStatus(target.getAttribute('data-action-id'), target.getAttribute('data-status')); return true; }
6683
6806
  if (action === 'action-clear-filters') { state.actions.search = ''; state.actions.statusFilter = ''; var ai = document.getElementById('actions-search'); if (ai) ai.value = ''; renderActions(); return true; }
6684
6807
  if (action === 'activity-type' || action === 'activity-file' || action === 'activity-day' || action === 'activity-clear') { handleActivityAction(action, target); return true; }
@@ -7051,7 +7174,7 @@
7051
7174
  }
7052
7175
 
7053
7176
  var BM25_EXPLAINER = 'The keyword index holds observations, memories and lessons, so its total is larger than the observation count.';
7054
- var LLM_KEY_FIX = 'Add one LLM provider key (for example ANTHROPIC_API_KEY or OPENAI_API_KEY) to ~/.agentmemory/.env and restart.';
7177
+ var LLM_KEY_FIX = 'Set AGENTMEMORY_LLM_PROVIDER and that provider\'s key (for example AGENTMEMORY_LLM_PROVIDER=anthropic and ANTHROPIC_API_KEY) in ~/.agentmemory/.env and restart.';
7055
7178
 
7056
7179
  function hrow(label, valueHtml, helpKey, note) {
7057
7180
  return '<div class="detail-row"><div class="dl">' + esc(label) + (helpKey ? ' ' + help(helpKey) : '') + '</div><div class="dv">' + valueHtml + (note ? '<div class="hnote">' + note + '</div>' : '') + '</div></div>';
@@ -7079,7 +7202,7 @@
7079
7202
  }
7080
7203
  if (store.lastHealthAt && Date.now() - store.lastHealthAt > HEALTH_STALE_MS) {
7081
7204
  var secs = Math.round((Date.now() - store.lastHealthAt) / 1000);
7082
- return '<div class="hstale" id="health-live-state"><strong>Stale: no health report for ' + secs + 's.</strong> Reports normally arrive every 30 seconds. The stream is connected, so the worker is busy or the iii engine is paused' + (r && r.service && r.service.stateBackend === 'redis' ? ', or Redis restarted: the engine stops relaying live events after a Redis restart until agentmemory restarts' : '') + '. The numbers below are from ' + esc(last) + '.' +
7205
+ return '<div class="hstale" id="health-live-state"><strong>Stale: no health report for ' + secs + 's.</strong> Reports normally arrive every 30 seconds. The SSE feed is connected, but the worker may be busy or unable to publish a health report. The numbers below are from ' + esc(last) + '.' +
7083
7206
  '<div style="margin-top:6px;">Fix: wait a minute; if it stays stale, press Re-check now for the full report, check the server log and restart agentmemory.</div></div>';
7084
7207
  }
7085
7208
  return '';
@@ -7105,7 +7228,7 @@
7105
7228
  }
7106
7229
  html += '<div class="verdict"><span class="verdict-badge ' + esc(level) + '">' + esc(badgeText) + '</span><div style="min-width:0;flex:1;">' +
7107
7230
  '<div class="verdict-text">' + esc(text) + '</div>' +
7108
- '<div class="verdict-meta">' + (r ? 'Report ' + relTime(r.checkedAt) + ' · ' : '') + 'updates every 30 s over the live stream, no refresh needed ' + help('live') + '</div></div></div>';
7231
+ '<div class="verdict-meta">' + (r ? 'Report ' + relTime(r.checkedAt) + ' · ' : '') + 'updates every 30 s over SSE, no refresh needed ' + help('live') + '</div></div></div>';
7109
7232
  html += healthStaleHtml(r);
7110
7233
  if (state.health.error) html += '<div class="mem-note err" style="margin-top:10px;">' + esc(state.health.error) + '</div>';
7111
7234
  if (r && r.problems.length) {
@@ -7120,20 +7243,45 @@
7120
7243
  return html + '</div>';
7121
7244
  }
7122
7245
 
7246
+ function workerRowHtml(w) {
7247
+ var status = w.status || 'unknown';
7248
+ var statusClass = status === 'running' || status === 'connected' ? 'running' :
7249
+ status === 'starting' || status === 'reconnecting' ? 'starting' :
7250
+ ['stopped', 'disconnected', 'failed', 'error'].indexOf(status) !== -1 ? 'stopped' : 'unknown';
7251
+ var badge = statusClass === 'running' ? 'badge-green' : statusClass === 'starting' ? 'badge-yellow' : statusClass === 'stopped' ? 'badge-red' : 'badge-muted';
7252
+ return '<div class="worker-row"><span class="worker-dot ' + statusClass + '" aria-hidden="true"></span><strong>' + esc(w.name || w.id || 'Unnamed worker') + '</strong><span class="badge ' + badge + '">' + esc(status) + '</span></div>';
7253
+ }
7254
+
7255
+ function workersHtml(workers, expanded) {
7256
+ if (!workers || !workers.length) return '';
7257
+ var application = [], internal = [];
7258
+ workers.forEach(function(w) {
7259
+ var engine = w.runtime === 'engine' || w.isolation === 'in-process' || w.name === 'compose';
7260
+ (engine ? internal : application).push(w);
7261
+ });
7262
+ var html = '<div style="margin-top:16px;"><div class="card-title">Workers ' + help('workers') + '</div>';
7263
+ html += application.map(workerRowHtml).join('');
7264
+ if (internal.length) {
7265
+ html += '<details id="health-engine-details"' + (expanded ? ' open' : '') + ' style="margin-top:12px;"><summary style="cursor:pointer;">Engine details (' + internal.length + ')</summary>' +
7266
+ '<p class="hnote">Runtime coordination and internal services. Available means registered; it does not indicate whether an optional feature such as telemetry is enabled.</p>' +
7267
+ internal.map(workerRowHtml).join('') + '</details>';
7268
+ }
7269
+ return html + '</div>';
7270
+ }
7271
+
7123
7272
  function engineCardHtml(r, snap) {
7124
7273
  var ports = (r && r.service && r.service.ports) || {};
7125
7274
  var kv = snap.kvConnectivity;
7126
7275
  var backend = r && r.service ? r.service.stateBackend : null;
7127
7276
  var streamText = conn.state === 'live' ? 'live' : conn.state;
7128
7277
  return '<div class="card"><div class="card-title">Engine and connection ' + help('engineConnection') + '</div>' +
7129
- hrow('Engine connection', esc(snap.connectionState || 'unknown'), 'engineConnection', 'The worker\'s link to the iii engine, which stores the data and runs the functions.') +
7130
- ((kv || backend) ? hrow('State store', esc((backend ? backend + (kv ? ' · ' : '') : '') + (kv ? kv.status + (kv.latencyMs !== undefined ? ' · ' + kv.latencyMs + ' ms' : '') + (kv.error ? ' · ' + kv.error : '') : '')), 'stateBackend', backend === 'redis' ? 'Stored in Redis (AGENTMEMORY_STATE_BACKEND=redis). If Redis restarts, restart agentmemory so the live stream resumes.' : 'Stored in the engine\'s local files (the default). Set AGENTMEMORY_STATE_BACKEND=redis to use Redis.') : '') +
7131
- hrow('Viewer stream', esc(streamText), 'live', 'This page\'s own connection. Offline means the numbers here stop changing.') +
7132
- (snap.streamRelay ? hrow('Redis event relay', esc(snap.streamRelay), 'live', snap.streamRelay === 'down' ? 'The engine stopped relaying live events through Redis, usually after Redis restarted. Data is still saved. Restart agentmemory to resume live updates.' : 'A test event sent through Redis every 30 seconds came back to the worker.') : '') +
7278
+ hrow('Engine connection', esc(snap.connectionState || 'unknown'), 'engineConnection', 'The worker\'s link to the iii engine, which routes calls between workers.') +
7279
+ ((kv || backend) ? hrow('State store', esc((backend ? backend + (kv ? ' · ' : '') : '') + (kv ? kv.status + (kv.latencyMs !== undefined ? ' · ' + kv.latencyMs + ' ms' : '') + (kv.error ? ' · ' + kv.error : '') : '')), 'stateBackend', backend === 'redis' ? 'The state worker stores records in Redis (AGENTMEMORY_STATE_BACKEND=redis).' : 'The state worker stores records in local files (the default). Set AGENTMEMORY_STATE_BACKEND=redis to use Redis.') : '') +
7280
+ hrow('Viewer feed', esc(streamText), 'live', 'This page\'s own connection. Offline means the numbers here stop changing.') +
7133
7281
  hrow('Uptime', esc(formatUptime(snap.uptimeSeconds)), 'uptime') +
7134
- hrow('Version', esc('v' + (store.version || (r && r.service.version) || '?') + (r ? ' · engine ' + r.service.engineVersion : ''))) +
7135
- hrow('Ports', esc('REST ' + (ports.rest || '?') + ' · streams ' + (ports.streams || '?') + ' · viewer ' + (ports.viewer || 'off'))) +
7136
- '</div>';
7282
+ hrow('Version', esc('v' + (store.version || (r && r.service.version) || '?') + (r ? ' · engine pinned ' + r.service.enginePinnedVersion : ''))) +
7283
+ hrow('Ports', esc('REST ' + (ports.rest || '?') + ' · viewer ' + (ports.viewer || 'off'))) +
7284
+ workersHtml(snap.workers, state.health.engineDetailsOpen) + '</div>';
7137
7285
  }
7138
7286
 
7139
7287
  function legText(leg) {
@@ -7152,7 +7300,7 @@
7152
7300
  html += hrow('Keyword index', esc(fmtNum(idx.bm25Documents) + ' documents'), 'bm25',
7153
7301
  (b ? esc(fmtNum(b.observations) + ' observations + ' + fmtNum(b.memories) + ' memories + ' + fmtNum(b.lessons) + ' lessons. ') : '') + esc(BM25_EXPLAINER));
7154
7302
  html += hrow('Vector index', hasVectors ? esc(fmtNum(idx.vectorDocuments) + ' vectors') : 'off', 'vector',
7155
- hasVectors ? 'Meaning-based search. Each vector is one embedded record.' : 'No embedding provider, so search is keyword only. Set an embedding provider key (for example OPENAI_API_KEY) and restart.');
7303
+ hasVectors ? 'Meaning-based search. Each vector is one embedded record.' : 'No embedding provider, so search is keyword only. Set EMBEDDING_PROVIDER (local runs on-device; openai, voyage, cohere, gemini or openrouter also need that provider\'s key) and restart.');
7156
7304
  var bfState = ri.vectorBackfillState;
7157
7305
  var bfNote = bfState === 'paused' ? 'Paused because the embedding provider did not return vectors. Retried on the next start.' :
7158
7306
  bfState === 'waiting-for-opt-in' ? 'Waiting for AGENTMEMORY_VECTOR_BACKFILL=all and a restart.' :
@@ -7168,17 +7316,18 @@
7168
7316
  'New and removed vectors are written here right away and replayed after a crash, so nothing is embedded twice. Each save empties it.');
7169
7317
  }
7170
7318
  }
7171
- html += hrow('Graph', esc(fmtNum(c.graphNodes) + ' nodes · ' + fmtNum(c.graphEdges) + ' edges'), 'graph', store.graph && store.graph.updatedAt ? 'Snapshot rebuilt ' + esc(timeAgo(store.graph.updatedAt)) + '.' : '');
7319
+ html += hrow('Graph', esc(fmtNum(c.graphNodes) + ' nodes · ' + fmtNum(c.graphEdges) + ' edges'), 'graph', store.graph && store.graph.updatedAt ? 'Graph index updated ' + esc(timeAgo(store.graph.updatedAt)) + '.' : '');
7172
7320
  if (r && r.graphCompaction) html += graphCompactionRowHtml(r.graphCompaction);
7173
7321
  return html + '</div>';
7174
7322
  }
7175
7323
 
7176
7324
  function graphCompactionRowHtml(g) {
7177
7325
  var where = g.phase === 'snapshot' || g.total === null || g.total === undefined ? (g.phase || 'nodes') : g.phase + ' ' + fmtNum(g.processed || 0) + ' of ' + fmtNum(g.total);
7178
- var text = g.state === 'off' ? 'off' : g.state === 'pending' ? 'pending' : g.state === 'running' ? 'running, ' + where : g.state === 'failed' ? 'failed at ' + where : 'done';
7326
+ var text = g.state === 'off' ? 'off' : g.state === 'pending' ? 'pending' : g.state === 'deferred' ? 'deferred' : g.state === 'running' ? 'running, ' + where : g.state === 'failed' ? 'failed at ' + where : 'done';
7179
7327
  var note = 'A one-time pass that trims each graph node and edge to its newest 32 source observations, so stores from older versions stop slowing graph search.';
7180
7328
  if (g.state === 'done') note = fmtNum(g.trimmed || 0) + ' records trimmed, ' + fmtNum(g.idsRemoved || 0) + ' ids removed' + (g.completedAt ? ', finished ' + timeAgo(g.completedAt) : '') + '. It does not run again.';
7181
7329
  else if (g.state === 'running') note += ' It runs in the background, resumes after a restart and stops by itself.';
7330
+ else if (g.state === 'deferred') note = 'Compaction is deferred because it reads complete legacy graph scopes. Run it only during explicit maintenance.';
7182
7331
  else if (g.state === 'off') note = 'Turned off with AGENTMEMORY_GRAPH_COMPACT_ON_BOOT=false. Run it by hand if graph search is slow:';
7183
7332
  else if (g.state === 'failed') note = esc(g.error || 'unknown error') + '. It resumes on the next start. To run it now:';
7184
7333
  var cmd = g.state === 'off' || g.state === 'failed' ? cmdBlock(curlPost('graph/compact', {})) : '';
@@ -7193,7 +7342,7 @@
7193
7342
  html += hrow('LLM provider', noLlm ? '<span class="badge badge-muted">none</span>' : '<span class="badge badge-green">configured</span>', null,
7194
7343
  noLlm ? 'Capture, keyword and vector search, saved memories, lessons and the structural graph all work without one.' : 'Used for summaries, consolidation, typed graph extraction and optional compression.');
7195
7344
  if (noLlm && off.length) {
7196
- html += hrow('Off without an LLM', esc(off.join(', ')), null, esc(LLM_KEY_FIX) + cmdBlock("echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.agentmemory/.env"));
7345
+ html += hrow('Off without an LLM', esc(off.join(', ')), null, esc(LLM_KEY_FIX) + cmdBlock("printf 'AGENTMEMORY_LLM_PROVIDER=anthropic\\nANTHROPIC_API_KEY=sk-ant-...\\n' >> ~/.agentmemory/.env"));
7197
7346
  }
7198
7347
  var emb = (r && r.provider.embeddings) || store.embeddingProvider || 'none';
7199
7348
  html += hrow('Embeddings', esc(emb === 'none' ? 'none (keyword search only)' : emb), 'vector', emb === 'none' ? 'Meaning-based search is off.' : 'The number in brackets is the vector size (dimensions) the provider returns.');
@@ -7312,7 +7461,7 @@
7312
7461
  }
7313
7462
 
7314
7463
  function consolidationWhyEmpty(cs, c) {
7315
- if (!cs.llmConfigured) return { text: 'No LLM provider is configured on this instance, so no session summaries are written and consolidation never runs. Semantic facts and procedures stay at zero.', cmd: "echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.agentmemory/.env" };
7464
+ if (!cs.llmConfigured) return { text: 'No LLM provider is configured on this instance, so no session summaries are written and consolidation never runs. Semantic facts and procedures stay at zero.', cmd: "printf 'AGENTMEMORY_LLM_PROVIDER=anthropic\\nANTHROPIC_API_KEY=sk-ant-...\\n' >> ~/.agentmemory/.env" };
7316
7465
  if (!cs.enabled) return { text: 'CONSOLIDATION_ENABLED is set to false, so consolidation is switched off even though an LLM key exists. Remove that line from ~/.agentmemory/.env (or set it to true) and restart. Find it with:', cmd: 'grep -n CONSOLIDATION_ENABLED ~/.agentmemory/.env' };
7317
7466
  var t = cs.thresholds || {};
7318
7467
  if ((c.semantic || 0) === 0 && cs.summaries < (t.semanticMinSummaries || 5)) return { text: 'Not enough finished sessions yet: semantic facts need ' + (t.semanticMinSummaries || 5) + ' session summaries and there are ' + cs.summaries + '. End sessions normally (the Stop hook does it) so each one gets a summary.', cmd: curlPost('session/end', { sessionId: 'ses_...' }) };
@@ -7401,12 +7550,14 @@
7401
7550
  var snap = store.health || {};
7402
7551
  var idx = store.indexes || {};
7403
7552
  var c = store.counts || {};
7404
- var html = '<div class="inline-note"><strong>Health</strong> ' + help('health') + ' is the live version of the <a class="empty-link" href="' + esc(REST) + '/agentmemory/status" target="_blank" rel="noopener">/agentmemory/status</a> page: the same checks and verdict, pushed over the stream every 30 seconds. Every problem says what to do. Scroll down for how consolidation works.</div>';
7553
+ var html = '<div class="inline-note"><strong>Health</strong> ' + help('health') + ' is the live version of the <a class="empty-link" href="' + esc(REST) + '/agentmemory/status" target="_blank" rel="noopener">/agentmemory/status</a> page: the same checks and verdict, pushed over SSE every 30 seconds. Every problem says what to do. Scroll down for how consolidation works.</div>';
7405
7554
  html += healthVerdictHtml(r);
7406
7555
  html += '<div class="hgrid">' + engineCardHtml(r, snap) + indexCardHtml(r, idx, c) + captureCardHtml(r) + llmCardHtml(r) + flagsCardHtml(r) + processCardHtml(snap) + recordsCardHtml(c) + '</div>';
7407
7556
  html += consolidationPanelHtml(store.consolidationStatus, c);
7408
7557
  html += functionsCardHtml(r);
7409
7558
  setViewHtml(el, html);
7559
+ var details = document.getElementById('health-engine-details');
7560
+ if (details) details.ontoggle = function() { state.health.engineDetailsOpen = details.open; };
7410
7561
  var reveal = state.health.reveal && document.getElementById('health-' + state.health.reveal);
7411
7562
  if (reveal) {
7412
7563
  state.health.reveal = '';
@@ -7611,14 +7762,12 @@
7611
7762
 
7612
7763
  var wsReconnectTimer = null;
7613
7764
  var wsRetries = 0;
7614
- var directFailed = true;
7615
- var directFailures = 0;
7616
7765
  var wsHasConnected = false;
7617
7766
  var wsOpenedAt = null;
7618
- var DIRECT_FAILURE_THRESHOLD = 2;
7619
7767
  var OFFLINE_AFTER_RETRIES = 3;
7620
7768
  var HEALTH_STALE_MS = 90000;
7621
7769
  var conn = { state: 'connecting', nextRetryAt: 0, lostAt: 0 };
7770
+ var liveAbortController = null;
7622
7771
 
7623
7772
  function reconnectDelay(attempt) {
7624
7773
  var n = Math.max(1, attempt || 1);
@@ -7704,90 +7853,110 @@
7704
7853
  function reconnectNow() {
7705
7854
  if (wsReconnectTimer) clearTimeout(wsReconnectTimer);
7706
7855
  wsReconnectTimer = null;
7707
- directFailures = 0;
7708
- directFailed = true;
7709
7856
  conn.nextRetryAt = 0;
7710
- var ws = state.ws;
7711
- if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
7857
+ if (liveAbortController) {
7858
+ liveAbortController.abort();
7859
+ liveAbortController = null;
7860
+ }
7712
7861
  connectWs();
7713
7862
  }
7714
7863
 
7715
- var WS_CONNECT_TIMEOUT_MS = 5000;
7864
+ function parseSseFrame(frame) {
7865
+ var id = '';
7866
+ var type = '';
7867
+ var data = [];
7868
+ frame.split(/\r?\n/).forEach(function(line) {
7869
+ if (!line || line.charAt(0) === ':') return;
7870
+ var colon = line.indexOf(':');
7871
+ var field = colon < 0 ? line : line.slice(0, colon);
7872
+ var value = colon < 0 ? '' : line.slice(colon + 1).replace(/^ /, '');
7873
+ if (field === 'id') id = value;
7874
+ else if (field === 'event') type = value;
7875
+ else if (field === 'data') data.push(value);
7876
+ });
7877
+ if (!type || !data.length) return null;
7878
+ try { return { id: id, type: type, data: JSON.parse(data.join('\n')) }; } catch (_) { return null; }
7879
+ }
7880
+
7881
+ function consumeSseChunk(buffer, chunk) {
7882
+ buffer += chunk;
7883
+ var frames = [];
7884
+ var match;
7885
+ while ((match = /\r?\n\r?\n/.exec(buffer))) {
7886
+ var frame = buffer.slice(0, match.index);
7887
+ buffer = buffer.slice(match.index + match[0].length);
7888
+ var parsed = parseSseFrame(frame);
7889
+ if (parsed) frames.push(parsed);
7890
+ }
7891
+ return { buffer: buffer, frames: frames };
7892
+ }
7716
7893
 
7717
- function connectWs() {
7894
+ async function connectWs() {
7718
7895
  wsReconnectTimer = null;
7719
7896
  conn.nextRetryAt = 0;
7720
- var useDirect = !directFailed;
7721
- var ws;
7897
+ var controller = new AbortController();
7898
+ liveAbortController = controller;
7722
7899
  try {
7723
- ws = new WebSocket(useDirect ? WS_DIRECT_URL : WS_URL);
7724
- ws.__direct = useDirect;
7725
- } catch (_) {
7726
- scheduleReconnect();
7727
- return;
7728
- }
7729
- var connectTimer = setTimeout(function() {
7730
- if (ws.readyState === WebSocket.CONNECTING) {
7731
- try { ws.close(); } catch {}
7900
+ var headers = { 'Cache-Control': 'no-cache', Accept: 'text/event-stream' };
7901
+ var viewerToken = getViewerToken();
7902
+ if (viewerToken) headers.Authorization = 'Bearer ' + viewerToken;
7903
+ var response = await fetch(LIVE_EVENTS_URL, { headers: headers, signal: controller.signal });
7904
+ if (!response.ok || !response.body) {
7905
+ if (response.status === 401) showViewerAuthPrompt();
7906
+ throw new Error('SSE request failed: ' + response.status);
7732
7907
  }
7733
- }, WS_CONNECT_TIMEOUT_MS);
7734
- ws.onopen = function() {
7735
- clearTimeout(connectTimer);
7736
- if (state.ws !== ws) return;
7908
+ hideViewerAuthPrompt();
7737
7909
  wsRetries = 0;
7738
- if (!ws.__direct) {
7739
- ws.send(JSON.stringify({
7740
- type: 'join',
7741
- data: {
7742
- subscriptionId: 'viewer-' + Date.now(),
7743
- streamName: 'mem-live',
7744
- groupId: 'viewer'
7745
- }
7746
- }));
7747
- }
7748
7910
  var firstConnect = !wsHasConnected;
7749
7911
  wsHasConnected = true;
7750
7912
  wsOpenedAt = Date.now();
7751
7913
  setConnection('live');
7752
7914
  updateFooter();
7753
- resyncAfterConnect(firstConnect);
7754
- };
7755
- ws.onmessage = function(e) {
7756
- if (state.ws !== ws) return;
7757
- if (!ws.__usable) {
7758
- ws.__usable = true;
7759
- directFailures = 0;
7915
+ await resyncAfterConnect(firstConnect);
7916
+ var reader = response.body.getReader();
7917
+ var decoder = new TextDecoder();
7918
+ var buffer = '';
7919
+ while (true) {
7920
+ var result = await reader.read();
7921
+ if (result.done) break;
7922
+ buffer += decoder.decode(result.value, { stream: true });
7923
+ var parsed = consumeSseChunk(buffer, '');
7924
+ buffer = parsed.buffer;
7925
+ parsed.frames.forEach(function(frame) {
7926
+ store.lastEventAt = Date.now();
7927
+ if (frame.type === 'raw_observation') {
7928
+ var obs = frame.data && frame.data.data && (frame.data.data.observation || frame.data.data);
7929
+ if (looksLikeObservation(obs)) routeWsMessage({ observation: obs, fresh: true });
7930
+ } else if (frame.type === 'compressed_observation') {
7931
+ var compressed = frame.data && frame.data.data && (frame.data.data.observation || frame.data.data);
7932
+ if (looksLikeObservation(compressed)) routeWsMessage({ observation: compressed, fresh: true });
7933
+ } else {
7934
+ handleLiveEvent(frame.type, frame.data && frame.data.data || {});
7935
+ }
7936
+ });
7760
7937
  }
7761
- store.lastEventAt = Date.now();
7762
- try {
7763
- var msg = JSON.parse(e.data);
7764
- if (msg.type === 'stream' && msg.event) {
7765
- handleStreamEvent(msg);
7766
- } else if (msg.event_type && msg.data) {
7767
- handleStreamEvent({ event: { type: 'create', data: msg.data, event_type: msg.event_type } });
7768
- }
7769
- } catch {}
7770
- };
7771
- ws.onclose = function() {
7772
- clearTimeout(connectTimer);
7773
- if (state.ws !== ws) return;
7774
- wsOpenedAt = null;
7775
- updateFooter();
7776
- directFailures += 1;
7777
- if (directFailures >= DIRECT_FAILURE_THRESHOLD) {
7778
- directFailed = ws.__direct;
7779
- directFailures = 0;
7938
+ if (!controller.signal.aborted) throw new Error('SSE stream ended');
7939
+ } catch (err) {
7940
+ if (!controller.signal.aborted) console.warn('[viewer] SSE connection failed:', err);
7941
+ } finally {
7942
+ if (liveAbortController === controller) liveAbortController = null;
7943
+ if (!controller.signal.aborted) {
7944
+ wsOpenedAt = null;
7945
+ updateFooter();
7946
+ scheduleReconnect();
7780
7947
  }
7781
- scheduleReconnect();
7782
- };
7783
- ws.onerror = function() {
7784
- if (state.ws !== ws) return;
7785
- try { ws.close(); } catch {}
7786
- };
7787
- state.ws = ws;
7948
+ }
7788
7949
  }
7789
7950
 
7790
7951
  var resyncing = null;
7952
+ function resetLoadedKindForReload(kind) {
7953
+ store.loaded[kind] = false;
7954
+ if (kind === 'action') {
7955
+ state.actions.detailAttemptedId = '';
7956
+ state.actions.detailRequestFailedId = '';
7957
+ }
7958
+ }
7959
+
7791
7960
  async function resyncAfterConnect(firstConnect) {
7792
7961
  openBootGate();
7793
7962
  if (resyncing) return resyncing;
@@ -7795,9 +7964,10 @@
7795
7964
  resyncing = (async function() {
7796
7965
  try {
7797
7966
  await loadSnapshot();
7967
+ await loadRecentObservations();
7798
7968
  scheduleHealthStaleCheck();
7799
7969
  if (!firstConnect || reloadKinds.length) {
7800
- reloadKinds.forEach(function(k) { store.loaded[k] = false; });
7970
+ reloadKinds.forEach(resetLoadedKindForReload);
7801
7971
  await Promise.all(reloadKinds.map(ensureLoaded));
7802
7972
  state.graph.dirty = state.graph.loaded;
7803
7973
  state.activity.loaded = false;
@@ -7814,6 +7984,24 @@
7814
7984
  }
7815
7985
 
7816
7986
  var LIVE_BUFFER_MAX = 200;
7987
+ var ACTIVITY_SESSIONS = 10;
7988
+ async function loadRecentObservations() {
7989
+ var sessionResult = await apiGet('sessions?limit=' + ACTIVITY_SESSIONS);
7990
+ var sessions = sortByTimeDesc((sessionResult && sessionResult.sessions) || [], ['startedAt']).filter(function(x) { return sessionId(x); }).slice(0, ACTIVITY_SESSIONS);
7991
+ mergeSessionRows(sessions);
7992
+ var results = await Promise.all(sessions.map(function(x) {
7993
+ return apiGet('observations?sessionId=' + encodeURIComponent(sessionId(x)) + '&limit=100');
7994
+ }));
7995
+ var seen = {};
7996
+ results.forEach(function(result) {
7997
+ ((result && result.observations) || []).slice(-LIVE_BUFFER_MAX).forEach(function(observation) {
7998
+ if (!observation || !observation.id || seen[observation.id]) return;
7999
+ seen[observation.id] = true;
8000
+ routeWsMessage({ observation: observation });
8001
+ });
8002
+ });
8003
+ }
8004
+
7817
8005
 
7818
8006
  function looksLikeObservation(obj) {
7819
8007
  return !!(obj && typeof obj === 'object' && obj.id && obj.timestamp);
@@ -8055,15 +8243,6 @@
8055
8243
  if (looksLikeObservation(observation)) {
8056
8244
  routeWsMessage({ observation: observation, fresh: true });
8057
8245
  }
8058
- } else if (evt.type === 'sync') {
8059
- var items = Array.isArray(evt.data) ? evt.data.slice(-LIVE_BUFFER_MAX) : [];
8060
- items.forEach(function(item) {
8061
- var payload = item.data || item;
8062
- observation = payload.observation || payload;
8063
- if (looksLikeObservation(observation)) {
8064
- routeWsMessage({ observation: observation });
8065
- }
8066
- });
8067
8246
  }
8068
8247
  }
8069
8248
 
@@ -8159,9 +8338,9 @@
8159
8338
  items.push({
8160
8339
  on: false,
8161
8340
  title: 'No LLM provider',
8162
- key: 'ANTHROPIC_API_KEY',
8163
- desc: 'Compression, summaries, consolidation and graph extraction stay off until an LLM key is set. Memories, search and sessions still work.',
8164
- enable: 'export ANTHROPIC_API_KEY=sk-ant-...\n# then restart: npx @agentmemory/agentmemory',
8341
+ key: 'AGENTMEMORY_LLM_PROVIDER',
8342
+ desc: 'Compression, summaries, consolidation and graph extraction stay off until you choose an LLM provider. Memories, search and sessions still work.',
8343
+ enable: 'export AGENTMEMORY_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...\n# then restart: npx @agentmemory/agentmemory',
8165
8344
  affects: ['Overview', 'Graph', 'Crystals']
8166
8345
  });
8167
8346
  }
@@ -8169,9 +8348,9 @@
8169
8348
  items.push({
8170
8349
  on: false,
8171
8350
  title: 'No embedding provider',
8172
- key: 'OPENAI_API_KEY',
8351
+ key: 'EMBEDDING_PROVIDER',
8173
8352
  desc: 'Search matches exact keywords only. With embeddings it also finds memories by meaning.',
8174
- enable: 'export OPENAI_API_KEY=sk-...\n# or VOYAGE_API_KEY, COHERE_API_KEY, OLLAMA_HOST',
8353
+ enable: 'export EMBEDDING_PROVIDER=local\n# or EMBEDDING_PROVIDER=openai with OPENAI_API_KEY (also voyage, cohere, gemini, openrouter)',
8175
8354
  affects: ['Memories', 'Health']
8176
8355
  });
8177
8356
  }
@@ -8226,7 +8405,7 @@
8226
8405
  var sync = document.getElementById('footer-sync');
8227
8406
  if (sync) {
8228
8407
  var syncText = store.snapshotAt ? 'full sync ' + new Date(store.snapshotAt).toLocaleTimeString() : 'waiting for first sync';
8229
- if (store.snapshotAt) syncText += wsOpenedAt ? ', streaming since ' + new Date(wsOpenedAt).toLocaleTimeString() : ', stream reconnecting';
8408
+ if (store.snapshotAt) syncText += wsOpenedAt ? ', live via SSE since ' + new Date(wsOpenedAt).toLocaleTimeString() : ', SSE reconnecting';
8230
8409
  sync.textContent = syncText;
8231
8410
  }
8232
8411
  var fbEl = document.getElementById('footer-feedback');
@@ -8323,7 +8502,7 @@
8323
8502
  var modal = document.getElementById('modal');
8324
8503
  var overlay = document.getElementById('modal-overlay');
8325
8504
  var html = '<h3>How to read this viewer</h3>';
8326
- html += '<p style="margin-bottom:8px;">Everything on these pages updates live over one streaming connection; nothing refreshes on a timer. Click any <span class="help" style="cursor:default;">?</span> for a short explanation, or use the keys below.</p>';
8505
+ html += '<p style="margin-bottom:8px;">Everything on these pages updates live over one SSE connection; nothing refreshes on a timer. Click any <span class="help" style="cursor:default;">?</span> for a short explanation, or use the keys below.</p>';
8327
8506
  html += '<div class="kbd-list">' +
8328
8507
  '<div><kbd>/</kbd> focus search on this page</div>' +
8329
8508
  '<div><kbd>?</kbd> open this help</div>' +
@@ -8894,16 +9073,7 @@
8894
9073
 
8895
9074
  var bootRoute = parseRoute(window.location.hash);
8896
9075
  switchTab(bootRoute.tab, { replaceRoute: true, skipRoute: !!bootRoute.obs && !bootRoute.redirect, id: bootRoute.id, obs: bootRoute.obs });
8897
- (async function initWs() {
8898
- try {
8899
- var live = await api('livez', { signal: AbortSignal.timeout(5000) });
8900
- if (live && typeof live.streamsPort === 'number' && live.streamsPort > 0) {
8901
- WS_URL = wsProto + '//' + window.location.hostname + ':' + live.streamsPort;
8902
- WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer';
8903
- }
8904
- } catch (_) {}
8905
- connectWs();
8906
- })();
9076
+ connectWs();
8907
9077
  setTimeout(function() {
8908
9078
  if (wsHasConnected) return;
8909
9079
  openBootGate();
@@ -8913,8 +9083,7 @@
8913
9083
 
8914
9084
  document.addEventListener('visibilitychange', function() {
8915
9085
  if (document.visibilityState !== 'visible') return;
8916
- var ws = state.ws;
8917
- if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
9086
+ if (liveAbortController) return;
8918
9087
  wsRetries = 0;
8919
9088
  reconnectNow();
8920
9089
  });