aimeat 1.36.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 (240) hide show
  1. package/dist/.env.example +6 -0
  2. package/dist/locales/en.json +186 -25
  3. package/dist/locales/fi.json +186 -25
  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 +34 -1
  16. package/dist/public/sw.js +50 -34
  17. package/dist/public/views/portfolio.js +267 -44
  18. package/dist/public/views/profile/agents/agent-card.js +4 -0
  19. package/dist/public/views/profile/agents/tab-contracts.js +112 -0
  20. package/dist/public/views/profile/landing-page.js +10 -1
  21. package/dist/public/views/profile/memory-tab.js +6 -2
  22. package/dist/public/views/profile/notifications-tab.js +9 -1
  23. package/dist/public/views/profile/organisms/home.js +23 -3
  24. package/dist/public/views/profile/organisms/participants-panel.js +89 -17
  25. package/dist/public/views/profile/organisms/workspace-apps.js +130 -0
  26. package/dist/public/views/profile/organisms/workspace.js +5 -0
  27. package/dist/public/views/profile/organisms-tab.js +1 -1
  28. package/dist/src/cli/init-wizard.d.ts.map +1 -1
  29. package/dist/src/cli/init-wizard.js +32 -0
  30. package/dist/src/cli/init-wizard.js.map +1 -1
  31. package/dist/src/config.d.ts +13 -0
  32. package/dist/src/config.d.ts.map +1 -1
  33. package/dist/src/config.js +9 -0
  34. package/dist/src/config.js.map +1 -1
  35. package/dist/src/data/aimeat-iam-package.d.ts +10 -2
  36. package/dist/src/data/aimeat-iam-package.d.ts.map +1 -1
  37. package/dist/src/data/aimeat-iam-package.js +39 -10
  38. package/dist/src/data/aimeat-iam-package.js.map +1 -1
  39. package/dist/src/generated/api-types.d.ts +328 -23
  40. package/dist/src/generated/api-types.d.ts.map +1 -1
  41. package/dist/src/generated/prisma-postgres/edge.js +4 -3
  42. package/dist/src/generated/prisma-postgres/index-browser.js +1 -0
  43. package/dist/src/generated/prisma-postgres/index.d.ts +30 -1
  44. package/dist/src/generated/prisma-postgres/index.js +4 -3
  45. package/dist/src/generated/prisma-postgres/package.json +1 -1
  46. package/dist/src/generated/prisma-postgres/schema.prisma +1 -0
  47. package/dist/src/generated/prisma-postgres/wasm.js +4 -3
  48. package/dist/src/mcp/annotations.d.ts.map +1 -1
  49. package/dist/src/mcp/annotations.js +1 -0
  50. package/dist/src/mcp/annotations.js.map +1 -1
  51. package/dist/src/mcp/catalog/definitions.d.ts +3 -0
  52. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  53. package/dist/src/mcp/catalog/definitions.js +21 -7
  54. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  55. package/dist/src/mcp/catalog/surfaces.js +1 -1
  56. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  57. package/dist/src/mcp/core.js +1 -1
  58. package/dist/src/mcp/core.js.map +1 -1
  59. package/dist/src/mcp/extensions.d.ts.map +1 -1
  60. package/dist/src/mcp/extensions.js +21 -0
  61. package/dist/src/mcp/extensions.js.map +1 -1
  62. package/dist/src/mcp/memory-extended.js +1 -1
  63. package/dist/src/mcp/memory-extended.js.map +1 -1
  64. package/dist/src/mcp/organisms.d.ts +1 -0
  65. package/dist/src/mcp/organisms.d.ts.map +1 -1
  66. package/dist/src/mcp/organisms.js +23 -6
  67. package/dist/src/mcp/organisms.js.map +1 -1
  68. package/dist/src/mcp/workspaces.d.ts +3 -0
  69. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  70. package/dist/src/mcp/workspaces.js +7 -2
  71. package/dist/src/mcp/workspaces.js.map +1 -1
  72. package/dist/src/middleware/subdomain.d.ts +9 -0
  73. package/dist/src/middleware/subdomain.d.ts.map +1 -1
  74. package/dist/src/middleware/subdomain.js +24 -3
  75. package/dist/src/middleware/subdomain.js.map +1 -1
  76. package/dist/src/models/schemas.d.ts +2 -0
  77. package/dist/src/models/schemas.d.ts.map +1 -1
  78. package/dist/src/models/schemas.js +2 -2
  79. package/dist/src/models/schemas.js.map +1 -1
  80. package/dist/src/routes/admin-features.js +1 -1
  81. package/dist/src/routes/admin-features.js.map +1 -1
  82. package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
  83. package/dist/src/routes/agent-onboarding.js +44 -7
  84. package/dist/src/routes/agent-onboarding.js.map +1 -1
  85. package/dist/src/routes/agents.d.ts +1 -0
  86. package/dist/src/routes/agents.d.ts.map +1 -1
  87. package/dist/src/routes/agents.js +43 -0
  88. package/dist/src/routes/agents.js.map +1 -1
  89. package/dist/src/routes/app-grants.d.ts +5 -0
  90. package/dist/src/routes/app-grants.d.ts.map +1 -1
  91. package/dist/src/routes/app-grants.js +52 -20
  92. package/dist/src/routes/app-grants.js.map +1 -1
  93. package/dist/src/routes/boards.d.ts.map +1 -1
  94. package/dist/src/routes/boards.js +4 -1
  95. package/dist/src/routes/boards.js.map +1 -1
  96. package/dist/src/routes/bootstrap.d.ts +2 -0
  97. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  98. package/dist/src/routes/bootstrap.js +5 -0
  99. package/dist/src/routes/bootstrap.js.map +1 -1
  100. package/dist/src/routes/capabilities.d.ts.map +1 -1
  101. package/dist/src/routes/capabilities.js +4 -1
  102. package/dist/src/routes/capabilities.js.map +1 -1
  103. package/dist/src/routes/catalogue.d.ts.map +1 -1
  104. package/dist/src/routes/catalogue.js +5 -4
  105. package/dist/src/routes/catalogue.js.map +1 -1
  106. package/dist/src/routes/extensions.d.ts.map +1 -1
  107. package/dist/src/routes/extensions.js +11 -0
  108. package/dist/src/routes/extensions.js.map +1 -1
  109. package/dist/src/routes/ghii.d.ts.map +1 -1
  110. package/dist/src/routes/ghii.js +65 -17
  111. package/dist/src/routes/ghii.js.map +1 -1
  112. package/dist/src/routes/lib-editor.d.ts +26 -0
  113. package/dist/src/routes/lib-editor.d.ts.map +1 -0
  114. package/dist/src/routes/lib-editor.js +215 -0
  115. package/dist/src/routes/lib-editor.js.map +1 -0
  116. package/dist/src/routes/lib-markdown.d.ts +16 -1
  117. package/dist/src/routes/lib-markdown.d.ts.map +1 -1
  118. package/dist/src/routes/lib-markdown.js +281 -2
  119. package/dist/src/routes/lib-markdown.js.map +1 -1
  120. package/dist/src/routes/lib-organism.d.ts +28 -0
  121. package/dist/src/routes/lib-organism.d.ts.map +1 -0
  122. package/dist/src/routes/lib-organism.js +298 -0
  123. package/dist/src/routes/lib-organism.js.map +1 -0
  124. package/dist/src/routes/lib-portfolio-standalone.d.ts +22 -0
  125. package/dist/src/routes/lib-portfolio-standalone.d.ts.map +1 -0
  126. package/dist/src/routes/lib-portfolio-standalone.js +68 -0
  127. package/dist/src/routes/lib-portfolio-standalone.js.map +1 -0
  128. package/dist/src/routes/libs.d.ts +4 -0
  129. package/dist/src/routes/libs.d.ts.map +1 -1
  130. package/dist/src/routes/libs.js +60 -1
  131. package/dist/src/routes/libs.js.map +1 -1
  132. package/dist/src/routes/memory.d.ts +3 -0
  133. package/dist/src/routes/memory.d.ts.map +1 -1
  134. package/dist/src/routes/memory.js +58 -4
  135. package/dist/src/routes/memory.js.map +1 -1
  136. package/dist/src/routes/notifications.d.ts +5 -1
  137. package/dist/src/routes/notifications.d.ts.map +1 -1
  138. package/dist/src/routes/notifications.js +68 -3
  139. package/dist/src/routes/notifications.js.map +1 -1
  140. package/dist/src/routes/organisms.d.ts +2 -0
  141. package/dist/src/routes/organisms.d.ts.map +1 -1
  142. package/dist/src/routes/organisms.js +171 -11
  143. package/dist/src/routes/organisms.js.map +1 -1
  144. package/dist/src/routes/owners.d.ts.map +1 -1
  145. package/dist/src/routes/owners.js +12 -8
  146. package/dist/src/routes/owners.js.map +1 -1
  147. package/dist/src/routes/portfolio.d.ts +41 -1
  148. package/dist/src/routes/portfolio.d.ts.map +1 -1
  149. package/dist/src/routes/portfolio.js +94 -30
  150. package/dist/src/routes/portfolio.js.map +1 -1
  151. package/dist/src/routes/push.d.ts +15 -0
  152. package/dist/src/routes/push.d.ts.map +1 -1
  153. package/dist/src/routes/push.js +16 -1
  154. package/dist/src/routes/push.js.map +1 -1
  155. package/dist/src/routes/subdomains.d.ts +5 -0
  156. package/dist/src/routes/subdomains.d.ts.map +1 -1
  157. package/dist/src/routes/subdomains.js +56 -0
  158. package/dist/src/routes/subdomains.js.map +1 -1
  159. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  160. package/dist/src/server-bootstrap/routes-loader.js +2 -0
  161. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  162. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  163. package/dist/src/server-bootstrap/static-files.js +9 -4
  164. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  165. package/dist/src/services/access-guard.d.ts.map +1 -1
  166. package/dist/src/services/access-guard.js +7 -0
  167. package/dist/src/services/access-guard.js.map +1 -1
  168. package/dist/src/services/ecosystem-access.d.ts +24 -8
  169. package/dist/src/services/ecosystem-access.d.ts.map +1 -1
  170. package/dist/src/services/ecosystem-access.js +27 -5
  171. package/dist/src/services/ecosystem-access.js.map +1 -1
  172. package/dist/src/services/iam/app-commands.d.ts +75 -0
  173. package/dist/src/services/iam/app-commands.d.ts.map +1 -0
  174. package/dist/src/services/iam/app-commands.js +55 -0
  175. package/dist/src/services/iam/app-commands.js.map +1 -0
  176. package/dist/src/services/iam/define-app-iam.d.ts +45 -0
  177. package/dist/src/services/iam/define-app-iam.d.ts.map +1 -0
  178. package/dist/src/services/iam/define-app-iam.js +75 -0
  179. package/dist/src/services/iam/define-app-iam.js.map +1 -0
  180. package/dist/src/services/iam/model.d.ts +113 -0
  181. package/dist/src/services/iam/model.d.ts.map +1 -0
  182. package/dist/src/services/iam/model.js +115 -0
  183. package/dist/src/services/iam/model.js.map +1 -0
  184. package/dist/src/services/mailbox-notification.d.ts +1 -0
  185. package/dist/src/services/mailbox-notification.d.ts.map +1 -1
  186. package/dist/src/services/mailbox-notification.js +3 -0
  187. package/dist/src/services/mailbox-notification.js.map +1 -1
  188. package/dist/src/services/notify.d.ts +11 -1
  189. package/dist/src/services/notify.d.ts.map +1 -1
  190. package/dist/src/services/notify.js +39 -2
  191. package/dist/src/services/notify.js.map +1 -1
  192. package/dist/src/services/organism-privacy.d.ts +46 -0
  193. package/dist/src/services/organism-privacy.d.ts.map +1 -0
  194. package/dist/src/services/organism-privacy.js +42 -0
  195. package/dist/src/services/organism-privacy.js.map +1 -0
  196. package/dist/src/services/prompt-defaults.d.ts +2 -0
  197. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  198. package/dist/src/services/prompt-defaults.js +5 -0
  199. package/dist/src/services/prompt-defaults.js.map +1 -1
  200. package/dist/src/services/scheduler.d.ts.map +1 -1
  201. package/dist/src/services/scheduler.js +11 -3
  202. package/dist/src/services/scheduler.js.map +1 -1
  203. package/dist/src/services/workspace-access.d.ts +2 -0
  204. package/dist/src/services/workspace-access.d.ts.map +1 -1
  205. package/dist/src/services/workspace-access.js +20 -8
  206. package/dist/src/services/workspace-access.js.map +1 -1
  207. package/dist/src/services/workspace-engagements.d.ts +65 -0
  208. package/dist/src/services/workspace-engagements.d.ts.map +1 -0
  209. package/dist/src/services/workspace-engagements.js +92 -0
  210. package/dist/src/services/workspace-engagements.js.map +1 -0
  211. package/dist/src/services/workspace-meta.d.ts +9 -2
  212. package/dist/src/services/workspace-meta.d.ts.map +1 -1
  213. package/dist/src/services/workspace-meta.js +22 -2
  214. package/dist/src/services/workspace-meta.js.map +1 -1
  215. package/dist/src/storage/interface.d.ts +8 -1
  216. package/dist/src/storage/interface.d.ts.map +1 -1
  217. package/dist/src/storage/interface.js.map +1 -1
  218. package/dist/src/storage/providers/mongodb/index.js +2 -2
  219. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  220. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  221. package/dist/src/storage/providers/sqlite/index.js +5 -4
  222. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  223. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  224. package/dist/src/storage/providers/sqlite/schema.js +3 -0
  225. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  226. package/dist/src/utils/env-config.d.ts.map +1 -1
  227. package/dist/src/utils/env-config.js +12 -0
  228. package/dist/src/utils/env-config.js.map +1 -1
  229. package/dist/src/utils/env-validator.d.ts.map +1 -1
  230. package/dist/src/utils/env-validator.js +8 -0
  231. package/dist/src/utils/env-validator.js.map +1 -1
  232. package/dist/static/app-catalog.html +8 -2
  233. package/dist/static/icons/badge-96.png +0 -0
  234. package/dist/static/icons/icon-192.png +0 -0
  235. package/dist/static/icons/icon-512.png +0 -0
  236. package/dist/static/manifest.json +19 -17
  237. package/dist/static/sw.js +13 -11
  238. package/package.json +1 -1
  239. package/prisma/schema.postgres.prisma +1 -0
  240. package/prisma/schema.prisma +1 -0
@@ -8,6 +8,8 @@
8
8
  * all colors/spacing via theme.css variables.
9
9
  * @version-history
10
10
  * v1.0.0 — 2026-06-19 — Initial: extracted the per-view duplicates into one global component sheet.
11
+ * v1.1.0 — 2026-07-02 — Styles for the live memory embed (aimeat-memory fence → MemoryEmbed): .md-mem
12
+ * container/header/refresh/table/notes.
11
13
  */
12
14
 
13
15
  .md-body {
@@ -65,3 +67,16 @@
65
67
  .md-body .md-img { max-width: 100%; height: auto; border-radius: 6px; margin: 0.6em 0; }
66
68
  .md-body hr,
67
69
  .md-body .md-hr { border: none; border-top: 1px solid var(--border); margin: 1.6em 0; }
70
+
71
+ /* ── Live memory embed (```aimeat-memory fence → components/MemoryEmbed.js) ── */
72
+ .md-body .md-mem { border: 1px solid var(--border); border-radius: 8px; padding: 0.5em 0.8em 0.7em; margin: 1em 0; background: var(--bg-dim); }
73
+ .md-body .md-mem-head { display: flex; align-items: center; gap: 0.6em; font-size: 0.78rem; color: var(--muted); margin-bottom: 0.35em; }
74
+ .md-body .md-mem-title { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); overflow-wrap: anywhere; flex: 1; }
75
+ .md-body .md-mem-upd { white-space: nowrap; }
76
+ .md-body .md-mem-refresh { border: none; background: transparent; color: var(--muted); cursor: pointer; border-radius: 4px; padding: 0 0.3em; font-size: 0.9rem; line-height: 1.4; }
77
+ .md-body .md-mem-refresh:hover { color: var(--text); background: var(--surface, var(--card)); }
78
+ .md-body .md-mem table { margin: 0.3em 0 0; background: transparent; }
79
+ .md-body .md-mem-note { color: var(--muted); font-size: 0.88rem; padding: 0.2em 0; }
80
+ .md-body .md-mem-err { color: var(--warning, var(--muted)); }
81
+ .md-body .md-mem-more { color: var(--muted); font-size: 0.78rem; margin-top: 0.3em; }
82
+ .md-body .md-mem-value { font-size: 0.95rem; white-space: pre-wrap; overflow-wrap: anywhere; }
@@ -4,6 +4,8 @@
4
4
  * Uses agd- prefix to avoid collisions with other profile styles.
5
5
  * All colors use CSS variables from theme.css.
6
6
  * @version-history
7
+ * v1.14.0 -- 2026-07-03 -- Add Contracts sub-tab styles (.pf-agd-contracts, .agc-caps, .agc-list,
8
+ * .agc-row, .agc-where, .agc-org, .agc-since) — agent-side contract-engagement visibility.
7
9
  * v1.13.0 -- 2026-06-24 -- Secretary P5 (S-A): add .pf-agd-badge--specialist (specialist agent type badge).
8
10
  * v1.12.0 -- 2026-06-22 -- Add .pf-agd-capwrap rule so the capabilities summary
9
11
  * ("N tools · N skills · N langs") gets the card content inset (1rem) instead of
@@ -3646,3 +3648,18 @@
3646
3648
  .pf .pf-agd-json-bool { color: var(--accent, #E8564A); font-family: var(--font-mono, monospace); }
3647
3649
  .pf .pf-agd-json-null,
3648
3650
  .pf .pf-agd-json-empty { color: var(--text-muted, #9CA3AF); font-style: italic; }
3651
+
3652
+ /* ── Contracts sub-tab — agent-side contract-engagement visibility (active / retired). ── */
3653
+ .pf .pf-agd-contracts { padding: 0.25rem 0; }
3654
+ .pf .pf-agd-contracts .agc-mt { margin-top: 1rem; }
3655
+ .pf .pf-agd-contracts .agc-caps { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.4rem 0 0.2rem; }
3656
+ .pf .pf-agd-contracts .agc-list { display: flex; flex-direction: column; gap: 0.35rem; margin-top: 0.4rem; }
3657
+ .pf .pf-agd-contracts .agc-row {
3658
+ display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
3659
+ padding: 0.45rem 0.7rem; border: 1px solid var(--border); border-radius: var(--radius-xs, 6px);
3660
+ background: var(--card);
3661
+ }
3662
+ .pf .pf-agd-contracts .agc-row--retired { opacity: 0.72; }
3663
+ .pf .pf-agd-contracts .agc-where { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; font-size: 0.85rem; }
3664
+ .pf .pf-agd-contracts .agc-org { color: var(--text-dim); font-size: 0.78rem; }
3665
+ .pf .pf-agd-contracts .agc-since { color: var(--text-muted); font-size: 0.75rem; }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @file portfolio.css
3
+ * @description Styles for the Portfolio view — builder wizard (.portfolio-container
4
+ * column) and full-bleed public viewer (.portfolio-viewer-full).
5
+ * @usage Loaded by spa.html; classes consumed by public/views/portfolio.js.
6
+ * @version-history
7
+ * v1.1.0 — 2026-07-03 — Full-bleed public viewer: .portfolio-viewer-full column fills
8
+ * the viewport below the nav, slim toolbar, iframe flexes to remaining height.
9
+ * v1.0.0 — 2026-03-06 — Initial portfolio builder + viewer styles.
10
+ */
11
+
1
12
  /* ── Portfolio View ────────────────────────────────────── */
2
13
 
3
14
  .portfolio-container {
@@ -248,6 +259,37 @@
248
259
  min-height: 60vh;
249
260
  }
250
261
 
262
+ /* Full-bleed published-portfolio view: a flex column that fills the viewport
263
+ below the sticky nav; the iframe takes all remaining height and the
264
+ portfolio scrolls inside it. */
265
+ .portfolio-viewer-full {
266
+ display: flex;
267
+ flex-direction: column;
268
+ height: calc(100vh - var(--nav-height, 64px));
269
+ height: calc(100dvh - var(--nav-height, 64px));
270
+ }
271
+
272
+ .portfolio-viewer-bar {
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 0.75rem;
276
+ flex-wrap: wrap;
277
+ padding: 0.45rem 1rem;
278
+ }
279
+
280
+ .portfolio-viewer-owner {
281
+ color: var(--text-dim, #6B7280);
282
+ font-size: 0.9rem;
283
+ min-width: 0;
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ white-space: nowrap;
287
+ }
288
+
289
+ .portfolio-viewer-fs {
290
+ margin-left: auto;
291
+ }
292
+
251
293
  .portfolio-viewer-frame {
252
294
  width: 100%;
253
295
  border: none;
@@ -256,6 +298,14 @@
256
298
  background: #fff;
257
299
  }
258
300
 
301
+ /* Keep after the base .portfolio-viewer-frame rule — same specificity,
302
+ the override must win in the cascade. */
303
+ .portfolio-viewer-frame-full {
304
+ flex: 1;
305
+ min-height: 0;
306
+ border-radius: 0;
307
+ }
308
+
259
309
  .portfolio-not-found {
260
310
  text-align: center;
261
311
  padding: 4rem 2rem;
@@ -337,4 +387,5 @@
337
387
  .portfolio-publish-target { font-size: .85rem; color: var(--text-dim); margin-bottom: .75rem; }
338
388
  .portfolio-publish-url { font-family: monospace; word-break: break-all; }
339
389
  .portfolio-paste-actions { display: flex; gap: .6rem; margin-top: .5rem; flex-wrap: wrap; }
390
+ .portfolio-badge-toggle { margin: -0.25rem 0 0.75rem; }
340
391
  .portfolio-preview-frame { margin-top: .75rem; min-height: 420px; width: 100%; border: 1px solid var(--border); border-radius: 8px; background: #fff; }
@@ -4,6 +4,10 @@
4
4
  * wallet, memory, agents, extensions, packages, knowledge, generator, and
5
5
  * edit panels. Consumes theme.css tokens so it flips with dark/light mode.
6
6
  * @version-history
7
+ * v2.10.0 — 2026-07-03 — Contract-engagement chips: .pj-eng + .pj-retire-btn for the People panel.
8
+ * v2.9.0 — 2026-07-02 — Workspace Apps strip: .pj-apps-strip/-head/-title/-hint/-grid, launch
9
+ * cards .pj-app-card/-name/-desc/-meta/-missing, and the pin/unpin picker .pj-apps-picker(-row/
10
+ * -name/-owner) + .pj-apps-empty.
7
11
  * v2.8.0 — 2026-06-24 — Secretary P5 (S-C): .ext-instance-cfg-field/-label/-secret/-desc for the
8
12
  * extension instance-create config form (masked secret fields).
9
13
  * v2.7.0 — 2026-06-22 — Home "Usage & quotas" card: .pf-usage-card/.pf-usage-row/.pf-usage-bar/
@@ -1799,6 +1803,11 @@
1799
1803
  border: 1px solid var(--border-soft, #f0f0ec);
1800
1804
  }
1801
1805
  .pf-edit-hint { font-size: .75rem; color: var(--text-dim, #9CA3AF); }
1806
+ /* Directory opt-in checkbox row (member "phone book"). */
1807
+ .pf-edit-check { display: flex; align-items: flex-start; gap: .6rem; cursor: pointer; }
1808
+ .pf-edit-check input { margin-top: .2rem; flex-shrink: 0; }
1809
+ .pf-edit-check > span { display: flex; flex-direction: column; gap: .15rem; }
1810
+ .pf-edit-check-title { font-size: .9rem; }
1802
1811
  .pf-edit-error { font-size: .81rem; color: var(--danger, #ef4444); padding: 8px 12px; background: var(--danger-soft, #FEF2F2); border-radius: 8px; }
1803
1812
  .pf-edit-footer {
1804
1813
  display: flex; justify-content: flex-end; gap: 10px;
@@ -2173,6 +2182,10 @@
2173
2182
  .pj-contract-agents { border: 1px solid var(--border); border-radius: 10px; padding: 0.5rem 0.75rem; margin-bottom: 0.6rem; background: var(--card); }
2174
2183
  /* The per-contract one-click adopt action inside a contract-agent chip. */
2175
2184
  .pj-adopt-btn { margin-left: 0.4rem; padding: 0.1rem 0.5rem; font-size: 0.78rem; }
2185
+ /* A per-contract engagement group (state badge + its adopt/retire action). */
2186
+ .pj-eng { display: inline-flex; align-items: center; gap: 0.2rem; }
2187
+ /* Retire an active contract engagement — quiet, so it reads as secondary to the green "active" badge. */
2188
+ .pj-retire-btn { margin-left: 0.25rem; padding: 0.1rem 0.5rem; font-size: 0.78rem; color: var(--text-dim); }
2176
2189
  .pj-draft-form { margin: 0.4rem 0; }
2177
2190
  .pj-drafts { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 0.3rem; }
2178
2191
  .pj-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0; }
@@ -3099,3 +3112,21 @@
3099
3112
  .dsc-item-meta { margin-top: .5rem; display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
3100
3113
  .dsc-tag { font-size: .72rem; color: var(--muted); background: var(--bg-dim); border-radius: var(--radius-full); padding: .08rem .45rem; }
3101
3114
  .dsc-owner { margin-left: auto; font-size: .72rem; color: var(--muted); font-family: var(--mono); }
3115
+
3116
+ /* ── Workspace Apps strip (pinned apps on the workspace Overview) ── */
3117
+ .pj-apps-strip { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem .9rem; margin-bottom: 1rem; }
3118
+ .pj-apps-head { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: .5rem; }
3119
+ .pj-apps-title { font-weight: 600; font-size: .95rem; }
3120
+ .pj-apps-hint { flex: 1; font-size: .78rem; color: var(--muted); }
3121
+ .pj-apps-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: .6rem; }
3122
+ .pj-app-card { background: var(--bg-dim); border: 1px solid var(--border); border-radius: var(--radius); padding: .6rem .75rem; cursor: pointer; transition: border-color .12s; }
3123
+ .pj-app-card:hover, .pj-app-card:focus-visible { border-color: var(--accent-border); box-shadow: var(--shadow-sm); }
3124
+ .pj-app-name { font-weight: 600; font-size: .9rem; }
3125
+ .pj-app-desc { margin-top: .25rem; font-size: .78rem; color: var(--text-dim); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
3126
+ .pj-app-meta { margin-top: .35rem; font-size: .72rem; color: var(--muted); }
3127
+ .pj-app-missing { color: var(--warning, var(--muted)); }
3128
+ .pj-apps-empty { font-size: .82rem; color: var(--muted); padding: .25rem 0; }
3129
+ .pj-apps-picker { margin-top: .75rem; border-top: 1px solid var(--border); padding-top: .6rem; display: flex; flex-direction: column; gap: .45rem; }
3130
+ .pj-apps-picker-row { display: flex; align-items: center; gap: .6rem; padding: .35rem .5rem; background: var(--bg-dim); border: 1px solid var(--border); border-radius: var(--radius); }
3131
+ .pj-apps-picker-name { font-size: .87rem; }
3132
+ .pj-apps-picker-owner { flex: 1; font-size: .74rem; color: var(--muted); font-family: var(--mono); }
@@ -53,6 +53,15 @@ export async function adoptContractTask(agentName, { organismId, ws, contract })
53
53
  });
54
54
  }
55
55
 
56
+ /** The agent's contract engagements across every organism/workspace (active + retired), enriched
57
+ * with organism + workspace display names. Powers the agent-detail Contracts tab. Returns array. */
58
+ export async function getAgentEngagements(agentName) {
59
+ try {
60
+ const data = await apiGet(`/v1/agents/${encodeURIComponent(agentName)}/engagements`);
61
+ return Array.isArray(data?.data?.engagements) ? data.data.engagements : [];
62
+ } catch { return []; }
63
+ }
64
+
56
65
  /** List chat session agents (name starts with 'session-'). */
57
66
  export async function listChatSessions(owner) {
58
67
  const agents = await listAgents(owner);
@@ -6,6 +6,9 @@
6
6
  * resolve gate approvals.
7
7
  * @usage import * as orgService from '/js/services/organisms.js';
8
8
  * @version-history
9
+ * v1.6.0 — 2026-07-02 — Workspace generator prompt + agent connect prompt teach rich document
10
+ * markdown: mermaid diagrams and aimeat-memory LIVE data blocks (embed a memory key; fresh on
11
+ * every open) so AI-authored documentspaces/documents use live embeds instead of static tables.
9
12
  * v1.5.0 — 2026-06-24 — uploadFile(orgId, file): generic blob upload (any document/image) to an
10
13
  * organism's private storage, returning {key,url,mime} — used by the Secretary doc/image intake.
11
14
  * v1.4.0 — 2026-06-23 — Optional color tags: getAllColors()/saveColors() read/write the per-item
@@ -276,7 +279,7 @@ const GENERATOR_PROMPT = `You are the AIMEAT Workspace Architect. Design a works
276
279
 
277
280
  A workspace is a set of object types. Each type is one of two modes:
278
281
  - "records" — structured items with consistent fields, validated by a schema. Use this for trackable things: milestones, tasks, risks, contacts, decisions.
279
- - "document" — free-form markdown that grows organically, with no schema. Use this whenever the user asks for documents, a wiki, notes, guides, design docs, or an open / free-form format.
282
+ - "document" — free-form markdown that grows organically, with no schema. Use this whenever the user asks for documents, a wiki, notes, guides, design docs, or an open / free-form format. Documents render rich markdown: \`\`\`mermaid fenced blocks become diagrams, and \`\`\`aimeat-memory fenced blocks become LIVE data (the block names a memory key; the document shows that key's current value — array of objects renders as a table — fresh on every open, so agent-maintained data belongs in a memory key + embed, not in pasted static tables). Mention these in a document space's "description" when the space is meant for dashboards or agent-fed reports.
280
283
 
281
284
  Respond with this shape:
282
285
  {
@@ -620,6 +623,12 @@ export async function saveWorkspaceReadme(orgId, wsId, readme) {
620
623
  return apiPut(`/v1/organisms/${encodeURIComponent(orgId)}/workspace?ws=${encodeURIComponent(wsId)}`, { ws: wsId, readme });
621
624
  }
622
625
 
626
+ /** Replace the workspace's pinned-apps list (FULL replace; [] clears). Each entry references a
627
+ * published app ({ owner, filename, label? }). Creator/admin only (enforced server-side). */
628
+ export async function saveWorkspaceApps(orgId, wsId, apps) {
629
+ return apiPut(`/v1/organisms/${encodeURIComponent(orgId)}/workspace?ws=${encodeURIComponent(wsId)}`, { ws: wsId, apps });
630
+ }
631
+
623
632
  /** Overwrite a workspace's manifest (e.g. edited name/summary/policy from Settings). */
624
633
  export async function saveManifest(orgId, wsId, manifest) {
625
634
  return apiPost('/v1/memory', { key: `${wsRoot(orgId, wsId)}.meta.manifest`, value: manifest, visibility: 'private' });
@@ -709,6 +718,29 @@ export async function revokeWorkspaceRole(orgId, ws, grantee) {
709
718
  return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/workspace-access/revoke`, { ws, grantee });
710
719
  }
711
720
 
721
+ /* ── Contract engagements — the first-class (agent × contract × workspace) binding with an
722
+ * active/retired lifecycle. Adopt writes `active`; Retire flips to `retired` (kept as history).
723
+ * Distinct from the derived "active here" trace: the engagement is the deactivatable INTENT. ── */
724
+
725
+ /** List the contract engagements declared in a workspace (active + retired). Returns array. */
726
+ export async function getWorkspaceEngagements(orgId, ws) {
727
+ try {
728
+ const resp = await apiGet(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/engagements?ws=${encodeURIComponent(ws)}`);
729
+ return Array.isArray(resp?.data?.engagements) ? resp.data.engagements : [];
730
+ } catch { return []; }
731
+ }
732
+
733
+ /** Activate (adopt) a contract engagement for one of your agents. `agent` = full GAII. */
734
+ export async function activateEngagement(orgId, ws, agent, contract) {
735
+ return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/engagements`, { ws, agent, contract });
736
+ }
737
+
738
+ /** Retire a contract engagement — the agent's loop then skips this workspace (real off-switch).
739
+ * Allowed for the agent's owner or the workspace creator. `agent` = full GAII. */
740
+ export async function retireEngagement(orgId, ws, agent, contract) {
741
+ return apiPost(`/v1/organisms/${encodeURIComponent(orgId)}/workspace/engagements/retire`, { ws, agent, contract });
742
+ }
743
+
712
744
  /* ── Public document-space sharing (meta.share) ──
713
745
  * Independent of the access roles above: this controls what PUBLISHED document-space pages are
714
746
  * readable by ANYONE with the public viewer link (no login). Creator/admin manages it. ── */
@@ -902,6 +934,10 @@ export async function buildAccessPrompt(orgId, orgName, wsId, ws, variant = 'hum
902
934
  `- Write/refresh a draft: aimeat_memory_write key="organism.${orgId}.w.${wsId}.{namespace}.{id}.draft" value={...}`,
903
935
  `- Attach a file/screenshot: aimeat_storage_upload key="organism.${orgId}.w.${wsId}.img.{name}"`,
904
936
  ` then embed it in a document's markdown as ![alt](/v1/storage/<returned key>)`,
937
+ `- Live data in documents: a \`\`\`aimeat-memory fenced block (body: key: <memory key>, optional view: table|props|list,`,
938
+ ` fields: a,b and title: ...) renders that key's CURRENT value on every open — write changing data with`,
939
+ ` aimeat_memory_write as an array of objects (consistent field names) and embed the key instead of a static table;`,
940
+ ` re-writing the SAME key updates every document that embeds it. \`\`\`mermaid blocks render as diagrams.`,
905
941
  `- Publish a draft: POST ${nodeUrl}/v1/organisms/${orgId}/publish body { "ws":"${wsId}", "namespace":"...", "id":"..." }`,
906
942
  ` (snapshots .version.N + .latest, consumes the draft; may require operator approval if the publish gate is on)`,
907
943
  ].join('\n');
@@ -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