@theihtisham/agent-shadow-brain 7.2.0 → 7.22.0

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 (438) hide show
  1. package/CHANGELOG.md +478 -429
  2. package/README.md +198 -68
  3. package/dist/activity-cli.d.ts +3 -0
  4. package/dist/activity-cli.d.ts.map +1 -0
  5. package/dist/activity-cli.js +78 -0
  6. package/dist/activity-cli.js.map +1 -0
  7. package/dist/adapters/index.d.ts +2 -1
  8. package/dist/adapters/index.d.ts.map +1 -1
  9. package/dist/adapters/index.js +3 -2
  10. package/dist/adapters/index.js.map +1 -1
  11. package/dist/brain/activity-journal.d.ts +82 -0
  12. package/dist/brain/activity-journal.d.ts.map +1 -0
  13. package/dist/brain/activity-journal.js +315 -0
  14. package/dist/brain/activity-journal.js.map +1 -0
  15. package/dist/brain/activity-reporting.d.ts +14 -0
  16. package/dist/brain/activity-reporting.d.ts.map +1 -0
  17. package/dist/brain/activity-reporting.js +17 -0
  18. package/dist/brain/activity-reporting.js.map +1 -0
  19. package/dist/brain/auto-update.js +1 -1
  20. package/dist/brain/auto-update.js.map +1 -1
  21. package/dist/brain/brain-portability.js +1 -1
  22. package/dist/brain/brain-portability.js.map +1 -1
  23. package/dist/brain/client-config.d.ts +19 -0
  24. package/dist/brain/client-config.d.ts.map +1 -0
  25. package/dist/brain/client-config.js +40 -0
  26. package/dist/brain/client-config.js.map +1 -0
  27. package/dist/brain/client-integrations.d.ts +63 -0
  28. package/dist/brain/client-integrations.d.ts.map +1 -0
  29. package/dist/brain/client-integrations.js +79 -0
  30. package/dist/brain/client-integrations.js.map +1 -0
  31. package/dist/brain/context-assembly.d.ts +41 -5
  32. package/dist/brain/context-assembly.d.ts.map +1 -1
  33. package/dist/brain/context-assembly.js +366 -35
  34. package/dist/brain/context-assembly.js.map +1 -1
  35. package/dist/brain/context-efficiency.d.ts +48 -0
  36. package/dist/brain/context-efficiency.d.ts.map +1 -0
  37. package/dist/brain/context-efficiency.js +34 -0
  38. package/dist/brain/context-efficiency.js.map +1 -0
  39. package/dist/brain/context-request.d.ts +4 -0
  40. package/dist/brain/context-request.d.ts.map +1 -0
  41. package/dist/brain/context-request.js +24 -0
  42. package/dist/brain/context-request.js.map +1 -0
  43. package/dist/brain/context-retrieval.d.ts +88 -0
  44. package/dist/brain/context-retrieval.d.ts.map +1 -0
  45. package/dist/brain/context-retrieval.js +301 -0
  46. package/dist/brain/context-retrieval.js.map +1 -0
  47. package/dist/brain/feature-settings.d.ts +10 -0
  48. package/dist/brain/feature-settings.d.ts.map +1 -1
  49. package/dist/brain/feature-settings.js +18 -2
  50. package/dist/brain/feature-settings.js.map +1 -1
  51. package/dist/brain/file-identity.d.ts +4 -0
  52. package/dist/brain/file-identity.d.ts.map +1 -0
  53. package/dist/brain/file-identity.js +27 -0
  54. package/dist/brain/file-identity.js.map +1 -0
  55. package/dist/brain/global-brain.d.ts +18 -2
  56. package/dist/brain/global-brain.d.ts.map +1 -1
  57. package/dist/brain/global-brain.js +75 -7
  58. package/dist/brain/global-brain.js.map +1 -1
  59. package/dist/brain/managed-project-skills.d.ts +48 -0
  60. package/dist/brain/managed-project-skills.d.ts.map +1 -0
  61. package/dist/brain/managed-project-skills.js +230 -0
  62. package/dist/brain/managed-project-skills.js.map +1 -0
  63. package/dist/brain/mcp-presence.d.ts +50 -0
  64. package/dist/brain/mcp-presence.d.ts.map +1 -0
  65. package/dist/brain/mcp-presence.js +214 -0
  66. package/dist/brain/mcp-presence.js.map +1 -0
  67. package/dist/brain/mcp-server.d.ts +40 -1
  68. package/dist/brain/mcp-server.d.ts.map +1 -1
  69. package/dist/brain/mcp-server.js +506 -52
  70. package/dist/brain/mcp-server.js.map +1 -1
  71. package/dist/brain/mcp-stdio.d.ts +12 -0
  72. package/dist/brain/mcp-stdio.d.ts.map +1 -0
  73. package/dist/brain/mcp-stdio.js +158 -0
  74. package/dist/brain/mcp-stdio.js.map +1 -0
  75. package/dist/brain/memory-annotations.d.ts +99 -0
  76. package/dist/brain/memory-annotations.d.ts.map +1 -0
  77. package/dist/brain/memory-annotations.js +402 -0
  78. package/dist/brain/memory-annotations.js.map +1 -0
  79. package/dist/brain/memory-graph.d.ts.map +1 -1
  80. package/dist/brain/memory-graph.js +12 -23
  81. package/dist/brain/memory-graph.js.map +1 -1
  82. package/dist/brain/memory-inspection.d.ts +73 -0
  83. package/dist/brain/memory-inspection.d.ts.map +1 -0
  84. package/dist/brain/memory-inspection.js +131 -0
  85. package/dist/brain/memory-inspection.js.map +1 -0
  86. package/dist/brain/memory-mcp-content.d.ts +46 -0
  87. package/dist/brain/memory-mcp-content.d.ts.map +1 -0
  88. package/dist/brain/memory-mcp-content.js +95 -0
  89. package/dist/brain/memory-mcp-content.js.map +1 -0
  90. package/dist/brain/memory-sources.d.ts +8 -0
  91. package/dist/brain/memory-sources.d.ts.map +1 -0
  92. package/dist/brain/memory-sources.js +27 -0
  93. package/dist/brain/memory-sources.js.map +1 -0
  94. package/dist/brain/model-registry.d.ts +4 -5
  95. package/dist/brain/model-registry.d.ts.map +1 -1
  96. package/dist/brain/model-registry.js +22 -207
  97. package/dist/brain/model-registry.js.map +1 -1
  98. package/dist/brain/model-usage.d.ts +66 -0
  99. package/dist/brain/model-usage.d.ts.map +1 -0
  100. package/dist/brain/model-usage.js +246 -0
  101. package/dist/brain/model-usage.js.map +1 -0
  102. package/dist/brain/native-codex.d.ts +105 -0
  103. package/dist/brain/native-codex.d.ts.map +1 -0
  104. package/dist/brain/native-codex.js +292 -0
  105. package/dist/brain/native-codex.js.map +1 -0
  106. package/dist/brain/native-session-import.d.ts +56 -0
  107. package/dist/brain/native-session-import.d.ts.map +1 -0
  108. package/dist/brain/native-session-import.js +307 -0
  109. package/dist/brain/native-session-import.js.map +1 -0
  110. package/dist/brain/orchestrator.js +1 -1
  111. package/dist/brain/orchestrator.js.map +1 -1
  112. package/dist/brain/project-feedback.d.ts +63 -0
  113. package/dist/brain/project-feedback.d.ts.map +1 -0
  114. package/dist/brain/project-feedback.js +300 -0
  115. package/dist/brain/project-feedback.js.map +1 -0
  116. package/dist/brain/project-hub-contract.d.ts +332 -0
  117. package/dist/brain/project-hub-contract.d.ts.map +1 -0
  118. package/dist/brain/project-hub-contract.js +57 -0
  119. package/dist/brain/project-hub-contract.js.map +1 -0
  120. package/dist/brain/project-hub-mcp.d.ts +182 -0
  121. package/dist/brain/project-hub-mcp.d.ts.map +1 -0
  122. package/dist/brain/project-hub-mcp.js +46 -0
  123. package/dist/brain/project-hub-mcp.js.map +1 -0
  124. package/dist/brain/project-hub-pdf-worker.d.ts +13 -0
  125. package/dist/brain/project-hub-pdf-worker.d.ts.map +1 -0
  126. package/dist/brain/project-hub-pdf-worker.js +84 -0
  127. package/dist/brain/project-hub-pdf-worker.js.map +1 -0
  128. package/dist/brain/project-hub-store.d.ts +50 -0
  129. package/dist/brain/project-hub-store.d.ts.map +1 -0
  130. package/dist/brain/project-hub-store.js +257 -0
  131. package/dist/brain/project-hub-store.js.map +1 -0
  132. package/dist/brain/project-hub.d.ts +132 -0
  133. package/dist/brain/project-hub.d.ts.map +1 -0
  134. package/dist/brain/project-hub.js +403 -0
  135. package/dist/brain/project-hub.js.map +1 -0
  136. package/dist/brain/project-kit-inspection.d.ts +28 -0
  137. package/dist/brain/project-kit-inspection.d.ts.map +1 -0
  138. package/dist/brain/project-kit-inspection.js +200 -0
  139. package/dist/brain/project-kit-inspection.js.map +1 -0
  140. package/dist/brain/project-kit-paths.d.ts +13 -0
  141. package/dist/brain/project-kit-paths.d.ts.map +1 -0
  142. package/dist/brain/project-kit-paths.js +141 -0
  143. package/dist/brain/project-kit-paths.js.map +1 -0
  144. package/dist/brain/project-kit.d.ts +51 -0
  145. package/dist/brain/project-kit.d.ts.map +1 -0
  146. package/dist/brain/project-kit.js +319 -0
  147. package/dist/brain/project-kit.js.map +1 -0
  148. package/dist/brain/project-memory.d.ts +38 -0
  149. package/dist/brain/project-memory.d.ts.map +1 -0
  150. package/dist/brain/project-memory.js +76 -0
  151. package/dist/brain/project-memory.js.map +1 -0
  152. package/dist/brain/project-policy-delivery.d.ts +8 -0
  153. package/dist/brain/project-policy-delivery.d.ts.map +1 -0
  154. package/dist/brain/project-policy-delivery.js +38 -0
  155. package/dist/brain/project-policy-delivery.js.map +1 -0
  156. package/dist/brain/project-policy.d.ts +88 -0
  157. package/dist/brain/project-policy.d.ts.map +1 -0
  158. package/dist/brain/project-policy.js +267 -0
  159. package/dist/brain/project-policy.js.map +1 -0
  160. package/dist/brain/project-session-contract.d.ts +180 -0
  161. package/dist/brain/project-session-contract.d.ts.map +1 -0
  162. package/dist/brain/project-session-contract.js +50 -0
  163. package/dist/brain/project-session-contract.js.map +1 -0
  164. package/dist/brain/project-session-mcp.d.ts +145 -0
  165. package/dist/brain/project-session-mcp.d.ts.map +1 -0
  166. package/dist/brain/project-session-mcp.js +58 -0
  167. package/dist/brain/project-session-mcp.js.map +1 -0
  168. package/dist/brain/project-sessions.d.ts +97 -0
  169. package/dist/brain/project-sessions.d.ts.map +1 -0
  170. package/dist/brain/project-sessions.js +223 -0
  171. package/dist/brain/project-sessions.js.map +1 -0
  172. package/dist/brain/project-source-contract.d.ts +109 -0
  173. package/dist/brain/project-source-contract.d.ts.map +1 -0
  174. package/dist/brain/project-source-contract.js +2 -0
  175. package/dist/brain/project-source-contract.js.map +1 -0
  176. package/dist/brain/project-source-intake.d.ts +26 -0
  177. package/dist/brain/project-source-intake.d.ts.map +1 -0
  178. package/dist/brain/project-source-intake.js +457 -0
  179. package/dist/brain/project-source-intake.js.map +1 -0
  180. package/dist/brain/subconscious.d.ts +3 -1
  181. package/dist/brain/subconscious.d.ts.map +1 -1
  182. package/dist/brain/subconscious.js +8 -3
  183. package/dist/brain/subconscious.js.map +1 -1
  184. package/dist/brain/workflow-skills.d.ts +5 -5
  185. package/dist/brain/workflow-skills.d.ts.map +1 -1
  186. package/dist/brain/workflow-skills.js +3 -2
  187. package/dist/brain/workflow-skills.js.map +1 -1
  188. package/dist/cli.js +37 -3
  189. package/dist/cli.js.map +1 -1
  190. package/dist/context-cli.d.ts.map +1 -1
  191. package/dist/context-cli.js +49 -7
  192. package/dist/context-cli.js.map +1 -1
  193. package/dist/curation-cli.d.ts +4 -0
  194. package/dist/curation-cli.d.ts.map +1 -0
  195. package/dist/curation-cli.js +118 -0
  196. package/dist/curation-cli.js.map +1 -0
  197. package/dist/dashboard/brain-hub-actions.d.ts +2 -0
  198. package/dist/dashboard/brain-hub-actions.d.ts.map +1 -0
  199. package/dist/dashboard/brain-hub-actions.js +234 -0
  200. package/dist/dashboard/brain-hub-actions.js.map +1 -0
  201. package/dist/dashboard/brain-hub-agent-dock.d.ts +2 -0
  202. package/dist/dashboard/brain-hub-agent-dock.d.ts.map +1 -0
  203. package/dist/dashboard/brain-hub-agent-dock.js +255 -0
  204. package/dist/dashboard/brain-hub-agent-dock.js.map +1 -0
  205. package/dist/dashboard/brain-hub-brand-icons.d.ts +2 -0
  206. package/dist/dashboard/brain-hub-brand-icons.d.ts.map +1 -0
  207. package/dist/dashboard/brain-hub-brand-icons.js +9 -0
  208. package/dist/dashboard/brain-hub-brand-icons.js.map +1 -0
  209. package/dist/dashboard/brain-hub-client.d.ts +2 -0
  210. package/dist/dashboard/brain-hub-client.d.ts.map +1 -0
  211. package/dist/dashboard/brain-hub-client.js +435 -0
  212. package/dist/dashboard/brain-hub-client.js.map +1 -0
  213. package/dist/dashboard/brain-hub-context.d.ts +2 -0
  214. package/dist/dashboard/brain-hub-context.d.ts.map +1 -0
  215. package/dist/dashboard/brain-hub-context.js +37 -0
  216. package/dist/dashboard/brain-hub-context.js.map +1 -0
  217. package/dist/dashboard/brain-hub-controls.d.ts +2 -0
  218. package/dist/dashboard/brain-hub-controls.d.ts.map +1 -0
  219. package/dist/dashboard/brain-hub-controls.js +63 -0
  220. package/dist/dashboard/brain-hub-controls.js.map +1 -0
  221. package/dist/dashboard/brain-hub-exchanges.d.ts +2 -0
  222. package/dist/dashboard/brain-hub-exchanges.d.ts.map +1 -0
  223. package/dist/dashboard/brain-hub-exchanges.js +82 -0
  224. package/dist/dashboard/brain-hub-exchanges.js.map +1 -0
  225. package/dist/dashboard/brain-hub-graph.d.ts +2 -0
  226. package/dist/dashboard/brain-hub-graph.d.ts.map +1 -0
  227. package/dist/dashboard/brain-hub-graph.js +338 -0
  228. package/dist/dashboard/brain-hub-graph.js.map +1 -0
  229. package/dist/dashboard/brain-hub-html.d.ts +2 -0
  230. package/dist/dashboard/brain-hub-html.d.ts.map +1 -0
  231. package/dist/dashboard/brain-hub-html.js +85 -0
  232. package/dist/dashboard/brain-hub-html.js.map +1 -0
  233. package/dist/dashboard/brain-hub-knowledge.d.ts +2 -0
  234. package/dist/dashboard/brain-hub-knowledge.d.ts.map +1 -0
  235. package/dist/dashboard/brain-hub-knowledge.js +33 -0
  236. package/dist/dashboard/brain-hub-knowledge.js.map +1 -0
  237. package/dist/dashboard/brain-hub-live.d.ts +2 -0
  238. package/dist/dashboard/brain-hub-live.d.ts.map +1 -0
  239. package/dist/dashboard/brain-hub-live.js +151 -0
  240. package/dist/dashboard/brain-hub-live.js.map +1 -0
  241. package/dist/dashboard/brain-hub-managed-skills.d.ts +2 -0
  242. package/dist/dashboard/brain-hub-managed-skills.d.ts.map +1 -0
  243. package/dist/dashboard/brain-hub-managed-skills.js +20 -0
  244. package/dist/dashboard/brain-hub-managed-skills.js.map +1 -0
  245. package/dist/dashboard/brain-hub-native.d.ts +2 -0
  246. package/dist/dashboard/brain-hub-native.d.ts.map +1 -0
  247. package/dist/dashboard/brain-hub-native.js +44 -0
  248. package/dist/dashboard/brain-hub-native.js.map +1 -0
  249. package/dist/dashboard/brain-hub-project-views.d.ts +2 -0
  250. package/dist/dashboard/brain-hub-project-views.d.ts.map +1 -0
  251. package/dist/dashboard/brain-hub-project-views.js +80 -0
  252. package/dist/dashboard/brain-hub-project-views.js.map +1 -0
  253. package/dist/dashboard/brain-hub-sessions.d.ts +2 -0
  254. package/dist/dashboard/brain-hub-sessions.d.ts.map +1 -0
  255. package/dist/dashboard/brain-hub-sessions.js +40 -0
  256. package/dist/dashboard/brain-hub-sessions.js.map +1 -0
  257. package/dist/dashboard/brain-hub-usage.d.ts +2 -0
  258. package/dist/dashboard/brain-hub-usage.d.ts.map +1 -0
  259. package/dist/dashboard/brain-hub-usage.js +28 -0
  260. package/dist/dashboard/brain-hub-usage.js.map +1 -0
  261. package/dist/dashboard/brain-hub-views.d.ts +2 -0
  262. package/dist/dashboard/brain-hub-views.d.ts.map +1 -0
  263. package/dist/dashboard/brain-hub-views.js +155 -0
  264. package/dist/dashboard/brain-hub-views.js.map +1 -0
  265. package/dist/dashboard/demo-workspace.d.ts +11 -0
  266. package/dist/dashboard/demo-workspace.d.ts.map +1 -0
  267. package/dist/dashboard/demo-workspace.js +178 -0
  268. package/dist/dashboard/demo-workspace.js.map +1 -0
  269. package/dist/dashboard/live-workspace-html.d.ts +2 -0
  270. package/dist/dashboard/live-workspace-html.d.ts.map +1 -0
  271. package/dist/dashboard/live-workspace-html.js +1042 -0
  272. package/dist/dashboard/live-workspace-html.js.map +1 -0
  273. package/dist/dashboard/managed-project-skills-api.d.ts +29 -0
  274. package/dist/dashboard/managed-project-skills-api.d.ts.map +1 -0
  275. package/dist/dashboard/managed-project-skills-api.js +28 -0
  276. package/dist/dashboard/managed-project-skills-api.js.map +1 -0
  277. package/dist/dashboard/native-codex-api.d.ts +83 -0
  278. package/dist/dashboard/native-codex-api.d.ts.map +1 -0
  279. package/dist/dashboard/native-codex-api.js +91 -0
  280. package/dist/dashboard/native-codex-api.js.map +1 -0
  281. package/dist/dashboard/project-feedback-html.d.ts +5 -0
  282. package/dist/dashboard/project-feedback-html.d.ts.map +1 -0
  283. package/dist/dashboard/project-feedback-html.js +216 -0
  284. package/dist/dashboard/project-feedback-html.js.map +1 -0
  285. package/dist/dashboard/project-file-watch.d.ts +46 -0
  286. package/dist/dashboard/project-file-watch.d.ts.map +1 -0
  287. package/dist/dashboard/project-file-watch.js +221 -0
  288. package/dist/dashboard/project-file-watch.js.map +1 -0
  289. package/dist/dashboard/project-hub-api.d.ts +5 -0
  290. package/dist/dashboard/project-hub-api.d.ts.map +1 -0
  291. package/dist/dashboard/project-hub-api.js +121 -0
  292. package/dist/dashboard/project-hub-api.js.map +1 -0
  293. package/dist/dashboard/project-kit-html.d.ts +4 -0
  294. package/dist/dashboard/project-kit-html.d.ts.map +1 -0
  295. package/dist/dashboard/project-kit-html.js +359 -0
  296. package/dist/dashboard/project-kit-html.js.map +1 -0
  297. package/dist/dashboard/project-live-stream.d.ts +33 -0
  298. package/dist/dashboard/project-live-stream.d.ts.map +1 -0
  299. package/dist/dashboard/project-live-stream.js +233 -0
  300. package/dist/dashboard/project-live-stream.js.map +1 -0
  301. package/dist/dashboard/project-policy-api.d.ts +37 -0
  302. package/dist/dashboard/project-policy-api.d.ts.map +1 -0
  303. package/dist/dashboard/project-policy-api.js +31 -0
  304. package/dist/dashboard/project-policy-api.js.map +1 -0
  305. package/dist/dashboard/project-session-api.d.ts +28 -0
  306. package/dist/dashboard/project-session-api.d.ts.map +1 -0
  307. package/dist/dashboard/project-session-api.js +84 -0
  308. package/dist/dashboard/project-session-api.js.map +1 -0
  309. package/dist/dashboard/project-source-api.d.ts +11 -0
  310. package/dist/dashboard/project-source-api.d.ts.map +1 -0
  311. package/dist/dashboard/project-source-api.js +67 -0
  312. package/dist/dashboard/project-source-api.js.map +1 -0
  313. package/dist/dashboard/project-workspace.d.ts +16 -0
  314. package/dist/dashboard/project-workspace.d.ts.map +1 -0
  315. package/dist/dashboard/project-workspace.js +124 -0
  316. package/dist/dashboard/project-workspace.js.map +1 -0
  317. package/dist/dashboard/server.d.ts +1 -0
  318. package/dist/dashboard/server.d.ts.map +1 -1
  319. package/dist/dashboard/server.js +99 -42
  320. package/dist/dashboard/server.js.map +1 -1
  321. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  322. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  323. package/dist/dashboard/v6-dashboard-html.js +4261 -4309
  324. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  325. package/dist/dashboard/workspace-api.d.ts +12 -0
  326. package/dist/dashboard/workspace-api.d.ts.map +1 -1
  327. package/dist/dashboard/workspace-api.js +359 -41
  328. package/dist/dashboard/workspace-api.js.map +1 -1
  329. package/dist/dashboard/workspace-html.d.ts.map +1 -1
  330. package/dist/dashboard/workspace-html.js +1118 -135
  331. package/dist/dashboard/workspace-html.js.map +1 -1
  332. package/dist/dashboard/workspace-status.d.ts +53 -0
  333. package/dist/dashboard/workspace-status.d.ts.map +1 -0
  334. package/dist/dashboard/workspace-status.js +23 -0
  335. package/dist/dashboard/workspace-status.js.map +1 -0
  336. package/dist/index.d.ts +16 -0
  337. package/dist/index.d.ts.map +1 -1
  338. package/dist/index.js +13 -0
  339. package/dist/index.js.map +1 -1
  340. package/dist/integration-cli.d.ts +3 -0
  341. package/dist/integration-cli.d.ts.map +1 -0
  342. package/dist/integration-cli.js +16 -0
  343. package/dist/integration-cli.js.map +1 -0
  344. package/dist/project-cli.d.ts +3 -0
  345. package/dist/project-cli.d.ts.map +1 -0
  346. package/dist/project-cli.js +100 -0
  347. package/dist/project-cli.js.map +1 -0
  348. package/dist/types.d.ts +8 -1
  349. package/dist/types.d.ts.map +1 -1
  350. package/docs/BRAND_ASSETS.md +36 -0
  351. package/docs/COMPARISON.md +22 -0
  352. package/docs/COMPONENTS.md +66 -0
  353. package/docs/CONTEXT_LAB.md +79 -0
  354. package/docs/FEATURES_AND_LIMITS.md +29 -0
  355. package/docs/INTEGRATIONS.md +70 -0
  356. package/docs/LEARNING.md +46 -0
  357. package/docs/PROJECT_KIT.md +98 -0
  358. package/docs/QUICKSTART.md +62 -0
  359. package/docs/README.md +36 -0
  360. package/docs/ROADMAP-v7.md +147 -0
  361. package/docs/SESSIONS.md +77 -0
  362. package/docs/SHARING.md +13 -0
  363. package/docs/SHOWCASE.md +87 -0
  364. package/docs/SOURCE_INTAKE.md +33 -0
  365. package/docs/STATS.md +21 -0
  366. package/docs/TROUBLESHOOTING.md +42 -0
  367. package/docs/UPGRADE_PLAN_v7.0.0.md +203 -0
  368. package/docs/VIRAL-PLAYBOOK.md +259 -0
  369. package/docs/VISUAL_GUIDE_v7.1.4.md +70 -0
  370. package/docs/VISUAL_GUIDE_v7.22.0.md +73 -0
  371. package/docs/WORKFLOW_INTEGRATION.md +21 -1
  372. package/docs/WORKSPACE.md +63 -70
  373. package/docs/assets/7.1.4-collaboration-design-preview.png +0 -0
  374. package/docs/assets/7.1.4-comparison.png +0 -0
  375. package/docs/assets/7.1.4-context-lab.png +0 -0
  376. package/docs/assets/7.1.4-control-room-dense-preview.png +0 -0
  377. package/docs/assets/7.1.4-control-room-focus-preview.png +0 -0
  378. package/docs/assets/7.1.4-features.png +0 -0
  379. package/docs/assets/7.1.4-graph.png +0 -0
  380. package/docs/assets/7.1.4-hub-control-room.png +0 -0
  381. package/docs/assets/7.1.4-hub-empty.png +0 -0
  382. package/docs/assets/7.1.4-hub-knowledge.png +0 -0
  383. package/docs/assets/7.1.4-hub-learning.png +0 -0
  384. package/docs/assets/7.1.4-hub-mobile.png +0 -0
  385. package/docs/assets/7.1.4-hub-public-docs-source.png +0 -0
  386. package/docs/assets/7.1.4-hub-public-docs.gif +0 -0
  387. package/docs/assets/7.1.4-hub-public-docs.png +0 -0
  388. package/docs/assets/7.1.4-hub-setup.png +0 -0
  389. package/docs/assets/7.1.4-hub-walkthrough.gif +0 -0
  390. package/docs/assets/7.1.4-integrations.png +0 -0
  391. package/docs/assets/7.1.4-knowledge-design-preview.png +0 -0
  392. package/docs/assets/7.1.4-learning-design-preview.png +0 -0
  393. package/docs/assets/7.1.4-learning.png +0 -0
  394. package/docs/assets/7.1.4-live.png +0 -0
  395. package/docs/assets/7.1.4-memory-review.png +0 -0
  396. package/docs/assets/7.1.4-mobile.png +0 -0
  397. package/docs/assets/7.1.4-models-design-preview.png +0 -0
  398. package/docs/assets/7.1.4-outcome-ranking.png +0 -0
  399. package/docs/assets/7.1.4-overview.png +0 -0
  400. package/docs/assets/7.1.4-project-kit.png +0 -0
  401. package/docs/assets/7.1.4-project-skill-preview.png +0 -0
  402. package/docs/assets/7.1.4-replay.png +0 -0
  403. package/docs/assets/7.1.4-setup-design-preview.png +0 -0
  404. package/docs/assets/7.1.4-usage-design-preview.png +0 -0
  405. package/docs/assets/7.22.0-agent-dock-mobile.png +0 -0
  406. package/docs/assets/7.22.0-agent-dock.gif +0 -0
  407. package/docs/assets/7.22.0-agent-dock.png +0 -0
  408. package/docs/assets/7.22.0-hub-exchange.png +0 -0
  409. package/docs/assets/7.22.0-hub-exchanges.gif +0 -0
  410. package/docs/assets/7.22.0-hub-graph.png +0 -0
  411. package/docs/assets/7.22.0-hub-mobile-empty.png +0 -0
  412. package/docs/assets/7.22.0-managed-skills.png +0 -0
  413. package/docs/assets/7.22.0-plain-evidence.png +0 -0
  414. package/docs/assets/7.22.0-project-controls.png +0 -0
  415. package/docs/assets/7.22.0-sessions.png +0 -0
  416. package/docs/assets/7.22.0-teach-brain.png +0 -0
  417. package/docs/assets/context-v7.1.3.png +0 -0
  418. package/docs/assets/features-v7.1.3.png +0 -0
  419. package/docs/assets/graph-v7.1.3.png +0 -0
  420. package/docs/assets/integrations-v7.1.3.png +0 -0
  421. package/docs/assets/live-v7.1.3.png +0 -0
  422. package/docs/assets/live-v7.1.4.gif +0 -0
  423. package/docs/assets/memory-detail-v7.1.3.png +0 -0
  424. package/docs/assets/overview-v7.1.3.png +0 -0
  425. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.json +1818 -0
  426. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.md +36 -0
  427. package/docs/launch/COMPONENT_MAP_v7.1.4.md +307 -0
  428. package/docs/launch/PROJECT_KIT_PLAN_v7.1.4.md +110 -0
  429. package/docs/launch/PROJECT_LEARNING_PLAN_v7.1.4.md +63 -0
  430. package/docs/launch/RELEASE_ACCEPTANCE_v7.22.0.md +48 -0
  431. package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -0
  432. package/docs/launch/RELEASE_NOTES_v7.1.4.md +30 -0
  433. package/docs/launch/RELEASE_NOTES_v7.22.0.md +53 -0
  434. package/docs/launch/RESEARCH_AND_PLAN_v7.1.4.md +255 -0
  435. package/docs/launch/RESEARCH_AND_PLAN_v7.22.0.md +672 -0
  436. package/package.json +135 -111
  437. package/skills/shadow-research/SKILL.md +2 -0
  438. package/skills/shadow-workflow/SKILL.md +8 -0
@@ -1,11 +1,12 @@
1
1
  // Dependency-free local workspace. The advanced dashboard is served separately.
2
+ import { PROJECT_KIT_MARKUP, PROJECT_KIT_SCRIPT, PROJECT_KIT_STYLE } from './project-kit-html.js';
2
3
  export const WORKSPACE_HTML = String.raw `<!doctype html>
3
4
  <html lang="en" data-theme="light" data-motion="system">
4
5
  <head>
5
6
  <meta charset="utf-8">
6
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
8
  <meta name="color-scheme" content="light dark">
8
- <title>Shadow Brain · Workspace</title>
9
+ <title>Agent Shadow Brain · Workspace</title>
9
10
  <script>
10
11
  try {
11
12
  const theme = localStorage.getItem('shadow-brain:workspace:theme');
@@ -20,7 +21,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
20
21
  --canvas: #f4f6f9; --surface: #fff; --inset: #eef2f6; --hover: #f2f5f8;
21
22
  --ink: #182539; --muted: #4e6176; --line: #dbe2e9; --control: #8a9bae;
22
23
  --teal: #096459; --teal-fill: #076b5e; --teal-hover: #05584d; --tint: #dff1ec;
23
- --bad: #a32940; --bad-bg: #fbeaf0; --focus: #167b9d;
24
+ --bad: #a32940; --bad-bg: #fbeaf0; --focus: #167b9d; --amber: #815116; --amber-bg: #f9edd8;
24
25
  --rail: #172539; --rail-ink: #f0f5fc; --rail-muted: #b5c5d7; --rail-line: #35475b;
25
26
  --rail-selected: #284657; --rail-highlight: #8ed9cb;
26
27
  --agent: #57539a; --agent-bg: #eeecfa; --project: var(--teal); --project-bg: var(--tint);
@@ -36,7 +37,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
36
37
  --canvas: #111b2a; --surface: #172437; --inset: #1e2e43; --hover: #23344a;
37
38
  --ink: #edf4fb; --muted: #b3c1d1; --line: #334459; --control: #74889e;
38
39
  --teal: #94e1d3; --teal-fill: #9be6d6; --teal-hover: #baf2e6; --tint: #203f43;
39
- --bad: #ffadbd; --bad-bg: #492a3a; --focus: #8cd6f3;
40
+ --bad: #ffadbd; --bad-bg: #492a3a; --focus: #8cd6f3; --amber: #f4cb89; --amber-bg: #43352a;
40
41
  --rail: #101a28; --rail-line: #2e4157; --rail-selected: #233e4f;
41
42
  --agent: #c7bffc; --agent-bg: #333451; --memory: #bccfea; --memory-bg: #2a3a50;
42
43
  --graph-line: #8295ac; --graph-project: #82bcb1; --graph-agent: #aaa0db; --graph-dot: #34465b; --accent-ink: #15372e;
@@ -70,7 +71,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
70
71
  .brand { color: var(--rail-ink); display: flex; align-items: center; gap: 11px; padding: 0 7px; font-weight: 710; }
71
72
  .brand-mark { width: 33px; height: 33px; display: grid; place-items: center; background: #294354; color: #9ce6d6; border-radius: 8px; }
72
73
  .brand-mark .icon { width: 21px; height: 21px; }
73
- .brand-name { letter-spacing: -.02em; white-space: nowrap; }
74
+ .brand-name { letter-spacing: -.02em; line-height: 1.13; }
74
75
  .rail-nav { display: grid; gap: 4px; }
75
76
  .nav-link { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 7px; color: var(--rail-muted); font-weight: 580; }
76
77
  .nav-link:hover { color: var(--rail-ink); background: #24384d; }
@@ -88,6 +89,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
88
89
  .crumb { display: flex; align-items: center; gap: 9px; min-width: 0; }
89
90
  .crumb .icon { width: 14px; height: 14px; color: var(--muted); }
90
91
  .top-actions { display: flex; align-items: center; gap: 14px; }
92
+ .live-link { white-space: nowrap; }
91
93
  .connection { display: inline-flex; gap: 6px; align-items: center; white-space: nowrap; }
92
94
  .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; background: currentColor; }
93
95
  .connection[data-state="connected"] .dot { color: var(--teal); }
@@ -130,7 +132,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
130
132
  .inline-check { display: flex; align-items: flex-start; gap: 7px; min-height: 28px; }
131
133
  .inline-check input { accent-color: var(--teal-fill); margin-top: 4px; }
132
134
  .form-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
133
- .filters { display: grid; grid-template-columns: minmax(180px, 1.7fr) repeat(3,minmax(100px,1fr)) auto; gap: 10px; align-items: end; }
135
+ .filters { display: grid; grid-template-columns: minmax(155px,1.45fr) repeat(4,minmax(96px,1fr)) auto; gap: 10px; align-items: end; }
134
136
  .filter-bar { display: flex; align-items: end; gap: 10px; flex-wrap: wrap; }
135
137
  .filter-bar .label { flex: 1 1 155px; }
136
138
  .status { padding: 13px 17px; border: 1px solid var(--line); border-radius: 7px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; background: var(--surface); }
@@ -151,6 +153,10 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
151
153
  .tag.project { background: var(--project-bg); color: var(--project); }
152
154
  .tag.agent { background: var(--agent-bg); color: var(--agent); }
153
155
  .tag.memory { background: var(--memory-bg); color: var(--memory); }
156
+ .tag.state-active, .tag.check-ok { background: var(--tint); color: var(--teal); }
157
+ .tag.state-contested, .tag.check-warn { background: var(--amber-bg); color: var(--amber); }
158
+ .tag.state-superseded { background: var(--agent-bg); color: var(--agent); }
159
+ .tag.check-error { background: var(--bad-bg); color: var(--bad); }
154
160
  .tags { display: flex; align-items: center; flex-wrap: wrap; gap: 5px 7px; }
155
161
  .summary {
156
162
  display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); background: var(--surface);
@@ -182,6 +188,31 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
182
188
  .memory-row { padding: 14px 19px; display: grid; gap: 8px; }
183
189
  .memory-head { display: flex; justify-content: space-between; align-items: start; gap: 10px; }
184
190
  .memory-head time { white-space: nowrap; }
191
+ .record-link { color: var(--ink); font-weight: 680; text-decoration: underline; text-decoration-color: var(--control); text-underline-offset: 3px; }
192
+ .record-link:hover { color: var(--teal); text-decoration-color: currentColor; }
193
+ .memory-content { white-space: pre-wrap; overflow-wrap: anywhere; max-width: none; }
194
+ .detail-head { display: grid; gap: 9px; }
195
+ .detail-head > .button { justify-self: start; }
196
+ .detail-layout { grid-template-columns: minmax(0,1.45fr) minmax(265px,1fr); }
197
+ .detail-form, .detail-sources, .check-result { display: grid; gap: 9px; }
198
+ .detail-note { white-space: pre-wrap; overflow-wrap: anywhere; }
199
+ .detail-row { display: grid; gap: 7px; padding: 13px 19px; }
200
+ .link-search { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: end; gap: 8px; }
201
+ .save-status:empty { display: none; }
202
+ .inline-error { color: var(--bad); }
203
+ .quality-track { display: flex; width: 100%; height: 12px; background: var(--inset); border-radius: 5px; overflow: hidden; }
204
+ .quality-track > span { flex: none; }
205
+ .quality-active { background: var(--teal-fill); }
206
+ .quality-contested { background: var(--amber); }
207
+ .quality-archived { background: var(--control); }
208
+ .quality-superseded { background: var(--agent); }
209
+ .quality-labels { display: flex; flex-wrap: wrap; gap: 8px 13px; }
210
+ .quality-labels a { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); font-size: .82rem; }
211
+ .quality-labels a:hover { text-decoration: underline; }
212
+ .quality-labels i { width: 8px; height: 8px; border-radius: 2px; }
213
+ .quality-source { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; }
214
+ .check-row { display: grid; grid-template-columns: minmax(130px,.9fr) minmax(0,1.7fr) auto; gap: 10px; align-items: start; padding: 13px 19px; }
215
+ .check-row .tag { justify-self: end; }
185
216
  .memory-row p { max-width: 90ch; white-space: pre-wrap; }
186
217
  .memory-row details summary { color: var(--teal); font-size: .85rem; font-weight: 650; cursor: pointer; width: fit-content; max-width: 100%; }
187
218
  .memory-row details ul { padding-left: 19px; margin-top: 4px; }
@@ -207,12 +238,20 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
207
238
  .graph-node.dot-node:is(:hover,:focus-visible,[aria-pressed="true"]) .node-surface { fill: var(--memory-bg); }
208
239
  .graph-node:focus-visible { outline: none; }
209
240
  .graph-node.unrelated { opacity: .34; }
210
- .graph-edge { fill: none; stroke: var(--graph-line); stroke-width: 1.15; opacity: .53; }
241
+ .graph-edge { fill: none; stroke: var(--graph-line); stroke-width: 1.15; opacity: .53; transition: opacity var(--ease); }
211
242
  .graph-edge.project-edge { stroke: var(--graph-project); }
212
243
  .graph-edge.agent-edge { stroke: var(--graph-agent); stroke-dasharray: 4 3; opacity: .35; }
213
244
  .graph-edge.active { stroke: var(--teal); stroke-width: 2.1; opacity: 1; }
214
245
  .graph-edge.agent-edge.active { stroke: var(--agent); }
246
+ .graph-edge.recorded-edge { stroke: var(--agent); stroke-width: 1.8; opacity: .8; }
247
+ .graph-edge.contradicts { stroke: var(--bad); stroke-dasharray: 5 3; }
248
+ .graph-edge.supersedes { stroke: var(--teal); stroke-dasharray: 2 2; }
249
+ .graph-edge.recorded-edge.active { stroke-width: 3; opacity: 1; }
215
250
  .graph-edge.unrelated { opacity: .13; }
251
+ .graph-edge-hit { fill: none; stroke: transparent; stroke-width: 14; pointer-events: stroke; cursor: pointer; }
252
+ .graph-link-group:is(:hover,:focus-visible) .graph-edge.recorded-edge { opacity: 1; stroke-width: 3; }
253
+ .graph-link-group:focus-visible { outline: none; }
254
+ .graph-arrow { fill: var(--agent); }
216
255
  .graph-tooltip { pointer-events: none; }
217
256
  .graph-tooltip rect { fill: var(--surface); stroke: var(--memory); }
218
257
  .graph-tooltip text { fill: var(--ink); font-size: 12px; font-weight: 650; }
@@ -234,6 +273,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
234
273
  .legend-line { display: inline-block; width: 16px; border-top: 2px solid var(--graph-line); }
235
274
  .legend-line.project-edge { border-color: var(--graph-project); }
236
275
  .legend-line.agent-edge { border-color: var(--graph-agent); border-top-style: dashed; }
276
+ .legend-line.recorded-edge { border-color: var(--agent); }
237
277
  .graph-list { min-width: 0; max-height: 480px; overflow-y: auto; }
238
278
  .graph-list .graph-row { padding: 9px 12px; }
239
279
  .graph-list .button { justify-content: flex-start; text-align: left; width: 100%; }
@@ -261,8 +301,105 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
261
301
  .save-status { padding: 8px 19px; min-height: 35px; border-top: 1px solid var(--line); }
262
302
  .save-status[data-error="true"] { color: var(--bad); }
263
303
  .notice { margin: 0 0 16px; }
264
- .entered { animation: enter var(--ease) both; }
265
- @keyframes enter { from { opacity: .7; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
304
+ :root { --signal: #8fe5d3; --instrument: #142d3b; --instrument-deep: #102333; --instrument-ink: #f3f9fd; --instrument-muted: #b6cbd5; --instrument-line: #395663; }
305
+ .rail { gap: 35px; }
306
+ .nav-link[aria-current="page"] { box-shadow: inset 0 0 0 1px #426371; }
307
+ .rail-live { margin-top: 11px; }
308
+ .rail-live .icon { color: var(--rail-highlight); }
309
+ .page-head h1 { font-size: 2.27rem; }
310
+ .page-head .head-actions { align-items: end; }
311
+ .page-head .head-actions .button { align-self: end; }
312
+ .demo-notice { display: none; }
313
+ html[data-demo="true"] .demo-notice { display: flex; gap: 7px 10px; flex-wrap: wrap; padding: 8px 12px; margin-bottom: 16px; background: var(--amber-bg); color: var(--amber); border-radius: 7px; font-size: .83rem; }
314
+ html[data-demo="true"] .advanced-link { display: none; }
315
+ .overview-summary { display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,1fr); color: var(--instrument-ink); background: var(--instrument); border-radius: 12px; overflow: hidden; margin-bottom: 17px; }
316
+ .overview-summary .summary { grid-template-columns: repeat(3,minmax(0,1fr)); background: var(--instrument-deep); color: var(--instrument-ink); border: 0; border-radius: 0; margin: 0; }
317
+ .overview-summary .summary-item { flex-direction: column; align-items: start; justify-content: center; gap: 11px; padding: 20px 16px; }
318
+ .overview-summary .summary-item + .summary-item { border-color: var(--instrument-line); }
319
+ .overview-summary .summary dt { color: var(--instrument-muted); font-size: .83rem; }
320
+ .overview-summary .summary dd { font-size: 1.72rem; }
321
+ .overview-summary .tile-icon { color: var(--signal); background: #234550; }
322
+ .overview-lead { display: flex; align-items: center; gap: 19px; padding: 21px; min-width: 0; }
323
+ .overview-lead h2 { font-size: 1.28rem; }
324
+ .overview-lead p { color: var(--instrument-muted); margin-top: 5px; }
325
+ .overview-lead .button { color: var(--signal); border-color: var(--instrument-line); margin-top: 12px; }
326
+ .overview-lead .button:hover { background: #254350; }
327
+ .coverage-ring { flex: none; display: grid; place-items: center; width: 98px; aspect-ratio: 1; border-radius: 50%; background: conic-gradient(var(--signal) var(--coverage), #3b5665 0); }
328
+ .coverage-ring span { display: grid; place-items: center; width: 80px; aspect-ratio: 1; border-radius: 50%; background: var(--instrument); font-size: 1.46rem; font-weight: 720; font-variant-numeric: tabular-nums; }
329
+ .overview-columns { grid-template-columns: minmax(0,1.33fr) minmax(0,1fr); }
330
+ .overview-activity .panel-head { background: var(--inset); }
331
+ .attention-list { max-height: 355px; overflow: auto; }
332
+ .attention-row { display: grid; grid-template-columns: 30px minmax(0,1fr); gap: 11px; align-items: start; padding: 12px 19px; }
333
+ .attention-row .tile-icon { width: 30px; height: 30px; background: var(--amber-bg); color: var(--amber); }
334
+ .attention-row .tags { margin-top: 5px; }
335
+ .graph-stage {
336
+ --canvas: #142534; --surface: #203546; --ink: #f0f7fc; --muted: #bed0db; --line: #4b6679;
337
+ --project: #a3e8d5; --project-bg: #234651; --agent: #d3c5ff; --agent-bg: #3a3e5e; --memory: #c2daf2; --memory-bg: #344d64;
338
+ --graph-line: #8ba9bd; --graph-project: #82b8ab; --graph-agent: #aa9ed0; --graph-dot: #40566a; --teal: #9ce9d8; --bad: #ff9eab;
339
+ height: 520px; color: var(--ink); color-scheme: dark;
340
+ }
341
+ .graph-stage .button:hover { background: #30485a; }
342
+ .graph-stage .cluster-area { fill-opacity: .47; stroke-opacity: .34; }
343
+ .graph-node { transition: opacity var(--ease); }
344
+ .graph-node.selected-memory .node-surface { stroke: var(--teal); }
345
+ .graph-stage .graph-edge.supports { stroke: var(--teal); }
346
+ .graph-inspector, .graph-list { max-height: 520px; }
347
+ .graph-body[data-lens="true"] .graph-stage { height: 390px; }
348
+ .graph-body[data-lens="true"] :is(.graph-inspector,.graph-list) { max-height: 390px; }
349
+ .graph-lens { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; padding: 8px 14px; border-bottom: 1px solid var(--line); }
350
+ .legend-line.supports { border-color: var(--teal); }
351
+ .legend-line.contradicts { border-color: var(--bad); border-top-style: dashed; }
352
+ .legend-line.supersedes { border-color: var(--teal); border-top-style: dotted; }
353
+ .context-layout { grid-template-columns: minmax(255px,.68fr) minmax(0,1.75fr); }
354
+ .context-config { gap: 16px; }
355
+ .context-config .form-actions .primary { width: 100%; }
356
+ .context-settings-pair { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 10px; }
357
+ .context-result-head { background: var(--inset); }
358
+ .context-output-head { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: start; gap: 10px; }
359
+ .context-output-head .form-actions { justify-content: end; }
360
+ .context-metrics { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); border-block: 1px solid var(--line); }
361
+ .context-metrics > div { padding: 11px 10px; min-width: 0; }
362
+ .context-metrics > div + div { border-left: 1px solid var(--line); }
363
+ .context-metrics dt { font-size: .78rem; color: var(--muted); }
364
+ .context-metrics dd { font-size: 1.21rem; font-weight: 710; overflow-wrap: anywhere; }
365
+ .context-budget-track { height: 6px; border-radius: 4px; background: var(--line); overflow: hidden; margin-top: 5px; }
366
+ .context-budget-track span { display: block; height: 100%; background: var(--teal-fill); border-radius: inherit; }
367
+ .context-section { display: grid; gap: 11px; min-width: 0; }
368
+ .context-section > summary, .candidate > summary { cursor: pointer; font-weight: 680; }
369
+ .context-section > summary { color: var(--teal); }
370
+ .context-section[open] > summary { margin-bottom: 10px; }
371
+ .retrieval-flow { display: grid; grid-template-columns: repeat(auto-fit,minmax(97px,1fr)); gap: 1px; list-style: none; padding: 1px; border-radius: 8px; background: var(--instrument-line); overflow: hidden; }
372
+ .retrieval-flow li { display: grid; grid-template-rows: auto 1fr auto; gap: 5px; min-width: 0; background: var(--instrument-deep); color: var(--instrument-ink); padding: 10px; }
373
+ .retrieval-flow strong { font-size: 1.28rem; font-variant-numeric: tabular-nums; }
374
+ .retrieval-flow span { font-size: .78rem; color: var(--instrument-muted); overflow-wrap: anywhere; }
375
+ .retrieval-flow li:last-child { background: var(--instrument); }
376
+ .retrieval-flow li:last-child strong { color: var(--signal); }
377
+ .retrieval-stage-bar { height: 3px; background: var(--instrument-line); border-radius: 2px; overflow: hidden; }
378
+ .retrieval-stage-bar i { display: block; height: 100%; background: var(--signal); opacity: .74; }
379
+ .candidate-list { max-height: 415px; overflow: auto; border-block: 1px solid var(--line); }
380
+ .candidate { padding: 9px 9px 9px 12px; }
381
+ .candidate > summary { color: var(--ink); }
382
+ .candidate > summary:hover { color: var(--teal); }
383
+ .candidate-title { display: inline-grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: baseline; gap: 7px; width: calc(100% - 20px); vertical-align: top; }
384
+ .candidate-title .rank { color: var(--muted); font: .8rem Consolas,monospace; }
385
+ .candidate-body { display: grid; gap: 7px; padding: 9px 0 4px; }
386
+ .candidate-signals { display: grid; grid-template-columns: repeat(auto-fit,minmax(110px,1fr)); gap: 5px; }
387
+ .candidate-signals div { background: var(--inset); border-radius: 5px; padding: 6px; }
388
+ .candidate-signals dt { color: var(--muted); font-size: .75rem; }
389
+ .candidate-signals dd { font-size: .89rem; font-variant-numeric: tabular-nums; }
390
+ .context-compare { display: grid; gap: 8px; }
391
+ .compare-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
392
+ .compare-grid > div { padding: 10px; min-width: 0; }
393
+ .compare-grid > div + div { border-left: 1px solid var(--line); }
394
+ .compare-grid ul { padding-left: 16px; }
395
+ .compare-grid h3 { margin-bottom: 5px; }
396
+ .integration-meta { display: flex; flex-wrap: wrap; gap: 7px; padding: 12px 19px; border-bottom: 1px solid var(--line); }
397
+ .integration-config { display: grid; gap: 8px; }
398
+ .integration-config > summary { cursor: pointer; color: var(--teal); font-weight: 650; }
399
+ .integration-config[open] > summary { margin-bottom: 8px; }
400
+ .integration-config .context-preview { margin-block: 7px; max-height: 280px; }
401
+ .setting .settings-description { display: flex; align-items: start; gap: 11px; }
402
+ .setting .tile-icon { width: 32px; height: 32px; }
266
403
  html[data-motion="reduce"] *, html[data-motion="reduce"] *::before, html[data-motion="reduce"] *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
267
404
  @media (prefers-reduced-motion: reduce) {
268
405
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
@@ -270,9 +407,9 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
270
407
  @media (max-width: 1140px) {
271
408
  :root { --sidebar: 188px; }
272
409
  .top, .content { padding-left: 24px; padding-right: 24px; }
273
- .filters { grid-template-columns: repeat(3,minmax(0,1fr)); }
274
- .filters .search-field { grid-column: span 2; }
275
- .filters .button { grid-column: 3; grid-row: 1; }
410
+ .filters { grid-template-columns: repeat(4,minmax(0,1fr)); }
411
+ .filters .search-field { grid-column: span 3; }
412
+ .filters .button { grid-column: 4; grid-row: 1; }
276
413
  .overview-columns { grid-template-columns: minmax(0,1.25fr) minmax(0,1fr); }
277
414
  .event { grid-template-columns: 27px minmax(0,1fr); }
278
415
  .event time { grid-column: 2; }
@@ -289,6 +426,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
289
426
  :root { --header: 48px; font-size: 14px; }
290
427
  .shell { display: block; }
291
428
  .rail { z-index: var(--sticky); height: auto; flex-direction: row; align-items: center; gap: 7px; padding: 5px 10px; }
429
+ .rail-live { display: none; }
292
430
  .rail-nav { display: flex; gap: 3px; }
293
431
  .nav-link { padding: 7px 9px; min-height: 35px; }
294
432
  .rail-foot { margin: 0 0 0 auto; padding: 0; border: 0; }
@@ -296,46 +434,71 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
296
434
  .top { padding: 7px 13px; }
297
435
  .top-actions { gap: 8px; }
298
436
  .content { padding: 22px 12px 32px; }
299
- h1 { font-size: 1.68rem; }
437
+ h1, .page-head h1 { font-size: 1.68rem; }
300
438
  .page-head { gap: 8px; margin-bottom: 17px; flex-wrap: wrap; }
301
439
  .summary { margin-bottom: 13px; }
302
440
  .summary-item { gap: 6px; padding: 11px 9px; }
303
441
  .summary-item .tile-icon { display: none; }
304
442
  .split { grid-template-columns: minmax(0,1fr); gap: 13px; }
443
+ .filters { grid-template-columns: repeat(2,minmax(0,1fr)); }
444
+ .filters .search-field { grid-column: 1; }
445
+ .filters .button { grid-column: 2; grid-row: 1; }
446
+ .check-row { grid-template-columns: minmax(0,1fr) auto; padding-left: 12px; padding-right: 12px; }
447
+ .check-row .check-result { grid-column: 1 / -1; grid-row: 2; }
448
+ .check-row .tag { grid-column: 2; grid-row: 1; }
305
449
  .graph-body { grid-template-columns: minmax(0,1fr); }
306
450
  .graph-stage, .graph-list { height: 365px; }
451
+ .graph-body[data-lens="true"] .graph-stage { height: 310px; }
307
452
  .graph-inspector { border-left: 0; border-top: 1px solid var(--line); max-height: 340px; }
308
453
  .panel-head, .panel-body, .memory-row, .setting { padding-left: 12px; padding-right: 12px; }
309
454
  .project-row, .event { padding-left: 12px; padding-right: 12px; }
310
455
  .list-footer { flex-wrap: wrap; }
311
456
  .graph-mode { margin-left: 0; }
457
+ .overview-summary { grid-template-columns: minmax(0,1fr); }
458
+ .overview-summary .summary-item { padding: 11px 10px; }
459
+ .overview-summary .summary dd { font-size: 1.42rem; }
460
+ .overview-lead { padding: 15px; gap: 12px; }
461
+ .coverage-ring { width: 77px; }
462
+ .coverage-ring span { width: 63px; font-size: 1.16rem; }
463
+ .context-output-head { grid-template-columns: minmax(0,1fr); }
464
+ .context-output-head .form-actions { justify-content: start; }
465
+ #context-result, #attention-heading { scroll-margin-top: 55px; }
466
+ .context-metrics > div { padding: 8px 6px; }
467
+ .context-metrics dd { font-size: 1rem; }
468
+ .retrieval-flow { grid-template-columns: repeat(3,minmax(0,1fr)); }
469
+ .candidate-title { grid-template-columns: auto minmax(0,1fr); }
470
+ .candidate-title .tag { grid-column: 2; justify-self: start; }
312
471
  }
313
472
  @media (max-width: 355px) {
314
473
  .rail { gap: 3px; }
315
- .nav-link { padding-left: 7px; padding-right: 7px; }
474
+ .nav-link { padding-left: 5px; padding-right: 5px; }
316
475
  .crumb > .muted, .crumb .icon { display: none; }
317
476
  .filters { grid-template-columns: repeat(2,minmax(0,1fr)); }
318
477
  .filters .search-field { grid-column: 1 / -1; }
319
478
  .filters .button { grid-row: auto; grid-column: auto; }
320
479
  }
480
+ ${PROJECT_KIT_STYLE}
321
481
  </style>
322
482
  </head>
323
483
  <body>
324
484
  <a class="skip button" href="#main-content">Skip to content</a>
325
485
  <div class="shell">
326
486
  <aside class="rail">
327
- <a class="brand" href="#overview" aria-label="Shadow Brain overview">
328
- <span class="brand-mark" data-icon="brand"></span><span class="brand-name">Shadow Brain</span>
487
+ <a class="brand" href="#overview" aria-label="Agent Shadow Brain overview">
488
+ <span class="brand-mark" data-icon="brand"></span><span class="brand-name">Agent Shadow Brain</span>
329
489
  </a>
330
490
  <nav class="rail-nav" aria-label="Workspace">
331
491
  <a class="nav-link" href="#overview" aria-label="Overview" title="Overview" data-view="overview" aria-current="page"><span data-icon="overview"></span><span class="nav-label">Overview</span></a>
332
492
  <a class="nav-link" href="#memories" aria-label="Memories" title="Memories" data-view="memories"><span data-icon="memory"></span><span class="nav-label">Memories</span></a>
333
493
  <a class="nav-link" href="#graph" aria-label="Graph" title="Graph" data-view="graph"><span data-icon="graph"></span><span class="nav-label">Graph</span></a>
334
- <a class="nav-link" href="#context" aria-label="Context" title="Context" data-view="context"><span data-icon="context"></span><span class="nav-label">Context</span></a>
494
+ <a class="nav-link" href="#context" aria-label="Context Lab" title="Context Lab" data-view="context"><span data-icon="context"></span><span class="nav-label">Context Lab</span></a>
495
+ <a class="nav-link" href="#project-kit" aria-label="Project Kit" title="Project Kit" data-view="project-kit"><span data-icon="project"></span><span class="nav-label">Project Kit</span></a>
496
+ <a class="nav-link" href="#integrations" aria-label="Integrations and health" title="Integrations and health" data-view="integrations"><span data-icon="integrations"></span><span class="nav-label">Integrations</span></a>
335
497
  <a class="nav-link" href="#features" aria-label="Features" title="Features" data-view="features"><span data-icon="settings"></span><span class="nav-label">Features</span></a>
498
+ <a class="nav-link rail-live" href="/live" aria-label="Live operations" title="Live operations"><span data-icon="activity"></span><span class="nav-label">Live operations</span></a>
336
499
  </nav>
337
500
  <div class="rail-foot">
338
- <a class="nav-link" href="/advanced" aria-label="Advanced dashboard" title="Advanced dashboard"><span data-icon="advanced"></span><span class="nav-label">Advanced tools</span><span class="arrow" data-icon="diagonal"></span></a>
501
+ <a class="nav-link advanced-link" href="/advanced" aria-label="Advanced dashboard" title="Advanced dashboard"><span data-icon="advanced"></span><span class="nav-label">Advanced tools</span><span class="arrow" data-icon="diagonal"></span></a>
339
502
  <p class="rail-build" id="rail-version">Local workspace</p>
340
503
  </div>
341
504
  </aside>
@@ -343,16 +506,20 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
343
506
  <header class="top">
344
507
  <div class="crumb"><span class="muted">Workspace</span><span data-icon="chevron"></span><strong id="crumb">Overview</strong></div>
345
508
  <div class="top-actions">
509
+ <a class="button quiet live-link" href="/live"><span data-icon="activity"></span>Live</a>
346
510
  <span class="connection muted small" id="connection" data-state="loading"><span class="dot"></span><span id="connection-label">Connecting</span></span>
347
511
  <button type="button" class="button icon-button quiet" id="theme-toggle" aria-label="Switch to dark theme" title="Switch to dark theme"><span data-icon="moon"></span></button>
348
512
  </div>
349
513
  </header>
350
514
  <main id="main-content" class="content" tabindex="-1">
515
+ <div class="demo-notice"><strong>Synthetic demo</strong><span>Scripted local MCP calls use sample data. Changes affect this temporary demo only.</span></div>
516
+ <div class="project-local-notice"><strong>Project workspace</strong><span>Memory and settings are stored with this project.</span><a href="#project-kit">Open Project Kit</a></div>
351
517
  <div class="notice" id="workspace-notice" role="status" hidden></div>
352
518
  <section id="page-overview" aria-labelledby="title-overview">
353
519
  <div class="page-head">
354
- <div><h1 id="title-overview" tabindex="-1">Overview</h1><p class="muted">See what your agents remember and what they’ve done recently.</p></div>
355
- <a href="#context" class="button primary"><span data-icon="context"></span>Assemble context</a>
520
+ <div><h1 id="title-overview" tabindex="-1">Memory workspace</h1><p class="muted">Review saved knowledge and prepare a brief for your next task.</p></div>
521
+ <div class="form-actions head-actions"><label class="label" for="overview-quality-project">Project<select id="overview-quality-project"><option value="">All projects</option></select></label>
522
+ <a href="#context" id="overview-context" class="button primary"><span data-icon="context"></span>Open Context Lab</a></div>
356
523
  </div>
357
524
  <div id="overview-body" aria-busy="true"></div>
358
525
  </section>
@@ -364,18 +531,31 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
364
531
  <label class="label" for="memory-project">Project<select id="memory-project"><option value="">All projects</option></select></label>
365
532
  <label class="label" for="memory-category">Category<select id="memory-category"><option value="">All categories</option></select></label>
366
533
  <label class="label" for="memory-agent">Agent<select id="memory-agent"><option value="">All agents</option></select></label>
534
+ <label class="label" for="memory-state">Status<select id="memory-state"><option value="">All statuses</option><option value="active">Active</option><option value="contested">Needs review</option><option value="archived">Archived</option><option value="superseded">Superseded</option></select></label>
367
535
  <button class="button primary" id="memory-search" type="submit"><span data-icon="search"></span>Search</button>
368
536
  </form>
369
537
  <div id="memory-results" class="panel" aria-busy="true"></div>
370
538
  </div>
371
539
  </section>
540
+ <section id="page-memory" aria-labelledby="title-memory" hidden>
541
+ <div class="page-head">
542
+ <div class="detail-head"><a id="detail-back" href="#memories" class="button quiet"><span data-icon="left"></span>Memories</a><h1 id="title-memory" tabindex="-1">Memory detail</h1><div id="detail-meta"></div></div>
543
+ <div class="form-actions" id="detail-actions" hidden>
544
+ <a id="detail-graph" class="button" href="#graph"><span data-icon="graph"></span>View on graph</a>
545
+ <a id="detail-context" class="button" href="#context"><span data-icon="context"></span>Assemble context</a>
546
+ <a id="detail-outcome" class="button" href="#project-kit" hidden><span data-icon="activity"></span>Record outcome</a>
547
+ </div>
548
+ </div>
549
+ <div id="detail-body" aria-busy="true"></div>
550
+ </section>
372
551
  <section id="page-graph" aria-labelledby="title-graph" hidden>
373
- <div class="page-head"><div><h1 id="title-graph" tabindex="-1">Graph</h1><p class="muted">Explore how stored memories connect to projects and agents.</p></div></div>
552
+ <div class="page-head"><div><h1 id="title-graph" tabindex="-1">Graph</h1><p class="muted">Explore project membership, recording agents, and links between memories.</p></div></div>
374
553
  <div class="stack">
375
554
  <div class="panel panel-body filter-bar">
376
555
  <label class="label search-field" for="graph-query">Find a node<span data-icon="search"></span><input id="graph-query" type="search" maxlength="200" placeholder="Filter by name"></label>
377
556
  <label class="label" for="graph-project">Project<select id="graph-project"><option value="">All projects</option></select></label>
378
557
  <label class="label" for="graph-kind">Node type<select id="graph-kind"><option value="">All types</option><option value="project">Projects</option><option value="agent">Agents</option><option value="memory">Memories</option></select></label>
558
+ <label class="label" for="graph-relation">Relationship<select id="graph-relation"><option value="">All relationships</option><option value="recorded">Memory links</option><option value="belongs-to">Project membership</option><option value="recorded-by">Recording agent</option><option value="related">Related</option><option value="supports">Supports</option><option value="contradicts">Contradicts</option><option value="supersedes">Supersedes</option></select></label>
379
559
  <div class="form-actions toolbar-end graph-mode" role="group" aria-label="Graph view">
380
560
  <button type="button" class="button" id="graph-mode-map" aria-pressed="true"><span data-icon="graph"></span>Map</button>
381
561
  <button type="button" class="button" id="graph-mode-list" aria-pressed="false"><span data-icon="list"></span>List</button>
@@ -383,10 +563,11 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
383
563
  </div>
384
564
  <div class="panel" id="graph-panel" aria-busy="true">
385
565
  <div class="panel-head"><h2>Memory relationships</h2><span id="graph-count" class="muted small" role="status"></span></div>
566
+ <div id="graph-lens" class="graph-lens" hidden><p class="small muted" id="graph-lens-label"></p><button class="button" type="button" id="graph-lens-clear">Show full graph</button></div>
386
567
  <div id="graph-status"></div>
387
568
  <div class="graph-body" id="graph-body" hidden>
388
569
  <div class="graph-stage" id="graph-stage">
389
- <svg id="graph-svg" xmlns="http://www.w3.org/2000/svg" role="group" aria-label="Memory relationship map. Use arrow keys to navigate nodes."></svg>
570
+ <svg id="graph-svg" xmlns="http://www.w3.org/2000/svg" role="group" aria-label="Memory relationship map. Use arrow keys to navigate nodes or links."></svg>
390
571
  <div class="graph-tools" role="group" aria-label="Map zoom">
391
572
  <button class="button icon-button" type="button" id="graph-out" aria-label="Zoom out" title="Zoom out"><span data-icon="minus"></span></button>
392
573
  <button class="button icon-button" type="button" id="graph-in" aria-label="Zoom in" title="Zoom in"><span data-icon="plus"></span></button>
@@ -400,27 +581,42 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
400
581
  <div id="graph-legend" class="legend" hidden>
401
582
  <span><i class="legend-glyph project"></i> Project</span><span><i class="legend-glyph agent"></i> Agent</span><span><i class="legend-glyph memory"></i> Memory</span>
402
583
  <span><i class="legend-line project-edge"></i> Belongs to project</span><span><i class="legend-line agent-edge"></i> Recorded by agent</span>
584
+ <span><i class="legend-line recorded-edge"></i> Related</span><span><i class="legend-line supports"></i> Supports</span>
585
+ <span><i class="legend-line contradicts"></i> Contradicts</span><span><i class="legend-line supersedes"></i> Supersedes</span>
403
586
  </div>
404
587
  </div>
405
588
  </div>
406
589
  </section>
407
590
  <section id="page-context" aria-labelledby="title-context" hidden>
408
- <div class="page-head"><div><h1 id="title-context" tabindex="-1">Context</h1><p class="muted">Build a project brief for a specific task and review what it includes.</p></div></div>
591
+ <div class="page-head"><div><h1 id="title-context" tabindex="-1">Context Lab</h1><p class="muted">Assemble a task brief, inspect its evidence, and compare retrieval profiles.</p></div></div>
409
592
  <div class="split context-layout">
410
593
  <div class="panel">
411
- <div class="panel-head"><h2>Task details</h2></div>
594
+ <div class="panel-head"><h2>Build a brief</h2></div>
412
595
  <form class="panel-body context-config" id="context-form">
413
596
  <label class="label" for="context-project">Project<select id="context-project" required><option value="">Select a project</option></select></label>
414
597
  <label class="label" for="context-task">Task<textarea id="context-task" rows="5" maxlength="4096" required placeholder="Describe what the agent will work on."></textarea></label>
415
- <label class="label" for="context-budget">Estimated token budget<input id="context-budget" type="number" inputmode="numeric" min="0" max="12000" step="1" value="2000" required></label>
598
+ <div class="context-settings-pair">
599
+ <label class="label" for="context-budget">Token budget<input id="context-budget" type="number" inputmode="numeric" min="0" max="12000" step="1" value="2000" required aria-describedby="context-estimate"></label>
600
+ <label class="label" for="context-profile">Retrieval<select id="context-profile"><option value="adaptive">Adaptive</option><option value="legacy">Legacy</option></select></label>
601
+ </div>
602
+ <p class="small muted" id="context-estimate">Token usage is estimated for the assembled brief.</p>
603
+ <label class="inline-check small" id="context-related-row"><input type="checkbox" id="context-related" checked>Expand through recorded supporting and related links</label>
416
604
  <label class="inline-check small" id="context-cross-row" hidden><input type="checkbox" id="context-cross">Include eligible memories from other projects</label>
417
605
  <div id="context-gate"></div>
418
606
  <div class="form-actions"><button class="button primary" id="context-submit" type="submit" disabled><span data-icon="context"></span>Assemble context</button></div>
419
607
  </form>
420
608
  </div>
421
- <div class="panel" id="context-result"><div class="panel-head"><h2>Context preview</h2></div><div id="context-output"></div></div>
609
+ <div class="panel" id="context-result"><div class="panel-head context-result-head"><h2>Brief and evidence</h2><span class="small muted" id="context-result-state" role="status">Ready for a task</span></div><div id="context-output"></div></div>
422
610
  </div>
423
611
  </section>
612
+ ${PROJECT_KIT_MARKUP}
613
+ <section id="page-integrations" aria-labelledby="title-integrations" hidden>
614
+ <div class="page-head">
615
+ <div><h1 id="title-integrations" tabindex="-1">Integrations &amp; health</h1><p class="muted">Check local storage and see which capabilities each agent supports.</p></div>
616
+ <button class="button" type="button" id="integrations-refresh"><span data-icon="refresh"></span>Run checks</button>
617
+ </div>
618
+ <div id="integrations-body" aria-busy="true"></div>
619
+ </section>
424
620
  <section id="page-features" aria-labelledby="title-features" hidden>
425
621
  <div class="page-head"><div><h1 id="title-features" tabindex="-1">Features</h1><p class="muted">Control how Shadow Brain assembles context and updates the workspace.</p></div></div>
426
622
  <div class="stack settings">
@@ -457,6 +653,7 @@ const glyphs = {
457
653
  project: '<path d="M3.5 7V6A1.5 1.5 0 0 1 5 4.5h4l2 2h8A1.5 1.5 0 0 1 20.5 8v10A1.5 1.5 0 0 1 19 19.5H5A1.5 1.5 0 0 1 3.5 18Z"/>',
458
654
  agent: '<rect x="4.5" y="7" width="15" height="12" rx="3"/><path d="M12 7V4m-8 9H2m20 0h-2m-11-.5h.02m5.98 0H15m-5 3h4"/>',
459
655
  activity: '<path d="M3 12h4l3-7 4 14 3-7h4"/>',
656
+ integrations: '<path d="M9 3v4m6-4v4m-8 0h10v4a5 5 0 0 1-5 5h0a5 5 0 0 1-5-5zm5 9v5"/>',
460
657
  search: '<circle cx="10.5" cy="10.5" r="6"/><path d="m15 15 5 5"/>',
461
658
  source: '<path d="M9.5 14.5 14.5 9m-6 8.5-1 1a3.5 3.5 0 0 1-5-5L7 9a3.5 3.5 0 0 1 5 0m3.5-2.5 1-1a3.5 3.5 0 0 1 5 5L17 15a3.5 3.5 0 0 1-5 0"/>',
462
659
  list: '<path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/>',
@@ -466,12 +663,15 @@ const glyphs = {
466
663
  warning: '<path d="m12 3 10 17H2L12 3zm0 6v4m0 3h.01"/>',
467
664
  refresh: '<path d="M19.5 9a8 8 0 1 0 .3 5.5M20 4v5h-5"/>'
468
665
  };
469
- const viewNames = { overview: 'Overview', memories: 'Memories', graph: 'Graph', context: 'Context', features: 'Features' };
666
+ const viewNames = { overview: 'Overview', memories: 'Memories', memory: 'Memory detail', graph: 'Graph', context: 'Context Lab', 'project-kit': 'Project Kit', integrations: 'Integrations', features: 'Features' };
667
+ const reviewStates = { active: 'Active', contested: 'Needs review', archived: 'Archived', superseded: 'Superseded' };
668
+ const linkTypes = { related: 'Related', supports: 'Supports', contradicts: 'Contradicts', supersedes: 'Supersedes' };
470
669
  const definitions = [
471
670
  { id: 'contextAssembly', label: 'Context assembly', description: 'Build a task-specific brief from saved project memories.' },
472
671
  { id: 'crossProjectContext', label: 'Cross-project context', description: 'Allow context assembly to include eligible memories from other projects when requested.' },
473
672
  { id: 'automaticBriefing', label: 'Automatic briefing', description: 'Include a context brief when a supported agent starts work in a project.' },
474
- { id: 'liveActivity', label: 'Live activity', description: 'Refresh the workspace activity feed automatically.' }
673
+ { id: 'liveActivity', label: 'Live activity', description: 'Refresh the workspace activity feed automatically.' },
674
+ { id: 'activityRecording', label: 'Record agent activity', description: 'Record bounded tool timing and explicitly reported session phases.' }
475
675
  ];
476
676
  const $ = (id) => document.getElementById(id);
477
677
  const esc = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
@@ -487,11 +687,14 @@ const tags = (items) => '<div class="tags">' + items.filter(([, value]) => value
487
687
  const DATE = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
488
688
  const TIME = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
489
689
  const state = {
490
- view: '', projects: [], overviewLoaded: false, overviewPending: null,
690
+ view: '', route: '', projects: [], overviewLoaded: false, overviewPending: null, overviewScope: '', overviewRequest: 0, overviewRequestScope: '',
491
691
  activity: [], activityEnabled: null, activityPending: false, features: null, featurePending: null, featureSaving: false,
692
+ health: { snapshot: null, project: '', filter: '', request: 0 }, projectKit: null,
492
693
  memory: { query: null, request: 0, controller: null, limit: 20, offset: 0 },
493
- graph: { snapshot: null, project: null, mode: matchMedia('(max-width: 690px)').matches ? 'list' : 'map', selected: '', nodes: [], edges: [], layout: null, zoom: 1, panX: 0, panY: 0, request: 0, controller: null, drag: null },
494
- context: { pack: null, pending: false, dirty: false }
694
+ detail: { snapshot: null, id: '', project: '', back: '#memories', request: 0, targetRequest: 0, saving: false },
695
+ graph: { snapshot: null, project: null, mode: matchMedia('(max-width: 690px)').matches ? 'list' : 'map', selected: '', selectedEdge: '', focusMemory: '', lens: '', nodes: [], edges: [], layout: null, zoom: 1, panX: 0, panY: 0, request: 0, controller: null, drag: null },
696
+ context: { pack: null, params: null, comparison: null, comparing: false, pending: false, dirty: false, routeProject: '' },
697
+ integrations: { snapshot: null, pending: false }
495
698
  };
496
699
 
497
700
  document.querySelectorAll('[data-icon]').forEach((element) => { element.innerHTML = icon(element.dataset.icon); });
@@ -575,13 +778,40 @@ function routeLink(view, params) {
575
778
  return '#' + queryPath(view, params);
576
779
  }
577
780
 
781
+ function memoryLink(id, project, label, className = 'record-link') {
782
+ if (!id || !project) return esc(label);
783
+ return '<a class="' + className + '" href="' + esc(routeLink('memory', { id, project })) + '">' + esc(label) + '</a>';
784
+ }
785
+
786
+ function reviewLabel(value) {
787
+ return Object.hasOwn(reviewStates, value) ? reviewStates[value] : '';
788
+ }
789
+
790
+ function reviewTag(value) {
791
+ const label = reviewLabel(value);
792
+ return label ? '<span class="tag state-' + value + '">' + label + '</span>' : '';
793
+ }
794
+
578
795
  function renderSummary(totals) {
579
796
  return '<dl class="summary">' + [
580
- ['memory', 'Stored memories', totals.memories], ['project', 'Projects', totals.projects], ['agent', 'Contributing agents', totals.agents]
797
+ ['memory', 'Total stored memories', totals.memories], ['project', 'Projects', totals.projects], ['agent', 'Contributing agents', totals.agents]
581
798
  ].map(([image, label, value]) => '<div class="summary-item"><span class="tile-icon">' + icon(image) +
582
799
  '</span><div><dt>' + label + '</dt><dd>' + number(value) + '</dd></div></div>').join('') + '</dl>';
583
800
  }
584
801
 
802
+ function renderOverviewSummary(data) {
803
+ const counts = data.quality || {};
804
+ const total = Number(counts.total) || 0;
805
+ const sources = Number(counts.sourceBacked) || 0;
806
+ const percent = total ? Math.round(Math.min(1, sources / total) * 100) : 0;
807
+ const project = data.scope?.projectId;
808
+ const scope = project ? state.projects.find((item) => item.id === project)?.name || project : 'All projects';
809
+ return '<div class="overview-summary"><div class="overview-lead"><div class="coverage-ring" aria-hidden="true" style="--coverage:' + percent + '%"><span>' +
810
+ (total ? percent + '%' : '—') + '</span></div><div><h2>Recorded source coverage</h2><p class="small">' +
811
+ esc(scope) + ' · ' + number(sources) + ' of ' + number(total) + (data.truncated ? ' sampled memories' : ' memories') +
812
+ '</p><button class="button quiet" type="button" id="attention-jump">Review attention queue ' + icon('chevron', true) + '</button></div></div>' + renderSummary(data.totals || {}) + '</div>';
813
+ }
814
+
585
815
  function renderProjects(projects) {
586
816
  if (!projects.length) return blank('project', 'No projects yet', 'Projects appear after an agent stores a memory with a project scope.');
587
817
  const max = Math.max(1, ...projects.map((project) => Number(project.memories) || 0));
@@ -601,8 +831,85 @@ function renderOverviewMemories(memories) {
601
831
  if (!memories.length) return '';
602
832
  return '<div class="panel"><div class="panel-head"><h2>Latest memories</h2><a class="button quiet" href="#memories">Browse ' + icon('chevron', true) +
603
833
  '</a></div><ul class="list">' + memories.map((item) => '<li class="overview-memory"><div class="memory-head"><strong>' +
604
- esc(item.title || nice(item.category) || 'Memory') + '</strong>' + dateMarkup(item.updatedAt, true) + '</div>' +
605
- tags([['project', item.projectName || item.project], ['agent', item.agent], ['memory', nice(item.category)]]) + '</li>').join('') + '</ul></div>';
834
+ memoryLink(item.id, item.project, item.title || nice(item.category) || 'Memory') + '</strong>' + dateMarkup(item.updatedAt, true) + '</div>' +
835
+ tags([['project', item.projectName || item.project], ['agent', item.agent], ['memory', nice(item.category)], ['state-' + (item.effectiveState || ''), reviewLabel(item.effectiveState)]]) + '</li>').join('') + '</ul></div>';
836
+ }
837
+
838
+ function renderQuality(data) {
839
+ const quality = data.quality;
840
+ if (!quality || !Number.isFinite(quality.total)) return '';
841
+ const project = data.scope?.projectId || '';
842
+ const scope = data.truncated ? 'Based on the latest ' + number(data.inventoryLimit) + ' stored memories.' :
843
+ project ? 'Stored memories in this project.' : 'Stored memories across projects.';
844
+ const statusCounts = Object.entries(reviewStates).map(([key, label]) => ({ key, label, count: Number(quality[key]) || 0 }));
845
+ const coverage = quality.total ? Math.max(0, Math.min(100, Number(quality.sourceBacked || 0) / quality.total * 100)) : 0;
846
+ const statusBars = statusCounts.map(({ key, count }) =>
847
+ '<span class="quality-' + key + '" style="width:' + Math.max(0, Math.min(100, count / quality.total * 100)) + '%"></span>').join('');
848
+ const statusLinks = statusCounts.map(({ key, label, count }) => '<a href="' + esc(routeLink('memories', { project, state: key })) +
849
+ '"><i aria-hidden="true" class="quality-' + key + '"></i>' + esc(label) + ' <strong>' + number(count) + '</strong></a>').join('');
850
+ return '<div class="panel"><div class="panel-head"><div><h2>Review and sources</h2><p class="small muted">' + esc(scope) +
851
+ '</p></div></div>' + (quality.total ? '<div class="panel-body stack"><div class="stack" style="gap:8px"><h3>Memory status</h3>' +
852
+ '<div class="quality-track" aria-hidden="true">' + statusBars + '</div><div class="quality-labels">' + statusLinks + '</div></div>' +
853
+ '<div class="stack" style="gap:8px"><div class="quality-source"><h3>Recorded sources</h3><span class="small muted">' +
854
+ number(quality.sourceBacked) + ' of ' + number(quality.total) + ' memories</span></div>' +
855
+ '<div class="quality-track" aria-hidden="true"><span class="quality-active" style="width:' + coverage + '%"></span></div></div>' +
856
+ (Number.isFinite(quality.reviewed) ? '<p class="small muted">' + number(quality.reviewed) + ' with a recorded review</p>' : '') +
857
+ '</div>' : blank('memory', 'No memories in scope', 'Save a memory to see review and source counts here.')) + '</div>';
858
+ }
859
+
860
+ const healthIssues = {
861
+ contested: 'Needs review', contradiction: 'Contradictory link', 'orphaned-link': 'Missing linked memory',
862
+ duplicate: 'Repeated content', 'no-source': 'No recorded source', 'older-memory': 'Older than 90 days'
863
+ };
864
+
865
+ function renderAttention() {
866
+ const target = $('attention-results');
867
+ const store = state.health;
868
+ if (!target || !store.snapshot || store.project !== state.overviewScope) return;
869
+ const data = store.snapshot;
870
+ const counts = data.counts || {};
871
+ const rows = list(data.queue).filter((item) => !store.filter || list(item.issues).some((issue) => issue.kind === store.filter));
872
+ const options = Object.entries(healthIssues).map(([value, label]) => '<option value="' + value + '">' + label + '</option>').join('');
873
+ const controls = $('attention-filter');
874
+ if (!controls.childElementCount) controls.innerHTML = '<option value="">All issues</option>' + options;
875
+ controls.value = store.filter;
876
+ controls.disabled = !list(data.queue).length;
877
+ const scopeNote = (data.coverage?.truncated ? 'Checked the latest ' + number(data.coverage.limit) + ' memories in scope.' : 'Checked ' + number(counts.total) + ' memories in scope.');
878
+ $('attention-scope').textContent = scopeNote;
879
+ const warnings = list(data.warnings).map((warning) => '<p class="small muted panel-body">' + esc(warning) + '</p>').join('');
880
+ const count = '<p class="small muted panel-body">Showing ' + number(rows.length) + ' of ' + number(data.queueTotal) + ' flagged memories' +
881
+ (store.filter ? ' · filter applied to loaded items' : '') + '.</p>';
882
+ const items = rows.map((item) => '<li class="attention-row"><span class="tile-icon">' + icon('warning', true) + '</span><div>' +
883
+ memoryLink(item.id, item.projectId, item.title || item.id) + '<div class="tags">' +
884
+ tags([['project', state.projects.find((project) => project.id === item.projectId)?.name || item.projectId], ['memory', nice(item.category)]]) +
885
+ list(item.issues).map((issue) => '<span class="tag check-' + (issue.severity === 'review' ? 'warn' : '') + '">' +
886
+ esc(issue.label || healthIssues[issue.kind] || 'Review') + '</span>').join('') + '</div></div></li>').join('');
887
+ target.innerHTML = rows.length ? count + '<ul class="list attention-list" tabindex="0" aria-label="Flagged memories">' + items + '</ul>' + warnings :
888
+ blank('memory', store.filter ? 'No loaded items match' : 'No attention items', store.filter ? 'Choose another issue or show all loaded items.' : 'Review flags will appear here as the memory store changes.') + warnings;
889
+ }
890
+
891
+ async function loadHealth(project) {
892
+ const target = $('attention-results');
893
+ if (!target) return;
894
+ const store = state.health;
895
+ const request = ++store.request;
896
+ if (store.project !== project) store.filter = '';
897
+ store.project = project;
898
+ store.snapshot = null;
899
+ $('attention-filter').disabled = true;
900
+ $('attention-scope').textContent = '';
901
+ target.innerHTML = skeleton('Checking memory health…');
902
+ setBusy(target, true);
903
+ try {
904
+ const snapshot = await api(queryPath('memory-health', { project, limit: 30 }));
905
+ if (request !== store.request || target !== $('attention-results')) return;
906
+ store.snapshot = snapshot;
907
+ renderAttention();
908
+ } catch (error) {
909
+ if (request === store.request && target === $('attention-results')) target.innerHTML = errorMarkup(error.message, 'health');
910
+ } finally {
911
+ if (request === store.request && target === $('attention-results')) setBusy(target, false);
912
+ }
606
913
  }
607
914
 
608
915
  function renderActivity() {
@@ -633,32 +940,46 @@ function renderOverview(data) {
633
940
  number(data.inventoryLimit) + ' stored memories.</p>' : '';
634
941
  const pending = data.storageHealth?.status === 'pending' ? '<p class="status small muted notice">The memory store has pending writes.</p>' : '';
635
942
  const projectNote = state.projects.length > 8 ? 'Top projects by saved memory count' : 'Saved memories by project';
636
- $('overview-body').innerHTML = '<div id="overview-refresh"></div>' + pending + sample + renderSummary(data.totals || {}) +
637
- '<div class="split overview-columns"><div class="stack"><div class="panel"><div class="panel-head"><div><h2>Project inventory</h2>' +
943
+ optionSet('overview-quality-project', 'All projects', state.projects, data.scope?.projectId || '');
944
+ $('overview-context').href = routeLink('context', { project: data.scope?.projectId });
945
+ $('overview-body').innerHTML = '<div id="overview-refresh"></div>' + pending + renderOverviewSummary(data) + sample +
946
+ '<div class="split overview-columns"><div class="stack"><section class="panel" aria-labelledby="attention-heading"><div class="panel-head"><div>' +
947
+ '<h2 id="attention-heading" tabindex="-1">Attention queue</h2><p class="small muted" id="attention-scope"></p></div>' +
948
+ '<label class="label" for="attention-filter">Issue<select id="attention-filter" disabled></select></label></div>' +
949
+ '<div id="attention-results"></div></section>' + renderQuality(data) + '<div class="panel"><div class="panel-head"><div><h2>Project inventory</h2>' +
638
950
  '<p class="muted small">' + projectNote + '</p></div><a class="button quiet" href="#memories">Browse ' + icon('chevron', true) +
639
951
  '</a></div>' + renderProjects(state.projects) + '</div>' + renderOverviewMemories(list(data.recentMemories)) +
640
- '</div><div class="panel"><div class="panel-head"><h2>Recent activity</h2><span id="activity-status" class="small muted"></span></div><div id="activity-body"></div></div></div>';
952
+ '</div><div class="panel overview-activity"><div class="panel-head"><div><h2>Recent activity</h2><span id="activity-status" class="small muted"></span></div><a class="button quiet" href="/live">Open live operations ' + icon('chevron', true) + '</a></div><div id="activity-body"></div></div></div>';
641
953
  renderActivity();
954
+ if (state.view === 'overview') void loadHealth(data.scope?.projectId || '');
642
955
  }
643
956
 
644
957
  function syncProjectOptions() {
645
- const contextProject = $('context-project').value;
958
+ const contextProject = state.context.routeProject || $('context-project').value;
646
959
  const graphProject = state.view === 'graph' ? routeParams().get('project') : null;
647
- optionSet('context-project', 'Select a project', state.projects, contextProject || routeParams().get('project'));
960
+ optionSet('context-project', 'Select a project', state.projects, contextProject);
961
+ if ($('context-project').value === state.context.routeProject) state.context.routeProject = '';
648
962
  optionSet('graph-project', 'All projects', state.projects, $('graph-project').value || graphProject);
649
963
  }
650
964
 
651
- async function loadOverview(force = false) {
652
- if (state.overviewPending) return state.overviewPending;
653
- if (state.overviewLoaded && !force) return;
965
+ async function loadOverview(force = false, project = '') {
966
+ if (state.overviewPending && state.overviewRequestScope === project) return state.overviewPending;
967
+ if (state.overviewLoaded && !force && state.overviewScope === project) return;
968
+ const request = ++state.overviewRequest;
969
+ state.overviewRequestScope = project;
654
970
  const target = $('overview-body');
655
971
  if (!state.overviewLoaded) target.innerHTML = '<div class="panel">' + skeleton('Loading workspace…') + '</div>';
972
+ else if (state.overviewScope !== project && $('overview-refresh')) $('overview-refresh').innerHTML =
973
+ '<div class="notice status small muted" role="status">Loading the selected project. Showing the previous scope.</div>';
656
974
  setBusy(target, true);
657
975
  state.overviewPending = (async () => {
658
976
  try {
659
- const data = await api('overview');
977
+ const data = await api(queryPath('overview', { project }));
978
+ if (request !== state.overviewRequest) return;
660
979
  state.overviewLoaded = true;
980
+ state.overviewScope = project;
661
981
  state.projects = list(data.projects);
982
+ state.projectKit = data.projectKit ?? null;
662
983
  state.activity = list(data.activity);
663
984
  if (typeof data.features?.liveActivity === 'boolean') state.activityEnabled = data.features.liveActivity;
664
985
  if (data.version) $('rail-version').textContent = 'Version ' + data.version;
@@ -666,12 +987,15 @@ async function loadOverview(force = false) {
666
987
  renderOverview(data);
667
988
  renderContextControls();
668
989
  } catch (error) {
990
+ if (request !== state.overviewRequest) return;
669
991
  if (!state.overviewLoaded) target.innerHTML = errorMarkup(error.message, 'overview');
670
- else $('overview-refresh').innerHTML = '<div class="notice">' + errorMarkup('Could not refresh the workspace. Showing the last loaded overview.', 'overview') + '</div>';
992
+ else if ($('overview-refresh')) $('overview-refresh').innerHTML = '<div class="notice">' + errorMarkup('Could not refresh the workspace. Showing the last loaded overview.', 'overview') + '</div>';
671
993
  renderContextControls();
672
994
  } finally {
673
- setBusy(target, false);
674
- state.overviewPending = null;
995
+ if (request === state.overviewRequest) {
996
+ setBusy(target, false);
997
+ state.overviewPending = null;
998
+ }
675
999
  }
676
1000
  })();
677
1001
  return state.overviewPending;
@@ -705,7 +1029,8 @@ function memoryQuery() {
705
1029
  q: $('memory-query').value.trim(),
706
1030
  project: $('memory-project').value,
707
1031
  category: $('memory-category').value,
708
- agent: $('memory-agent').value
1032
+ agent: $('memory-agent').value,
1033
+ state: $('memory-state').value
709
1034
  };
710
1035
  }
711
1036
 
@@ -731,8 +1056,8 @@ function renderMemories(data) {
731
1056
  const detail = reasons.length ? '<details><summary>Why this matched</summary><ul class="small muted">' +
732
1057
  reasons.map((reason) => '<li>' + esc(reason) + '</li>').join('') + '</ul></details>' : '';
733
1058
  const snippet = compact(item.snippet) && compact(item.snippet) !== compact(title) ? '<p>' + esc(item.snippet) + '</p>' : '';
734
- return '<li class="memory-row"><div class="memory-head"><h3>' + esc(title) + '</h3>' + dateMarkup(item.updatedAt, true) + '</div>' +
735
- tags([['project', item.projectName || item.project], ['agent', item.agent], ['memory', nice(item.category)]]) + snippet +
1059
+ return '<li class="memory-row"><div class="memory-head"><h3>' + memoryLink(item.id, item.project, title) + '</h3>' + dateMarkup(item.updatedAt, true) + '</div>' +
1060
+ tags([['project', item.projectName || item.project], ['agent', item.agent], ['memory', nice(item.category)], ['state-' + (item.effectiveState || ''), reviewLabel(item.effectiveState)]]) + snippet +
736
1061
  sourceMarkup([...list(item.sources), item.source]) + detail + '</li>';
737
1062
  }).join('') + '</ul>' + (offset || total > items.length ? '<div class="list-footer"><span class="muted small">' +
738
1063
  number(offset + 1) + '–' + number(offset + items.length) + ' of ' + number(total) + '</span><div class="form-actions">' +
@@ -771,9 +1096,193 @@ async function loadMemories(query, offset = 0) {
771
1096
  }
772
1097
  }
773
1098
 
1099
+ function linkCaption(link, memoryId) {
1100
+ if (link.type === 'related') return 'Related to';
1101
+ if (link.fromId === memoryId) return linkTypes[link.type] || nice(link.type);
1102
+ return { supports: 'Supported by', contradicts: 'Contradicted by', supersedes: 'Superseded by' }[link.type] || nice(link.type);
1103
+ }
1104
+
1105
+ function renderReviewHistory(events) {
1106
+ const recent = [...list(events)].sort((a, b) => Number(b.revision) - Number(a.revision)).slice(0, 12);
1107
+ if (!recent.length) return '';
1108
+ const rows = recent.map((event) => {
1109
+ const command = event.command || {};
1110
+ const label = command.kind === 'review' ? 'Recorded review · ' + reviewLabel(command.state) :
1111
+ command.kind === 'link' ? 'Added link · ' + (linkTypes[command.type] || nice(command.type)) :
1112
+ command.kind === 'unlink' ? 'Removed link' : 'Memory change';
1113
+ return '<li class="detail-row"><div class="memory-head"><strong>' + esc(label) + '</strong>' + dateMarkup(event.at) + '</div>' +
1114
+ (command.note ? '<p class="detail-note small muted">' + esc(command.note) + '</p>' : '') + '</li>';
1115
+ }).join('');
1116
+ return '<section class="panel"><div class="panel-head"><h2>Recent changes</h2></div><ul class="list">' + rows + '</ul></section>';
1117
+ }
1118
+
1119
+ function renderDetail(snapshot) {
1120
+ const { memory, review, links } = snapshot;
1121
+ const project = memory.project || memory.projectId || state.detail.project;
1122
+ const effective = snapshot.effectiveState || memory.effectiveState || review?.state;
1123
+ const title = memory.title || nice(memory.category) || 'Memory detail';
1124
+ const content = memory.content ?? memory.snippet;
1125
+ const ownState = review?.state || 'active';
1126
+ const reviewOptions = Object.entries(reviewStates).filter(([key]) => key !== 'superseded')
1127
+ .map(([key, label]) => '<option value="' + key + '"' + (key === ownState ? ' selected' : '') + '>' + label + '</option>').join('');
1128
+ const relationOptions = Object.entries(linkTypes).map(([key, label]) => '<option value="' + key + '">' + label + '</option>').join('');
1129
+ const replacement = list(links).find((link) => link.type === 'supersedes' && link.toId === memory.id);
1130
+ const titleEl = $('title-memory');
1131
+ titleEl.textContent = title;
1132
+ $('detail-meta').innerHTML = tags([
1133
+ ['project', memory.projectName || project], ['agent', memory.agent], ['memory', nice(memory.category)],
1134
+ ['state-' + (effective || ''), reviewLabel(effective)]
1135
+ ]) + (memory.createdAt || memory.updatedAt ? '<div class="small muted">' + dateMarkup(memory.createdAt || memory.updatedAt, true) + '</div>' : '');
1136
+ $('detail-graph').href = routeLink('graph', { project, memory: memory.id });
1137
+ $('detail-context').href = routeLink('context', { project });
1138
+ const projectFeedback = document.documentElement.dataset.projectLocal === 'true' || state.projectKit?.projectId === project;
1139
+ $('detail-outcome').hidden = !projectFeedback;
1140
+ $('detail-outcome').href = routeLink('project-kit', { memory: memory.id });
1141
+ $('detail-actions').hidden = false;
1142
+ const linkRows = list(links).map((link) => {
1143
+ const outgoing = link.fromId === memory.id;
1144
+ const endpoint = outgoing ? link.toId : link.fromId;
1145
+ const endpointTitle = (outgoing ? link.toTitle : link.fromTitle) || endpoint;
1146
+ const endpointLink = link.orphaned ? esc(endpointTitle) : memoryLink(endpoint, project, endpointTitle);
1147
+ return '<li class="detail-row"><div class="tags"><span class="tag memory">' + esc(linkCaption(link, memory.id)) + '</span>' +
1148
+ endpointLink + '</div>' +
1149
+ (link.note ? '<p class="detail-note small muted">' + esc(link.note) + '</p>' : '') +
1150
+ '<details><summary class="small muted">Remove link</summary><div class="form-actions"><button class="button" type="button" data-unlink="' +
1151
+ esc(link.id) + '">Remove this link</button></div></details></li>';
1152
+ }).join('');
1153
+ const warnings = list(snapshot.warnings).map((warning) => '<p class="status small muted notice">' + esc(warning) + '</p>').join('');
1154
+ $('detail-body').innerHTML = warnings + '<div class="split detail-layout"><div class="stack">' +
1155
+ '<section class="panel"><div class="panel-head"><h2>Saved memory</h2></div><div class="panel-body">' +
1156
+ '<p class="memory-content">' + esc(content || 'No saved text.') + '</p></div></section>' +
1157
+ '<section class="panel"><div class="panel-head"><h2>Recorded sources</h2></div><div class="panel-body detail-sources">' +
1158
+ sourceMarkup([...list(memory.sources), ...list(snapshot.sources), memory.source]) + '</div></section>' +
1159
+ '<section class="panel"><div class="panel-head"><h2>Connections</h2><span class="small muted">' + countLabel(list(links).length, 'link') +
1160
+ '</span></div>' + (linkRows ? '<ul class="list">' + linkRows + '</ul>' :
1161
+ '<div class="panel-body"><p class="small muted">No links recorded for this memory.</p></div>') +
1162
+ '<div class="panel-body"><details id="link-editor"><summary>Add a memory link</summary><div class="detail-form" style="margin-top:10px">' +
1163
+ '<form id="link-search" class="link-search"><label class="label" for="link-query">Find a memory in this project<input id="link-query" type="search" maxlength="500" placeholder="Search by keyword"></label>' +
1164
+ '<button type="submit" class="button">Find</button></form>' +
1165
+ '<form id="link-form" class="detail-form"><label class="label" for="link-target">Target memory<select id="link-target" required><option value="">Choose a memory</option></select></label>' +
1166
+ '<p id="link-search-status" class="small muted" role="status"></p>' +
1167
+ '<label class="label" for="link-type">Relationship from this memory<select id="link-type">' + relationOptions + '</select></label>' +
1168
+ '<label class="label" for="link-note">Note<textarea id="link-note" maxlength="1000" rows="2" placeholder="Describe the relationship"></textarea></label>' +
1169
+ '<div class="form-actions"><button class="button primary" id="link-submit" type="submit">Save link</button></div></form></div></details></div>' +
1170
+ '<p id="link-edit-status" class="save-status small muted" role="status"></p></section>' +
1171
+ '</div><div class="stack"><section class="panel"><div class="panel-head"><h2>Recorded review</h2>' + reviewTag(effective) + '</div>' +
1172
+ '<div class="panel-body detail-form">' +
1173
+ (effective === 'superseded' ? '<p class="small muted">This memory is superseded' +
1174
+ (replacement?.fromId && !replacement.orphaned ? ' by ' + memoryLink(replacement.fromId, project, replacement.fromTitle || replacement.fromId) : '') + '.</p>' : '') +
1175
+ (review?.updatedAt ? '<div class="small muted">Reviewed ' + dateMarkup(review.updatedAt, true) + '</div>' :
1176
+ '<p class="small muted">No review recorded.</p>') +
1177
+ '<form id="review-form" class="detail-form"><label class="label" for="review-state">Manual status<select id="review-state">' +
1178
+ reviewOptions + '</select></label><label class="label" for="review-note">Review note<textarea id="review-note" maxlength="1000" rows="3" placeholder="Record what you checked">' +
1179
+ esc(review?.note) + '</textarea></label><div class="form-actions"><button class="button primary" id="review-submit" type="submit">Save review</button></div></form>' +
1180
+ '</div><p id="detail-status" class="save-status small muted" role="status"></p></section>' +
1181
+ renderReviewHistory(snapshot.history) + '</div></div>';
1182
+ }
1183
+
1184
+ async function loadDetail(id, project, failurePrefix = '') {
1185
+ const store = state.detail;
1186
+ const request = ++store.request;
1187
+ store.id = id;
1188
+ store.project = project;
1189
+ store.snapshot = null;
1190
+ $('detail-actions').hidden = true;
1191
+ $('title-memory').textContent = 'Memory detail';
1192
+ $('detail-meta').replaceChildren();
1193
+ const target = $('detail-body');
1194
+ setBusy(target, true);
1195
+ target.innerHTML = skeleton('Loading memory…');
1196
+ try {
1197
+ if (!id || !project) {
1198
+ target.innerHTML = '<div class="panel">' + blank('memory', 'Choose a memory', 'Open a record from your memories, context, or graph.',
1199
+ '<a href="#memories" class="button quiet">Browse memories</a>') + '</div>';
1200
+ return false;
1201
+ }
1202
+ const snapshot = await api(queryPath('memory', { id, project }));
1203
+ if (request !== store.request) return false;
1204
+ if (snapshot.memory?.id !== id || (snapshot.memory.project || snapshot.memory.projectId) !== project ||
1205
+ !Number.isSafeInteger(snapshot.revision)) throw new Error('The server returned an unreadable memory.');
1206
+ store.snapshot = snapshot;
1207
+ renderDetail(snapshot);
1208
+ return true;
1209
+ } catch (error) {
1210
+ if (request !== store.request) return false;
1211
+ target.innerHTML = errorMarkup(failurePrefix + error.message, error.status === 404 ? null : 'detail');
1212
+ return false;
1213
+ } finally {
1214
+ if (request === store.request) setBusy(target, false);
1215
+ }
1216
+ }
1217
+
1218
+ async function searchLinkTargets() {
1219
+ const store = state.detail;
1220
+ if (!store.snapshot || store.saving) return;
1221
+ const request = ++store.targetRequest;
1222
+ const detailRequest = store.request;
1223
+ const target = $('link-search-status');
1224
+ target.textContent = 'Looking for memories…';
1225
+ try {
1226
+ const q = $('link-query').value.trim();
1227
+ const result = await api(queryPath('memories', { q, project: store.project, limit: 15 }));
1228
+ if (request !== store.targetRequest || detailRequest !== store.request) return;
1229
+ const choices = list(result.items).filter((item) => item.id !== store.id && (item.project || item.projectId) === store.project)
1230
+ .map((item) => ({ id: item.id, name: item.title || item.id }));
1231
+ optionSet('link-target', 'Choose a memory', choices, '');
1232
+ target.textContent = choices.length ? countLabel(choices.length, 'memory', 'memories') + ' found.' : 'No other memories matched.';
1233
+ } catch (error) {
1234
+ if (request === store.targetRequest && detailRequest === store.request) target.textContent = error.message;
1235
+ }
1236
+ }
1237
+
1238
+ function detailStatus(id, message, error = false) {
1239
+ const status = $(id);
1240
+ if (!status) return;
1241
+ status.textContent = message;
1242
+ status.dataset.error = String(error);
1243
+ }
1244
+
1245
+ async function saveDetail(kind, payload, focus) {
1246
+ const store = state.detail;
1247
+ if (!store.snapshot || store.saving) return;
1248
+ const { id, project, request } = store;
1249
+ const status = kind === 'memory-review' ? 'detail-status' : 'link-edit-status';
1250
+ const controls = () => $('detail-body').querySelectorAll('button, select, textarea');
1251
+ store.saving = true;
1252
+ controls().forEach((element) => { element.disabled = true; });
1253
+ detailStatus(status, 'Saving…');
1254
+ try {
1255
+ await api(kind, { body: { projectId: project, expectedRevision: store.snapshot.revision, ...payload } });
1256
+ state.graph.snapshot = null;
1257
+ state.overviewLoaded = false;
1258
+ if (state.context.pack || state.context.pending) {
1259
+ state.context.dirty = true;
1260
+ if (!state.context.pending) renderContext();
1261
+ }
1262
+ if (request !== store.request || state.view !== 'memory') return;
1263
+ if (await loadDetail(id, project, 'Saved. Could not reload the memory. ')) detailStatus(status, 'Saved.');
1264
+ } catch (error) {
1265
+ if (request !== store.request || state.view !== 'memory') return;
1266
+ if (error.status === 409) {
1267
+ if (await loadDetail(id, project, 'This memory changed elsewhere. ')) detailStatus(status, 'This memory changed elsewhere. Review the latest state and try again.', true);
1268
+ } else {
1269
+ detailStatus(status, error.message, true);
1270
+ }
1271
+ } finally {
1272
+ store.saving = false;
1273
+ if (state.view === 'memory' && id === store.id && project === store.project) {
1274
+ controls().forEach((element) => { element.disabled = false; });
1275
+ if (focus === 'link-editor' && $(focus)) $(focus).querySelector('summary')?.focus();
1276
+ else ($(focus) || $('title-memory')).focus();
1277
+ }
1278
+ }
1279
+ }
1280
+
774
1281
  const graphKinds = { project: 'Project', agent: 'Agent', memory: 'Memory' };
775
- const graphEdgeLabels = { 'belongs-to': 'Belongs to project', 'recorded-by': 'Recorded by agent' };
1282
+ const graphEdgeLabels = { 'belongs-to': 'Belongs to project', 'recorded-by': 'Recorded by agent', ...linkTypes };
776
1283
  const graphEdgeClass = { 'belongs-to': ' project-edge', 'recorded-by': ' agent-edge' };
1284
+ const recordedEdge = (edge) => edge?.id && Object.hasOwn(linkTypes, edge.kind);
1285
+ const graphMemoryId = (node) => node.memoryId || (node.kind === 'memory' && node.id.startsWith('memory:') ? node.id.slice(7) : '');
777
1286
 
778
1287
  function graphNeighbors(edges, id) {
779
1288
  return edges.filter((edge) => edge.source === id || edge.target === id).map((edge) => ({
@@ -893,6 +1402,15 @@ function graphEdgePath(edge, layout) {
893
1402
  const by = b.y + b.height / 2;
894
1403
  const start = 'M' + ax + ' ' + ay;
895
1404
  if (edge.kind === 'belongs-to') return start + ' L' + bx + ' ' + by;
1405
+ if (recordedEdge(edge)) {
1406
+ const dx = bx - ax;
1407
+ const dy = by - ay;
1408
+ const length = Math.hypot(dx, dy) || 1;
1409
+ const pad = Math.min(b.width / 2 / Math.abs(dx), b.height / 2 / Math.abs(dy), .38);
1410
+ const lift = Math.min(63, Math.max(20, length * .25));
1411
+ return start + ' Q' + ((ax + bx) / 2 - dy / length * lift) + ' ' + ((ay + by) / 2 + dx / length * lift) +
1412
+ ' ' + (bx - dx * pad) + ' ' + (by - dy * pad);
1413
+ }
896
1414
  const bend = (ay + by) / 2;
897
1415
  return start + ' C' + ax + ' ' + bend + ' ' + bx + ' ' + bend + ' ' + bx + ' ' + by;
898
1416
  }
@@ -915,7 +1433,13 @@ function graphNodeMarkup(node, position, tabIndex) {
915
1433
  }
916
1434
 
917
1435
  function renderGraphList() {
918
- $('graph-list').innerHTML = '<ul class="list">' + state.graph.nodes.map((node) => '<li class="graph-row"><button type="button" class="button" data-node="' +
1436
+ const graph = state.graph;
1437
+ const byId = new Map(graph.nodes.map((node) => [node.id, node]));
1438
+ const links = graph.edges.filter(recordedEdge).map((edge) => '<li class="graph-row"><button type="button" class="button" data-edge-record="' +
1439
+ esc(edge.id) + '" aria-pressed="false"><span>' + esc(byId.get(edge.source)?.label || edge.source) + ' · ' +
1440
+ esc(graphEdgeLabels[edge.kind]) + ' · ' + esc(byId.get(edge.target)?.label || edge.target) + '</span></button></li>').join('');
1441
+ $('graph-list').innerHTML = (links ? '<details open class="panel-body"><summary>Recorded memory links</summary><ul class="list">' + links + '</ul></details>' : '') +
1442
+ '<ul class="list">' + graph.nodes.map((node) => '<li class="graph-row"><button type="button" class="button" data-node="' +
919
1443
  esc(node.id) + '" aria-pressed="false">' + icon(node.kind, true) + '<span>' + esc(node.label || node.id) +
920
1444
  '</span><span class="tag ' + node.kind + '">' + graphKinds[node.kind] + '</span></button></li>').join('') + '</ul>';
921
1445
  }
@@ -926,12 +1450,27 @@ function renderGraphMap() {
926
1450
  const svg = $('graph-svg');
927
1451
  const clusters = layout.clusters.map(({ cx, cy, rx, ry }) => '<ellipse class="cluster-area" cx="' + cx + '" cy="' + cy +
928
1452
  '" rx="' + rx + '" ry="' + ry + '"/>').join('');
929
- const edges = graph.edges.map((edge, i) => '<path class="graph-edge' + (graphEdgeClass[edge.kind] || '') +
930
- '" data-edge="' + i + '" d="' + graphEdgePath(edge, layout) + '"/>').join('');
1453
+ const byId = new Map(graph.nodes.map((node) => [node.id, node]));
1454
+ const structural = [];
1455
+ const recordedLinks = [];
1456
+ graph.edges.forEach((edge, i) => {
1457
+ const path = graphEdgePath(edge, layout);
1458
+ const recorded = recordedEdge(edge);
1459
+ const visual = '<path class="graph-edge' + (recorded ? ' recorded-edge ' + edge.kind : graphEdgeClass[edge.kind] || '') +
1460
+ '" data-edge="' + i + '" d="' + path + '"' + (recorded && edge.kind !== 'related' ? ' marker-end="url(#graph-arrow)"' : '') + '/>';
1461
+ if (recorded) {
1462
+ const from = byId.get(edge.source)?.label || edge.source;
1463
+ const to = byId.get(edge.target)?.label || edge.target;
1464
+ const label = from + ' · ' + graphEdgeLabels[edge.kind] + ' · ' + to;
1465
+ recordedLinks.push('<g class="graph-link-group" data-edge-record="' + esc(edge.id) + '" role="button" tabindex="-1" aria-pressed="false" aria-label="' +
1466
+ esc(label) + '"><title>' + esc(label) + '</title>' + visual + '<path class="graph-edge-hit" d="' + path + '"/></g>');
1467
+ } else structural.push(visual);
1468
+ });
931
1469
  const nodes = graph.nodes.map((node, i) => graphNodeMarkup(node, layout.positions.get(node.id), i === 0 ? 0 : -1)).join('');
932
- svg.innerHTML = '<defs><pattern id="map-dots" width="18" height="18" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1" fill="var(--graph-dot)"/></pattern></defs>' +
933
- '<rect width="100%" height="100%" fill="url(#map-dots)"/><g id="graph-transform"><g aria-hidden="true">' + clusters + edges + '</g>' +
934
- nodes + '<g class="graph-tooltip" id="graph-tooltip" aria-hidden="true"></g></g>';
1470
+ svg.innerHTML = '<defs><pattern id="map-dots" width="18" height="18" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1" fill="var(--graph-dot)"/></pattern>' +
1471
+ '<marker id="graph-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path class="graph-arrow" d="M0 1 9 5 0 9z"/></marker></defs>' +
1472
+ '<rect width="100%" height="100%" fill="url(#map-dots)"/><g id="graph-transform"><g aria-hidden="true">' + clusters + structural.join('') + '</g>' +
1473
+ recordedLinks.join('') + nodes + '<g class="graph-tooltip" id="graph-tooltip" aria-hidden="true"></g></g>';
935
1474
  fitGraph();
936
1475
  }
937
1476
 
@@ -985,6 +1524,10 @@ function centerGraphNode(id) {
985
1524
  const position = graph.layout?.positions.get(id);
986
1525
  if (!position || graph.mode !== 'map') return;
987
1526
  const { width, height } = graphViewport();
1527
+ const left = graph.panX + position.x * graph.zoom;
1528
+ const top = graph.panY + position.y * graph.zoom;
1529
+ if (graph.zoom >= .8 && left >= 14 && top >= 14 && left + position.width * graph.zoom <= width - 14 &&
1530
+ top + position.height * graph.zoom <= height - 52) return;
988
1531
  const zoom = Math.max(.8, graph.zoom);
989
1532
  graph.zoom = zoom;
990
1533
  graph.panX = width / 2 - (position.x + position.width / 2) * zoom;
@@ -1015,15 +1558,28 @@ function renderGraphInspector() {
1015
1558
  const all = new Map(list(graph.snapshot?.nodes).map((node) => [node.id, node]));
1016
1559
  const selected = all.get(graph.selected);
1017
1560
  const inspector = $('graph-inspector');
1561
+ const selectedEdge = graph.edges.find((edge) => recordedEdge(edge) && edge.id === graph.selectedEdge);
1562
+ if (selectedEdge) {
1563
+ const from = all.get(selectedEdge.source);
1564
+ const to = all.get(selectedEdge.target);
1565
+ const record = (node) => node ? memoryLink(graphMemoryId(node), node.project, node.label || node.id) : '';
1566
+ const endpoint = (label, node) => (selectedEdge.kind === 'related' ? '' : '<p class="small muted">' + label + '</p>') + '<p>' + record(node) + '</p>';
1567
+ inspector.innerHTML = '<div class="panel-head"><h3>Link details</h3></div><div class="panel-body">' +
1568
+ '<div>' + endpoint('From memory', from) + '</div>' + tags([['memory', graphEdgeLabels[selectedEdge.kind]], ['agent', 'Recorded memory link']]) +
1569
+ '<div>' + endpoint('To memory', to) + '</div>' + (selectedEdge.note ? '<p class="small muted detail-note">' + esc(selectedEdge.note) + '</p>' : '') + '</div>';
1570
+ return;
1571
+ }
1018
1572
  if (!selected) {
1019
1573
  inspector.innerHTML = '<div class="panel-head"><h3>Node details</h3></div>' +
1020
1574
  blank('graph', 'Select a node', 'See its stored details and follow its recorded connections.');
1021
1575
  return;
1022
1576
  }
1023
- const adjacent = graphNeighbors(list(graph.snapshot.edges), selected.id).filter((item) => all.has(item.id));
1577
+ const adjacent = graphNeighbors(graph.edges, selected.id).filter((item) => all.has(item.id));
1024
1578
  const connected = adjacent.length ? '<details open><summary>' + countLabel(adjacent.length, 'connection') + '</summary><div class="related">' +
1025
1579
  adjacent.map(({ id, edge }) => {
1026
1580
  const node = all.get(id);
1581
+ if (recordedEdge(edge)) return '<button type="button" class="button quiet" data-edge-record="' + esc(edge.id) +
1582
+ '"><span>' + esc(linkCaption({ fromId: edge.source, type: edge.kind }, selected.id)) + ' · ' + esc(node.label || node.id) + '</span></button>';
1027
1583
  return '<button type="button" class="button quiet" data-related="' + esc(id) + '" title="' +
1028
1584
  esc(graphEdgeLabels[edge.kind] || nice(edge.kind) || 'Connection') + '">' + icon(node.kind, true) + '<span>' +
1029
1585
  esc(node.label || node.id) + '</span></button>';
@@ -1031,37 +1587,59 @@ function renderGraphInspector() {
1031
1587
  const project = state.projects.find((item) => item.id === selected.project)?.name || selected.project;
1032
1588
  const snippet = compact(selected.snippet) && compact(selected.snippet) !== compact(selected.label)
1033
1589
  ? '<p class="small">' + esc(selected.snippet) + '</p>' : '';
1590
+ const canFocus = selected.kind === 'memory' && adjacent.some(({ edge }) => recordedEdge(edge)) && graph.lens !== selected.id;
1034
1591
  inspector.innerHTML = '<div class="panel-head"><h3>Node details</h3></div><div class="panel-body">' +
1035
- tags([[selected.kind, graphKinds[selected.kind]], ['memory', nice(selected.category)]]) + '<h3>' + esc(selected.label || selected.id) + '</h3>' +
1592
+ tags([[selected.kind, graphKinds[selected.kind]], ['memory', nice(selected.category)], ['state-' + (selected.effectiveState || ''), reviewLabel(selected.effectiveState)]]) +
1593
+ '<h3>' + (selected.kind === 'memory' ? memoryLink(graphMemoryId(selected), selected.project, selected.label || selected.id) : esc(selected.label || selected.id)) + '</h3>' +
1036
1594
  (project && selected.kind !== 'project' ? '<p class="muted small">' + esc(project) + '</p>' : '') + snippet +
1037
- (selected.kind === 'memory' ? sourceMarkup([...list(selected.sources), selected.source]) : '') + connected + '</div>';
1595
+ (selected.kind === 'memory' ? sourceMarkup([...list(selected.sources), selected.source]) : '') +
1596
+ (canFocus ? '<button class="button" type="button" data-graph-lens="' + esc(selected.id) + '">' + icon('graph', true) + 'Focus recorded links</button>' : '') +
1597
+ connected + '</div>';
1038
1598
  }
1039
1599
 
1040
- function selectGraphNode(id, { center = false, focus = false } = {}) {
1600
+ function updateGraphSelection() {
1041
1601
  const graph = state.graph;
1042
- graph.selected = id;
1602
+ const id = graph.selected;
1603
+ const selectedEdge = graph.edges.find((edge) => recordedEdge(edge) && edge.id === graph.selectedEdge);
1043
1604
  const connected = new Set(graphNeighbors(graph.edges, id).map((item) => item.id));
1044
1605
  connected.add(id);
1045
- const focusable = $('graph-svg').querySelector('[data-node][tabindex="0"]')?.dataset.node;
1046
- const tabStop = id || focusable || graph.nodes[0]?.id;
1606
+ if (selectedEdge) { connected.add(selectedEdge.source); connected.add(selectedEdge.target); }
1607
+ const hasSelection = !!id || !!selectedEdge;
1608
+ const tabStop = id || selectedEdge?.source || graph.nodes[0]?.id;
1047
1609
  $('graph-body').querySelectorAll('[data-node]').forEach((element) => {
1048
1610
  const nodeId = element.dataset.node;
1049
1611
  element.setAttribute('aria-pressed', String(id === nodeId));
1050
1612
  if (element instanceof SVGElement) {
1051
- element.classList.toggle('unrelated', !!id && !connected.has(nodeId));
1613
+ element.classList.toggle('unrelated', hasSelection && !connected.has(nodeId));
1614
+ element.classList.toggle('selected-memory', id === nodeId && nodeId.startsWith('memory:'));
1052
1615
  element.setAttribute('tabindex', nodeId === tabStop ? '0' : '-1');
1053
1616
  }
1054
1617
  });
1055
1618
  if (graph.mode === 'map') {
1056
1619
  $('graph-svg').querySelectorAll('[data-edge]').forEach((element) => {
1057
1620
  const edge = graph.edges[Number(element.dataset.edge)];
1058
- const active = edge.source === id || edge.target === id;
1621
+ const active = edge === selectedEdge || edge.source === id || edge.target === id;
1059
1622
  element.classList.toggle('active', active);
1060
- element.classList.toggle('unrelated', !!id && !active);
1623
+ element.classList.toggle('unrelated', hasSelection && !active);
1624
+ });
1625
+ const linkTabStop = graph.selectedEdge || graph.edges.find(recordedEdge)?.id;
1626
+ $('graph-svg').querySelectorAll('[data-edge-record]').forEach((element) => {
1627
+ element.setAttribute('aria-pressed', String(element.dataset.edgeRecord === graph.selectedEdge));
1628
+ element.setAttribute('tabindex', element.dataset.edgeRecord === linkTabStop ? '0' : '-1');
1061
1629
  });
1062
1630
  renderGraphTooltip(id);
1063
1631
  }
1632
+ $('graph-list').querySelectorAll('[data-edge-record]').forEach((element) => {
1633
+ element.setAttribute('aria-pressed', String(element.dataset.edgeRecord === graph.selectedEdge));
1634
+ });
1064
1635
  renderGraphInspector();
1636
+ }
1637
+
1638
+ function selectGraphNode(id, { center = false, focus = false } = {}) {
1639
+ const graph = state.graph;
1640
+ graph.selected = id;
1641
+ graph.selectedEdge = '';
1642
+ updateGraphSelection();
1065
1643
  if (center) centerGraphNode(id);
1066
1644
  if (focus) {
1067
1645
  const target = graph.mode === 'map' ? $('graph-svg') : $('graph-list');
@@ -1069,29 +1647,56 @@ function selectGraphNode(id, { center = false, focus = false } = {}) {
1069
1647
  }
1070
1648
  }
1071
1649
 
1650
+ function selectGraphEdge(id, fromInspector) {
1651
+ const graph = state.graph;
1652
+ if (!graph.edges.some((edge) => recordedEdge(edge) && edge.id === id)) return;
1653
+ graph.selected = '';
1654
+ graph.selectedEdge = id;
1655
+ updateGraphSelection();
1656
+ if (fromInspector) $('graph-inspector').querySelector('a')?.focus();
1657
+ }
1658
+
1072
1659
  function renderGraph() {
1073
1660
  const graph = state.graph;
1074
1661
  if (!graph.snapshot) return;
1075
1662
  const search = $('graph-query').value.trim().toLowerCase();
1076
1663
  const kind = $('graph-kind').value;
1077
- const nodes = list(graph.snapshot.nodes).filter((node) => Object.hasOwn(graphKinds, node.kind));
1664
+ const relation = $('graph-relation').value;
1665
+ const allNodes = list(graph.snapshot.nodes).filter((node) => Object.hasOwn(graphKinds, node.kind));
1666
+ const allEdges = list(graph.snapshot.edges);
1667
+ const anchor = allNodes.find((node) => node.id === graph.lens);
1668
+ const edges = anchor ? allEdges.filter((edge) => recordedEdge(edge) && (edge.source === anchor.id || edge.target === anchor.id)) : allEdges;
1669
+ const lensIds = anchor ? new Set([anchor.id, ...edges.flatMap((edge) => [edge.source, edge.target])]) : null;
1670
+ const nodes = lensIds ? allNodes.filter((node) => lensIds.has(node.id)) : allNodes;
1671
+ $('graph-lens').hidden = !anchor;
1672
+ $('graph-body').dataset.lens = String(!!anchor);
1673
+ $('graph-lens-label').textContent = anchor ? 'Recorded links for ' + shorten(compact(anchor.label || anchor.id), 94) : '';
1078
1674
  const matches = nodes.filter((node) => (!kind || node.kind === kind) &&
1079
1675
  (!search || [node.label, node.snippet, node.project, node.category].filter(Boolean).join(' ').toLowerCase().includes(search)));
1080
1676
  const ids = new Set(matches.map((node) => node.id));
1081
1677
  if (search && !kind) {
1082
- matches.forEach((node) => graphNeighbors(list(graph.snapshot.edges), node.id).forEach((item) => ids.add(item.id)));
1678
+ matches.forEach((node) => graphNeighbors(edges, node.id).forEach((item) => ids.add(item.id)));
1679
+ }
1680
+ const matchingEdges = edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target) &&
1681
+ (!relation || (relation === 'recorded' ? recordedEdge(edge) : edge.kind === relation)));
1682
+ if (relation) {
1683
+ const endpoints = new Set(matchingEdges.flatMap((edge) => [edge.source, edge.target]));
1684
+ for (const id of ids) if (!endpoints.has(id)) ids.delete(id);
1083
1685
  }
1084
1686
  graph.nodes = nodes.filter((node) => ids.has(node.id));
1085
- graph.edges = list(graph.snapshot.edges).filter((edge) => ids.has(edge.source) && ids.has(edge.target));
1687
+ graph.edges = matchingEdges;
1086
1688
  if (graph.selected && !ids.has(graph.selected)) graph.selected = '';
1689
+ if (graph.selectedEdge && !graph.edges.some((edge) => recordedEdge(edge) && edge.id === graph.selectedEdge)) graph.selectedEdge = '';
1087
1690
  const matchingCount = search && matches.length !== graph.nodes.length ? countLabel(matches.length, 'match', 'matches') + ' · ' : '';
1088
1691
  $('graph-count').textContent = matchingCount + countLabel(graph.nodes.length, 'node') + ' · ' + countLabel(graph.edges.length, 'connection');
1089
1692
  $('graph-body').hidden = !graph.nodes.length;
1090
1693
  $('graph-legend').hidden = !graph.nodes.length;
1091
- const note = graph.snapshot.truncated ? '<div class="panel-body small muted">Showing ' +
1092
- number(graph.snapshot.shownMemories) + ' memories in this snapshot. Filter by project to explore a narrower set.</div>' : '';
1093
- $('graph-status').innerHTML = graph.nodes.length ? note : blank('graph', search || kind ? 'No matching nodes' : 'No relationships yet',
1094
- search || kind ? 'Change the name or node type filter.' : 'The graph fills in as project and agent memories are stored.');
1694
+ const note = graph.snapshot.truncated ? '<div class="panel-body small muted">This snapshot includes ' +
1695
+ number(graph.snapshot.shownMemories) + ' memories. Filter by project to explore a narrower set.</div>' : '';
1696
+ const focused = graph.focusMemory && graph.nodes.find((node) => graphMemoryId(node) === graph.focusMemory);
1697
+ const missing = graph.focusMemory && !focused ? '<p class="panel-body small muted">This memory is outside the current graph view.</p>' : '';
1698
+ $('graph-status').innerHTML = graph.nodes.length ? note + missing : missing + blank('graph', search || kind || relation ? 'No matching nodes' : 'No relationships yet',
1699
+ search || kind || relation ? 'Change the name, node type, or relationship filter.' : 'The graph fills in as project and agent memories are stored.');
1095
1700
  if (!graph.nodes.length) return;
1096
1701
  $('graph-stage').hidden = graph.mode !== 'map';
1097
1702
  $('graph-list').hidden = graph.mode !== 'list';
@@ -1099,12 +1704,16 @@ function renderGraph() {
1099
1704
  $('graph-mode-list').setAttribute('aria-pressed', String(graph.mode === 'list'));
1100
1705
  renderGraphList();
1101
1706
  if (graph.mode === 'map') renderGraphMap();
1102
- selectGraphNode(graph.selected);
1707
+ if (focused) {
1708
+ graph.focusMemory = '';
1709
+ selectGraphNode(focused.id);
1710
+ } else updateGraphSelection();
1103
1711
  }
1104
1712
 
1105
- async function loadGraph(project, force = false) {
1713
+ async function loadGraph(project, force = false, memory = '') {
1106
1714
  const graph = state.graph;
1107
- if (!force && graph.snapshot && graph.project === project) {
1715
+ graph.focusMemory = memory;
1716
+ if (!force && !memory && graph.snapshot && graph.project === project) {
1108
1717
  renderGraph();
1109
1718
  return;
1110
1719
  }
@@ -1113,15 +1722,18 @@ async function loadGraph(project, force = false) {
1113
1722
  const controller = new AbortController();
1114
1723
  graph.controller = controller;
1115
1724
  graph.snapshot = null;
1725
+ if (graph.project !== project || memory) graph.lens = '';
1116
1726
  graph.project = project;
1117
1727
  graph.selected = '';
1728
+ graph.selectedEdge = '';
1118
1729
  setBusy($('graph-panel'), true);
1119
1730
  $('graph-status').innerHTML = skeleton('Loading relationships…');
1120
1731
  $('graph-count').textContent = '';
1121
1732
  $('graph-body').hidden = true;
1733
+ $('graph-lens').hidden = true;
1122
1734
  $('graph-legend').hidden = true;
1123
1735
  try {
1124
- const snapshot = await api(queryPath('graph', { project }), { signal: AbortSignal.any([controller.signal, AbortSignal.timeout(12000)]) });
1736
+ const snapshot = await api(queryPath('graph', { project, memory }), { signal: AbortSignal.any([controller.signal, AbortSignal.timeout(12000)]) });
1125
1737
  if (seq !== graph.request) return;
1126
1738
  graph.snapshot = snapshot;
1127
1739
  renderGraph();
@@ -1135,10 +1747,11 @@ async function loadGraph(project, force = false) {
1135
1747
  function renderContextControls() {
1136
1748
  const input = $('context-cross');
1137
1749
  const toggle = state.features?.features;
1138
- const canCross = toggle?.contextAssembly === true && toggle?.crossProjectContext === true;
1750
+ const canCross = document.documentElement.dataset.projectLocal !== 'true' && toggle?.contextAssembly === true && toggle?.crossProjectContext === true;
1139
1751
  $('context-cross-row').hidden = !canCross;
1140
1752
  if (!canCross) input.checked = false;
1141
1753
  $('context-submit').disabled = !toggle?.contextAssembly || !state.projects.length || state.context.pending;
1754
+ $('context-related-row').hidden = $('context-profile').value !== 'adaptive';
1142
1755
  const gate = $('context-gate');
1143
1756
  if (!toggle && state.featurePending) {
1144
1757
  gate.innerHTML = '<p class="muted small" role="status">Checking feature settings…</p>';
@@ -1161,11 +1774,99 @@ function contextReasonList(items, format) {
1161
1774
  return '<ul class="list">' + items.map(format).join('') + '</ul>';
1162
1775
  }
1163
1776
 
1777
+ const contextReasons = {
1778
+ selected: 'Included in the brief.', duplicate: 'Matches a memory already considered.', budget: 'Would exceed the selected budget.',
1779
+ limit: 'Beyond the brief’s memory limit.', 'candidate-limit': 'Beyond the evaluated candidate limit.', empty: 'Contains no memory text.',
1780
+ lookback: 'Falls outside the selected time range.', archived: 'Archived in a recorded review.', contested: 'Marked as needing review.',
1781
+ superseded: 'Superseded by a recorded replacement.'
1782
+ };
1783
+ const rankScore = (value) => Number.isFinite(value) ? value.toLocaleString(undefined, { maximumFractionDigits: 3 }) : '—';
1784
+
1785
+ function renderRetrieval(pack) {
1786
+ const retrieval = pack.retrieval;
1787
+ if (!retrieval || !pack.enabled) return '';
1788
+ const counts = list(retrieval.stages);
1789
+ const max = Math.max(1, ...counts.map((stage) => Number(stage.count) || 0));
1790
+ const stages = counts.map((stage) => '<li><strong>' + number(stage.count) + '</strong><span>' + esc(stage.label) +
1791
+ '</span><div class="retrieval-stage-bar" aria-hidden="true"><i style="width:' + Math.max(0, (Number(stage.count) || 0) / max * 100) + '%"></i></div></li>').join('');
1792
+ const candidates = list(retrieval.candidates);
1793
+ const labels = new Map([...list(pack.entries), ...candidates].map((item) => [item.id, item.title || shorten(compact(item.excerpt), 95) || item.id]));
1794
+ const rows = candidates.map((candidate) => {
1795
+ const signals = list(candidate.signals).map((signal) => {
1796
+ const sign = signal.label === 'Project task feedback' && signal.value > 0 ? '+' : '';
1797
+ return '<div><dt>' + esc(signal.label) + '</dt><dd>' + sign + esc(rankScore(signal.value)) + '</dd></div>';
1798
+ }).join('');
1799
+ const terms = list(candidate.matchedTerms);
1800
+ const fields = list(candidate.matchedFields);
1801
+ const paths = list(candidate.paths).map((path) => '<li><span class="tag agent">' + esc(linkTypes[path.relationType] || nice(path.relationType)) +
1802
+ '</span> ' + memoryLink(path.fromId, candidate.projectId, labels.get(path.fromId) || path.fromId) + ' ' + icon('chevron', true) + ' ' +
1803
+ memoryLink(path.toId, candidate.projectId, labels.get(path.toId) || path.toId) + '</li>').join('');
1804
+ const feedback = candidate.feedback;
1805
+ const reporters = list(feedback?.reporterKinds).map((kind) => kind === 'agent' ? 'Agent' : 'User');
1806
+ const feedbackSource = feedback ? '<p class="small muted">' + countLabel(list(feedback.recordIds).length, 'matching task report') +
1807
+ (reporters.length ? ' · ' + esc(reporters.join(' and ')) : '') +
1808
+ (state.projectKit?.projectId === candidate.projectId ? ' · <a href="' + esc(routeLink('project-kit', { memory: candidate.id })) + '">Review project outcomes</a>' : '') + '</p>' : '';
1809
+ const included = candidate.decision === 'selected';
1810
+ return '<li><details class="candidate"><summary><span class="candidate-title"><span class="rank">#' + number(candidate.rank) + '</span>' +
1811
+ '<span>' + esc(candidate.title || candidate.id) + '</span><span class="tag check-' + (included ? 'ok' : 'warn') + '">' +
1812
+ (included ? 'Included' : 'Excluded') + '</span></span></summary><div class="candidate-body"><p class="small muted">' +
1813
+ esc(contextReasons[candidate.reason] || nice(candidate.reason)) + ' Rank score ' + esc(rankScore(candidate.score)) + '.</p>' +
1814
+ (terms.length ? '<p class="small"><strong>Matched terms</strong> ' + esc(terms.join(', ')) + '</p>' : '') +
1815
+ (fields.length ? '<p class="small"><strong>Matched fields</strong> ' + esc(fields.map(nice).join(', ')) + '</p>' : '') +
1816
+ (signals ? '<dl class="candidate-signals" aria-label="Ranking contributions">' + signals + '</dl>' : '') + feedbackSource + sourceMarkup(candidate.sources) +
1817
+ (paths ? '<div><h3>Recorded link paths</h3><ul class="list">' + paths + '</ul></div>' : '') +
1818
+ '<p>' + memoryLink(candidate.id, candidate.projectId, 'Open memory') + '</p></div></details></li>';
1819
+ }).join('');
1820
+ const reportCount = retrieval.truncated ? ' Candidate diagnostics are bounded to ' + number(retrieval.candidateLimit) + ' records.' : '';
1821
+ return '<section class="context-section" aria-labelledby="retrieval-heading"><h3 id="retrieval-heading">Retrieval pipeline</h3>' +
1822
+ '<ol class="retrieval-flow" aria-label="Retrieval stages">' + stages + '</ol><p class="small muted">Scanned ' + number(retrieval.scanned) +
1823
+ ' memories. Ranking contributions explain ordering.' + esc(reportCount) + '</p>' +
1824
+ (rows ? '<details class="context-section" open><summary>Candidate decisions · ' + number(candidates.length) + '</summary>' +
1825
+ '<ul class="list candidate-list" aria-label="Candidate decisions">' + rows + '</ul></details>' : '') + '</section>';
1826
+ }
1827
+
1828
+ function renderComparison() {
1829
+ const target = $('context-comparison');
1830
+ if (!target) return;
1831
+ const { comparison, comparing, dirty } = state.context;
1832
+ if (dirty || !comparison && !comparing) {
1833
+ target.replaceChildren();
1834
+ return;
1835
+ }
1836
+ if (comparing) {
1837
+ target.innerHTML = skeleton('Comparing retrieval profiles…');
1838
+ return;
1839
+ }
1840
+ if (comparison.error) {
1841
+ target.innerHTML = errorMarkup(comparison.error);
1842
+ return;
1843
+ }
1844
+ const { legacy, adaptive, delta } = comparison;
1845
+ const shared = list(delta?.sharedIds);
1846
+ const column = (label, pack, ids) => {
1847
+ const unique = new Set(ids);
1848
+ const entries = list(pack.entries);
1849
+ const differences = entries.filter((entry) => unique.has(entry.id));
1850
+ return '<div><h3>' + label + '</h3><p class="small muted">' + countLabel(entries.length, 'included memory', 'included memories') + ' · ' +
1851
+ number(pack.budget?.estimatedTokens) + ' estimated tokens</p><p class="small"><strong>' + number(differences.length) + ' unique to ' + label + '</strong></p>' +
1852
+ (differences.length ? '<ul class="small">' + differences.map((entry) => '<li>' +
1853
+ memoryLink(entry.id, entry.projectId, shorten(compact(entry.excerpt), 95) || entry.id) + '</li>').join('') + '</ul>' : '') + '</div>';
1854
+ };
1855
+ target.innerHTML = '<h3>Profile comparison</h3><p class="small muted">Same task, scope, and requested budget · ' +
1856
+ countLabel(shared.length, 'shared memory', 'shared memories') + '</p><div class="compare-grid">' +
1857
+ column('Adaptive', adaptive, list(delta?.addedIds)) + column('Legacy', legacy, list(delta?.removedIds)) + '</div>' +
1858
+ list(comparison.warnings).map((warning) => '<p class="small muted">' + esc(warning) + '</p>').join('');
1859
+ }
1860
+
1164
1861
  function renderContext() {
1165
1862
  const output = $('context-output');
1166
- const { pack, dirty } = state.context;
1863
+ const { pack, dirty, params } = state.context;
1864
+ const status = $('context-result-state');
1167
1865
  if (!pack) {
1168
- output.innerHTML = blank('context', 'Ready for a task', 'Choose a project and describe the work to see the context and its sources here.');
1866
+ const enabled = state.features?.features?.contextAssembly !== false;
1867
+ status.textContent = enabled ? '' : 'Disabled';
1868
+ output.innerHTML = enabled ? blank('context', 'Ready for a task', 'Choose a project and describe the work to see the context and its sources here.') :
1869
+ blank('context', 'Context assembly is turned off', 'Enable it in Features to build a brief.');
1169
1870
  return;
1170
1871
  }
1171
1872
  const entries = list(pack.entries);
@@ -1173,34 +1874,45 @@ function renderContext() {
1173
1874
  const budget = pack.budget || {};
1174
1875
  const warnings = list(pack.warnings);
1175
1876
  const allowed = state.features?.features?.contextAssembly === true;
1176
- const canCopy = allowed && !dirty && !!pack.markdown && pack.enabled && entries.length > 0;
1877
+ const canInspect = allowed && !dirty && pack.enabled;
1878
+ const canCopy = canInspect && !!pack.markdown && entries.length > 0;
1879
+ status.textContent = dirty ? 'Rebuild needed' : pack.enabled ? 'Assembled' : 'Disabled';
1177
1880
  const warningMarkup = warnings.map((warning) => '<p class="small muted">' + esc(warning) + '</p>').join('');
1178
1881
  const selection = contextReasonList(entries, (entry) => {
1179
1882
  const title = shorten(compact(entry.excerpt), 120) || nice(entry.category) || entry.id;
1180
1883
  const reason = list(entry.reasons).map((detail) => '<p class="small muted">' + esc(detail) + '</p>').join('');
1181
- return '<li><strong>' + esc(title) + '</strong><div class="small muted">' + esc([entry.projectName, entry.agentTool, nice(entry.category)].filter(Boolean).join(' · ')) +
1182
- '</div>' + reason + sourceMarkup(entry.sources) + '</li>';
1884
+ const links = list(entry.related).map((relation) => {
1885
+ const directional = { fromId: relation.direction === 'outgoing' ? entry.id : relation.otherMemoryId, type: relation.type };
1886
+ return '<p class="small">' + esc(linkCaption(directional, entry.id)) + ' ' +
1887
+ memoryLink(relation.otherMemoryId, entry.projectId, relation.title || relation.otherMemoryId) + '</p>';
1888
+ }).join('');
1889
+ return '<li><strong>' + memoryLink(entry.id, entry.projectId, title) + '</strong><div class="small muted">' +
1890
+ esc([entry.projectName, entry.agentTool, nice(entry.category)].filter(Boolean).join(' · ')) +
1891
+ '</div>' + reason + sourceMarkup(entry.sources) + links + '</li>';
1183
1892
  });
1184
- const reasonLabels = {
1185
- duplicate: 'Matches a memory already considered.',
1186
- budget: 'Would exceed the selected budget.',
1187
- limit: 'Beyond the brief’s memory limit.',
1188
- empty: 'Contains no memory text.',
1189
- lookback: 'Falls outside the selected time range.'
1190
- };
1191
- const rejections = contextReasonList(excluded, (entry) => '<li><strong class="mono">' + esc(entry.id) +
1192
- '</strong><p class="small muted">' + esc(reasonLabels[entry.reason] || nice(entry.reason)) + '</p></li>');
1193
- const scope = pack.scope === 'cross-project' ? 'Cross-project' : 'Selected project';
1194
- output.innerHTML = '<div class="panel-body stack">' + (dirty ? '<p class="status small" role="status">Task details changed. Assemble again to update this preview.</p>' : '') +
1195
- '<div class="form-actions">' + tags([['project', scope], ['memory', number(budget.estimatedTokens) + ' / ' + number(budget.tokenBudget) + ' estimated tokens']]) +
1196
- (canCopy ? '<button class="button toolbar-end" id="context-copy" type="button">' + icon('copy') + 'Copy brief</button>' : '') + '</div>' +
1197
- warningMarkup +
1198
- (pack.markdown && pack.enabled && entries.length ? '<pre class="context-preview mono" id="context-text" tabindex="0" aria-label="Assembled context markdown">' + esc(pack.markdown) + '</pre>' :
1199
- blank('context', pack.enabled ? 'No memories fit this task' : 'Context assembly is turned off', pack.enabled ? 'Try a more specific task or a larger budget.' : 'Enable it in Features to build a brief.')) +
1893
+ const rejections = contextReasonList(excluded, (entry) => '<li><strong class="mono">' +
1894
+ memoryLink(entry.id, entry.projectId || (pack.scope === 'project' ? pack.projectId : ''), entry.title || entry.id) +
1895
+ '</strong><p class="small muted">' + esc(contextReasons[entry.reason] || nice(entry.reason)) + '</p></li>');
1896
+ const projectName = state.projects.find((item) => item.id === pack.projectId)?.name || pack.projectId;
1897
+ const scope = pack.scope === 'cross-project' ? 'Cross-project · ' + projectName : projectName;
1898
+ const usage = budget.tokenBudget > 0 ? Math.min(100, Math.max(0, budget.estimatedTokens / budget.tokenBudget * 100)) : 0;
1899
+ output.innerHTML = '<div class="panel-body stack">' + (dirty ? '<p class="status small" role="status">This preview is out of date. Assemble again to refresh it.</p>' : '') +
1900
+ '<div class="context-output-head">' + tags([['project', scope], ['memory', params?.profile === 'adaptive' ? 'Adaptive' : 'Legacy']]) +
1901
+ '<div class="form-actions">' + (canInspect && params ? '<button type="button" class="button" id="context-compare-button"' +
1902
+ (state.context.comparing ? ' disabled' : '') + '>Compare profiles</button>' : '') +
1903
+ (canCopy ? '<button class="button" id="context-download" type="button">Download .md</button>' +
1904
+ '<button class="button primary" id="context-copy" type="button">' + icon('copy') + 'Copy brief</button>' : '') + '</div></div>' +
1905
+ '<p id="copy-status" class="small muted" role="status" hidden></p><dl class="context-metrics">' +
1906
+ '<div><dt>Estimated tokens</dt><dd>' + number(budget.estimatedTokens) + ' / ' + number(budget.tokenBudget) + '</dd><div class="context-budget-track" aria-hidden="true"><span style="width:' + usage + '%"></span></div></div>' +
1907
+ '<div><dt>Characters</dt><dd>' + number(budget.usedChars) + ' / ' + number(budget.maxChars) + '</dd></div>' +
1908
+ '<div><dt>Included / excluded</dt><dd>' + number(entries.length) + ' / ' + number(excluded.length) + '</dd></div></dl>' +
1909
+ warningMarkup + '<section class="context-compare" id="context-comparison" aria-live="polite"></section>' + renderRetrieval(pack) +
1200
1910
  '<div class="context-reasons">' +
1201
- (entries.length ? '<details open><summary>Included · ' + number(entries.length) + '</summary>' + selection + '</details>' : '') +
1202
- (excluded.length ? '<details><summary>Excluded · ' + number(excluded.length) + '</summary>' + rejections + '</details>' : '') +
1203
- '</div><p id="copy-status" class="small muted" role="status" hidden></p></div>';
1911
+ (entries.length ? '<details class="context-section"><summary>Included memories · ' + number(entries.length) + '</summary>' + selection + '</details>' : '') +
1912
+ (excluded.length ? '<details class="context-section"><summary>Excluded memories · ' + number(excluded.length) + '</summary>' + rejections + '</details>' : '') +
1913
+ '</div>' + (pack.markdown && pack.enabled && entries.length ? '<details class="context-section" open><summary>Assembled brief</summary><pre class="context-preview mono" id="context-text" tabindex="0" aria-label="Assembled context markdown">' + esc(pack.markdown) + '</pre></details>' :
1914
+ blank('context', pack.enabled ? 'No memories fit this task' : 'Context assembly is turned off', pack.enabled ? 'Review any exclusions or try a different task.' : 'Enable it in Features to build a brief.')) + '</div>';
1915
+ renderComparison();
1204
1916
  }
1205
1917
 
1206
1918
  async function assembleContext() {
@@ -1215,18 +1927,29 @@ async function assembleContext() {
1215
1927
  $('context-task').reportValidity();
1216
1928
  return;
1217
1929
  }
1218
- const body = { query, projectId, tokenBudget, includeCrossProject: toggle.crossProjectContext && $('context-cross').checked };
1930
+ const profile = $('context-profile').value;
1931
+ const body = { query, projectId, tokenBudget, includeCrossProject: toggle.crossProjectContext && $('context-cross').checked,
1932
+ profile, ...(profile === 'adaptive' ? { expandRelated: $('context-related').checked } : {}) };
1219
1933
  store.pending = true;
1220
1934
  store.dirty = false;
1935
+ store.pack = null;
1936
+ store.params = body;
1937
+ store.comparison = null;
1938
+ store.comparing = false;
1221
1939
  renderContextControls();
1222
1940
  setBusy($('context-result'), true);
1941
+ $('context-result-state').textContent = 'Assembling';
1223
1942
  $('context-output').innerHTML = skeleton('Assembling context…');
1224
1943
  try {
1225
- store.pack = await api('context', { body });
1944
+ const pack = await api('context', { body });
1945
+ const current = state.features?.features;
1946
+ if (store.params === body && current?.contextAssembly && (pack.scope !== 'cross-project' || current.crossProjectContext)) store.pack = pack;
1226
1947
  renderContext();
1227
1948
  } catch (error) {
1228
- store.pack = null;
1229
- $('context-output').innerHTML = errorMarkup(error.message, 'context');
1949
+ if (store.params === body) {
1950
+ $('context-result-state').textContent = 'Request failed';
1951
+ $('context-output').innerHTML = errorMarkup(error.message, 'context');
1952
+ }
1230
1953
  } finally {
1231
1954
  store.pending = false;
1232
1955
  setBusy($('context-result'), false);
@@ -1234,10 +1957,44 @@ async function assembleContext() {
1234
1957
  }
1235
1958
  }
1236
1959
 
1960
+ async function compareContext() {
1961
+ const store = state.context;
1962
+ if (!store.params || store.dirty || store.pending || store.comparing || !store.pack?.enabled) return;
1963
+ const params = store.params;
1964
+ const { profile, ...body } = params;
1965
+ store.comparing = true;
1966
+ $('context-compare-button').disabled = true;
1967
+ renderComparison();
1968
+ try {
1969
+ const result = await api('context-compare', { body });
1970
+ if (!result.adaptive || !result.legacy || !result.delta) throw new Error('The server returned an unreadable comparison.');
1971
+ if (store.params === params && !store.dirty) store.comparison = result;
1972
+ } catch (error) {
1973
+ if (store.params === params && !store.dirty) store.comparison = { error: error.message };
1974
+ } finally {
1975
+ if (store.params === params) {
1976
+ store.comparing = false;
1977
+ if ($('context-compare-button')) $('context-compare-button').disabled = false;
1978
+ renderComparison();
1979
+ }
1980
+ }
1981
+ }
1982
+
1983
+ function downloadContext() {
1984
+ const { pack, dirty } = state.context;
1985
+ if (!pack?.markdown || !pack.enabled || dirty || !state.features?.features.contextAssembly) return;
1986
+ const url = URL.createObjectURL(new Blob([pack.markdown], { type: 'text/markdown;charset=utf-8' }));
1987
+ const link = document.createElement('a');
1988
+ link.href = url;
1989
+ link.download = 'shadow-brain-context.md';
1990
+ link.click();
1991
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
1992
+ }
1993
+
1237
1994
  async function copyContext() {
1238
1995
  const store = state.context;
1239
1996
  const status = $('copy-status');
1240
- if (!store.pack?.markdown || store.dirty || !status) return;
1997
+ if (!store.pack?.markdown || !store.pack.enabled || store.dirty || !status || !state.features?.features.contextAssembly) return;
1241
1998
  try {
1242
1999
  await navigator.clipboard.writeText(store.pack.markdown);
1243
2000
  status.textContent = 'Brief copied.';
@@ -1247,18 +2004,110 @@ async function copyContext() {
1247
2004
  status.hidden = false;
1248
2005
  }
1249
2006
 
2007
+ const checkStates = { pass: ['ok', 'Passed'], warn: ['warn', 'Review'], unknown: ['', 'Unknown'], error: ['error', 'Error'] };
2008
+ const installStates = {
2009
+ complete: ['ok', 'Files present'], partial: ['warn', 'Some files present'], missing: ['', 'Files missing'],
2010
+ 'not-checked': ['', 'Unchecked'], blocked: ['error', 'Check blocked']
2011
+ };
2012
+
2013
+ function checkTag(value, labels) {
2014
+ const [kind, label] = Object.hasOwn(labels, value) ? labels[value] : ['', 'Unknown'];
2015
+ return '<span class="tag check-' + kind + '">' + label + '</span>';
2016
+ }
2017
+
2018
+ function commandMarkup(command) {
2019
+ if (typeof command !== 'string' || !command.trim()) return '';
2020
+ return '<div class="form-actions"><code class="mono">' + esc(command) + '</code><button type="button" class="button quiet" data-copy-command="' +
2021
+ esc(command) + '" aria-label="Copy command">' + icon('copy', true) + 'Copy</button><span class="small muted" role="status"></span></div>';
2022
+ }
2023
+
2024
+ function docsLink(value) {
2025
+ if (typeof value !== 'string') return '';
2026
+ try {
2027
+ const url = new URL(value);
2028
+ if (url.protocol !== 'https:') return '';
2029
+ return '<a class="button quiet" href="' + esc(url.href) + '" target="_blank" rel="noopener noreferrer">Documentation ' +
2030
+ icon('diagonal', true) + '</a>';
2031
+ } catch { return ''; }
2032
+ }
2033
+
2034
+ function renderIntegrations(data) {
2035
+ const checks = list(data.checks);
2036
+ const agents = list(data.agents);
2037
+ const checkRows = checks.map((check) => '<li class="check-row"><h3>' + esc(check.label || check.id) + '</h3><div class="check-result"><p class="small muted">' +
2038
+ esc(check.detail) + '</p>' + commandMarkup(check.command) + '</div>' + checkTag(check.status, checkStates) + '</li>').join('');
2039
+ const agentRows = agents.map((agent) => '<li class="check-row"><h3>' + esc(agent.label || agent.id) + '</h3><div class="check-result">' +
2040
+ tags(list(agent.capabilities).map((value) => ['memory', value])) + (agent.detail ? '<p class="small muted">' + esc(agent.detail) + '</p>' : '') +
2041
+ (agent.note ? '<p class="small muted">' + esc(agent.note) + '</p>' : '') + commandMarkup(agent.command) +
2042
+ (typeof agent.config?.content === 'string' ? '<details class="integration-config"><summary>View MCP setup example</summary>' +
2043
+ '<p class="small muted">' + esc([agent.config.format, agent.config.filename].filter(Boolean).join(' · ')) + '</p>' +
2044
+ '<pre class="context-preview mono" tabindex="0">' + esc(agent.config.content) + '</pre>' +
2045
+ list(agent.config.notes).map((note) => '<p class="small muted">' + esc(note) + '</p>').join('') +
2046
+ '<div class="form-actions"><button type="button" class="button quiet" data-copy-command="' + esc(agent.config.content) + '">' +
2047
+ icon('copy', true) + 'Copy example</button><span class="small muted" role="status"></span></div></details>' : '') +
2048
+ '<div class="form-actions">' + docsLink(agent.docs) + '</div></div>' + checkTag(agent.installation, installStates) + '</li>').join('');
2049
+ $('integrations-body').innerHTML = '<div class="stack"><section class="panel"><div class="panel-head"><div><h2>Local diagnostics</h2>' +
2050
+ (data.checkedAt ? '<p class="small muted">Last checked ' + dateMarkup(data.checkedAt) + '</p>' : '') + '</div>' +
2051
+ (typeof data.diagnostics === 'string' && data.diagnostics ? '<div class="form-actions"><button class="button" type="button" id="diagnostics-copy">' + icon('copy', true) +
2052
+ 'Copy report</button><span class="small muted" role="status"></span></div>' : '') + '</div>' +
2053
+ (checkRows ? '<ul class="list">' + checkRows + '</ul>' : blank('activity', 'No diagnostic results', 'Run checks to refresh the local status.')) +
2054
+ '</section><section class="panel"><div class="panel-head"><div><h2>Supported agents</h2>' +
2055
+ '<p class="small muted">Installation status reflects project skill files. Use Live operations to inspect recorded sessions.</p></div></div>' +
2056
+ '<div class="integration-meta"><span class="tag project">Project skill files</span><span class="tag memory">MCP setup examples</span><a class="small" href="/live">Recorded sessions ' + icon('chevron', true) + '</a></div>' +
2057
+ (agentRows ? '<ul class="list">' + agentRows + '</ul>' : blank('integrations', 'No agent data', 'Run checks to load the support catalog.')) +
2058
+ '</section></div>';
2059
+ }
2060
+
2061
+ async function loadIntegrations() {
2062
+ const store = state.integrations;
2063
+ if (store.pending) return;
2064
+ store.pending = true;
2065
+ const target = $('integrations-body');
2066
+ $('integrations-refresh').disabled = true;
2067
+ setBusy(target, true);
2068
+ target.innerHTML = '<div class="panel">' + skeleton('Running local checks…') + '</div>';
2069
+ try {
2070
+ store.snapshot = await api('integrations');
2071
+ renderIntegrations(store.snapshot);
2072
+ } catch (error) {
2073
+ target.innerHTML = errorMarkup(error.message, 'integrations');
2074
+ } finally {
2075
+ store.pending = false;
2076
+ $('integrations-refresh').disabled = false;
2077
+ setBusy(target, false);
2078
+ }
2079
+ }
2080
+
2081
+ async function copyIntegrationText(value, message, button) {
2082
+ const status = button.nextElementSibling;
2083
+ if (!value || !status) return;
2084
+ try {
2085
+ await navigator.clipboard.writeText(value);
2086
+ status.textContent = message;
2087
+ } catch {
2088
+ status.textContent = 'Clipboard access is unavailable.';
2089
+ }
2090
+ }
2091
+
2092
+ function projectFeatureLimit(id) {
2093
+ if (document.documentElement.dataset.projectLocal !== 'true') return undefined;
2094
+ return { crossProjectContext: 'Project memory stays scoped to this project.', automaticBriefing: 'Automatic briefing requires a legacy analysis session.' }[id];
2095
+ }
2096
+
1250
2097
  function renderFeatures() {
1251
2098
  const target = $('feature-list');
1252
2099
  if (!state.features) return;
1253
2100
  const current = state.features.features;
1254
2101
  const overrides = new Map(list(state.features.definitions).map((item) => [item.id, item]));
2102
+ const icons = { contextAssembly: 'context', crossProjectContext: 'graph', automaticBriefing: 'agent', liveActivity: 'activity', activityRecording: 'memory' };
1255
2103
  target.innerHTML = '<ul class="list">' + definitions.map((definition) => {
1256
2104
  const display = overrides.get(definition.id) || definition;
1257
- const on = current[definition.id] === true;
1258
- return '<li class="setting"><div><h3 id="label-' + definition.id + '">' + esc(display.label) + '</h3><p class="muted small" id="desc-' +
1259
- definition.id + '">' + esc(display.description) + '</p></div><button type="button" class="button switch" role="switch" aria-checked="' +
2105
+ const reason = projectFeatureLimit(definition.id);
2106
+ const on = !reason && current[definition.id] === true;
2107
+ return '<li class="setting"><div class="settings-description"><span class="tile-icon">' + icon(icons[definition.id], true) + '</span><div><h3 id="label-' + definition.id + '">' + esc(display.label) + '</h3><p class="muted small" id="desc-' +
2108
+ definition.id + '">' + esc(reason || display.description) + '</p></div></div><button type="button" class="button switch" role="switch" aria-checked="' +
1260
2109
  on + '" aria-labelledby="label-' + definition.id + '" aria-describedby="desc-' + definition.id + '" data-feature="' + definition.id + '"' +
1261
- (state.featureSaving ? ' disabled' : '') + '><span aria-hidden="true">' + (on ? 'On' : 'Off') + '</span><span class="switch-track" aria-hidden="true"></span></button></li>';
2110
+ (state.featureSaving || reason ? ' disabled' : '') + '><span aria-hidden="true">' + (reason ? 'Unavailable' : on ? 'On' : 'Off') + '</span><span class="switch-track" aria-hidden="true"></span></button></li>';
1262
2111
  }).join('') + '</ul>';
1263
2112
  }
1264
2113
 
@@ -1271,11 +2120,17 @@ function applyFeatures(snapshot) {
1271
2120
  if (state.features && snapshot.revision < state.features.revision) return;
1272
2121
  state.features = snapshot;
1273
2122
  state.activityEnabled = current.liveActivity;
1274
- if (before?.crossProjectContext && !current.crossProjectContext && state.context.pack?.scope === 'cross-project') state.context.dirty = true;
2123
+ const context = state.context;
2124
+ if (!current.contextAssembly || !current.crossProjectContext && context.params?.includeCrossProject) {
2125
+ context.pack = null;
2126
+ context.params = null;
2127
+ context.comparison = null;
2128
+ context.comparing = false;
2129
+ }
1275
2130
  renderFeatures();
1276
2131
  renderActivity();
1277
2132
  renderContextControls();
1278
- if (state.context.pack && !state.context.pending) renderContext();
2133
+ if (!context.pending) renderContext();
1279
2134
  if (current.liveActivity && before?.liveActivity !== true) void refreshActivity();
1280
2135
  }
1281
2136
 
@@ -1310,7 +2165,7 @@ async function loadFeatures(force = false) {
1310
2165
  }
1311
2166
 
1312
2167
  async function toggleFeature(key) {
1313
- if (state.featureSaving || !state.features || !definitions.some((definition) => definition.id === key)) return;
2168
+ if (state.featureSaving || !state.features || projectFeatureLimit(key) || !definitions.some((definition) => definition.id === key)) return;
1314
2169
  state.featureSaving = true;
1315
2170
  $('feature-list').querySelectorAll('[data-feature]').forEach((button) => { button.disabled = true; });
1316
2171
  featureStatus('Saving…');
@@ -1362,23 +2217,30 @@ function routeParams() {
1362
2217
  return new URLSearchParams(location.hash.split('?')[1] || '');
1363
2218
  }
1364
2219
 
2220
+ function changeMemorySearch() {
2221
+ const query = memoryQuery();
2222
+ const route = routeLink('memories', query);
2223
+ if (location.hash !== route) location.hash = route;
2224
+ else void loadMemories(query);
2225
+ }
2226
+
1365
2227
  function changeView(moveFocus = false) {
1366
2228
  const hash = location.hash.slice(1).split('?')[0];
1367
2229
  const view = Object.hasOwn(viewNames, hash) ? hash : 'overview';
1368
2230
  const previous = state.view;
2231
+ const previousRoute = state.route;
1369
2232
  state.view = view;
2233
+ state.route = location.hash || '#overview';
1370
2234
  $('crumb').textContent = viewNames[view];
1371
- document.title = viewNames[view] + ' · Shadow Brain';
2235
+ document.title = viewNames[view] + ' · Agent Shadow Brain';
1372
2236
  for (const name of Object.keys(viewNames)) {
1373
2237
  const section = $('page-' + name);
1374
2238
  section.hidden = name !== view;
1375
- section.classList.remove('entered');
1376
2239
  const link = document.querySelector('[data-view="' + name + '"]');
1377
- if (name === view) link.setAttribute('aria-current', 'page');
1378
- else link.removeAttribute('aria-current');
2240
+ if (name === view || name === 'memories' && view === 'memory') link?.setAttribute('aria-current', 'page');
2241
+ else link?.removeAttribute('aria-current');
1379
2242
  }
1380
- if (previous !== view) $('page-' + view).classList.add('entered');
1381
- if (moveFocus) {
2243
+ if (moveFocus && (previous !== view || view === 'memory')) {
1382
2244
  window.scrollTo(0, 0);
1383
2245
  $('title-' + view).focus({ preventScroll: true });
1384
2246
  }
@@ -1386,19 +2248,44 @@ function changeView(moveFocus = false) {
1386
2248
  const project = params.get('project') || '';
1387
2249
  switch (view) {
1388
2250
  case 'overview':
1389
- void loadOverview(previous !== '' && previous !== 'overview');
2251
+ void loadOverview(previous !== '' && previous !== 'overview', project);
1390
2252
  void loadFeatures();
1391
2253
  break;
1392
- case 'memories':
1393
- if (project) optionSet('memory-project', 'All projects', state.projects, project);
1394
- void loadMemories({ ...memoryQuery(), project: project || $('memory-project').value });
2254
+ case 'memories': {
2255
+ const query = { q: params.get('q') || '', project, category: params.get('category') || '', agent: params.get('agent') || '', state: params.get('state') || '' };
2256
+ $('memory-query').value = query.q;
2257
+ if (state.projects.length) optionSet('memory-project', 'All projects', state.projects, query.project);
2258
+ $('memory-state').value = query.state;
2259
+ void loadMemories(query);
2260
+ break;
2261
+ }
2262
+ case 'memory': {
2263
+ if (previous && previous !== 'memory') state.detail.back = previousRoute;
2264
+ const back = $('detail-back');
2265
+ const name = state.detail.back.slice(1).split('?')[0];
2266
+ back.href = state.detail.back;
2267
+ back.innerHTML = icon('left', true) + esc(viewNames[name] || 'Memories');
2268
+ void loadDetail(params.get('id') || '', project);
1395
2269
  break;
1396
- case 'graph':
2270
+ }
2271
+ case 'graph': {
1397
2272
  void loadOverview();
1398
2273
  if (project) optionSet('graph-project', 'All projects', state.projects, project);
1399
- void loadGraph(project || $('graph-project').value, previous !== '' && previous !== 'graph');
2274
+ else $('graph-project').value = '';
2275
+ const memory = params.get('memory') || '';
2276
+ if (memory) ['graph-query', 'graph-kind', 'graph-relation'].forEach((id) => { $(id).value = ''; });
2277
+ void loadGraph(project, previous !== '' && previous !== 'graph', memory);
1400
2278
  break;
2279
+ }
1401
2280
  case 'context':
2281
+ if (project) {
2282
+ state.context.routeProject = project;
2283
+ syncProjectOptions();
2284
+ if (state.context.params && state.context.params.projectId !== project) {
2285
+ state.context.dirty = true;
2286
+ if (!state.context.pending) renderContext();
2287
+ }
2288
+ }
1402
2289
  void loadOverview();
1403
2290
  void loadFeatures(previous !== '');
1404
2291
  renderContextControls();
@@ -1407,6 +2294,13 @@ function changeView(moveFocus = false) {
1407
2294
  featureStatus('');
1408
2295
  void loadFeatures(true);
1409
2296
  break;
2297
+ case 'project-kit':
2298
+ void loadProjectKit();
2299
+ break;
2300
+ case 'integrations':
2301
+ if (state.integrations.snapshot) renderIntegrations(state.integrations.snapshot);
2302
+ else void loadIntegrations();
2303
+ break;
1410
2304
  }
1411
2305
  }
1412
2306
 
@@ -1414,12 +2308,29 @@ function graphSelectFromList(id, fromInspector) {
1414
2308
  if (!state.graph.nodes.some((node) => node.id === id)) {
1415
2309
  $('graph-query').value = '';
1416
2310
  $('graph-kind').value = '';
2311
+ $('graph-relation').value = '';
1417
2312
  renderGraph();
1418
2313
  }
1419
2314
  selectGraphNode(id, { center: fromInspector, focus: fromInspector });
1420
2315
  }
1421
2316
 
1422
2317
  function handleGraphKeyboard(event) {
2318
+ const edge = event.target.closest('[data-edge-record]');
2319
+ if (edge) {
2320
+ const links = Array.from($('graph-svg').querySelectorAll('[data-edge-record]'));
2321
+ const index = links.indexOf(edge);
2322
+ const nextIndex = { ArrowLeft: Math.max(index - 1, 0), ArrowRight: Math.min(index + 1, links.length - 1), Home: 0, End: links.length - 1 }[event.key];
2323
+ if (event.key === 'Enter' || event.key === ' ') {
2324
+ event.preventDefault();
2325
+ selectGraphEdge(edge.dataset.edgeRecord);
2326
+ } else if (Number.isInteger(nextIndex)) {
2327
+ event.preventDefault();
2328
+ const next = links[nextIndex];
2329
+ selectGraphEdge(next.dataset.edgeRecord);
2330
+ next.focus();
2331
+ }
2332
+ return;
2333
+ }
1423
2334
  const target = event.target.closest('[data-node]');
1424
2335
  if (!target) return;
1425
2336
  if (event.key === 'Enter' || event.key === ' ') {
@@ -1451,11 +2362,34 @@ function handleGraphKeyboard(event) {
1451
2362
  $('theme-toggle').addEventListener('click', () => setPreference('theme', document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
1452
2363
  $('theme-setting').addEventListener('change', (event) => setPreference('theme', event.target.value));
1453
2364
  $('motion-setting').addEventListener('change', (event) => setPreference('motion', event.target.value));
1454
- $('memory-form').addEventListener('submit', (event) => { event.preventDefault(); void loadMemories(memoryQuery()); });
1455
- ['memory-project', 'memory-category', 'memory-agent'].forEach((id) => $(id).addEventListener('change', () => { void loadMemories(memoryQuery()); }));
2365
+ $('memory-form').addEventListener('submit', (event) => { event.preventDefault(); changeMemorySearch(); });
2366
+ ['memory-project', 'memory-category', 'memory-agent', 'memory-state'].forEach((id) => $(id).addEventListener('change', changeMemorySearch));
2367
+ $('overview-quality-project').addEventListener('change', (event) => {
2368
+ const project = event.target.value;
2369
+ const route = routeLink('overview', { project });
2370
+ window.history.replaceState(null, '', route);
2371
+ state.route = route;
2372
+ void loadOverview(true, project).then(() => {
2373
+ if (state.view === 'overview' && state.route === route) $('overview-quality-project')?.focus();
2374
+ });
2375
+ });
2376
+ $('overview-body').addEventListener('change', (event) => {
2377
+ if (event.target.id !== 'attention-filter') return;
2378
+ state.health.filter = event.target.value;
2379
+ renderAttention();
2380
+ });
2381
+ $('overview-body').addEventListener('click', (event) => {
2382
+ if (event.target.closest('#attention-jump')) $('attention-heading')?.focus();
2383
+ });
2384
+ $('integrations-refresh').addEventListener('click', () => { void loadIntegrations(); });
1456
2385
  $('graph-query').addEventListener('input', renderGraph);
1457
2386
  $('graph-kind').addEventListener('change', renderGraph);
1458
- $('graph-project').addEventListener('change', (event) => { void loadGraph(event.target.value); });
2387
+ $('graph-relation').addEventListener('change', renderGraph);
2388
+ $('graph-project').addEventListener('change', (event) => {
2389
+ const route = routeLink('graph', { project: event.target.value });
2390
+ if (location.hash !== route) location.hash = route;
2391
+ else void loadGraph(event.target.value);
2392
+ });
1459
2393
  ['map', 'list'].forEach((mode) => $('graph-mode-' + mode).addEventListener('click', () => {
1460
2394
  state.graph.mode = mode;
1461
2395
  $('graph-mode-map').setAttribute('aria-pressed', String(mode === 'map'));
@@ -1463,6 +2397,13 @@ $('graph-project').addEventListener('change', (event) => { void loadGraph(event.
1463
2397
  renderGraph();
1464
2398
  }));
1465
2399
  $('graph-fit').addEventListener('click', () => fitGraph());
2400
+ $('graph-lens-clear').addEventListener('click', () => {
2401
+ const anchor = state.graph.lens;
2402
+ state.graph.lens = '';
2403
+ renderGraph();
2404
+ if (state.graph.nodes.some((node) => node.id === anchor)) selectGraphNode(anchor, { focus: true });
2405
+ else $('graph-query').focus();
2406
+ });
1466
2407
  $('graph-in').addEventListener('click', () => zoomGraph(1.35));
1467
2408
  $('graph-out').addEventListener('click', () => zoomGraph(1 / 1.35));
1468
2409
  $('graph-svg').addEventListener('keydown', handleGraphKeyboard);
@@ -1472,7 +2413,7 @@ $('graph-svg').addEventListener('focusin', (event) => {
1472
2413
  });
1473
2414
  $('graph-svg').addEventListener('focusout', () => renderGraphTooltip(state.graph.selected));
1474
2415
  $('graph-svg').addEventListener('pointerdown', (event) => {
1475
- if (event.button !== 0 || !event.isPrimary || event.target.closest('[data-node]')) return;
2416
+ if (event.button !== 0 || !event.isPrimary || event.target.closest('[data-node],[data-edge-record]')) return;
1476
2417
  const graph = state.graph;
1477
2418
  graph.drag = { id: event.pointerId, x: event.clientX - graph.panX, y: event.clientY - graph.panY };
1478
2419
  $('graph-svg').setPointerCapture(event.pointerId);
@@ -1492,13 +2433,14 @@ $('graph-svg').addEventListener('lostpointercapture', () => {
1492
2433
  window.addEventListener('resize', () => {
1493
2434
  if (state.view === 'graph' && state.graph.mode === 'map' && state.graph.nodes.length) {
1494
2435
  renderGraphMap();
1495
- selectGraphNode(state.graph.selected);
2436
+ updateGraphSelection();
1496
2437
  }
1497
2438
  });
1498
2439
 
1499
2440
  $('context-form').addEventListener('submit', (event) => { event.preventDefault(); void assembleContext(); });
1500
2441
  $('context-form').addEventListener('input', () => {
1501
2442
  $('context-task').setCustomValidity('');
2443
+ $('context-related-row').hidden = $('context-profile').value !== 'adaptive';
1502
2444
  const store = state.context;
1503
2445
  if (store.pack || store.pending) {
1504
2446
  if (!store.dirty) {
@@ -1508,22 +2450,44 @@ $('context-form').addEventListener('input', () => {
1508
2450
  }
1509
2451
  });
1510
2452
 
2453
+ $('detail-body').addEventListener('submit', (event) => {
2454
+ const { id } = state.detail;
2455
+ switch (event.target.id) {
2456
+ case 'review-form':
2457
+ event.preventDefault();
2458
+ void saveDetail('memory-review', { memoryId: id, state: $('review-state').value, note: $('review-note').value.trim() }, 'review-submit');
2459
+ break;
2460
+ case 'link-search':
2461
+ event.preventDefault();
2462
+ void searchLinkTargets();
2463
+ break;
2464
+ case 'link-form':
2465
+ event.preventDefault();
2466
+ void saveDetail('memory-link', { fromId: id, toId: $('link-target').value, type: $('link-type').value, note: $('link-note').value.trim() }, 'link-editor');
2467
+ break;
2468
+ }
2469
+ });
2470
+
1511
2471
  document.addEventListener('click', (event) => {
1512
- const element = event.target.closest('[data-retry],[data-memory-clear],[data-memory-page],[data-node],[data-related],[data-feature],#context-copy,.skip');
2472
+ const element = event.target.closest('[data-retry],[data-memory-clear],[data-memory-page],[data-node],[data-related],[data-edge-record],[data-graph-lens],[data-feature],[data-unlink],[data-copy-command],#diagnostics-copy,#context-copy,#context-download,#context-compare-button,.skip');
1513
2473
  if (!element) return;
1514
2474
  const data = element.dataset;
1515
2475
  if (data.retry) {
1516
2476
  switch (data.retry) {
1517
- case 'overview': void loadOverview(true); break;
2477
+ case 'overview': void loadOverview(true, state.view === 'overview' ? routeParams().get('project') || '' : ''); break;
2478
+ case 'health': void loadHealth(state.overviewScope); break;
2479
+ case 'kit-profile': void loadProjectKit(true); break;
1518
2480
  case 'memories': void loadMemories(state.memory.query || memoryQuery(), state.memory.offset); break;
1519
- case 'graph': void loadGraph(state.graph.project, true); break;
2481
+ case 'graph': void loadGraph(state.graph.project, true, state.graph.focusMemory); break;
2482
+ case 'detail': void loadDetail(state.detail.id, state.detail.project); break;
2483
+ case 'integrations': void loadIntegrations(); break;
1520
2484
  case 'context': $('context-form').requestSubmit(); break;
1521
2485
  case 'features': void loadFeatures(true); break;
1522
2486
  }
1523
2487
  } else if (Object.hasOwn(data, 'memoryClear')) {
1524
- ['memory-query', 'memory-project', 'memory-category', 'memory-agent'].forEach((id) => { $(id).value = ''; });
2488
+ ['memory-query', 'memory-project', 'memory-category', 'memory-agent', 'memory-state'].forEach((id) => { $(id).value = ''; });
1525
2489
  $('memory-query').focus();
1526
- void loadMemories(memoryQuery());
2490
+ changeMemorySearch();
1527
2491
  } else if (data.memoryPage) {
1528
2492
  const { query, offset, limit } = state.memory;
1529
2493
  const step = data.memoryPage === 'next' ? limit : -limit;
@@ -1533,16 +2497,35 @@ document.addEventListener('click', (event) => {
1533
2497
  selectGraphNode(data.node);
1534
2498
  } else if (data.related) {
1535
2499
  graphSelectFromList(data.related, true);
2500
+ } else if (data.edgeRecord) {
2501
+ selectGraphEdge(data.edgeRecord, $('graph-inspector').contains(element));
2502
+ } else if (data.graphLens) {
2503
+ state.graph.lens = data.graphLens;
2504
+ ['graph-query', 'graph-kind', 'graph-relation'].forEach((id) => { $(id).value = ''; });
2505
+ renderGraph();
2506
+ $('graph-lens-clear').focus();
2507
+ } else if (data.unlink) {
2508
+ void saveDetail('memory-unlink', { memoryId: state.detail.id, linkId: data.unlink }, 'link-editor');
2509
+ } else if (data.copyCommand) {
2510
+ void copyIntegrationText(data.copyCommand, 'Command copied.', element);
1536
2511
  } else if (data.feature) {
1537
2512
  void toggleFeature(data.feature);
1538
2513
  } else if (element.id === 'context-copy') {
1539
2514
  void copyContext();
2515
+ } else if (element.id === 'context-download') {
2516
+ downloadContext();
2517
+ } else if (element.id === 'context-compare-button') {
2518
+ void compareContext();
2519
+ } else if (element.id === 'diagnostics-copy') {
2520
+ void copyIntegrationText(state.integrations.snapshot?.diagnostics, 'Diagnostic report copied.', element);
1540
2521
  } else if (element.classList.contains('skip')) {
1541
2522
  event.preventDefault();
1542
2523
  $('main-content').focus();
1543
2524
  }
1544
2525
  });
1545
2526
 
2527
+ ${PROJECT_KIT_SCRIPT}
2528
+
1546
2529
  window.addEventListener('hashchange', () => changeView(true));
1547
2530
  document.addEventListener('visibilitychange', () => { if (!document.hidden) void refreshActivity(); });
1548
2531
  setInterval(() => { void refreshActivity(); }, 10000);