aimeat 1.19.0 → 1.20.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 (310) hide show
  1. package/README.md +39 -3
  2. package/dist/.env.example +5 -1
  3. package/dist/locales/en.json +231 -1
  4. package/dist/locales/fi.json +233 -1
  5. package/dist/public/components/Markdown.js +88 -15
  6. package/dist/public/components/Mermaid.js +59 -0
  7. package/dist/public/components/NotificationBell.js +94 -0
  8. package/dist/public/css/theme.css +26 -0
  9. package/dist/public/css/views/admin.css +16 -0
  10. package/dist/public/css/views/agents-detail.css +93 -0
  11. package/dist/public/css/views/profile.css +241 -2
  12. package/dist/public/js/api.js +11 -5
  13. package/dist/public/js/services/admin.js +4 -0
  14. package/dist/public/js/services/agents.js +19 -0
  15. package/dist/public/js/services/organisms.js +822 -2
  16. package/dist/public/lib/mermaid/README.md +6 -0
  17. package/dist/public/lib/mermaid/mermaid.min.js +3405 -0
  18. package/dist/public/lib/toastui/toastui-editor-all.min.js +24 -0
  19. package/dist/public/lib/toastui/toastui-editor.min.css +6 -0
  20. package/dist/public/llms-template.txt +4 -0
  21. package/dist/public/spa.html +8 -0
  22. package/dist/public/views/admin/security-tab.js +93 -0
  23. package/dist/public/views/admin.js +2 -0
  24. package/dist/public/views/doc-solo.js +102 -0
  25. package/dist/public/views/profile/agents/agent-card.js +67 -8
  26. package/dist/public/views/profile/agents/tab-messages.js +51 -7
  27. package/dist/public/views/profile/agents-tab.js +184 -4
  28. package/dist/public/views/profile/agents-tasks-subtab.js +11 -1
  29. package/dist/public/views/profile/generator-dashboard/use-autopilot.js +5 -2
  30. package/dist/public/views/profile/landing-page.js +12 -4
  31. package/dist/public/views/profile/organisms-tab.js +1640 -2
  32. package/dist/public/views/profile/schedule-item.js +15 -1
  33. package/dist/public/views/profile.js +6 -3
  34. package/dist/src/cli/connect/mcp/tools/index.d.ts.map +1 -1
  35. package/dist/src/cli/connect/mcp/tools/index.js +2 -0
  36. package/dist/src/cli/connect/mcp/tools/index.js.map +1 -1
  37. package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
  38. package/dist/src/cli/connect/mcp/tools/organisms.js +31 -0
  39. package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
  40. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts +15 -0
  41. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -0
  42. package/dist/src/cli/connect/mcp/tools/workspaces.js +240 -0
  43. package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -0
  44. package/dist/src/cli/connect/tool-call.d.ts +5 -0
  45. package/dist/src/cli/connect/tool-call.d.ts.map +1 -1
  46. package/dist/src/cli/connect/tool-call.js +255 -0
  47. package/dist/src/cli/connect/tool-call.js.map +1 -1
  48. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  49. package/dist/src/cli/init-wizard.js +16 -8
  50. package/dist/src/cli/init-wizard.js.map +1 -1
  51. package/dist/src/config.d.ts +1 -1
  52. package/dist/src/config.d.ts.map +1 -1
  53. package/dist/src/config.js +4 -1
  54. package/dist/src/config.js.map +1 -1
  55. package/dist/src/generated/api-types.d.ts +998 -23
  56. package/dist/src/generated/api-types.d.ts.map +1 -1
  57. package/dist/src/generated/prisma-postgres/client.d.ts +1 -0
  58. package/dist/src/generated/prisma-postgres/client.js +5 -0
  59. package/dist/src/generated/prisma-postgres/default.d.ts +1 -0
  60. package/dist/src/generated/prisma-postgres/default.js +5 -0
  61. package/dist/src/generated/prisma-postgres/edge.d.ts +1 -0
  62. package/dist/src/generated/prisma-postgres/edge.js +1530 -0
  63. package/dist/src/generated/prisma-postgres/index-browser.js +1516 -0
  64. package/dist/src/generated/prisma-postgres/index.d.ts +128983 -0
  65. package/dist/src/generated/prisma-postgres/index.js +1551 -0
  66. package/dist/src/generated/prisma-postgres/package.json +183 -0
  67. package/dist/src/generated/prisma-postgres/query_engine-windows.dll.node +0 -0
  68. package/dist/src/generated/prisma-postgres/query_engine_bg.js +2 -0
  69. package/dist/src/generated/prisma-postgres/query_engine_bg.wasm +0 -0
  70. package/dist/src/generated/prisma-postgres/runtime/edge-esm.js +35 -0
  71. package/dist/src/generated/prisma-postgres/runtime/edge.js +35 -0
  72. package/dist/src/generated/prisma-postgres/runtime/index-browser.d.ts +370 -0
  73. package/dist/src/generated/prisma-postgres/runtime/index-browser.js +17 -0
  74. package/dist/src/generated/prisma-postgres/runtime/library.d.ts +3982 -0
  75. package/dist/src/generated/prisma-postgres/runtime/library.js +147 -0
  76. package/dist/src/generated/prisma-postgres/runtime/react-native.js +84 -0
  77. package/dist/src/generated/prisma-postgres/runtime/wasm-compiler-edge.js +85 -0
  78. package/dist/src/generated/prisma-postgres/runtime/wasm-engine-edge.js +38 -0
  79. package/dist/src/generated/prisma-postgres/schema.prisma +1465 -0
  80. package/dist/src/generated/prisma-postgres/wasm-edge-light-loader.mjs +5 -0
  81. package/dist/src/generated/prisma-postgres/wasm-worker-loader.mjs +5 -0
  82. package/dist/src/generated/prisma-postgres/wasm.d.ts +1 -0
  83. package/dist/src/generated/prisma-postgres/wasm.js +1537 -0
  84. package/dist/src/index.js +1 -1
  85. package/dist/src/mcp/agent-messages.d.ts +3 -0
  86. package/dist/src/mcp/agent-messages.d.ts.map +1 -1
  87. package/dist/src/mcp/agent-messages.js +9 -3
  88. package/dist/src/mcp/agent-messages.js.map +1 -1
  89. package/dist/src/mcp/annotations.d.ts.map +1 -1
  90. package/dist/src/mcp/annotations.js +12 -0
  91. package/dist/src/mcp/annotations.js.map +1 -1
  92. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  93. package/dist/src/mcp/catalog/definitions.js +139 -0
  94. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  95. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  96. package/dist/src/mcp/catalog/surfaces.js +9 -2
  97. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  98. package/dist/src/mcp/index.d.ts.map +1 -1
  99. package/dist/src/mcp/index.js +2 -0
  100. package/dist/src/mcp/index.js.map +1 -1
  101. package/dist/src/mcp/organisms.d.ts +2 -0
  102. package/dist/src/mcp/organisms.d.ts.map +1 -1
  103. package/dist/src/mcp/organisms.js +106 -21
  104. package/dist/src/mcp/organisms.js.map +1 -1
  105. package/dist/src/mcp/workspaces.d.ts +34 -0
  106. package/dist/src/mcp/workspaces.d.ts.map +1 -0
  107. package/dist/src/mcp/workspaces.js +464 -0
  108. package/dist/src/mcp/workspaces.js.map +1 -0
  109. package/dist/src/middleware/workspace-access.d.ts +13 -0
  110. package/dist/src/middleware/workspace-access.d.ts.map +1 -1
  111. package/dist/src/middleware/workspace-access.js +12 -7
  112. package/dist/src/middleware/workspace-access.js.map +1 -1
  113. package/dist/src/models/schemas.d.ts +2 -2
  114. package/dist/src/routes/admin-prompts.d.ts.map +1 -1
  115. package/dist/src/routes/admin-prompts.js +4 -1
  116. package/dist/src/routes/admin-prompts.js.map +1 -1
  117. package/dist/src/routes/admin-security.d.ts +19 -0
  118. package/dist/src/routes/admin-security.d.ts.map +1 -0
  119. package/dist/src/routes/admin-security.js +87 -0
  120. package/dist/src/routes/admin-security.js.map +1 -0
  121. package/dist/src/routes/agent-messages.d.ts +3 -0
  122. package/dist/src/routes/agent-messages.d.ts.map +1 -1
  123. package/dist/src/routes/agent-messages.js +27 -2
  124. package/dist/src/routes/agent-messages.js.map +1 -1
  125. package/dist/src/routes/agent-tasks.d.ts +5 -1
  126. package/dist/src/routes/agent-tasks.d.ts.map +1 -1
  127. package/dist/src/routes/agent-tasks.js +43 -3
  128. package/dist/src/routes/agent-tasks.js.map +1 -1
  129. package/dist/src/routes/apps.d.ts +13 -0
  130. package/dist/src/routes/apps.d.ts.map +1 -1
  131. package/dist/src/routes/apps.js +74 -42
  132. package/dist/src/routes/apps.js.map +1 -1
  133. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  134. package/dist/src/routes/bootstrap.js +1 -0
  135. package/dist/src/routes/bootstrap.js.map +1 -1
  136. package/dist/src/routes/extensions.d.ts.map +1 -1
  137. package/dist/src/routes/extensions.js +5 -1
  138. package/dist/src/routes/extensions.js.map +1 -1
  139. package/dist/src/routes/generator-autopilot.d.ts +1 -0
  140. package/dist/src/routes/generator-autopilot.d.ts.map +1 -1
  141. package/dist/src/routes/generator-autopilot.js +8 -2
  142. package/dist/src/routes/generator-autopilot.js.map +1 -1
  143. package/dist/src/routes/generator.d.ts.map +1 -1
  144. package/dist/src/routes/generator.js +20 -0
  145. package/dist/src/routes/generator.js.map +1 -1
  146. package/dist/src/routes/libs.d.ts +4 -0
  147. package/dist/src/routes/libs.d.ts.map +1 -1
  148. package/dist/src/routes/libs.js +134 -5
  149. package/dist/src/routes/libs.js.map +1 -1
  150. package/dist/src/routes/memory.d.ts +2 -0
  151. package/dist/src/routes/memory.d.ts.map +1 -1
  152. package/dist/src/routes/memory.js +20 -10
  153. package/dist/src/routes/memory.js.map +1 -1
  154. package/dist/src/routes/notifications.d.ts +18 -0
  155. package/dist/src/routes/notifications.d.ts.map +1 -0
  156. package/dist/src/routes/notifications.js +64 -0
  157. package/dist/src/routes/notifications.js.map +1 -0
  158. package/dist/src/routes/organisms.d.ts +29 -0
  159. package/dist/src/routes/organisms.d.ts.map +1 -1
  160. package/dist/src/routes/organisms.js +1042 -2
  161. package/dist/src/routes/organisms.js.map +1 -1
  162. package/dist/src/routes/schedules.d.ts.map +1 -1
  163. package/dist/src/routes/schedules.js +10 -2
  164. package/dist/src/routes/schedules.js.map +1 -1
  165. package/dist/src/routes/storage-files.d.ts +5 -0
  166. package/dist/src/routes/storage-files.d.ts.map +1 -1
  167. package/dist/src/routes/storage-files.js +83 -16
  168. package/dist/src/routes/storage-files.js.map +1 -1
  169. package/dist/src/routes/upload.d.ts +3 -0
  170. package/dist/src/routes/upload.d.ts.map +1 -1
  171. package/dist/src/routes/upload.js +9 -1
  172. package/dist/src/routes/upload.js.map +1 -1
  173. package/dist/src/server-bootstrap/config-init.d.ts.map +1 -1
  174. package/dist/src/server-bootstrap/config-init.js +1 -0
  175. package/dist/src/server-bootstrap/config-init.js.map +1 -1
  176. package/dist/src/server-bootstrap/middleware-guards.d.ts.map +1 -1
  177. package/dist/src/server-bootstrap/middleware-guards.js +4 -2
  178. package/dist/src/server-bootstrap/middleware-guards.js.map +1 -1
  179. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  180. package/dist/src/server-bootstrap/routes-loader.js +4 -0
  181. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  182. package/dist/src/server-bootstrap/service-init.d.ts +17 -0
  183. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  184. package/dist/src/server-bootstrap/service-init.js +26 -0
  185. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  186. package/dist/src/services/access-guard.d.ts +52 -0
  187. package/dist/src/services/access-guard.d.ts.map +1 -0
  188. package/dist/src/services/access-guard.js +30 -0
  189. package/dist/src/services/access-guard.js.map +1 -0
  190. package/dist/src/services/config-schema.js +1 -1
  191. package/dist/src/services/config-schema.js.map +1 -1
  192. package/dist/src/services/consent.d.ts.map +1 -1
  193. package/dist/src/services/consent.js +37 -1
  194. package/dist/src/services/consent.js.map +1 -1
  195. package/dist/src/services/gate-expiry.d.ts +25 -0
  196. package/dist/src/services/gate-expiry.d.ts.map +1 -0
  197. package/dist/src/services/gate-expiry.js +26 -0
  198. package/dist/src/services/gate-expiry.js.map +1 -0
  199. package/dist/src/services/gate-policy.d.ts +50 -0
  200. package/dist/src/services/gate-policy.d.ts.map +1 -0
  201. package/dist/src/services/gate-policy.js +82 -0
  202. package/dist/src/services/gate-policy.js.map +1 -0
  203. package/dist/src/services/generator-autopilot.d.ts +18 -1
  204. package/dist/src/services/generator-autopilot.d.ts.map +1 -1
  205. package/dist/src/services/generator-autopilot.js +94 -154
  206. package/dist/src/services/generator-autopilot.js.map +1 -1
  207. package/dist/src/services/handbooks/agent.d.ts +3 -1
  208. package/dist/src/services/handbooks/agent.d.ts.map +1 -1
  209. package/dist/src/services/handbooks/agent.js +10 -3
  210. package/dist/src/services/handbooks/agent.js.map +1 -1
  211. package/dist/src/services/manifest-schema.d.ts +48 -0
  212. package/dist/src/services/manifest-schema.d.ts.map +1 -0
  213. package/dist/src/services/manifest-schema.js +88 -0
  214. package/dist/src/services/manifest-schema.js.map +1 -0
  215. package/dist/src/services/notify.d.ts +19 -0
  216. package/dist/src/services/notify.d.ts.map +1 -0
  217. package/dist/src/services/notify.js +43 -0
  218. package/dist/src/services/notify.js.map +1 -0
  219. package/dist/src/services/organism-export.d.ts +13 -0
  220. package/dist/src/services/organism-export.d.ts.map +1 -0
  221. package/dist/src/services/organism-export.js +58 -0
  222. package/dist/src/services/organism-export.js.map +1 -0
  223. package/dist/src/services/organism-import.d.ts +16 -0
  224. package/dist/src/services/organism-import.d.ts.map +1 -0
  225. package/dist/src/services/organism-import.js +82 -0
  226. package/dist/src/services/organism-import.js.map +1 -0
  227. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  228. package/dist/src/services/prompt-defaults.js +75 -0
  229. package/dist/src/services/prompt-defaults.js.map +1 -1
  230. package/dist/src/services/safe-zip.d.ts +27 -0
  231. package/dist/src/services/safe-zip.d.ts.map +1 -0
  232. package/dist/src/services/safe-zip.js +131 -0
  233. package/dist/src/services/safe-zip.js.map +1 -0
  234. package/dist/src/services/scheduler.d.ts +19 -1
  235. package/dist/src/services/scheduler.d.ts.map +1 -1
  236. package/dist/src/services/scheduler.js +49 -17
  237. package/dist/src/services/scheduler.js.map +1 -1
  238. package/dist/src/services/security-incident.d.ts +25 -0
  239. package/dist/src/services/security-incident.d.ts.map +1 -0
  240. package/dist/src/services/security-incident.js +49 -0
  241. package/dist/src/services/security-incident.js.map +1 -0
  242. package/dist/src/services/skill-bundle/generator.d.ts +2 -0
  243. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
  244. package/dist/src/services/skill-bundle/generator.js +11 -5
  245. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  246. package/dist/src/services/template-bundles.d.ts +37 -0
  247. package/dist/src/services/template-bundles.d.ts.map +1 -0
  248. package/dist/src/services/template-bundles.js +130 -0
  249. package/dist/src/services/template-bundles.js.map +1 -0
  250. package/dist/src/services/workspace-export.d.ts +63 -0
  251. package/dist/src/services/workspace-export.d.ts.map +1 -0
  252. package/dist/src/services/workspace-export.js +145 -0
  253. package/dist/src/services/workspace-export.js.map +1 -0
  254. package/dist/src/services/workspace-import.d.ts +44 -0
  255. package/dist/src/services/workspace-import.d.ts.map +1 -0
  256. package/dist/src/services/workspace-import.js +120 -0
  257. package/dist/src/services/workspace-import.js.map +1 -0
  258. package/dist/src/services/workspace-meta.d.ts +39 -0
  259. package/dist/src/services/workspace-meta.d.ts.map +1 -0
  260. package/dist/src/services/workspace-meta.js +95 -0
  261. package/dist/src/services/workspace-meta.js.map +1 -0
  262. package/dist/src/storage/interface.d.ts +26 -0
  263. package/dist/src/storage/interface.d.ts.map +1 -1
  264. package/dist/src/storage/interface.js.map +1 -1
  265. package/dist/src/storage/pattern-utils.d.ts +26 -1
  266. package/dist/src/storage/pattern-utils.d.ts.map +1 -1
  267. package/dist/src/storage/pattern-utils.js +37 -8
  268. package/dist/src/storage/pattern-utils.js.map +1 -1
  269. package/dist/src/storage/providers/mongodb/index.d.ts +50 -7
  270. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  271. package/dist/src/storage/providers/mongodb/index.js +244 -15
  272. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  273. package/dist/src/storage/providers/postgres/index.d.ts +6 -0
  274. package/dist/src/storage/providers/postgres/index.d.ts.map +1 -0
  275. package/dist/src/storage/providers/postgres/index.js +33 -0
  276. package/dist/src/storage/providers/postgres/index.js.map +1 -0
  277. package/dist/src/storage/providers/sqlite/index.d.ts +11 -1
  278. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  279. package/dist/src/storage/providers/sqlite/index.js +152 -2
  280. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  281. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts +1 -0
  282. package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
  283. package/dist/src/storage/providers/sqlite/repos/agent-task.js +5 -1
  284. package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
  285. package/dist/src/storage/providers/sqlite/repos/storage-file.d.ts.map +1 -1
  286. package/dist/src/storage/providers/sqlite/repos/storage-file.js +6 -2
  287. package/dist/src/storage/providers/sqlite/repos/storage-file.js.map +1 -1
  288. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  289. package/dist/src/storage/providers/sqlite/schema.js +23 -0
  290. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  291. package/dist/src/storage/repositories/app.repository.d.ts +42 -0
  292. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  293. package/dist/src/storage/repositories/organism.repository.d.ts +8 -1
  294. package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
  295. package/dist/src/storage/storage-factory.d.ts +1 -1
  296. package/dist/src/storage/storage-factory.d.ts.map +1 -1
  297. package/dist/src/storage/storage-factory.js +6 -0
  298. package/dist/src/storage/storage-factory.js.map +1 -1
  299. package/dist/src/utils/env-config.js +5 -5
  300. package/dist/src/utils/env-config.js.map +1 -1
  301. package/dist/src/utils/env-validator.js +6 -6
  302. package/dist/src/utils/env-validator.js.map +1 -1
  303. package/dist/src/utils/stable-json.d.ts +22 -0
  304. package/dist/src/utils/stable-json.d.ts.map +1 -0
  305. package/dist/src/utils/stable-json.js +36 -0
  306. package/dist/src/utils/stable-json.js.map +1 -0
  307. package/dist/static/app-catalog.html +936 -221
  308. package/package.json +6 -3
  309. package/prisma/schema.postgres.prisma +1467 -0
  310. package/prisma/schema.prisma +27 -0
@@ -9,6 +9,10 @@
9
9
  "profile.scheduler.newSchedule": "Uusi ajastus",
10
10
  "profile.scheduler.close": "Sulje",
11
11
  "profile.scheduler.triggered": "Ajastuksen suoritus käynnistetty",
12
+ "profile.scheduler.runCreated": "Suoritus aloitettu — uusi tehtävä luotiin",
13
+ "profile.scheduler.runBusy": "Tehtävää ei luotu — edellinen suoritus on yhä käynnissä. Päätä, merkitse epäonnistuneeksi tai poista se, ja suorita uudelleen.",
14
+ "profile.scheduler.runLimited": "Ei suoritettu — tämän ajastuksen suoritusraja (päivittäinen tai maksimi) on saavutettu.",
15
+ "profile.scheduler.runError": "Suoritus epäonnistui",
12
16
  "profile.scheduler.cancelled": "Ajastus peruttu",
13
17
  "profile.scheduler.created": "Ajastus luotu",
14
18
  "profile.scheduler.saving": "Tallennetaan…",
@@ -107,6 +111,10 @@
107
111
  "copied": "✓ Kopioitu",
108
112
  "error": "Virhe"
109
113
  },
114
+ "notif": {
115
+ "title": "Ilmoitukset",
116
+ "empty": "Ei ilmoituksia vielä"
117
+ },
110
118
  "nav": {
111
119
  "try": "Kokeile",
112
120
  "devView": "Kehittäjille",
@@ -490,12 +498,27 @@
490
498
  "byTag": "Suodata tagilla:",
491
499
  "groupBy": "Ryhmittele:",
492
500
  "groupByNone": "Ei ryhmittelyä",
501
+ "groupByCustom": "Omat ryhmät",
493
502
  "groupByTag": "Tagin mukaan",
494
503
  "groupByMode": "Tilan mukaan",
495
504
  "clear": "Tyhjennä",
496
505
  "untagged": "Ei tageja",
497
506
  "noMatches": "Yksikään agentti ei vastaa nykyistä suodatinta."
498
507
  },
508
+ "groups": {
509
+ "addGroup": "Lisää ryhmä",
510
+ "hint": "Raahaa agentti kahvasta ryhmän päälle liittääksesi sen siihen.",
511
+ "ungrouped": "Ryhmittelemättömät",
512
+ "unnamed": "Nimetön ryhmä",
513
+ "namePlaceholder": "Ryhmän nimi",
514
+ "rename": "Nimeä uudelleen napsauttamalla",
515
+ "remove": "Poista ryhmä",
516
+ "toggle": "Pienennä / laajenna",
517
+ "dragHint": "Raahaa ryhmän päälle liittääksesi",
518
+ "emptyDrop": "Raahaa agentteja tähän lisätäksesi ne tähän ryhmään.",
519
+ "confirmRemove": "Poistetaanko ryhmä ”{name}”? Sen agentit siirtyvät Ryhmittelemättömiin — niitä ei poisteta.",
520
+ "saveError": "Ryhmien tallennus epäonnistui."
521
+ },
499
522
  "reorderHint": "Raahaa järjestääksesi",
500
523
  "solo": {
501
524
  "notFound": "Agenttia ei löytynyt.",
@@ -743,6 +766,9 @@
743
766
  "online": "online",
744
767
  "offline": "offline",
745
768
  "threads": "Keskustelut",
769
+ "threadFallback": "Keskustelu",
770
+ "threadsShowMore": "Näytä {count} lisää",
771
+ "threadsShowLess": "Näytä vähemmän",
746
772
  "proposedTask": "Ehdotettu tehtävä",
747
773
  "createTask": "Luo tämä tehtävä",
748
774
  "adjustTask": "Muokkaan ensin",
@@ -2875,7 +2901,7 @@
2875
2901
  "baseUrlInvalid": "Pitää alkaa http:// tai https://",
2876
2902
  "dbUrl": "MongoDB URL",
2877
2903
  "dbUrlHint": "Jätä tyhjäksi muistinsisäiseen tallennukseen (data katoaa uudelleenkäynnistyksessä)",
2878
- "dbUrlInvalid": "Pitää alkaa mongodb:// tai mongodb+srv://",
2904
+ "dbUrlInvalid": "Pitää olla kelvollinen mongodb:// (tai mongodb+srv://) tai postgresql:// -yhteysosoite",
2879
2905
  "adminPassword": "Ylläpitäjän salasana",
2880
2906
  "adminPasswordHint": "Salasana operaattorin hallintapaneeliin (vähintään 8 merkkiä)",
2881
2907
  "adminPasswordWeak": "Salasanan pitää olla vähintään 8 merkkiä",
@@ -3018,6 +3044,7 @@
3018
3044
  "storage_memory": "Muistissa (vain kehitys, data häviää uudelleenkäynnistyksessä)",
3019
3045
  "storage_sqlite": "SQLite (henkilökohtainen käyttö, tiedostopohjainen)",
3020
3046
  "storage_mongodb": "MongoDB (tuotanto, vaatii erillisen palvelimen)",
3047
+ "storage_postgresql": "PostgreSQL (tuotanto, vaatii erillisen palvelimen)",
3021
3048
  "sqlite_path_label": "SQLite-tietokannan polku",
3022
3049
  "sqlite_path_hint": "Tietokantatiedoston polku (luodaan automaattisesti)",
3023
3050
  "pushEnabled": "Ota käyttöön push-ilmoitukset henkilökohtaisille nodeille?",
@@ -5434,6 +5461,197 @@
5434
5461
  },
5435
5462
  "organisms": {
5436
5463
  "title": "Organismit",
5464
+ "openWorkspace": "Avaa työtila",
5465
+ "backToList": "Kaikki organismit",
5466
+ "noWorkspace": "Tällä organismilla ei ole vielä työtilaa. Luo sellainen seurataksesi tavoitteita, suunnitelmia, tuotoksia ja päätöksiä — versioituna julkaistaessa.",
5467
+ "setupWorkspace": "Luo työtila",
5468
+ "workspaceReady": "Työtila valmis",
5469
+ "setupError": "Työtilan luonti epäonnistui",
5470
+ "addDraft": "Lisää luonnos",
5471
+ "saveDraft": "Tallenna luonnos",
5472
+ "draftSaved": "Luonnos tallennettu",
5473
+ "idOptional": "tunnus (valinnainen)",
5474
+ "draft": "luonnos",
5475
+ "publish": "Julkaise",
5476
+ "published": "Julkaistu",
5477
+ "draftVersion": "Luonnos",
5478
+ "publishedVersion": "Julkaistu",
5479
+ "workspacesDesc": "Tämän organismin työtilat — jokainen on itsenäinen tila omine dokumentteineen, tietueineen ja historioineen.",
5480
+ "newWorkspace": "Uusi työtila",
5481
+ "workspaceName": "Työtilan nimi",
5482
+ "noWorkspaces": "Ei vielä työtiloja — luo ensimmäinen aloittaaksesi.",
5483
+ "deleteWorkspaceConfirm": "Poistetaanko työtila “{name}” ja kaikki sen sisältö? Tätä ei voi perua.",
5484
+ "backToWorkspaces": "Työtilat",
5485
+ "allOrganisms": "Kaikki organismit",
5486
+ "currentWorkspaces": "Tämän organismin workspacet",
5487
+ "import": "Tuo",
5488
+ "importHint": "Palauta workspace .zip-varmuuskopiosta",
5489
+ "export": "Vie varmuuskopio (.zip)",
5490
+ "imported": "Workspace tuotu",
5491
+ "exportOrg": "Vie organismi",
5492
+ "exportOrgHint": "Lataa ZIP-varmuuskopio koko organismista (kaikki workspacet)",
5493
+ "importOrg": "Tuo organismi",
5494
+ "importOrgHint": "Palauta organismi .zip-varmuuskopiosta",
5495
+ "orgImported": "Organismi tuotu",
5496
+ "requestAccess": "Pyydä pääsyä",
5497
+ "accessRequested": "Pääsyä pyydetty — {creator} päättää.",
5498
+ "byCreator": "tekijä {creator}",
5499
+ "yours": "sinun",
5500
+ "requests": "Pääsypyynnöt",
5501
+ "noRequests": "Ei pääsypyyntöjä.",
5502
+ "approve": "Hyväksy",
5503
+ "deny": "Hylkää",
5504
+ "approved": "hyväksytty",
5505
+ "revoke": "Peru",
5506
+ "editFlow": "Miten muokkaus toimii täällä",
5507
+ "activity": "Aktiivisuus",
5508
+ "draftEdit": "Luonnoksen muokkaus",
5509
+ "publishedWord": "Julkaistu",
5510
+ "publishedVerb": "julkaisi",
5511
+ "editedVerb": "muokkasi",
5512
+ "confirmDeleteItem": "Poistetaanko “{name}”? Sen luonnos, julkaistu versio ja koko historia poistetaan. Tätä ei voi perua.",
5513
+ "deletedItem": "Poistettu",
5514
+ "popOut": "Avaa omaan ikkunaan",
5515
+ "docNotFound2": "Dokumenttia ei löytynyt.",
5516
+ "loginToView": "Kirjaudu nähdäksesi tämän dokumentin.",
5517
+ "docRejected": "Dokumentti hylättiin",
5518
+ "participants": "Ketkä työskentelevät täällä",
5519
+ "you": "sinä",
5520
+ "creatorTag": "luoja",
5521
+ "guest": "vieras",
5522
+ "anAgent": "agentti",
5523
+ "hiddenAgent": "Agentti jonka tietoja et näe",
5524
+ "otherAgentHint": "Toisen omistajan agentti — näet mitä se on tehnyt täällä, et sen nykytilaa",
5525
+ "viaAgent": "tehty tämän agentin kautta",
5526
+ "contributions": "kontribuutiota",
5527
+ "events": "tapahtumaa",
5528
+ "less": "Vähemmän",
5529
+ "more": "Enemmän",
5530
+ "mon": "Ma",
5531
+ "wed": "Ke",
5532
+ "fri": "Pe",
5533
+ "docsDraft": "Dokumentit luonnos",
5534
+ "docsPublished": "Dokumentit julkaistu",
5535
+ "recordsDraft": "Tietueet luonnos",
5536
+ "recordsPublished": "Tietueet julkaistu",
5537
+ "overview": "Yleiskuva — ketkä ja mitkä käyttävät tätä organismia",
5538
+ "hide": "Piilota",
5539
+ "show": "Näytä",
5540
+ "aiPrompt": "AI-käyttöohje:",
5541
+ "aiPromptHuman": "Chattiin",
5542
+ "aiPromptAgent": "Koodausagentille",
5543
+ "promptCopied": "Käyttöohje kopioitu — liitä se tekoälyllesi.",
5544
+ "addSpaceBtn": "Tila",
5545
+ "addSpaceTitle": "Lisää tila",
5546
+ "addSpaceDesc": "Dokumenttitila on vapaamuotoinen wiki (osiot + markdown-sivut). Tietuetila on skeemalukittu lista (lomakkeet). Voit poistaa tiloja asetuksissa.",
5547
+ "modeDocument": "Dokumentti (wiki)",
5548
+ "modeRecords": "Tietueet (lomake)",
5549
+ "noFields": "Ei kenttiä",
5550
+ "createdAt": "Luotu",
5551
+ "lastSaved": "Viimeksi tallennettu",
5552
+ "publishedAt": "Julkaistu",
5553
+ "fileVisibility": "Tiedostojen näkyvyys",
5554
+ "fileVisibilityNote": "Yksityiset tiedostot näkyvät vain sinulle — tee niistä julkisia jakaaksesi dokumentin.",
5555
+ "imageVisibility": "Kuvien näkyvyys",
5556
+ "imageVisibilityNote": "Yksityiset kuvat näkyvät vain sinulle — tee niistä julkisia jakaaksesi dokumentin.",
5557
+ "makeAllPublic": "Tee kaikista julkisia",
5558
+ "toggleVisibility": "Vaihda julkinen / yksityinen",
5559
+ "public": "Julkinen",
5560
+ "private": "Yksityinen",
5561
+ "sources": "Lähteet",
5562
+ "addSource": "Lisää lähde",
5563
+ "close": "Sulje",
5564
+ "sourcesDesc": "Viitteet, joita tämä työtila hyödyntää — muisti, tiedostot ja tietopaketit. Vain osoittimia; alkuperäiset pysyvät paikoillaan.",
5565
+ "src_memory": "Muisti",
5566
+ "src_storage": "Tiedostot",
5567
+ "src_knowledge": "Tieto",
5568
+ "mine": "Omat",
5569
+ "searchSources": "Hae…",
5570
+ "search": "Hae",
5571
+ "noResults": "Ei tuloksia",
5572
+ "entries": "merkintää",
5573
+ "attach": "Liitä",
5574
+ "noSources": "Ei vielä lähteitä",
5575
+ "external": "ulkoinen",
5576
+ "sourceAdded": "Lähde lisätty",
5577
+ "sourceExists": "Jo lisätty",
5578
+ "sourcesSaveError": "Lähteiden tallennus epäonnistui",
5579
+ "publishGated": "Lähetetty tarkistettavaksi (julkaisuportti päällä)",
5580
+ "publishGate": "Vaadi tarkistus ennen julkaisua",
5581
+ "gateToggled": "Julkaisuportti päivitetty",
5582
+ "needsDecision": "Vaatii päätöksesi",
5583
+ "approve": "Hyväksy",
5584
+ "reject": "Hylkää",
5585
+ "approved": "Hyväksytty",
5586
+ "rejected": "Hylätty",
5587
+ "decisions": "Viimeisimmät päätökset",
5588
+ "noneYet": "ei vielä mitään",
5589
+ "onePerLine": "yksi per rivi",
5590
+ "docs": "dokumentit",
5591
+ "newPage": "Uusi dokumentti",
5592
+ "edit": "Muokkaa",
5593
+ "pageTitle": "Dokumentin otsikko",
5594
+ "writeMarkdown": "Kirjoita markdownia…",
5595
+ "insertImage": "Lataa kuva tiedostosta",
5596
+ "orPaste": "…tai liitä / vedä kuva editoriin",
5597
+ "saving": "Tallennetaan…",
5598
+ "pageSaved": "Dokumentti tallennettu",
5599
+ "section": "Osio",
5600
+ "sectionName": "Osion nimi",
5601
+ "unsorted": "Lajittelematon",
5602
+ "moveTo": "siirrä…",
5603
+ "newDocHere": "Uusi dokumentti tähän",
5604
+ "addSubsection": "Alaosio",
5605
+ "selectDoc": "Valitse dokumentti tai luo uusi.",
5606
+ "docNotFound": "Ei dokumenttia nimeltä ”{title}”",
5607
+ "rename": "Nimeä uudelleen",
5608
+ "dragHint": "Vedä osioon",
5609
+ "unnamed": "(nimetön)",
5610
+ "removeSection": "Poista osio",
5611
+ "confirmRemoveSection": "Poistetaanko osio ”{name}”? Sen dokumentit siirtyvät Lajittelemattomiin — niitä ei poisteta.",
5612
+ "confirmDeleteWorkspace": "Haluatko varmasti poistaa tämän työtilan? Kaikki sen dokumentit, tietueet ja versiohistoria poistetaan pysyvästi. Tätä ei voi perua.",
5613
+ "spaces": "Tilat",
5614
+ "recordsMode": "tietueet",
5615
+ "remove": "Poista",
5616
+ "spaceName": "Uuden tilan nimi",
5617
+ "docsSpace": "dokumenttitila",
5618
+ "recordsSpace": "tietuetyyppi",
5619
+ "addSpace": "+ Lisää",
5620
+ "spaceAdded": "Tila lisätty",
5621
+ "spaceRemoved": "Tila poistettu",
5622
+ "removeSpace": "Poista tila",
5623
+ "confirmRemoveSpace": "Poistetaanko \"{name}\" tästä työtilasta? Sen data säilyy muistissa (orpona) mutta osio katoaa.",
5624
+ "restructure": "✨ Muokkaa rakennetta / lisää tyyppejä tekoälyllä",
5625
+ "restructureTitle": "Muokkaa rakennetta / lisää tyyppejä tekoälyllä",
5626
+ "restructureDesc": "Kuvaile mitä lisätä tai muuttaa. Olemassa olevat tyypit ja niiden data säilyvät — tekoäly laajentaa nykyistä rakennetta. (Aloita kokonaan alusta poistamalla työtila ensin alta.)",
5627
+ "dangerZone": "Vaaravyöhyke",
5628
+ "deleteWarn": "Työtilan poistaminen poistaa manifestin ja KAIKEN sen datan — luonnokset, julkaistut tietueet, versiohistorian — ja sen skeemat. Organismi säilyy. Tätä ei voi perua.",
5629
+ "deleteConfirmLabel": "Vahvista kirjoittamalla työtilan nimi",
5630
+ "deleteWorkspace": "Poista työtila",
5631
+ "workspaceDeleted": "Työtila poistettu",
5632
+ "settings": "Asetukset",
5633
+ "wsName": "Nimi",
5634
+ "wsSummary": "Kuvaus",
5635
+ "autonomy": "Tekoälyn autonomia (L1 varovainen → L5 vapaa)",
5636
+ "template": "Malli",
5637
+ "settingsSaved": "Asetukset tallennettu",
5638
+ "generateTitle": "Tai luo mukautettu työtila tekoälyllä",
5639
+ "generateDesc": "Kuvaile mihin tämä työtila on tarkoitettu — seuraa, järjestä tai jalosta tietoa, tai hoida kokonainen projektin elinkaari. Tekoäly suunnittelee objektityypit. Käytä OpenRouter-avaintasi yhden klikkauksen luontiin, tai kopioi kehote mihin tahansa AI-keskusteluun (ilmaiseksi) ja liitä tulos takaisin.",
5640
+ "generatePlaceholder": "esim. peliprojektin koko kehityskaari · tekoälyn hallinnan tutkimuksen järjestäminen · tuotespeksien jalostaminen",
5641
+ "generate": "Luo tekoälyllä",
5642
+ "generating": "Luodaan…",
5643
+ "generateError": "Luonti epäonnistui",
5644
+ "noAiKey": "Määritä OpenRouter-avaimesi yllä, tai kopioi kehote omaan AI-keskusteluusi.",
5645
+ "copyPrompt": "Kopioi kehote",
5646
+ "promptCopied": "Kehote kopioitu — liitä se mihin tahansa AI-keskusteluun ja liitä sen palauttama JSON alle.",
5647
+ "pasteHelp": "Ei avainta? Kopioi yllä oleva kehote mihin tahansa AI-keskusteluun ja liitä sen palauttama JSON tähän:",
5648
+ "pastePlaceholder": "Liitä tekoälyn JSON-vastaus tähän",
5649
+ "applyPasted": "Tarkista ja ota käyttöön",
5650
+ "applyError": "Käyttöönotto epäonnistui — tarkista liitetty JSON.",
5651
+ "genFailed": "Luonti epäonnistui — yritä uudelleen",
5652
+ "fixNeeded": "Tämä pitää korjata ennen tallennusta:",
5653
+ "copyFixPrompt": "Kopioi korjauskehote tekoälylle",
5654
+ "fixPromptCopied": "Korjauskehote kopioitu — liitä se takaisin tekoälylle ja liitä korjattu JSON.",
5437
5655
  "desc": "Organismit ovat ryhmiä — yhteisöjä, tiimejä, kerhoja tai projekteja. Luo oma tai liity olemassa oleviin jakaaksesi tietoa, koordinoidaksesi työtä ja rakentaaksesi yhdessä.",
5438
5656
  "loading": "Ladataan organismeja...",
5439
5657
  "empty": "Et kuulu vielä yhteenkään organismiin.",
@@ -5873,6 +6091,20 @@
5873
6091
  }
5874
6092
  },
5875
6093
  "admin": {
6094
+ "security": {
6095
+ "title": "Tietoturva",
6096
+ "desc": "Hylätyt / karanteeniin laitetut lataukset ja muut tietoturvatapahtumat.",
6097
+ "open": "auki",
6098
+ "none": "Ei tietoturvatapahtumia 🎉",
6099
+ "actor": "Kenen puolesta",
6100
+ "source": "Lähde",
6101
+ "resolve": "Merkitse hoidetuksi",
6102
+ "resolved": "Tapahtuma hoidettu",
6103
+ "delete": "Poista",
6104
+ "deleted": "Poistettu",
6105
+ "deleteConfirm": "Poistetaanko tämä tapahtuma ja sen karanteenitiedosto?",
6106
+ "evidence": "Tiedosto"
6107
+ },
5876
6108
  "tabs": {
5877
6109
  "agentIntegration": "Agenttiintegraatio"
5878
6110
  },
@@ -15,17 +15,31 @@
15
15
  * @structure
16
16
  * - Markdown ({ text }) -> vnode tree wrapped in a div.md-body
17
17
  * - sanitizeHref(url) -> safe href or null
18
+ * - sanitizeImgSrc(url) -> safe <img> src or null (also permits blob: object URLs)
18
19
  * @usage
19
20
  * import { Markdown } from '/components/Markdown.js';
20
21
  * html`<${Markdown} text=${someMarkdownString} />`
21
22
  * @version-history
22
23
  * v1.0.0 -- 2026-05-31 -- Initial creation for the agent README tab
24
+ * v1.1.0 -- 2026-06-08 -- Image src sanitized separately, allowing blob: (auth'd object URLs)
25
+ * so callers can swap a private /v1/storage URL to a fetched blob in the markdown text.
26
+ * v1.2.0 -- 2026-06-08 -- ```mermaid fenced blocks render as diagrams (lazy-loaded Mermaid),
27
+ * so documents (and any Markdown view) can embed charts.
23
28
  */
24
29
  import { h } from 'preact';
30
+ import { Mermaid } from './Mermaid.js';
25
31
 
26
32
  // Only these schemes may appear in a rendered link. Everything else (javascript:,
27
33
  // data:, vbscript:, file:, etc.) is dropped — the anchor renders without href.
28
34
  const SAFE_SCHEME = /^(https?:|mailto:)/i;
35
+ // Image src may additionally be a blob: object URL — a caller resolving a private /v1/storage image
36
+ // to an auth'd `blob:` URL (scripts never execute from an <img src>, and blob: is same-origin).
37
+ const SAFE_IMG_SCHEME = /^(https?:|blob:)/i;
38
+
39
+ // Slug used for heading anchor ids — kept in sync with the [[Doc#Heading]] wiki-link resolver.
40
+ export function slugifyHeading(text) {
41
+ return String(text).toLowerCase().trim().replace(/[`*_~[\]()#]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
42
+ }
29
43
 
30
44
  export function sanitizeHref(url) {
31
45
  if (typeof url !== 'string') return null;
@@ -35,9 +49,16 @@ export function sanitizeHref(url) {
35
49
  return SAFE_SCHEME.test(trimmed) ? trimmed : null;
36
50
  }
37
51
 
52
+ export function sanitizeImgSrc(url) {
53
+ if (typeof url !== 'string') return null;
54
+ const trimmed = url.trim();
55
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return trimmed; // relative (e.g. /v1/storage/…)
56
+ return SAFE_IMG_SCHEME.test(trimmed) ? trimmed : null;
57
+ }
58
+
38
59
  // ── Inline parsing: code, bold, italic, links. Returns an array of vnodes/strings.
39
60
  // Order matters: inline code is extracted first so markup inside it is literal.
40
- function parseInline(text) {
61
+ function parseInline(text, onWikiLink) {
41
62
  const out = [];
42
63
  let i = 0;
43
64
  let buf = '';
@@ -46,6 +67,36 @@ function parseInline(text) {
46
67
  while (i < text.length) {
47
68
  const c = text[i];
48
69
 
70
+ // Image ![alt](url) — checked before links so the leading ! is consumed.
71
+ if (c === '!' && text[i + 1] === '[') {
72
+ const close = text.indexOf(']', i + 2);
73
+ if (close > i && text[close + 1] === '(') {
74
+ const paren = text.indexOf(')', close + 2);
75
+ if (paren > close) {
76
+ const alt = text.slice(i + 2, close);
77
+ const src = sanitizeImgSrc(text.slice(close + 2, paren));
78
+ flush();
79
+ if (src) out.push(h('img', { src, alt, class: 'md-img', loading: 'lazy' }));
80
+ i = paren + 1;
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+
86
+ // Wiki link [[Document Title]] — resolves to another document when a resolver is supplied.
87
+ if (c === '[' && text[i + 1] === '[') {
88
+ const end = text.indexOf(']]', i + 2);
89
+ if (end > i) {
90
+ flush();
91
+ const title = text.slice(i + 2, end).trim();
92
+ out.push(onWikiLink
93
+ ? h('a', { href: '#', class: 'md-wikilink', onClick: (e) => { e.preventDefault(); onWikiLink(title); } }, title.replace('#', ' › '))
94
+ : '[[' + title + ']]');
95
+ i = end + 2;
96
+ continue;
97
+ }
98
+ }
99
+
49
100
  // Inline code `...` — contents are literal (no further parsing).
50
101
  if (c === '`') {
51
102
  const end = text.indexOf('`', i + 1);
@@ -69,8 +120,8 @@ function parseInline(text) {
69
120
  flush();
70
121
  out.push(
71
122
  href
72
- ? h('a', { href, target: '_blank', rel: 'noopener noreferrer nofollow' }, parseInline(label))
73
- : h('span', null, parseInline(label)),
123
+ ? h('a', { href, target: '_blank', rel: 'noopener noreferrer nofollow' }, parseInline(label, onWikiLink))
124
+ : h('span', null, parseInline(label, onWikiLink)),
74
125
  );
75
126
  i = paren + 1;
76
127
  continue;
@@ -83,7 +134,7 @@ function parseInline(text) {
83
134
  const end = text.indexOf('**', i + 2);
84
135
  if (end > i) {
85
136
  flush();
86
- out.push(h('strong', null, parseInline(text.slice(i + 2, end))));
137
+ out.push(h('strong', null, parseInline(text.slice(i + 2, end), onWikiLink)));
87
138
  i = end + 2;
88
139
  continue;
89
140
  }
@@ -94,12 +145,28 @@ function parseInline(text) {
94
145
  const end = text.indexOf('*', i + 1);
95
146
  if (end > i) {
96
147
  flush();
97
- out.push(h('em', null, parseInline(text.slice(i + 1, end))));
148
+ out.push(h('em', null, parseInline(text.slice(i + 1, end), onWikiLink)));
98
149
  i = end + 1;
99
150
  continue;
100
151
  }
101
152
  }
102
153
 
154
+ // Italic _..._ (underscore) — only at word boundaries, so snake_case identifiers stay literal.
155
+ if (c === '_' && (i === 0 || !/\w/.test(text[i - 1])) && text[i + 1] && text[i + 1] !== ' ') {
156
+ let j = i + 1;
157
+ while ((j = text.indexOf('_', j)) >= 0) {
158
+ const after = text[j + 1];
159
+ if (text[j - 1] !== ' ' && (after === undefined || !/\w/.test(after))) break;
160
+ j++;
161
+ }
162
+ if (j >= 0) {
163
+ flush();
164
+ out.push(h('em', null, parseInline(text.slice(i + 1, j), onWikiLink)));
165
+ i = j + 1;
166
+ continue;
167
+ }
168
+ }
169
+
103
170
  buf += c;
104
171
  i++;
105
172
  }
@@ -121,7 +188,7 @@ function isTableDivider(line) {
121
188
  }
122
189
 
123
190
  // ── Block parsing. Walks lines, emits block-level vnodes.
124
- function parseBlocks(src) {
191
+ function parseBlocks(src, onWikiLink) {
125
192
  const lines = src.replace(/\r\n?/g, '\n').split('\n');
126
193
  const blocks = [];
127
194
  let i = 0;
@@ -143,7 +210,13 @@ function parseBlocks(src) {
143
210
  i++;
144
211
  }
145
212
  i++; // consume closing fence (or EOF)
146
- blocks.push(h('pre', { class: 'md-pre' }, h('code', { class: 'md-code' }, codeLines.join('\n'))));
213
+ const lang = (fence[2] || '').trim().toLowerCase();
214
+ if (lang === 'mermaid') {
215
+ // ```mermaid → render the diagram (Mermaid lazy-loads its 3MB renderer only when used).
216
+ blocks.push(h(Mermaid, { chart: codeLines.join('\n') }));
217
+ } else {
218
+ blocks.push(h('pre', { class: 'md-pre' }, h('code', { class: 'md-code' }, codeLines.join('\n'))));
219
+ }
147
220
  continue;
148
221
  }
149
222
 
@@ -151,7 +224,7 @@ function parseBlocks(src) {
151
224
  const heading = line.match(/^(#{1,6})\s+(.*)$/);
152
225
  if (heading) {
153
226
  const level = heading[1].length;
154
- blocks.push(h('h' + level, null, parseInline(heading[2].trim())));
227
+ blocks.push(h('h' + level, { id: slugifyHeading(heading[2].trim()) }, parseInline(heading[2].trim(), onWikiLink)));
155
228
  i++;
156
229
  continue;
157
230
  }
@@ -163,7 +236,7 @@ function parseBlocks(src) {
163
236
  quoted.push(lines[i].replace(/^\s*>\s?/, ''));
164
237
  i++;
165
238
  }
166
- blocks.push(h('blockquote', null, parseBlocks(quoted.join('\n'))));
239
+ blocks.push(h('blockquote', null, parseBlocks(quoted.join('\n'), onWikiLink)));
167
240
  continue;
168
241
  }
169
242
 
@@ -177,9 +250,9 @@ function parseBlocks(src) {
177
250
  i++;
178
251
  }
179
252
  blocks.push(h('table', null, [
180
- h('thead', null, h('tr', null, header.map((c, ci) => h('th', { key: ci }, parseInline(c))))),
253
+ h('thead', null, h('tr', null, header.map((c, ci) => h('th', { key: ci }, parseInline(c, onWikiLink))))),
181
254
  h('tbody', null, rows.map((r, ri) =>
182
- h('tr', { key: ri }, r.map((c, ci) => h('td', { key: ci }, parseInline(c)))))),
255
+ h('tr', { key: ri }, r.map((c, ci) => h('td', { key: ci }, parseInline(c, onWikiLink)))))),
183
256
  ]));
184
257
  continue;
185
258
  }
@@ -193,7 +266,7 @@ function parseBlocks(src) {
193
266
  while (i < lines.length) {
194
267
  const m = lines[i].match(ordered ? /^(\s*)(\d+)\.\s+(.*)$/ : /^(\s*)([-*+])\s+(.*)$/);
195
268
  if (!m) break;
196
- items.push(h('li', { key: items.length }, parseInline(m[3])));
269
+ items.push(h('li', { key: items.length }, parseInline(m[3], onWikiLink)));
197
270
  i++;
198
271
  }
199
272
  blocks.push(h(ordered ? 'ol' : 'ul', null, items));
@@ -215,15 +288,15 @@ function parseBlocks(src) {
215
288
  i++;
216
289
  }
217
290
  // Join wrapped lines with a space (standard markdown paragraph behavior).
218
- blocks.push(h('p', null, parseInline(para.join(' '))));
291
+ blocks.push(h('p', null, parseInline(para.join(' '), onWikiLink)));
219
292
  }
220
293
 
221
294
  return blocks;
222
295
  }
223
296
 
224
- export function Markdown({ text }) {
297
+ export function Markdown({ text, onWikiLink }) {
225
298
  const src = typeof text === 'string' ? text : '';
226
- return h('div', { class: 'md-body' }, parseBlocks(src));
299
+ return h('div', { class: 'md-body' }, parseBlocks(src, onWikiLink));
227
300
  }
228
301
 
229
302
  export default Markdown;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @file Mermaid.js
3
+ * @description Renders a Mermaid diagram from its source text. Lazy-loads the vendored single-file
4
+ * Mermaid build (public/lib/mermaid/, MIT) only when a chart is first shown — it's ~3MB, so it
5
+ * must never be in the initial bundle. securityLevel:'strict' (no click handlers, sanitised). On
6
+ * any load/parse error it falls back to showing the raw source in a <pre> so nothing is lost.
7
+ * @structure Mermaid({ chart }) — chart is the Mermaid source string.
8
+ * @usage import { Mermaid } from '/components/Mermaid.js'; html`<${Mermaid} chart=${src} />`
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-08 — Initial: lazy-loaded vendored Mermaid renderer for organism/workspace charts.
11
+ */
12
+ import { h } from 'preact';
13
+ import { useEffect, useRef, useState } from 'preact/hooks';
14
+ import htm from 'htm';
15
+ const html = htm.bind(h);
16
+
17
+ let _promise = null;
18
+ let _seq = 0;
19
+
20
+ function loadMermaid() {
21
+ if (typeof window !== 'undefined' && window.mermaid) return Promise.resolve(window.mermaid);
22
+ if (_promise) return _promise;
23
+ _promise = new Promise((resolve, reject) => {
24
+ const s = document.createElement('script');
25
+ s.src = '/lib/mermaid/mermaid.min.js' + (window.__B || '');
26
+ s.onload = () => {
27
+ try {
28
+ window.mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'neutral', flowchart: { htmlLabels: false } });
29
+ resolve(window.mermaid);
30
+ } catch (e) { reject(e); }
31
+ };
32
+ s.onerror = () => reject(new Error('failed to load mermaid'));
33
+ document.head.appendChild(s);
34
+ });
35
+ return _promise;
36
+ }
37
+
38
+ /** Mermaid — render a diagram from source text (lazy-loads the renderer). */
39
+ export function Mermaid({ chart }) {
40
+ const ref = useRef(null);
41
+ const [err, setErr] = useState('');
42
+
43
+ useEffect(() => {
44
+ let cancelled = false;
45
+ setErr('');
46
+ if (!chart) return undefined;
47
+ loadMermaid().then(async (m) => {
48
+ if (cancelled || !ref.current) return;
49
+ try {
50
+ const { svg } = await m.render('mmd-' + (_seq++), chart);
51
+ if (!cancelled && ref.current) ref.current.innerHTML = svg;
52
+ } catch (e) { if (!cancelled) setErr((e && e.message) || 'diagram error'); }
53
+ }).catch(() => { if (!cancelled) setErr('renderer'); });
54
+ return () => { cancelled = true; };
55
+ }, [chart]);
56
+
57
+ if (err) return html`<pre class="mmd-fallback">${chart}</pre>`;
58
+ return html`<div class="mmd" ref=${ref}></div>`;
59
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @file NotificationBell.js
3
+ * @description Header notification bell — shows the unread count and a dropdown of the user's
4
+ * in-app notifications (GET /v1/notifications). Opening the dropdown marks them read. Refreshes on
5
+ * the SSE `aimeat-live-update` event and a 45s poll, so a workspace-access approval (or request)
6
+ * surfaces without the user having to guess. Self-contained: reads the JWT from window.AIMEAT.auth.
7
+ * @structure NotificationBell({ t, onNavigate }) — t = i18n fn, onNavigate(path) = SPA navigate.
8
+ * @usage import { NotificationBell } from '/components/NotificationBell.js'; html`<${NotificationBell} t=${t} onNavigate=${navigate} />`
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-08 — Initial: header bell + dropdown + mark-read for the notification inbox.
11
+ */
12
+ import { h } from 'preact';
13
+ import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
14
+ import htm from 'htm';
15
+ const html = htm.bind(h);
16
+
17
+ const jwt = () => { try { return window.AIMEAT?.auth?.getSession?.()?.jwt || ''; } catch { return ''; } };
18
+ async function api(path, opts = {}) {
19
+ const token = jwt();
20
+ if (!token) return null;
21
+ try {
22
+ const res = await fetch(path, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token, ...(opts.headers || {}) } });
23
+ return await res.json();
24
+ } catch { return null; }
25
+ }
26
+
27
+ function relTime(iso) {
28
+ try {
29
+ const d = new Date(iso), now = new Date(), s = Math.round((now - d) / 1000);
30
+ if (s < 60) return 'just now';
31
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
32
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
33
+ return d.toLocaleDateString();
34
+ } catch { return ''; }
35
+ }
36
+
37
+ export function NotificationBell({ t, onNavigate }) {
38
+ const tr = (k, fb) => (t ? t(k) : null) || fb;
39
+ const [items, setItems] = useState([]);
40
+ const [unread, setUnread] = useState(0);
41
+ const [open, setOpen] = useState(false);
42
+ const ref = useRef(null);
43
+
44
+ const load = useCallback(async () => {
45
+ const r = await api('/v1/notifications');
46
+ if (r && r.data) { setItems(r.data.notifications || []); setUnread(r.data.unread || 0); }
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ load();
51
+ const onUpd = () => load();
52
+ window.addEventListener('aimeat-live-update', onUpd);
53
+ const iv = setInterval(load, 45000);
54
+ return () => { window.removeEventListener('aimeat-live-update', onUpd); clearInterval(iv); };
55
+ }, [load]);
56
+
57
+ // Close the dropdown on an outside click.
58
+ useEffect(() => {
59
+ if (!open) return undefined;
60
+ const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
61
+ document.addEventListener('mousedown', onDoc);
62
+ return () => document.removeEventListener('mousedown', onDoc);
63
+ }, [open]);
64
+
65
+ const toggle = async () => {
66
+ const next = !open;
67
+ setOpen(next);
68
+ if (next && unread > 0) {
69
+ setUnread(0);
70
+ setItems(its => its.map(n => ({ ...n, read: true })));
71
+ await api('/v1/notifications/read', { method: 'POST', body: JSON.stringify({ all: true }) });
72
+ }
73
+ };
74
+ const clickNotif = (n) => { setOpen(false); if (n.link && onNavigate) onNavigate(n.link); };
75
+
76
+ return html`
77
+ <div class="notif-bell" ref=${ref}>
78
+ <button class="notif-bell-btn" aria-label=${tr('notif.title', 'Notifications')} title=${tr('notif.title', 'Notifications')} onClick=${toggle}>
79
+ ${'🔔'}${unread > 0 ? html`<span class="notif-badge">${unread > 99 ? '99+' : unread}</span>` : null}
80
+ </button>
81
+ ${open ? html`
82
+ <div class="notif-dropdown">
83
+ <div class="notif-dropdown-head">${tr('notif.title', 'Notifications')}</div>
84
+ ${items.length === 0
85
+ ? html`<div class="notif-empty">${tr('notif.empty', 'No notifications yet')}</div>`
86
+ : items.map(n => html`
87
+ <button class="notif-item ${n.read ? '' : 'unread'}" key=${n.id} onClick=${() => clickNotif(n)}>
88
+ <div class="notif-item-title">${n.title}</div>
89
+ ${n.body ? html`<div class="notif-item-body">${n.body}</div>` : null}
90
+ <div class="notif-item-time">${relTime(n.createdAt)}</div>
91
+ </button>`)}
92
+ </div>` : null}
93
+ </div>`;
94
+ }