aimeat 1.35.0 → 1.37.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 (296) hide show
  1. package/dist/.env.example +31 -0
  2. package/dist/locales/en.json +192 -27
  3. package/dist/locales/fi.json +192 -27
  4. package/dist/public/components/Markdown.js +9 -0
  5. package/dist/public/components/MemoryEmbed.js +151 -0
  6. package/dist/public/components/Mermaid.js +4 -1
  7. package/dist/public/components/NotificationBell.js +45 -19
  8. package/dist/public/css/components/markdown.css +15 -0
  9. package/dist/public/css/views/agents-detail.css +17 -0
  10. package/dist/public/css/views/portfolio.css +51 -0
  11. package/dist/public/css/views/profile.css +31 -0
  12. package/dist/public/js/services/agents.js +9 -0
  13. package/dist/public/js/services/organisms.js +37 -1
  14. package/dist/public/llms-template.txt +278 -0
  15. package/dist/public/spa.html +39 -2
  16. package/dist/public/sw.js +50 -34
  17. package/dist/public/views/my-company.js +13 -1
  18. package/dist/public/views/portfolio.js +267 -44
  19. package/dist/public/views/profile/agents/agent-card.js +4 -0
  20. package/dist/public/views/profile/agents/tab-contracts.js +112 -0
  21. package/dist/public/views/profile/landing-page.js +10 -1
  22. package/dist/public/views/profile/memory-tab.js +6 -2
  23. package/dist/public/views/profile/notifications-tab.js +9 -1
  24. package/dist/public/views/profile/organisms/home.js +23 -3
  25. package/dist/public/views/profile/organisms/participants-panel.js +89 -17
  26. package/dist/public/views/profile/organisms/workspace-apps.js +130 -0
  27. package/dist/public/views/profile/organisms/workspace.js +5 -0
  28. package/dist/public/views/profile/organisms-tab.js +1 -1
  29. package/dist/public/views/secretary.js +13 -0
  30. package/dist/scripts/archive-stale-versions.d.ts +33 -0
  31. package/dist/scripts/archive-stale-versions.d.ts.map +1 -0
  32. package/dist/scripts/archive-stale-versions.js +278 -0
  33. package/dist/scripts/archive-stale-versions.js.map +1 -0
  34. package/dist/src/cli/connect/mcp/tools/agent-management.d.ts +4 -1
  35. package/dist/src/cli/connect/mcp/tools/agent-management.d.ts.map +1 -1
  36. package/dist/src/cli/connect/mcp/tools/agent-management.js +1 -1
  37. package/dist/src/cli/connect/mcp/tools/agent-management.js.map +1 -1
  38. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  39. package/dist/src/cli/init-wizard.js +32 -0
  40. package/dist/src/cli/init-wizard.js.map +1 -1
  41. package/dist/src/config.d.ts +24 -0
  42. package/dist/src/config.d.ts.map +1 -1
  43. package/dist/src/config.js +21 -0
  44. package/dist/src/config.js.map +1 -1
  45. package/dist/src/data/aimeat-iam-package.d.ts +10 -2
  46. package/dist/src/data/aimeat-iam-package.d.ts.map +1 -1
  47. package/dist/src/data/aimeat-iam-package.js +39 -10
  48. package/dist/src/data/aimeat-iam-package.js.map +1 -1
  49. package/dist/src/generated/api-types.d.ts +802 -60
  50. package/dist/src/generated/api-types.d.ts.map +1 -1
  51. package/dist/src/generated/prisma-postgres/edge.js +5 -3
  52. package/dist/src/generated/prisma-postgres/index-browser.js +2 -0
  53. package/dist/src/generated/prisma-postgres/index.d.ts +54 -2
  54. package/dist/src/generated/prisma-postgres/index.js +5 -3
  55. package/dist/src/generated/prisma-postgres/package.json +1 -1
  56. package/dist/src/generated/prisma-postgres/schema.prisma +2 -0
  57. package/dist/src/generated/prisma-postgres/wasm.js +5 -3
  58. package/dist/src/mcp/annotations.d.ts.map +1 -1
  59. package/dist/src/mcp/annotations.js +1 -0
  60. package/dist/src/mcp/annotations.js.map +1 -1
  61. package/dist/src/mcp/catalog/definitions.d.ts +3 -0
  62. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  63. package/dist/src/mcp/catalog/definitions.js +21 -7
  64. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  65. package/dist/src/mcp/catalog/surfaces.js +1 -1
  66. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  67. package/dist/src/mcp/core.js +1 -1
  68. package/dist/src/mcp/core.js.map +1 -1
  69. package/dist/src/mcp/extensions.d.ts.map +1 -1
  70. package/dist/src/mcp/extensions.js +21 -0
  71. package/dist/src/mcp/extensions.js.map +1 -1
  72. package/dist/src/mcp/memory-extended.js +1 -1
  73. package/dist/src/mcp/memory-extended.js.map +1 -1
  74. package/dist/src/mcp/organisms.d.ts +1 -0
  75. package/dist/src/mcp/organisms.d.ts.map +1 -1
  76. package/dist/src/mcp/organisms.js +23 -6
  77. package/dist/src/mcp/organisms.js.map +1 -1
  78. package/dist/src/mcp/workspaces.d.ts +7 -0
  79. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  80. package/dist/src/mcp/workspaces.js +23 -5
  81. package/dist/src/mcp/workspaces.js.map +1 -1
  82. package/dist/src/middleware/subdomain.d.ts +9 -0
  83. package/dist/src/middleware/subdomain.d.ts.map +1 -1
  84. package/dist/src/middleware/subdomain.js +24 -3
  85. package/dist/src/middleware/subdomain.js.map +1 -1
  86. package/dist/src/models/schemas.d.ts +2 -0
  87. package/dist/src/models/schemas.d.ts.map +1 -1
  88. package/dist/src/models/schemas.js +2 -2
  89. package/dist/src/models/schemas.js.map +1 -1
  90. package/dist/src/routes/admin-features.js +1 -1
  91. package/dist/src/routes/admin-features.js.map +1 -1
  92. package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
  93. package/dist/src/routes/agent-onboarding.js +44 -7
  94. package/dist/src/routes/agent-onboarding.js.map +1 -1
  95. package/dist/src/routes/agents.d.ts +8 -0
  96. package/dist/src/routes/agents.d.ts.map +1 -1
  97. package/dist/src/routes/agents.js +91 -5
  98. package/dist/src/routes/agents.js.map +1 -1
  99. package/dist/src/routes/app-grants.d.ts +5 -0
  100. package/dist/src/routes/app-grants.d.ts.map +1 -1
  101. package/dist/src/routes/app-grants.js +52 -20
  102. package/dist/src/routes/app-grants.js.map +1 -1
  103. package/dist/src/routes/boards.d.ts.map +1 -1
  104. package/dist/src/routes/boards.js +4 -1
  105. package/dist/src/routes/boards.js.map +1 -1
  106. package/dist/src/routes/bootstrap.d.ts +2 -0
  107. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  108. package/dist/src/routes/bootstrap.js +5 -0
  109. package/dist/src/routes/bootstrap.js.map +1 -1
  110. package/dist/src/routes/capabilities.d.ts.map +1 -1
  111. package/dist/src/routes/capabilities.js +4 -1
  112. package/dist/src/routes/capabilities.js.map +1 -1
  113. package/dist/src/routes/catalogue.d.ts.map +1 -1
  114. package/dist/src/routes/catalogue.js +5 -4
  115. package/dist/src/routes/catalogue.js.map +1 -1
  116. package/dist/src/routes/extensions.d.ts.map +1 -1
  117. package/dist/src/routes/extensions.js +11 -0
  118. package/dist/src/routes/extensions.js.map +1 -1
  119. package/dist/src/routes/ghii.d.ts.map +1 -1
  120. package/dist/src/routes/ghii.js +65 -17
  121. package/dist/src/routes/ghii.js.map +1 -1
  122. package/dist/src/routes/lib-editor.d.ts +26 -0
  123. package/dist/src/routes/lib-editor.d.ts.map +1 -0
  124. package/dist/src/routes/lib-editor.js +215 -0
  125. package/dist/src/routes/lib-editor.js.map +1 -0
  126. package/dist/src/routes/lib-markdown.d.ts +16 -1
  127. package/dist/src/routes/lib-markdown.d.ts.map +1 -1
  128. package/dist/src/routes/lib-markdown.js +281 -2
  129. package/dist/src/routes/lib-markdown.js.map +1 -1
  130. package/dist/src/routes/lib-organism.d.ts +28 -0
  131. package/dist/src/routes/lib-organism.d.ts.map +1 -0
  132. package/dist/src/routes/lib-organism.js +298 -0
  133. package/dist/src/routes/lib-organism.js.map +1 -0
  134. package/dist/src/routes/lib-portfolio-standalone.d.ts +22 -0
  135. package/dist/src/routes/lib-portfolio-standalone.d.ts.map +1 -0
  136. package/dist/src/routes/lib-portfolio-standalone.js +68 -0
  137. package/dist/src/routes/lib-portfolio-standalone.js.map +1 -0
  138. package/dist/src/routes/libs.d.ts +18 -0
  139. package/dist/src/routes/libs.d.ts.map +1 -1
  140. package/dist/src/routes/libs.js +116 -18
  141. package/dist/src/routes/libs.js.map +1 -1
  142. package/dist/src/routes/memory.d.ts +3 -0
  143. package/dist/src/routes/memory.d.ts.map +1 -1
  144. package/dist/src/routes/memory.js +58 -4
  145. package/dist/src/routes/memory.js.map +1 -1
  146. package/dist/src/routes/notifications.d.ts +5 -1
  147. package/dist/src/routes/notifications.d.ts.map +1 -1
  148. package/dist/src/routes/notifications.js +68 -3
  149. package/dist/src/routes/notifications.js.map +1 -1
  150. package/dist/src/routes/oauth-login.d.ts +26 -21
  151. package/dist/src/routes/oauth-login.d.ts.map +1 -1
  152. package/dist/src/routes/oauth-login.js +257 -229
  153. package/dist/src/routes/oauth-login.js.map +1 -1
  154. package/dist/src/routes/openrouter.d.ts +3 -0
  155. package/dist/src/routes/openrouter.d.ts.map +1 -1
  156. package/dist/src/routes/openrouter.js +10 -5
  157. package/dist/src/routes/openrouter.js.map +1 -1
  158. package/dist/src/routes/organisms.d.ts +6 -0
  159. package/dist/src/routes/organisms.d.ts.map +1 -1
  160. package/dist/src/routes/organisms.js +203 -16
  161. package/dist/src/routes/organisms.js.map +1 -1
  162. package/dist/src/routes/owners.d.ts.map +1 -1
  163. package/dist/src/routes/owners.js +12 -8
  164. package/dist/src/routes/owners.js.map +1 -1
  165. package/dist/src/routes/portfolio.d.ts +41 -1
  166. package/dist/src/routes/portfolio.d.ts.map +1 -1
  167. package/dist/src/routes/portfolio.js +94 -30
  168. package/dist/src/routes/portfolio.js.map +1 -1
  169. package/dist/src/routes/push.d.ts +15 -0
  170. package/dist/src/routes/push.d.ts.map +1 -1
  171. package/dist/src/routes/push.js +16 -1
  172. package/dist/src/routes/push.js.map +1 -1
  173. package/dist/src/routes/site.d.ts +2 -0
  174. package/dist/src/routes/site.d.ts.map +1 -1
  175. package/dist/src/routes/site.js +10 -2
  176. package/dist/src/routes/site.js.map +1 -1
  177. package/dist/src/routes/subdomains.d.ts +5 -0
  178. package/dist/src/routes/subdomains.d.ts.map +1 -1
  179. package/dist/src/routes/subdomains.js +56 -0
  180. package/dist/src/routes/subdomains.js.map +1 -1
  181. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  182. package/dist/src/server-bootstrap/routes-loader.js +10 -15
  183. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  184. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  185. package/dist/src/server-bootstrap/static-files.js +9 -4
  186. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  187. package/dist/src/services/access-guard.d.ts.map +1 -1
  188. package/dist/src/services/access-guard.js +7 -0
  189. package/dist/src/services/access-guard.js.map +1 -1
  190. package/dist/src/services/ai-completion.d.ts +2 -0
  191. package/dist/src/services/ai-completion.d.ts.map +1 -1
  192. package/dist/src/services/ai-completion.js +2 -1
  193. package/dist/src/services/ai-completion.js.map +1 -1
  194. package/dist/src/services/config-schema.d.ts.map +1 -1
  195. package/dist/src/services/config-schema.js +2 -0
  196. package/dist/src/services/config-schema.js.map +1 -1
  197. package/dist/src/services/ecosystem-access.d.ts +24 -8
  198. package/dist/src/services/ecosystem-access.d.ts.map +1 -1
  199. package/dist/src/services/ecosystem-access.js +27 -5
  200. package/dist/src/services/ecosystem-access.js.map +1 -1
  201. package/dist/src/services/generator-autopilot.d.ts +2 -0
  202. package/dist/src/services/generator-autopilot.d.ts.map +1 -1
  203. package/dist/src/services/generator-autopilot.js +3 -1
  204. package/dist/src/services/generator-autopilot.js.map +1 -1
  205. package/dist/src/services/iam/app-commands.d.ts +75 -0
  206. package/dist/src/services/iam/app-commands.d.ts.map +1 -0
  207. package/dist/src/services/iam/app-commands.js +55 -0
  208. package/dist/src/services/iam/app-commands.js.map +1 -0
  209. package/dist/src/services/iam/define-app-iam.d.ts +45 -0
  210. package/dist/src/services/iam/define-app-iam.d.ts.map +1 -0
  211. package/dist/src/services/iam/define-app-iam.js +75 -0
  212. package/dist/src/services/iam/define-app-iam.js.map +1 -0
  213. package/dist/src/services/iam/model.d.ts +113 -0
  214. package/dist/src/services/iam/model.d.ts.map +1 -0
  215. package/dist/src/services/iam/model.js +115 -0
  216. package/dist/src/services/iam/model.js.map +1 -0
  217. package/dist/src/services/mailbox-notification.d.ts +1 -0
  218. package/dist/src/services/mailbox-notification.d.ts.map +1 -1
  219. package/dist/src/services/mailbox-notification.js +3 -0
  220. package/dist/src/services/mailbox-notification.js.map +1 -1
  221. package/dist/src/services/notebook-ai.d.ts +2 -0
  222. package/dist/src/services/notebook-ai.d.ts.map +1 -1
  223. package/dist/src/services/notebook-ai.js +1 -1
  224. package/dist/src/services/notebook-ai.js.map +1 -1
  225. package/dist/src/services/notify.d.ts +11 -1
  226. package/dist/src/services/notify.d.ts.map +1 -1
  227. package/dist/src/services/notify.js +39 -2
  228. package/dist/src/services/notify.js.map +1 -1
  229. package/dist/src/services/oidc-providers.d.ts +94 -0
  230. package/dist/src/services/oidc-providers.d.ts.map +1 -0
  231. package/dist/src/services/oidc-providers.js +181 -0
  232. package/dist/src/services/oidc-providers.js.map +1 -0
  233. package/dist/src/services/organism-privacy.d.ts +46 -0
  234. package/dist/src/services/organism-privacy.d.ts.map +1 -0
  235. package/dist/src/services/organism-privacy.js +42 -0
  236. package/dist/src/services/organism-privacy.js.map +1 -0
  237. package/dist/src/services/organism-template.d.ts.map +1 -1
  238. package/dist/src/services/organism-template.js +3 -1
  239. package/dist/src/services/organism-template.js.map +1 -1
  240. package/dist/src/services/prompt-defaults.d.ts +2 -0
  241. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  242. package/dist/src/services/prompt-defaults.js +5 -0
  243. package/dist/src/services/prompt-defaults.js.map +1 -1
  244. package/dist/src/services/scheduler.d.ts.map +1 -1
  245. package/dist/src/services/scheduler.js +16 -4
  246. package/dist/src/services/scheduler.js.map +1 -1
  247. package/dist/src/services/skill-bundle/generator.d.ts +2 -0
  248. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
  249. package/dist/src/services/skill-bundle/generator.js +3 -1
  250. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  251. package/dist/src/services/workspace-access.d.ts +2 -0
  252. package/dist/src/services/workspace-access.d.ts.map +1 -1
  253. package/dist/src/services/workspace-access.js +20 -8
  254. package/dist/src/services/workspace-access.js.map +1 -1
  255. package/dist/src/services/workspace-engagements.d.ts +65 -0
  256. package/dist/src/services/workspace-engagements.d.ts.map +1 -0
  257. package/dist/src/services/workspace-engagements.js +92 -0
  258. package/dist/src/services/workspace-engagements.js.map +1 -0
  259. package/dist/src/services/workspace-meta.d.ts +9 -2
  260. package/dist/src/services/workspace-meta.d.ts.map +1 -1
  261. package/dist/src/services/workspace-meta.js +22 -2
  262. package/dist/src/services/workspace-meta.js.map +1 -1
  263. package/dist/src/storage/interface.d.ts +10 -2
  264. package/dist/src/storage/interface.d.ts.map +1 -1
  265. package/dist/src/storage/interface.js.map +1 -1
  266. package/dist/src/storage/providers/mongodb/index.d.ts +1 -0
  267. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  268. package/dist/src/storage/providers/mongodb/index.js +29 -2
  269. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  270. package/dist/src/storage/providers/sqlite/index.d.ts +1 -0
  271. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  272. package/dist/src/storage/providers/sqlite/index.js +23 -8
  273. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  274. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  275. package/dist/src/storage/providers/sqlite/schema.js +9 -0
  276. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  277. package/dist/src/storage/repositories/identity.repository.d.ts +2 -0
  278. package/dist/src/storage/repositories/identity.repository.d.ts.map +1 -1
  279. package/dist/src/utils/env-config.d.ts.map +1 -1
  280. package/dist/src/utils/env-config.js +23 -0
  281. package/dist/src/utils/env-config.js.map +1 -1
  282. package/dist/src/utils/env-validator.d.ts.map +1 -1
  283. package/dist/src/utils/env-validator.js +8 -0
  284. package/dist/src/utils/env-validator.js.map +1 -1
  285. package/dist/src/utils/node-descriptor.d.ts.map +1 -1
  286. package/dist/src/utils/node-descriptor.js +1 -0
  287. package/dist/src/utils/node-descriptor.js.map +1 -1
  288. package/dist/static/app-catalog.html +8 -2
  289. package/dist/static/icons/badge-96.png +0 -0
  290. package/dist/static/icons/icon-192.png +0 -0
  291. package/dist/static/icons/icon-512.png +0 -0
  292. package/dist/static/manifest.json +19 -17
  293. package/dist/static/sw.js +13 -11
  294. package/package.json +3 -1
  295. package/prisma/schema.postgres.prisma +2 -0
  296. package/prisma/schema.prisma +2 -0
@@ -274,6 +274,9 @@ entry: index.html
274
274
  | aimeat-social | `loadScript('/v1/libs/aimeat-social.js')` | Discussion boards |
275
275
  | aimeat-wallet | `loadScript('/v1/libs/aimeat-wallet.js')` | Morsel balance display |
276
276
  | aimeat-ai | `loadScript('/v1/libs/aimeat-ai.js')` | `AIMEAT.ai.complete/completeJson/isAvailable` — runs LLM completions using the user's own OpenRouter key (zero cost to AIMEAT, user-owned spend budget) |
277
+ | aimeat-markdown | `loadScript('/v1/libs/aimeat-markdown.js')` | `AIMEAT.md.render(text, target)` — safe markdown INTO an element (returns an Element; never assign it to innerHTML — use the target param or `renderToString`). `await AIMEAT.md.renderRich(text, target)` adds task lists, footnotes, code highlighting, Mermaid and LIVE data embeds (an `aimeat-memory` fence naming a memory key renders as a fresh table on every open) |
278
+ | aimeat-organism | `loadScript('/v1/libs/aimeat-organism.js')` | `AIMEAT.organism.list/workspaces/read/writeDraft/publish` — organisms & workspaces with a NORMALIZED read (published + drafts merged per item; the raw workspace GET returns them as separate maps) |
279
+ | aimeat-editor | `loadScript('/v1/libs/aimeat-editor.js')` | `AIMEAT.editor.mount/toolbar/split` — CodeMirror 6 markdown editor with live preview (pairs with aimeat-markdown) |
277
280
 
278
281
  ### Key rules
279
282
 
@@ -470,6 +473,9 @@ The node serves browser-ready JavaScript libraries. Load them via `<script src="
470
473
  | aimeat-audio | `/v1/libs/aimeat-audio.js` | Audio: 6 instruments, custom synth, soundboard, sample loader |
471
474
  | aimeat-speech | `/v1/libs/aimeat-speech.js` | Speech: TTS, STT, voice commands, pluggable providers |
472
475
  | aimeat-ai | `/v1/libs/aimeat-ai.js` | LLM completions through the user's own OpenRouter key (`AIMEAT.ai.complete`, `completeJson`, `isAvailable`, `models`, `usage`). Server enforces daily USD budget + per-app quota; rejects with descriptive `err.code` (NO_API_KEY / QUOTA_EXHAUSTED / APP_QUOTA_EXHAUSTED / APP_NOT_ALLOWED / etc.). Pattern: **detect** with `isAvailable()`, **compose** the prompt yourself from app data, **call** with `app_id` so spend is attributable, **render** the result into an editable field so the human stays in the loop. Never bundle your own API key — use the user's. |
476
+ | aimeat-markdown | `/v1/libs/aimeat-markdown.js` | Markdown rendering. `AIMEAT.md.render(text, target)` renders a safe dependency-free GFM subset INTO an element — it returns an Element, so never assign the result to innerHTML (that shows "[object HTMLDivElement]"); use the target param, appendChild, or `renderToString(text)`. `await AIMEAT.md.renderRich(text, target)` upgrades to full GFM (task lists, footnotes, highlighted code, Mermaid diagrams), sanitized, with automatic fallback to the safe subset when CDNs are unreachable. LIVE DATA EMBEDS: a fenced block with language `aimeat-memory` (body lines `key: <memory key>`, optional `owner: <gaii>` for public reads, `view: table/props/list/value/json`, `fields: a, b`, `title: ...`) renders the named memory key as a table/props/list — fetched at render time, so the document shows fresh agent-produced data on every open; access is the server's memory read (workspace rules + entry visibility) and denials render a placeholder. |
477
+ | aimeat-organism | `/v1/libs/aimeat-organism.js` | Organisms & workspaces. `list()`, `workspaces(orgId)`, `read(orgId, wsId)` → `{ manifest, readmeText, spaces }` where each space's items merge published + draft per instance (`{ id, hasRealId, draft, latest, value, title, updatedAt, status }`) — the raw workspace GET returns `objects` (published) and `drafts` as SEPARATE maps; this lib does the merging, id resolution (value.id convention) and `_createdAt/_updatedAt/_version` metadata handling for you. `writeDraft`, `publish`, `revertToDraft`, `deleteObject` (needs the memory:delete scope — declare it via `<meta name="aimeat-scopes">`), `saveReadme`, `search`. Items with `hasRealId:false` cannot round-trip — render them read-only. Requires aimeat-auth. |
478
+ | aimeat-editor | `/v1/libs/aimeat-editor.js` | Markdown editor: `AIMEAT.editor.mount(el, {value, onChange})` (CodeMirror 6 with textarea fallback), `toolbar(adapter)` for formatting buttons, `split(el, {value, onChange})` for editor + live preview rendered through aimeat-markdown. |
473
479
 
474
480
  ### Standard App Template
475
481
 
@@ -560,6 +566,278 @@ Key rules:
560
566
  - Do NOT add manual token entry fields. The auth library handles everything.
561
567
  - Do NOT modify the AIMEAT header nav bar.
562
568
 
569
+ ### Workspace App Template (app pinned to organism workspaces)
570
+
571
+ A workspace app is a normal published app that WORKS ON a workspace's content. A workspace
572
+ creator/admin pins it to the workspace (workspace Overview → Apps → Manage, or the
573
+ `aimeat_workspace_update` MCP tool's `apps` param) and every member launches it from the
574
+ workspace's Apps cards. Three things make it a workspace app:
575
+
576
+ 1. **Access = workspace access.** There is no app-side permission system: every read/write the
577
+ app makes runs as the signed-in user through `/v1/organisms/...` and is gated by the
578
+ workspace's own rules (membership, creator/admin, granted roles). If the user can read the
579
+ workspace they can use the app — never build your own gate.
580
+ 2. **Launch context rides the URL fragment.** The workspace launch card opens the app with
581
+ `#aimeat-ws={organismId}/{workspaceId}`. The fragment survives the app-origin redirect, so
582
+ parse `location.hash` on boot and pin the app to that workspace.
583
+ 3. **Launched bare → offer the pinned workspaces.** Without a fragment, list the workspaces the
584
+ user can access and prefer the ones this app is pinned to: `AIMEAT.organism.workspaces(orgId)`
585
+ returns `enrichment.apps` = the pinned `{owner, filename}` list.
586
+
587
+ Use `aimeat-organism.js` for all content work — it does the objects/drafts merge, the `value.id`
588
+ convention and `_meta` stripping for you (see the SDK table above).
589
+
590
+ ```html
591
+ <!-- AIMEAT App Manifest
592
+ name: workspace-notes
593
+ version: 1.0.0
594
+ description: Notes on a shared workspace — pin it to any workspace with a records space
595
+ entry: index.html
596
+ -->
597
+ <!DOCTYPE html>
598
+ <html lang="en">
599
+ <head>
600
+ <meta charset="UTF-8">
601
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
602
+ <title>Workspace Notes</title>
603
+ <link href="/lib/daisyui@5.css" rel="stylesheet" type="text/css" />
604
+ <link href="/lib/aimeat-daisyui-bridge.css" rel="stylesheet" type="text/css" />
605
+ <script src="/lib/tailwindcss@4.js"></script>
606
+ </head>
607
+ <body class="bg-base-100 min-h-screen flex flex-col">
608
+ <nav class="navbar bg-base-200 shadow-sm px-4">
609
+ <div class="flex-1"><span class="text-lg font-bold">Workspace Notes</span>
610
+ <span id="ws-name" class="ml-3 text-sm opacity-70"></span></div>
611
+ <div class="flex-none"><span id="header-auth"></span></div>
612
+ </nav>
613
+ <div id="app" class="flex-1 p-4 max-w-2xl mx-auto w-full"><p>Loading…</p></div>
614
+ <script>
615
+ function loadScript(src) { return new Promise((res, rej) => { const s = document.createElement('script'); s.src = src; s.onload = res; s.onerror = rej; document.head.appendChild(s); }); }
616
+ const $app = () => document.getElementById('app');
617
+
618
+ // #aimeat-ws={organismId}/{workspaceId} — set by the workspace launch card.
619
+ function launchContext() {
620
+ const m = /[#&]aimeat-ws=([^/&]+)\/([^&]+)/.exec(location.hash || '');
621
+ return m ? { orgId: decodeURIComponent(m[1]), wsId: decodeURIComponent(m[2]) } : null;
622
+ }
623
+
624
+ // Which published app am I? Match my <title> against the public catalog — works on any
625
+ // origin (apex or app subdomain), no hardcoding.
626
+ async function discoverSelf() {
627
+ try {
628
+ const r = await fetch('/v1/apps'); const d = await r.json();
629
+ const me = (d?.data?.apps || []).find(a => a?.manifest?.name === document.title);
630
+ return me ? { owner: me.owner, filename: me.filename } : null;
631
+ } catch { return null; }
632
+ }
633
+
634
+ async function boot() {
635
+ await loadScript('/v1/libs/aimeat-auth.js');
636
+ await loadScript('/v1/libs/aimeat-organism.js');
637
+ AIMEAT.auth.mountLoginButton('#header-auth', { onLogin: start, onLogout: () => location.reload() });
638
+ // On the isolated APP ORIGIN the silent-SSO / consent-popup flows complete asynchronously and
639
+ // fire the 'login' EVENT (not the button's onLogin callback) — subscribe to both.
640
+ let started = false;
641
+ const startOnce = () => { if (!started) { started = true; start(); } };
642
+ AIMEAT.auth.on('login', startOnce);
643
+ const session = await AIMEAT.auth.login();
644
+ if (session) startOnce(); else if (!started) $app().innerHTML = '<p>Sign in to open a workspace.</p>';
645
+ }
646
+
647
+ async function start() {
648
+ const ctx = launchContext();
649
+ if (ctx) return openWorkspace(ctx.orgId, ctx.wsId);
650
+ // Launched bare: list accessible workspaces, pinned-here first.
651
+ const self = await discoverSelf();
652
+ const orgs = await AIMEAT.organism.list();
653
+ const rows = [];
654
+ for (const org of orgs) {
655
+ const wss = await AIMEAT.organism.workspaces(org.id).catch(() => []);
656
+ for (const w of wss) {
657
+ if (w.access === 'none') continue;
658
+ const pinned = !!self && (w.enrichment?.apps || []).some(a => a.owner === self.owner && a.filename === self.filename);
659
+ rows.push({ org, w, pinned });
660
+ }
661
+ }
662
+ rows.sort((a, b) => Number(b.pinned) - Number(a.pinned));
663
+ $app().innerHTML = '<h2 class="text-lg font-bold mb-2">Pick a workspace</h2>';
664
+ for (const r of rows) {
665
+ const btn = document.createElement('button');
666
+ btn.className = 'btn btn-outline btn-block justify-start mb-2';
667
+ btn.textContent = (r.pinned ? '📌 ' : '') + r.org.name + ' / ' + (r.w.name || r.w.id);
668
+ btn.onclick = () => { location.hash = '#aimeat-ws=' + encodeURIComponent(r.org.id) + '/' + encodeURIComponent(r.w.id); openWorkspace(r.org.id, r.w.id); };
669
+ $app().appendChild(btn);
670
+ }
671
+ if (!rows.length) $app().innerHTML += '<p>No accessible workspaces.</p>';
672
+ }
673
+
674
+ async function openWorkspace(orgId, wsId) {
675
+ const ws = await AIMEAT.organism.read(orgId, wsId);
676
+ document.getElementById('ws-name').textContent = ws.manifest?.name || wsId;
677
+ // First records-mode space is the notes home (documents work the same way).
678
+ const space = ws.spaces.find(s => (s.kind || 'records') !== 'document') || ws.spaces[0];
679
+ if (!space) { $app().innerHTML = '<p>This workspace has no spaces yet.</p>'; return; }
680
+ const el = $app(); el.innerHTML = '<h2 class="text-lg font-bold mb-2">' + space.name + '</h2>';
681
+ for (const item of space.items.sort((a, b) => b.updatedAt - a.updatedAt)) {
682
+ const card = document.createElement('div');
683
+ card.className = 'card bg-base-200 mb-2 p-3';
684
+ card.textContent = item.title + (item.status !== 'published' ? ' (' + item.status + ')' : '');
685
+ el.appendChild(card);
686
+ }
687
+ // Add form: draft + publish under the space's namespace. The server gates the write by
688
+ // workspace access; a gated workspace turns publish into a pending approval — show why.
689
+ const form = document.createElement('form'); form.className = 'mt-4 flex gap-2';
690
+ form.innerHTML = '<input class="input input-bordered flex-1" placeholder="New note…" required />' +
691
+ '<button class="btn btn-primary">Add</button>';
692
+ form.onsubmit = async (e) => {
693
+ e.preventDefault();
694
+ const text = form.querySelector('input').value.trim(); if (!text) return;
695
+ const id = 'note-' + Date.now();
696
+ try {
697
+ await AIMEAT.organism.writeDraft(orgId, wsId, space.namespace, id, { id, title: text });
698
+ await AIMEAT.organism.publish(orgId, wsId, space.namespace, id);
699
+ openWorkspace(orgId, wsId);
700
+ } catch (err) { alert(err.message); }
701
+ };
702
+ el.appendChild(form);
703
+ }
704
+
705
+ boot();
706
+ </script>
707
+ </body>
708
+ </html>
709
+ ```
710
+
711
+ Key rules for workspace apps:
712
+ - NEVER build an app-side permission gate — the server enforces workspace access on every call.
713
+ - Parse `#aimeat-ws=` on boot; keep the fragment when you navigate so reload stays pinned.
714
+ - `writeDraft` + `publish` is the write loop; items with `hasRealId:false` are read-only.
715
+ - A publish can return a pending approval when the workspace gates publishes — tell the user
716
+ instead of treating it as an error.
717
+
718
+ ### App-IAM Template (an app with its OWN users and BBS levels via aimeat-iam)
719
+
720
+ Use this when the app needs its own permission system — its own user roster and levels,
721
+ independent of workspaces. The app gets a per-app copy of the **aimeat-iam extension**: a
722
+ server-side decision oracle whose state (roles, levels, assignments, command manifest) lives in
723
+ the extension's own `ext:{name}` memory, sovereign and tamper-proof from the browser. The app
724
+ never decides permissions itself — it ASKS the extension, and shows/hides UI from the answer.
725
+ One model, two user kinds: a human GHII and an agent GAII are checked identically.
726
+
727
+ **The model (BBS ordinal levels — LOWER number = MORE power):**
728
+ - Roles carry capability lists and sit on levels: seeded `admin: 0 ['*']`,
729
+ `editor: 10 ['read','create','edit']`, `viewer: 20 ['read']` — all replaceable.
730
+ - A **command manifest** maps app commands → required capability + mutation tier
731
+ (`read | write | irreversible`). The `irreversible` tier makes `needsConfirmation: true`,
732
+ so the UI knows to confirm — the manifest decides, not the UI author.
733
+ - Unassigned users get `config.defaultRole` (seeded `viewer`).
734
+
735
+ **Setup (once, by the app owner or their AI):**
736
+ 1. Install a copy of the aimeat-iam extension under your app's own name (convention:
737
+ `{your-app}-iam`) — via the aimeat-iam package, or `aimeat_extension_install` over MCP.
738
+ 2. `POST /v1/ext/{ext}/admin` with `{ "op": "claim" }` (records you as owner + seeds defaults),
739
+ then `{ "op": "setCommands", "commands": [...] }` and `{ "op": "assign", "ghii": "you@node",
740
+ "role": "admin" }` — claiming does NOT auto-assign a role.
741
+ 3. AI route: the `aimeat_iam_define` MCP tool validates a level schema + command manifest,
742
+ computes the level→command matrix, and returns ready-to-apply admin payloads
743
+ (`setRoles` / `setLevels` / `setCommands`).
744
+
745
+ **The runtime contract (all calls need a signed-in session; `resp.data` is the answer):**
746
+
747
+ ```javascript
748
+ const EXT = 'my-app-iam'; // your app's own extension instance
749
+ const iam = async (action, body) => {
750
+ const r = await AIMEAT.auth.getSession().fetch('/v1/ext/' + EXT + '/' + action,
751
+ { method: 'POST', body: JSON.stringify(body || {}) });
752
+ if (r.ok === false) throw new Error(r.error?.message || action + ' failed');
753
+ return r.data;
754
+ };
755
+
756
+ // Gate EVERYTHING on check { command } — the decision is server-side:
757
+ const me = await iam('check', { command: 'post' });
758
+ // → { allowed, role, level, command, capability, tier, needsConfirmation }
759
+ if (me.allowed) showComposer();
760
+ if (me.needsConfirmation) askTheHumanFirst(); // irreversible tier
761
+
762
+ // Owner-only user management. getState answers for ANY caller but reports isOwner —
763
+ // gate the panel on state.isOwner (server truth, not a level guess); the mutating
764
+ // ops (assign/revoke/set*) are enforced owner-only server-side regardless:
765
+ const state = await iam('admin', { op: 'getState' }); // roles, levels, commands, assignments, isOwner
766
+ await iam('admin', { op: 'assign', ghii: 'friend@node-id', role: 'editor' });
767
+ await iam('admin', { op: 'revoke', ghii: 'friend@node-id' });
768
+
769
+ // The roster doubles as a content index — assignments are PUBLIC extension memory:
770
+ const assignments = await AIMEAT.data.getPublic('ext:' + EXT, 'iam.assignments') || {};
771
+ // { "user@node": "role", ... } — everyone who can write is here, so read exactly
772
+ // their public keys to aggregate shared content (each user writes their OWN key).
773
+ ```
774
+
775
+ Key rules for app-IAM apps:
776
+ - The extension is the ONLY permission truth; the app renders its answers. Never mirror the
777
+ rules into app JS — they would drift and can be bypassed anyway.
778
+ - `check {permission}` (legacy role/permission mode) still works; prefer `check {command}` —
779
+ agents and humans then share one verb vocabulary with tiers.
780
+ - Content pattern: each user writes their own public key (e.g. `my-app.posts`); readers
781
+ discover writers through `iam.assignments`. No shared-key write races, no extra backend.
782
+ - Full working example: the "Club Board" proof app (packages/club-board in the AIMEAT repo).
783
+
784
+ ### Agent-Faced App Template (one app, two faces — humans use the UI, agents use MCP)
785
+
786
+ Use this when AI agents should be first-class USERS of the app, working alongside humans.
787
+ The trick: there is NO agent-specific backend. Both faces operate on the SAME workspace
788
+ records — the human face is your app UI (the Workspace App Template above), the agent face
789
+ is the standard MCP workspace tools (`aimeat_workspace_read` / `_write` / `_publish`) driven
790
+ by a PROMPT your app generates. Access on both faces is the workspace's own access.
791
+
792
+ Canonical example — a kanban the human fills and agents work:
793
+
794
+ 1. **The shared record.** One records space, contract-tagged so agents recognise it, with an
795
+ OPEN schema (agents may add fields without rejections):
796
+ ```
797
+ PUT /v1/organisms/{id}/workspace?ws={ws}
798
+ { "add_spaces": [{ "name": "task", "namespace": "shared.kanban", "mode": "records",
799
+ "contract": "kanban", "description": "Tasks humans file and agents work" }],
800
+ "schemas": { "shared.kanban": { "type": "object", "required": ["id","title","status"],
801
+ "properties": { "id": {"type":"string"}, "title": {"type":"string"}, "brief": {"type":"string"},
802
+ "status": {"type":"string","enum":["todo","claimed","doing","done"]},
803
+ "assignee": {"type":"string"}, "deliverable": {"type":"string"}, "notes": {"type":"string"} } } } }
804
+ ```
805
+ 2. **The human face** is a normal workspace app: render columns by `status`, create tasks with
806
+ `status:"todo"`, let the human override any task — same `writeDraft` + `publish` loop the
807
+ agents use.
808
+ 3. **The agent face is a prompt, not code.** Put a "🤖 Copy agent prompt" button in the app that
809
+ emits the work loop with the ids baked in — the human pastes it into ANY MCP-connected agent:
810
+ ```
811
+ You work a kanban board in an AIMEAT workspace. Use your AIMEAT MCP tools.
812
+ Board: organism_id "<orgId>", ws "<wsId>", space "task".
813
+ A task is { id, title, brief, status: todo|claimed|doing|done, assignee, deliverable, notes }.
814
+ Work loop:
815
+ 1. aimeat_workspace_read { organism_id, ws } — look at objects["task"].
816
+ 2. Pick ONE task with status "todo" that matches your skills.
817
+ 3. CLAIM: aimeat_workspace_write with the task re-written as status "claimed" +
818
+ assignee "<your GAII>", then aimeat_workspace_publish. Re-read after publishing —
819
+ if the assignee is not you, someone else won; pick another task.
820
+ 4. Work. Record progress: status "doing" + a short note in `notes`, publish each update.
821
+ 5. Finish: status "done" + WHERE THE RESULT LIVES in `deliverable` (a URL, a workspace
822
+ document id, or a memory key). Publish.
823
+ 6. Repeat while matching "todo" tasks remain. Rules: one task at a time; keep every
824
+ existing field when re-writing; if a publish is gated, leave the draft for human review.
825
+ ```
826
+ 4. **Human control knobs come free from the workspace:** the publish gate turns every agent
827
+ publish into a pending human approval; workspace roles decide which members (and their
828
+ agents) may write at all.
829
+
830
+ Key rules for agent-faced apps:
831
+ - The claim convention (`assignee` + re-read after publish) resolves races without any
832
+ backend lock — first publish wins, losers stand down.
833
+ - `deliverable` is a POINTER, never the payload — the work product lives where it belongs
834
+ (a document space, a memory key, a URL) and the board links to it.
835
+ - Poll politely in the UI (the board changes while humans watch); agents poll via
836
+ `aimeat_workspace_read` on their own schedule.
837
+ - Full working example: the "Agent Kanban" proof app (packages/agent-kanban in the AIMEAT
838
+ repo) — verified end-to-end: human files a task, an agent claims it (assignee + status),
839
+ progresses it, and finishes with a deliverable link the human clicks.
840
+
563
841
  ### Realtime / Multiplayer Template
564
842
 
565
843
  For apps that need live collaboration, multiplayer, or real-time sync.
@@ -269,6 +269,7 @@
269
269
  "/views/profile/organisms/participants-panel.js": "/views/profile/organisms/participants-panel.js",
270
270
  "/views/profile/organisms/workspace-comments.js": "/views/profile/organisms/workspace-comments.js",
271
271
  "/views/profile/organisms/sources-panel.js": "/views/profile/organisms/sources-panel.js",
272
+ "/views/profile/organisms/workspace-apps.js": "/views/profile/organisms/workspace-apps.js",
272
273
  "/views/profile/organisms/schema-form.js": "/views/profile/organisms/schema-form.js",
273
274
  "/views/profile/organisms/document.js": "/views/profile/organisms/document.js",
274
275
  "/views/command-palette.js": "/views/command-palette.js",
@@ -317,13 +318,32 @@
317
318
  import htm from 'htm';
318
319
  import { loadTranslations, t, getLocale, switchLocale, onLocaleChange } from '/js/i18n.js';
319
320
  import { initThemeSystem } from '/js/theme.js';
320
- import { NotificationBell } from '/components/NotificationBell.js';
321
+ import { NotificationBell, openNotificationLink } from '/components/NotificationBell.js';
321
322
  import { CommandPalette } from '/views/command-palette.js';
322
323
  import { openAppSandboxed, isAppHtmlUrl } from '/js/app-sandbox.js';
323
324
  // generateStars no longer needed for light theme
324
325
 
325
326
  const html = htm.bind(h);
326
327
 
328
+ // ── Notification cold-open deep link ──
329
+ // A push-notification click can open '/v1/profile?tab=messages#inbox/<conversationId>' (or
330
+ // the bell's '#<tab>[/<rest>]' hash form) in a fresh window. Prime the landing page's
331
+ // sessionStorage contract ({tabId, slot} JSON — landing-page.js openView init) BEFORE the
332
+ // view mounts, plus the inbox thread key that inbox-tab.js consumes on mount. Overwriting the
333
+ // saved tab matters: landing-page prefers sessionStorage over ?tab=, so a stale value from
334
+ // earlier browsing would otherwise beat the deep link in a reused browser tab.
335
+ (() => {
336
+ if (location.pathname !== '/v1/profile') return;
337
+ const m = /^#([a-z]+)(?:\/(.+))?$/i.exec(location.hash || '');
338
+ const rawTab = new URLSearchParams(location.search).get('tab') || (m ? m[1] : '');
339
+ if (!rawTab) return;
340
+ const tabId = rawTab.toLowerCase() === 'inbox' ? 'messages' : rawTab;
341
+ try {
342
+ sessionStorage.setItem('aimeat-profile-tab', JSON.stringify({ tabId, slot: 'main' }));
343
+ if (tabId === 'messages' && m && m[2]) sessionStorage.setItem('aimeat.inbox.open', m[2]);
344
+ } catch { /* private mode — ?tab= still lands the tab */ }
345
+ })();
346
+
327
347
  // ── Error Boundary ──
328
348
  class ErrorBoundary extends Component {
329
349
  constructor(props) {
@@ -436,6 +456,19 @@
436
456
  }
437
457
  }, []);
438
458
 
459
+ // Push-notification clicks on an already-open window: the service worker (sw.js) focuses
460
+ // this window and posts the notification's target URL — deep-link it in place (tab switch,
461
+ // inbox thread) instead of a full reload.
462
+ useEffect(() => {
463
+ if (!('serviceWorker' in navigator)) return undefined;
464
+ const onSwMessage = (e) => {
465
+ if (!e.data || e.data.type !== 'aimeat-notification-click' || !e.data.url) return;
466
+ openNotificationLink(e.data.url, navigate);
467
+ };
468
+ navigator.serviceWorker.addEventListener('message', onSwMessage);
469
+ return () => navigator.serviceWorker.removeEventListener('message', onSwMessage);
470
+ }, [navigate]);
471
+
439
472
  // Initial load + popstate
440
473
  useEffect(() => {
441
474
  // Load initial route
@@ -544,6 +577,9 @@
544
577
  const [morsels, setMorsels] = useState(null);
545
578
  const [menuOpen, setMenuOpen] = useState(false);
546
579
  const [secretaryAvailable, setSecretaryAvailable] = useState(false);
580
+ // Node feature flags from /v1/site/header-nav (e.g. { secretary }). Empty until the fetch resolves,
581
+ // so optional surfaces stay hidden until we positively learn they're enabled (no flash).
582
+ const [features, setFeatures] = useState({});
547
583
  // Header nav config: { order, hidden }. Defaults to all visible in declared order
548
584
  // until the public /v1/site/header-nav fetch resolves (no flash of empty nav).
549
585
  const [navConfig, setNavConfig] = useState({ order: PUBLIC_NAV_LINKS.map(l => l.id), hidden: [] });
@@ -574,6 +610,7 @@
574
610
  const order = Array.isArray(d.data.order) ? d.data.order : PUBLIC_NAV_LINKS.map(l => l.id);
575
611
  const hidden = Array.isArray(d.data.hidden) ? d.data.hidden : [];
576
612
  setNavConfig({ order, hidden });
613
+ setFeatures(d.data.features || {});
577
614
  })
578
615
  .catch(e => console.warn('Header nav fetch failed:', e.message));
579
616
  return () => { cancelled = true; };
@@ -644,7 +681,7 @@
644
681
  class="${link.matchActive !== false && currentPath === link.path ? 'active' : ''}">${t(link.key) || link.fallback || link.key}</a>
645
682
  `)}
646
683
  ${session && html`
647
- ${secretaryAvailable && html`
684
+ ${secretaryAvailable && features.secretary && html`
648
685
  <a href="/v1/secretary" class="sec-nav-link ${currentPath === '/v1/secretary' ? 'active' : ''}"
649
686
  title=${t('nav.secretary')} onClick=${(e) => { handleNav(e, '/v1/secretary'); setMenuOpen(false); }}>
650
687
  <span class="sec-nav-icon"><svg viewBox="0 0 24 24" aria-hidden="true">
package/dist/public/sw.js CHANGED
@@ -1,34 +1,50 @@
1
- /* AIMEAT Push Notification Service Worker */
2
-
3
- self.addEventListener('push', (event) => {
4
- if (!event.data) return;
5
- try {
6
- const data = event.data.json();
7
- const title = data.title || 'AIMEAT';
8
- const options = {
9
- body: data.body || '',
10
- icon: data.icon || '/icons/icon-192.png',
11
- badge: data.badge || '/icons/icon-192.png',
12
- tag: data.tag || 'aimeat-notification',
13
- data: data.data || data,
14
- };
15
- if (data.url) options.data.url = data.url;
16
- event.waitUntil(self.registration.showNotification(title, options));
17
- } catch {
18
- const text = event.data.text();
19
- event.waitUntil(self.registration.showNotification('AIMEAT', { body: text }));
20
- }
21
- });
22
-
23
- self.addEventListener('notificationclick', (event) => {
24
- event.notification.close();
25
- const url = event.notification.data?.url || '/v1/portal';
26
- event.waitUntil(
27
- clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
28
- for (const client of windowClients) {
29
- if (client.url.includes(url) && 'focus' in client) return client.focus();
30
- }
31
- return clients.openWindow(url);
32
- })
33
- );
34
- });
1
+ /* AIMEAT Push Notification Service Worker
2
+ *
3
+ * Click routing: a notification payload carries `url` (or `data.url`) — a same-origin path such
4
+ * as '/v1/profile?tab=messages#inbox/<conversationId>' or '/v1/apps/<owner>/<file>?mode=inline'.
5
+ * On click we prefer an already-open window on the SAME path (focus + postMessage so the SPA can
6
+ * switch tabs in place — see the 'aimeat-notification-click' listener in spa.html); otherwise a
7
+ * new window opens the URL, and the SPA's cold-load deep-linking (?tab= / #hash) lands the view.
8
+ */
9
+
10
+ // Take over immediately so click-routing fixes apply without waiting for every tab to close.
11
+ self.addEventListener('install', () => self.skipWaiting());
12
+ self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()));
13
+
14
+ self.addEventListener('push', (event) => {
15
+ if (!event.data) return;
16
+ try {
17
+ const data = event.data.json();
18
+ const title = data.title || 'AIMEAT';
19
+ const options = {
20
+ body: data.body || '',
21
+ icon: data.icon || '/icons/icon-192.png',
22
+ badge: data.badge || '/icons/badge-96.png',
23
+ tag: data.tag || 'aimeat-notification',
24
+ data: data.data || data,
25
+ };
26
+ if (data.url) options.data.url = data.url;
27
+ event.waitUntil(self.registration.showNotification(title, options));
28
+ } catch {
29
+ const text = event.data.text();
30
+ event.waitUntil(self.registration.showNotification('AIMEAT', { body: text }));
31
+ }
32
+ });
33
+
34
+ self.addEventListener('notificationclick', (event) => {
35
+ event.notification.close();
36
+ const url = event.notification.data?.url || '/v1/profile';
37
+ const target = new URL(url, self.location.origin);
38
+ event.waitUntil(
39
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
40
+ for (const client of windowClients) {
41
+ let clientUrl;
42
+ try { clientUrl = new URL(client.url); } catch { continue; }
43
+ if (clientUrl.origin !== target.origin || clientUrl.pathname !== target.pathname) continue;
44
+ client.postMessage({ type: 'aimeat-notification-click', url: target.pathname + target.search + target.hash });
45
+ return 'focus' in client ? client.focus() : undefined;
46
+ }
47
+ return clients.openWindow(target.href);
48
+ })
49
+ );
50
+ });
@@ -22,6 +22,8 @@
22
22
  * (revenue / orders / cost-per-task / commission income + per-agent table) from GET /v1/orgs/:slug/governance.
23
23
  * v0.7.0 — 2026-06-24 — Secretary Phase 6 (slice 1): Company model admin subtab — prompt-driven builder
24
24
  * (describe → generate in-app / copy-prompt → review → apply) for departments/roles/people/reporting (GET/PUT /v1/orgs/:slug/model).
25
+ * v0.8.0 — 2026-07-01 — Company Secretary subtab hidden unless the node enables the Secretary feature
26
+ * (AIMEAT_SECRETARY_ENABLED, read from /v1/site/header-nav `features.secretary`). Governance + model unaffected.
25
27
  */
26
28
  import { h } from 'preact';
27
29
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -760,6 +762,7 @@ export default function MyCompanyView() {
760
762
  const [newName, setNewName] = useState('');
761
763
  const [newBusinessId, setNewBusinessId] = useState('');
762
764
  const [busy, setBusy] = useState(false);
765
+ const [featSecretary, setFeatSecretary] = useState(false); // node opt-in: show the company Secretary subtab
763
766
 
764
767
  async function loadOrgs() {
765
768
  setState('loading');
@@ -771,6 +774,15 @@ export default function MyCompanyView() {
771
774
  }
772
775
  }
773
776
  useEffect(() => { loadOrgs(); }, []);
777
+ // Company Secretary is opt-in per node (AIMEAT_SECRETARY_ENABLED); hide its admin subtab when off.
778
+ useEffect(() => {
779
+ let cancelled = false;
780
+ fetch('/v1/site/header-nav')
781
+ .then(r => r.json())
782
+ .then(d => { if (!cancelled && d?.ok) setFeatSecretary(!!(d.data?.features && d.data.features.secretary)); })
783
+ .catch(() => {});
784
+ return () => { cancelled = true; };
785
+ }, []);
774
786
 
775
787
  async function loadOrders(org) {
776
788
  try { const r = await apiGet(`/v1/orgs/${encodeURIComponent(org.slug)}/orders`); setOrdersReceived(r.data?.orders ?? []); }
@@ -872,7 +884,7 @@ export default function MyCompanyView() {
872
884
  </div>
873
885
 
874
886
  <div class="mc-subtabs">
875
- ${['catalog', ...(selected.role === 'admin' ? ['orders', 'customers', 'portfolio', 'secretary', 'governance', 'model', 'settings'] : []), 'members'].map(id => html`
887
+ ${['catalog', ...(selected.role === 'admin' ? ['orders', 'customers', 'portfolio', ...(featSecretary ? ['secretary'] : []), 'governance', 'model', 'settings'] : []), 'members'].map(id => html`
876
888
  <button class="mc-subtab ${subTab === id ? 'active' : ''}" key=${id} onClick=${() => setSubTab(id)}>${t('myCompany.tab' + id.charAt(0).toUpperCase() + id.slice(1))}</button>`)}
877
889
  <span class="mc-role-chip">${t('myCompany.role' + (selected.role || 'member').charAt(0).toUpperCase() + (selected.role || 'member').slice(1))}</span>
878
890
  </div>