aimeat 1.25.0 → 1.26.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 (252) hide show
  1. package/README.md +8 -1
  2. package/dist/locales/en.json +144 -0
  3. package/dist/locales/fi.json +144 -0
  4. package/dist/public/components/ImageDeliverable.js +2 -1
  5. package/dist/public/components/Modal.js +3 -2
  6. package/dist/public/components/NotificationBell.js +2 -1
  7. package/dist/public/components/PresenceDot.js +53 -0
  8. package/dist/public/css/components/markdown.css +67 -0
  9. package/dist/public/css/theme.css +30 -0
  10. package/dist/public/css/views/notebook.css +195 -0
  11. package/dist/public/css/views/profile.css +15 -0
  12. package/dist/public/js/api.js +5 -3
  13. package/dist/public/js/components/auth-image.js +3 -2
  14. package/dist/public/js/components/tag-cloud.js +1 -1
  15. package/dist/public/js/components/tag-editor.js +1 -1
  16. package/dist/public/js/hooks.js +1 -2
  17. package/dist/public/js/presence-store.js +97 -0
  18. package/dist/public/js/services/admin.js +14 -5
  19. package/dist/public/js/services/boards.js +1 -1
  20. package/dist/public/js/services/ecosystem.js +1 -1
  21. package/dist/public/js/services/foundry-prompts-base.js +2 -2
  22. package/dist/public/js/services/foundry-smoke.js +2 -1
  23. package/dist/public/js/services/foundry-validate.js +6 -8
  24. package/dist/public/js/services/foundry.js +4 -3
  25. package/dist/public/js/services/generator-prompts-base.js +2 -2
  26. package/dist/public/js/services/generator-smoke.js +2 -1
  27. package/dist/public/js/services/generator-validate.js +5 -7
  28. package/dist/public/js/services/generator.js +2 -1
  29. package/dist/public/js/services/memory.js +13 -0
  30. package/dist/public/js/services/messages.js +2 -1
  31. package/dist/public/js/services/notebook.js +124 -0
  32. package/dist/public/js/services/packages.js +3 -2
  33. package/dist/public/js/services/presence.js +35 -0
  34. package/dist/public/js/style-testbed.js +6 -5
  35. package/dist/public/lib/minidenticons.min.js +1 -0
  36. package/dist/public/lib/yaml.mjs +1 -0
  37. package/dist/public/spa.html +90 -8
  38. package/dist/public/types/aimeat-globals.d.ts +52 -0
  39. package/dist/public/types/vendored-libs.d.ts +56 -0
  40. package/dist/public/views/_template.js +3 -1
  41. package/dist/public/views/admin/agent-integration-tab.js +4 -3
  42. package/dist/public/views/admin/agents-tab.js +3 -2
  43. package/dist/public/views/admin/config-tab.js +4 -3
  44. package/dist/public/views/admin/economy-tab.js +4 -3
  45. package/dist/public/views/admin/federation-tab.js +138 -1
  46. package/dist/public/views/admin/maintenance-tab.js +1 -1
  47. package/dist/public/views/admin/messages-tab.js +115 -0
  48. package/dist/public/views/admin/overview-tab.js +4 -3
  49. package/dist/public/views/admin/portal-tab.js +90 -2
  50. package/dist/public/views/admin/push-tab.js +8 -6
  51. package/dist/public/views/admin/security-tab.js +7 -7
  52. package/dist/public/views/admin.js +29 -9
  53. package/dist/public/views/doc-solo.js +3 -1
  54. package/dist/public/views/guides.js +8 -4
  55. package/dist/public/views/hobbies.js +4 -2
  56. package/dist/public/views/marketplace.js +10 -9
  57. package/dist/public/views/openclaw.js +1 -1
  58. package/dist/public/views/portal-dev.js +5 -2
  59. package/dist/public/views/portal.js +3 -2
  60. package/dist/public/views/profile/agents/shared-board.js +4 -2
  61. package/dist/public/views/profile/agents/tab-messages.js +13 -11
  62. package/dist/public/views/profile/agents-messages-subtab.js +2 -1
  63. package/dist/public/views/profile/apps-tab.js +2 -1
  64. package/dist/public/views/profile/boards-tab.js +3 -2
  65. package/dist/public/views/profile/calibrator-batch.js +3 -2
  66. package/dist/public/views/profile/data-wallet-tab.js +2 -1
  67. package/dist/public/views/profile/extensions-tab.js +4 -3
  68. package/dist/public/views/profile/federation-tab.js +4 -0
  69. package/dist/public/views/profile/foundry-dashboard/use-autopilot.js +3 -2
  70. package/dist/public/views/profile/foundry-detail.js +1 -1
  71. package/dist/public/views/profile/generator-detail.js +1 -1
  72. package/dist/public/views/profile/inbox-tab.js +7 -3
  73. package/dist/public/views/profile/inline-panels.js +3 -1
  74. package/dist/public/views/profile/knowledge-tab.js +2 -1
  75. package/dist/public/views/profile/landing-page.js +88 -0
  76. package/dist/public/views/profile/memory-tab.js +6 -5
  77. package/dist/public/views/profile/notebook-tab.js +500 -0
  78. package/dist/public/views/profile/organisms/activity-panel.js +135 -0
  79. package/dist/public/views/profile/organisms/agents.js +141 -0
  80. package/dist/public/views/profile/organisms/document.js +274 -0
  81. package/dist/public/views/profile/organisms/helpers.js +61 -0
  82. package/dist/public/views/profile/organisms/home.js +315 -0
  83. package/dist/public/views/profile/organisms/members.js +220 -0
  84. package/dist/public/views/profile/organisms/panels.js +173 -0
  85. package/dist/public/views/profile/organisms/participants-panel.js +160 -0
  86. package/dist/public/views/profile/organisms/schema-form.js +127 -0
  87. package/dist/public/views/profile/organisms/sources-panel.js +164 -0
  88. package/dist/public/views/profile/organisms/widgets.js +58 -0
  89. package/dist/public/views/profile/organisms/workspace-comments.js +99 -0
  90. package/dist/public/views/profile/organisms/workspace-list.js +268 -0
  91. package/dist/public/views/profile/organisms/workspace.js +1272 -0
  92. package/dist/public/views/profile/organisms-tab.js +456 -3819
  93. package/dist/public/views/profile/shared.js +64 -2
  94. package/dist/public/views/profile/workflows-form.js +1 -1
  95. package/dist/public/views/profile.js +16 -14
  96. package/dist/public/views/public-knowledge-viewer.js +3 -2
  97. package/dist/public/views/public-workspace-viewer.js +2 -1
  98. package/dist/scripts/check-importmap.d.ts +2 -0
  99. package/dist/scripts/check-importmap.d.ts.map +1 -0
  100. package/dist/scripts/check-importmap.js +102 -0
  101. package/dist/scripts/check-importmap.js.map +1 -0
  102. package/dist/src/cli/scaffold.d.ts +26 -3
  103. package/dist/src/cli/scaffold.d.ts.map +1 -1
  104. package/dist/src/cli/scaffold.js +33 -3
  105. package/dist/src/cli/scaffold.js.map +1 -1
  106. package/dist/src/config.d.ts +10 -0
  107. package/dist/src/config.d.ts.map +1 -1
  108. package/dist/src/config.js +5 -0
  109. package/dist/src/config.js.map +1 -1
  110. package/dist/src/generated/api-types.d.ts +927 -11
  111. package/dist/src/generated/api-types.d.ts.map +1 -1
  112. package/dist/src/generated/prisma-postgres/edge.js +32 -6
  113. package/dist/src/generated/prisma-postgres/index-browser.js +29 -3
  114. package/dist/src/generated/prisma-postgres/index.d.ts +1970 -160
  115. package/dist/src/generated/prisma-postgres/index.js +32 -6
  116. package/dist/src/generated/prisma-postgres/package.json +1 -1
  117. package/dist/src/generated/prisma-postgres/schema.prisma +45 -12
  118. package/dist/src/generated/prisma-postgres/wasm.js +32 -6
  119. package/dist/src/index.js +36 -4
  120. package/dist/src/index.js.map +1 -1
  121. package/dist/src/routes/admin-monitoring.d.ts.map +1 -1
  122. package/dist/src/routes/admin-monitoring.js +8 -0
  123. package/dist/src/routes/admin-monitoring.js.map +1 -1
  124. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  125. package/dist/src/routes/federation-peer.js +327 -43
  126. package/dist/src/routes/federation-peer.js.map +1 -1
  127. package/dist/src/routes/federation-sync.d.ts +2 -0
  128. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  129. package/dist/src/routes/federation-sync.js +20 -1
  130. package/dist/src/routes/federation-sync.js.map +1 -1
  131. package/dist/src/routes/librarian.d.ts +18 -0
  132. package/dist/src/routes/librarian.d.ts.map +1 -0
  133. package/dist/src/routes/librarian.js +88 -0
  134. package/dist/src/routes/librarian.js.map +1 -0
  135. package/dist/src/routes/messages.d.ts.map +1 -1
  136. package/dist/src/routes/messages.js +2 -1
  137. package/dist/src/routes/messages.js.map +1 -1
  138. package/dist/src/routes/portal.d.ts.map +1 -1
  139. package/dist/src/routes/portal.js +9 -1
  140. package/dist/src/routes/portal.js.map +1 -1
  141. package/dist/src/routes/presence.d.ts +23 -0
  142. package/dist/src/routes/presence.d.ts.map +1 -0
  143. package/dist/src/routes/presence.js +88 -0
  144. package/dist/src/routes/presence.js.map +1 -0
  145. package/dist/src/routes/site.d.ts +12 -0
  146. package/dist/src/routes/site.d.ts.map +1 -1
  147. package/dist/src/routes/site.js +45 -1
  148. package/dist/src/routes/site.js.map +1 -1
  149. package/dist/src/routes/sse.d.ts +2 -0
  150. package/dist/src/routes/sse.d.ts.map +1 -1
  151. package/dist/src/routes/sse.js +9 -0
  152. package/dist/src/routes/sse.js.map +1 -1
  153. package/dist/src/routes/wellknown.d.ts.map +1 -1
  154. package/dist/src/routes/wellknown.js +5 -0
  155. package/dist/src/routes/wellknown.js.map +1 -1
  156. package/dist/src/routes/work.d.ts.map +1 -1
  157. package/dist/src/routes/work.js +6 -0
  158. package/dist/src/routes/work.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 +13 -0
  161. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  162. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  163. package/dist/src/server-bootstrap/service-init.js +34 -0
  164. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  165. package/dist/src/services/config-schema.d.ts.map +1 -1
  166. package/dist/src/services/config-schema.js +2 -0
  167. package/dist/src/services/config-schema.js.map +1 -1
  168. package/dist/src/services/federation-availability.d.ts +28 -0
  169. package/dist/src/services/federation-availability.d.ts.map +1 -0
  170. package/dist/src/services/federation-availability.js +58 -0
  171. package/dist/src/services/federation-availability.js.map +1 -0
  172. package/dist/src/services/federation-book.d.ts +70 -0
  173. package/dist/src/services/federation-book.d.ts.map +1 -0
  174. package/dist/src/services/federation-book.js +198 -0
  175. package/dist/src/services/federation-book.js.map +1 -0
  176. package/dist/src/services/federation-tiers.d.ts +45 -0
  177. package/dist/src/services/federation-tiers.d.ts.map +1 -0
  178. package/dist/src/services/federation-tiers.js +58 -0
  179. package/dist/src/services/federation-tiers.js.map +1 -0
  180. package/dist/src/services/federation.d.ts +15 -0
  181. package/dist/src/services/federation.d.ts.map +1 -1
  182. package/dist/src/services/federation.js +12 -0
  183. package/dist/src/services/federation.js.map +1 -1
  184. package/dist/src/services/librarian.d.ts +62 -0
  185. package/dist/src/services/librarian.d.ts.map +1 -0
  186. package/dist/src/services/librarian.js +135 -0
  187. package/dist/src/services/librarian.js.map +1 -0
  188. package/dist/src/services/message-delivery.d.ts +3 -1
  189. package/dist/src/services/message-delivery.d.ts.map +1 -1
  190. package/dist/src/services/message-delivery.js +23 -2
  191. package/dist/src/services/message-delivery.js.map +1 -1
  192. package/dist/src/services/network-policy.d.ts +88 -0
  193. package/dist/src/services/network-policy.d.ts.map +1 -0
  194. package/dist/src/services/network-policy.js +154 -0
  195. package/dist/src/services/network-policy.js.map +1 -0
  196. package/dist/src/services/notebook-classify-prompt.d.ts +15 -0
  197. package/dist/src/services/notebook-classify-prompt.d.ts.map +1 -0
  198. package/dist/src/services/notebook-classify-prompt.js +39 -0
  199. package/dist/src/services/notebook-classify-prompt.js.map +1 -0
  200. package/dist/src/services/notebook-classify.d.ts +80 -0
  201. package/dist/src/services/notebook-classify.d.ts.map +1 -0
  202. package/dist/src/services/notebook-classify.js +150 -0
  203. package/dist/src/services/notebook-classify.js.map +1 -0
  204. package/dist/src/services/presence.d.ts +108 -0
  205. package/dist/src/services/presence.d.ts.map +1 -0
  206. package/dist/src/services/presence.js +304 -0
  207. package/dist/src/services/presence.js.map +1 -0
  208. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  209. package/dist/src/services/prompt-defaults.js +13 -0
  210. package/dist/src/services/prompt-defaults.js.map +1 -1
  211. package/dist/src/services/site.d.ts +20 -0
  212. package/dist/src/services/site.d.ts.map +1 -1
  213. package/dist/src/services/site.js +70 -0
  214. package/dist/src/services/site.js.map +1 -1
  215. package/dist/src/storage/interface.d.ts +48 -0
  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.d.ts +11 -1
  219. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  220. package/dist/src/storage/providers/mongodb/index.js +128 -2
  221. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  222. package/dist/src/storage/providers/sqlite/index.d.ts +7 -1
  223. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  224. package/dist/src/storage/providers/sqlite/index.js +27 -2
  225. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  226. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts +5 -1
  227. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts.map +1 -1
  228. package/dist/src/storage/providers/sqlite/repos/direct-message.js +48 -0
  229. package/dist/src/storage/providers/sqlite/repos/direct-message.js.map +1 -1
  230. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +2 -0
  231. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  232. package/dist/src/storage/providers/sqlite/repos/memory.js +55 -0
  233. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  234. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  235. package/dist/src/storage/providers/sqlite/schema.js +61 -0
  236. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  237. package/dist/src/storage/repositories/direct-message.repository.d.ts +5 -1
  238. package/dist/src/storage/repositories/direct-message.repository.d.ts.map +1 -1
  239. package/dist/src/storage/repositories/memory.repository.d.ts +25 -0
  240. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  241. package/dist/src/utils/node-descriptor.d.ts +34 -0
  242. package/dist/src/utils/node-descriptor.d.ts.map +1 -0
  243. package/dist/src/utils/node-descriptor.js +42 -0
  244. package/dist/src/utils/node-descriptor.js.map +1 -0
  245. package/dist/src/utils/version.d.ts +3 -0
  246. package/dist/src/utils/version.d.ts.map +1 -0
  247. package/dist/src/utils/version.js +40 -0
  248. package/dist/src/utils/version.js.map +1 -0
  249. package/dist/static/app-catalog.html +643 -4
  250. package/package.json +5 -2
  251. package/prisma/schema.postgres.prisma +51 -18
  252. package/prisma/schema.prisma +29 -0
@@ -28,6 +28,19 @@ export async function searchMemory(query, agentGaii) {
28
28
  return Array.isArray(list) ? list : [];
29
29
  }
30
30
 
31
+ /**
32
+ * Librarian full-text search across all of the caller's content (GET /v1/librarian/search).
33
+ * Returns the ranked hits array (each: { key, title, snippet, score, organismId, workspaceId, ... }).
34
+ */
35
+ export async function librarianSearch(query, limit, scope) {
36
+ let url = `/v1/librarian/search?q=${encodeURIComponent(query)}`;
37
+ if (limit) url += `&limit=${encodeURIComponent(limit)}`;
38
+ if (scope) url += `&scope=${encodeURIComponent(scope)}`;
39
+ const data = await apiGet(url);
40
+ const hits = data?.data?.hits || [];
41
+ return Array.isArray(hits) ? hits : [];
42
+ }
43
+
31
44
  /**
32
45
  * Create a new memory entry.
33
46
  * @param {string} [agentGaii] Owner-session only — store the entry under one of
@@ -9,13 +9,14 @@
9
9
  * @usage import * as messages from '/js/services/messages.js';
10
10
  * @version-history
11
11
  * v1.0.0 -- 2026-06-16 -- Initial creation for user-to-user messaging (layer 5: Inbox tab).
12
+ * v1.0.1 -- 2026-06-19 -- JSDoc type annotations for frontend type-checking
12
13
  */
13
14
  import { api, apiGet } from '/js/api.js';
14
15
 
15
16
  const enc = encodeURIComponent;
16
17
 
17
18
  /** Send a direct message. `attachments` is an array of descriptors (storage_key, mime, size, kind, inline, id). */
18
- export async function send({ to, body, attachments, replyTo }) {
19
+ export async function send({ to, body, attachments, replyTo } = /** @type {{ to?: any, body?: any, attachments?: any, replyTo?: any }} */ ({})) {
19
20
  return api('/v1/messages', {
20
21
  method: 'POST',
21
22
  body: JSON.stringify({ to, body, attachments, reply_to: replyTo }),
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @file notebook.js
3
+ * @description Notebook slice B client service. classifyNote() asks the backend AI classifier where a
4
+ * note belongs; materializeDocument() then ORCHESTRATES the placement over the existing generic
5
+ * memory/organism APIs (no-SSR — the client composes create-organism / create-workspace /
6
+ * write-document; there is no per-feature backend write route). Supports an existing target, a new
7
+ * workspace in an existing organism, or a brand-new organism+workspace, always materializing the
8
+ * note as a workspace DOCUMENT.
9
+ * @structure
10
+ * - classifyNote(text) — POST /v1/librarian/classify
11
+ * - materializeDocument(plan) — resolve/create org → ws → document space → write doc → drop source
12
+ * @version-history
13
+ * v1.0.0 — 2026-06-19 — Initial: classify + materialize-document orchestration (slice B).
14
+ */
15
+ import { api, apiPost } from '/js/api.js';
16
+ import { createMemory, getMemory, deleteMemory } from '/js/services/memory.js';
17
+ import { createOrganism, saveManifest, listWorkspaces, saveWorkspaceRegistry, wsRoot } from '/js/services/organisms.js';
18
+
19
+ const DOC_SPACE = 'pages';
20
+
21
+ function rid(prefix) {
22
+ const r = (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10);
23
+ return prefix + r;
24
+ }
25
+
26
+ /** Ask the backend AI classifier where a note belongs. Returns the ClassifyResult envelope's data.
27
+ * This is a slow AI call — give it the full AI timeout (30 min) and DO NOT retry, otherwise the
28
+ * default 30s client timeout aborts a model that is still thinking and re-fires the request. */
29
+ export async function classifyNote(text) {
30
+ const resp = await api('/v1/librarian/classify', {
31
+ method: 'POST',
32
+ body: JSON.stringify({ text }),
33
+ timeoutMs: 1_800_000,
34
+ retries: 0,
35
+ });
36
+ if (resp?.ok === false) throw new Error(resp.error?.message || 'Classify failed');
37
+ return resp?.data || null;
38
+ }
39
+
40
+ /** A document-space objectType for a memory-backed workspace. */
41
+ function docSpaceObjectType() {
42
+ return { name: 'Pages', namespace: DOC_SPACE, schemaRef: 'schema:document@1', mode: 'document', backing: 'memory', writeRole: 'member', cardinality: 'many', versioned: true };
43
+ }
44
+
45
+ function newWorkspaceManifest(orgId, name) {
46
+ return { manifestVersion: '1.0', id: orgId, name: name || 'Notebook', kind: 'project', status: 'active', objectTypes: [docSpaceObjectType()] };
47
+ }
48
+
49
+ /** Read a workspace manifest (raw value), or null if it has none / is unreadable. */
50
+ async function readManifest(orgId, wsId) {
51
+ try {
52
+ const r = await getMemory(`${wsRoot(orgId, wsId)}.meta.manifest`);
53
+ return r?.data?.value || null;
54
+ } catch { return null; }
55
+ }
56
+
57
+ /**
58
+ * Materialize a note into a workspace document.
59
+ * @param {object} plan
60
+ * @param {string|null} plan.organismId Existing organism id, or null to create one.
61
+ * @param {string} [plan.organismName] Name for a new organism (when organismId is null).
62
+ * @param {string|null} plan.workspaceId Existing workspace id, or null to create one.
63
+ * @param {string} [plan.workspaceName] Name for a new workspace (when workspaceId is null).
64
+ * @param {string|null} plan.space Existing document-space namespace, or null to use/create the default.
65
+ * @param {string} plan.title
66
+ * @param {string} plan.markdown
67
+ * @param {string} [plan.sourceKey] Inbox note key to delete once filed.
68
+ * @returns {Promise<{organismId:string, workspaceId:string, space:string, docId:string}>}
69
+ */
70
+ export async function materializeDocument(plan) {
71
+ let { organismId, workspaceId, space } = plan;
72
+
73
+ // 1. Organism — create if needed.
74
+ if (!organismId) {
75
+ const resp = await createOrganism({
76
+ name: (plan.organismName || 'Notebook').trim(),
77
+ description: 'Created from the notebook',
78
+ type: 'project',
79
+ join_policy: 'approval_required',
80
+ visibility: 'private',
81
+ });
82
+ if (resp?.ok === false) throw new Error(resp.error?.message || 'Could not create organism');
83
+ organismId = resp?.data?.organism?.id;
84
+ if (!organismId) throw new Error('Organism creation returned no id');
85
+ }
86
+
87
+ // 2. Workspace — create if needed (registry + manifest with a document space).
88
+ if (!workspaceId) {
89
+ workspaceId = rid('ws-');
90
+ const manifest = newWorkspaceManifest(organismId, plan.workspaceName);
91
+ await saveManifest(organismId, workspaceId, manifest);
92
+ await apiPost('/v1/memory', { key: `${wsRoot(organismId, workspaceId)}.meta.readme`, value: `# ${manifest.name}\n\nCreated from the notebook.`, visibility: 'private' });
93
+ const existing = await listWorkspaces(organismId);
94
+ await saveWorkspaceRegistry(organismId, [...existing, { id: workspaceId, name: manifest.name, createdAt: new Date().toISOString() }]);
95
+ space = DOC_SPACE;
96
+ }
97
+
98
+ // 3. Document space — ensure one exists on the chosen workspace.
99
+ if (!space) {
100
+ const manifest = await readManifest(organismId, workspaceId);
101
+ const existingDoc = (manifest?.objectTypes || []).find(ot => (ot.mode === 'document') || (!ot.mode && ot.kind === 'document'));
102
+ if (existingDoc?.namespace) {
103
+ space = existingDoc.namespace;
104
+ } else if (manifest) {
105
+ // Best-effort: add a default document space to the manifest so the doc renders.
106
+ const updated = { ...manifest, objectTypes: [...(manifest.objectTypes || []), docSpaceObjectType()] };
107
+ await saveManifest(organismId, workspaceId, updated).catch(() => {});
108
+ space = DOC_SPACE;
109
+ } else {
110
+ space = DOC_SPACE;
111
+ }
112
+ }
113
+
114
+ // 4. Write the document.
115
+ const docId = rid('doc-');
116
+ const key = `${wsRoot(organismId, workspaceId)}.${space}.${docId}.latest`;
117
+ const resp = await createMemory(key, { id: docId, title: plan.title, markdown: plan.markdown }, 'private');
118
+ if (resp?.ok === false) throw new Error(resp.error?.message || 'Could not write document');
119
+
120
+ // 5. Drop the source inbox note (it has been filed).
121
+ if (plan.sourceKey) await deleteMemory(plan.sourceKey).catch(() => {});
122
+
123
+ return { organismId, workspaceId, space, docId };
124
+ }
@@ -16,6 +16,7 @@
16
16
  * v1.0.0 — 2026-03-15 — initial implementation (Phase 6)
17
17
  * v1.1.0 — 2026-03-20 — add exportPackageZip, importPackageZip, proposeAsTemplate
18
18
  * v1.2.0 — 2026-03-20 — add syncFederationTemplates, listFederationTemplates
19
+ * v1.2.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
19
20
  */
20
21
  import { apiGet, apiPost, apiPatch, apiDelete } from '/js/api.js';
21
22
 
@@ -54,7 +55,7 @@ export const toggleFeatured = (id, featured) => apiPatch(`/v1/templates/${enc(id
54
55
  // ── ZIP Import/Export ──
55
56
  export async function exportPackageZip(groupId) {
56
57
  const session = window.AIMEAT?.auth?.getSession?.();
57
- const headers = {};
58
+ const headers = /** @type {Record<string,string>} */ ({});
58
59
  if (session?.jwt) headers['Authorization'] = `Bearer ${session.jwt}`;
59
60
  const res = await fetch(`/v1/packages/${enc(groupId)}/export`, { headers });
60
61
  if (!res.ok) throw new Error(`Export failed: ${res.status}`);
@@ -65,7 +66,7 @@ export async function importPackageZip(file) {
65
66
  const session = window.AIMEAT?.auth?.getSession?.();
66
67
  const formData = new FormData();
67
68
  formData.append('file', file);
68
- const headers = {};
69
+ const headers = /** @type {Record<string,string>} */ ({});
69
70
  if (session?.jwt) headers['Authorization'] = `Bearer ${session.jwt}`;
70
71
  const res = await fetch('/v1/packages/import', {
71
72
  method: 'POST',
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @file presence.js
3
+ * @description AIMEAT Presence Service — thin API layer over /v1/presence/*. Read
4
+ * your own config + status, update it, and read viewer-scoped status for one or
5
+ * many identities. List/dot rendering should go through /js/presence-store.js
6
+ * (cache + batch coalescing) rather than calling getPresence per row.
7
+ * @structure getMyPresence / setMyPresence / getPresence / getPresenceBatch
8
+ * @usage import { getPresenceBatch } from '/js/services/presence.js';
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-19 — Initial presence service.
11
+ */
12
+ import { apiGet, apiPut } from '/js/api.js';
13
+
14
+ /** Read the caller's own presence config + computed status. Returns the envelope. */
15
+ export async function getMyPresence() {
16
+ return apiGet('/v1/presence/me');
17
+ }
18
+
19
+ /** Update the caller's presence config (partial: { mode, status, visibility }). */
20
+ export async function setMyPresence(config) {
21
+ return apiPut('/v1/presence/me', config);
22
+ }
23
+
24
+ /** Viewer-scoped status of a single identity. Returns the envelope ({ data: { ghii, status, since } }). */
25
+ export async function getPresence(ghii) {
26
+ return apiGet(`/v1/presence/${encodeURIComponent(ghii)}`);
27
+ }
28
+
29
+ /** Batch viewer-scoped status. Returns a map of ghii → { status, since }. */
30
+ export async function getPresenceBatch(ghiis) {
31
+ const ids = (ghiis || []).map(encodeURIComponent).join(',');
32
+ if (!ids) return {};
33
+ const r = await apiGet(`/v1/presence?ids=${ids}`);
34
+ return r?.data?.presence || {};
35
+ }
@@ -7,6 +7,7 @@
7
7
  * (no inline handlers) to satisfy the static-asset CSP (script-src 'self').
8
8
  * @version-history
9
9
  * v1.0.0 — 2026-06-02 — Initial testbed behavior
10
+ * v1.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
10
11
  */
11
12
  (function () {
12
13
  'use strict';
@@ -95,7 +96,7 @@
95
96
  const sel = variant.querySelector('.stb-decide');
96
97
  if (!sel) return; // canonical reference — no decision
97
98
  total += 1;
98
- const val = sel.value;
99
+ const val = /** @type {HTMLInputElement} */ (sel).value;
99
100
  if (val !== '') decided += 1;
100
101
  sel.classList.toggle('stb-chosen', val !== '');
101
102
  const clone = variant.getAttribute('data-clone') || '';
@@ -104,7 +105,7 @@
104
105
  rows.push(' [' + pad(LABEL[val] || val, 18) + '] ' + pad(clone, 46) + ' ' + sitesStr);
105
106
  });
106
107
  const note = fam.querySelector('.stb-note');
107
- const noteVal = note && note.value.trim();
108
+ const noteVal = note && /** @type {HTMLInputElement} */ (note).value.trim();
108
109
 
109
110
  lines.push('## ' + famName);
110
111
  rows.forEach((r) => lines.push(r));
@@ -113,7 +114,7 @@
113
114
  });
114
115
 
115
116
  const out = document.getElementById('stb-output');
116
- if (out) out.value = lines.join('\n');
117
+ if (out) /** @type {HTMLInputElement} */ (out).value = lines.join('\n');
117
118
  const count = document.getElementById('stb-count');
118
119
  if (count) count.textContent = '(' + decided + '/' + total + ' clones decided)';
119
120
  }
@@ -140,9 +141,9 @@
140
141
  setTimeout(() => { btn.classList.remove('stb-copied'); btn.textContent = prev; }, 1500);
141
142
  };
142
143
  if (navigator.clipboard && navigator.clipboard.writeText) {
143
- navigator.clipboard.writeText(out.value).then(done, () => { out.select(); document.execCommand('copy'); done(); });
144
+ navigator.clipboard.writeText(/** @type {HTMLInputElement} */ (out).value).then(done, () => { /** @type {HTMLInputElement} */ (out).select(); document.execCommand('copy'); done(); });
144
145
  } else {
145
- out.select();
146
+ /** @type {HTMLInputElement} */ (out).select();
146
147
  document.execCommand('copy');
147
148
  done();
148
149
  }
@@ -1 +1,2 @@
1
+ // @ts-nocheck — vendored minified third-party bundle; not type-checked by tsconfig.frontend.json
1
2
  const t=5;function e(e){return e.split("").reduce(((e,n)=>(e^n.charCodeAt(0))*-t),t)>>>2}export function minidenticon(t="",n=95,i=45,s=e){const o=s(t),c=o%9*40;return[...Array(t?25:0)].reduce(((t,e,n)=>o&1<<n%15?t+`<rect x="${n>14?7-~~(n/5):~~(n/5)}" y="${n%5}" width="1" height="1"/>`:t),`<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(${c} ${n}% ${i}%)">`)+"</svg>"}export const minidenticonSvg=globalThis.customElements?.get("minidenticon-svg")?null:globalThis.customElements?.define("minidenticon-svg",class t extends HTMLElement{static observedAttributes=["username","saturation","lightness"];static#t={};#e=!1;connectedCallback(){this.#n(),this.#e=!0}attributeChangedCallback(){this.#e&&this.#n()}#n(){const e=t.observedAttributes.map((t=>this.getAttribute(t)||void 0)),n=e.join(",");this.innerHTML=t.#t[n]??=minidenticon(...e)}});
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck — vendored minified third-party bundle; not type-checked by tsconfig.frontend.json
1
2
  /* esm.sh - yaml@2.8.2 */
2
3
  var un=Object.defineProperty;var ps=(s,e)=>{for(var t in e)un(s,t,{get:e[t],enumerable:!0})};var us={};ps(us,{Alias:()=>ee,CST:()=>ls,Composer:()=>be,Document:()=>se,Lexer:()=>Be,LineCounter:()=>De,Pair:()=>v,Parser:()=>we,Scalar:()=>y,Schema:()=>ve,YAMLError:()=>_e,YAMLMap:()=>$,YAMLParseError:()=>F,YAMLSeq:()=>B,YAMLWarning:()=>Pe,isAlias:()=>x,isCollection:()=>L,isDocument:()=>R,isMap:()=>U,isNode:()=>I,isPair:()=>T,isScalar:()=>E,isSeq:()=>V,parse:()=>cn,parseAllDocuments:()=>ln,parseDocument:()=>fs,stringify:()=>fn,visit:()=>Q,visitAsync:()=>je});var tt=Symbol.for("yaml.alias"),st=Symbol.for("yaml.document"),Y=Symbol.for("yaml.map"),jt=Symbol.for("yaml.pair"),q=Symbol.for("yaml.scalar"),re=Symbol.for("yaml.seq"),K=Symbol.for("yaml.node.type"),x=s=>!!s&&typeof s=="object"&&s[K]===tt,R=s=>!!s&&typeof s=="object"&&s[K]===st,U=s=>!!s&&typeof s=="object"&&s[K]===Y,T=s=>!!s&&typeof s=="object"&&s[K]===jt,E=s=>!!s&&typeof s=="object"&&s[K]===q,V=s=>!!s&&typeof s=="object"&&s[K]===re;function L(s){if(s&&typeof s=="object")switch(s[K]){case Y:case re:return!0}return!1}function I(s){if(s&&typeof s=="object")switch(s[K]){case tt:case Y:case q:case re:return!0}return!1}var nt=s=>(E(s)||L(s))&&!!s.anchor;var j=Symbol("break visit"),ms=Symbol("skip children"),z=Symbol("remove node");function Q(s,e){let t=ds(e);R(s)?ke(null,s.contents,t,Object.freeze([s]))===z&&(s.contents=null):ke(null,s,t,Object.freeze([]))}Q.BREAK=j;Q.SKIP=ms;Q.REMOVE=z;function ke(s,e,t,n){let i=gs(s,e,t,n);if(I(i)||T(i))return ys(s,n,i),ke(s,i,t,n);if(typeof i!="symbol"){if(L(e)){n=Object.freeze(n.concat(e));for(let r=0;r<e.items.length;++r){let o=ke(r,e.items[r],t,n);if(typeof o=="number")r=o-1;else{if(o===j)return j;o===z&&(e.items.splice(r,1),r-=1)}}}else if(T(e)){n=Object.freeze(n.concat(e));let r=ke("key",e.key,t,n);if(r===j)return j;r===z&&(e.key=null);let o=ke("value",e.value,t,n);if(o===j)return j;o===z&&(e.value=null)}}return i}async function je(s,e){let t=ds(e);R(s)?await Ne(null,s.contents,t,Object.freeze([s]))===z&&(s.contents=null):await Ne(null,s,t,Object.freeze([]))}je.BREAK=j;je.SKIP=ms;je.REMOVE=z;async function Ne(s,e,t,n){let i=await gs(s,e,t,n);if(I(i)||T(i))return ys(s,n,i),Ne(s,i,t,n);if(typeof i!="symbol"){if(L(e)){n=Object.freeze(n.concat(e));for(let r=0;r<e.items.length;++r){let o=await Ne(r,e.items[r],t,n);if(typeof o=="number")r=o-1;else{if(o===j)return j;o===z&&(e.items.splice(r,1),r-=1)}}}else if(T(e)){n=Object.freeze(n.concat(e));let r=await Ne("key",e.key,t,n);if(r===j)return j;r===z&&(e.key=null);let o=await Ne("value",e.value,t,n);if(o===j)return j;o===z&&(e.value=null)}}return i}function ds(s){return typeof s=="object"&&(s.Collection||s.Node||s.Value)?Object.assign({Alias:s.Node,Map:s.Node,Scalar:s.Node,Seq:s.Node},s.Value&&{Map:s.Value,Scalar:s.Value,Seq:s.Value},s.Collection&&{Map:s.Collection,Seq:s.Collection},s):s}function gs(s,e,t,n){if(typeof t=="function")return t(s,e,n);if(U(e))return t.Map?.(s,e,n);if(V(e))return t.Seq?.(s,e,n);if(T(e))return t.Pair?.(s,e,n);if(E(e))return t.Scalar?.(s,e,n);if(x(e))return t.Alias?.(s,e,n)}function ys(s,e,t){let n=e[e.length-1];if(L(n))n.items[s]=t;else if(T(n))s==="key"?n.key=t:n.value=t;else if(R(n))n.contents=t;else{let i=x(n)?"alias":"scalar";throw new Error(`Cannot replace node with ${i} parent`)}}var hn={"!":"%21",",":"%2C","[":"%5B","]":"%5D","{":"%7B","}":"%7D"},pn=s=>s.replace(/[!,[\]{}]/g,e=>hn[e]),Z=class s{constructor(e,t){this.docStart=null,this.docEnd=!1,this.yaml=Object.assign({},s.defaultYaml,e),this.tags=Object.assign({},s.defaultTags,t)}clone(){let e=new s(this.yaml,this.tags);return e.docStart=this.docStart,e}atDocument(){let e=new s(this.yaml,this.tags);switch(this.yaml.version){case"1.1":this.atNextDocument=!0;break;case"1.2":this.atNextDocument=!1,this.yaml={explicit:s.defaultYaml.explicit,version:"1.2"},this.tags=Object.assign({},s.defaultTags);break}return e}add(e,t){this.atNextDocument&&(this.yaml={explicit:s.defaultYaml.explicit,version:"1.1"},this.tags=Object.assign({},s.defaultTags),this.atNextDocument=!1);let n=e.trim().split(/[ \t]+/),i=n.shift();switch(i){case"%TAG":{if(n.length!==2&&(t(0,"%TAG directive should contain exactly two parts"),n.length<2))return!1;let[r,o]=n;return this.tags[r]=o,!0}case"%YAML":{if(this.yaml.explicit=!0,n.length!==1)return t(0,"%YAML directive should contain exactly one part"),!1;let[r]=n;if(r==="1.1"||r==="1.2")return this.yaml.version=r,!0;{let o=/^\d+\.\d+$/.test(r);return t(6,`Unsupported YAML version ${r}`,o),!1}}default:return t(0,`Unknown directive ${i}`,!0),!1}}tagName(e,t){if(e==="!")return"!";if(e[0]!=="!")return t(`Not a valid tag: ${e}`),null;if(e[1]==="<"){let o=e.slice(2,-1);return o==="!"||o==="!!"?(t(`Verbatim tags aren't resolved, so ${e} is invalid.`),null):(e[e.length-1]!==">"&&t("Verbatim tags must end with a >"),o)}let[,n,i]=e.match(/^(.*!)([^!]*)$/s);i||t(`The ${e} tag has no suffix`);let r=this.tags[n];if(r)try{return r+decodeURIComponent(i)}catch(o){return t(String(o)),null}return n==="!"?e:(t(`Could not resolve tag: ${e}`),null)}tagString(e){for(let[t,n]of Object.entries(this.tags))if(e.startsWith(n))return t+pn(e.substring(n.length));return e[0]==="!"?e:`!<${e}>`}toString(e){let t=this.yaml.explicit?[`%YAML ${this.yaml.version||"1.2"}`]:[],n=Object.entries(this.tags),i;if(e&&n.length>0&&I(e.contents)){let r={};Q(e.contents,(o,a)=>{I(a)&&a.tag&&(r[a.tag]=!0)}),i=Object.keys(r)}else i=[];for(let[r,o]of n)r==="!!"&&o==="tag:yaml.org,2002:"||(!e||i.some(a=>a.startsWith(o)))&&t.push(`%TAG ${r} ${o}`);return t.join(`
3
4
  `)}};Z.defaultYaml={explicit:!1,version:"1.2"};Z.defaultTags={"!!":"tag:yaml.org,2002:"};function it(s){if(/[\x00-\x19\s,[\]{}]/.test(s)){let t=`Anchor must not contain whitespace or control characters: ${JSON.stringify(s)}`;throw new Error(t)}return!0}function qt(s){let e=new Set;return Q(s,{Value(t,n){n.anchor&&e.add(n.anchor)}}),e}function xt(s,e){for(let t=1;;++t){let n=`${s}${t}`;if(!e.has(n))return n}}function bs(s,e){let t=[],n=new Map,i=null;return{onAnchor:r=>{t.push(r),i??(i=qt(s));let o=xt(e,i);return i.add(o),o},setAnchors:()=>{for(let r of t){let o=n.get(r);if(typeof o=="object"&&o.anchor&&(E(o.node)||L(o.node)))o.node.anchor=o.anchor;else{let a=new Error("Failed to resolve repeated object (this should not happen)");throw a.source=r,a}}},sourceObjects:n}}function fe(s,e,t,n){if(n&&typeof n=="object")if(Array.isArray(n))for(let i=0,r=n.length;i<r;++i){let o=n[i],a=fe(s,n,String(i),o);a===void 0?delete n[i]:a!==o&&(n[i]=a)}else if(n instanceof Map)for(let i of Array.from(n.keys())){let r=n.get(i),o=fe(s,n,i,r);o===void 0?n.delete(i):o!==r&&n.set(i,o)}else if(n instanceof Set)for(let i of Array.from(n)){let r=fe(s,n,i,i);r===void 0?n.delete(i):r!==i&&(n.delete(i),n.add(r))}else for(let[i,r]of Object.entries(n)){let o=fe(s,n,i,r);o===void 0?delete n[i]:o!==r&&(n[i]=o)}return s.call(e,t,n)}function M(s,e,t){if(Array.isArray(s))return s.map((n,i)=>M(n,String(i),t));if(s&&typeof s.toJSON=="function"){if(!t||!nt(s))return s.toJSON(e,t);let n={aliasCount:0,count:1,res:void 0};t.anchors.set(s,n),t.onCreate=r=>{n.res=r,delete t.onCreate};let i=s.toJSON(e,t);return t.onCreate&&t.onCreate(i),i}return typeof s=="bigint"&&!t?.keep?Number(s):s}var ue=class{constructor(e){Object.defineProperty(this,K,{value:e})}clone(){let e=Object.create(Object.getPrototypeOf(this),Object.getOwnPropertyDescriptors(this));return this.range&&(e.range=this.range.slice()),e}toJS(e,{mapAsMap:t,maxAliasCount:n,onAnchor:i,reviver:r}={}){if(!R(e))throw new TypeError("A document argument is required");let o={anchors:new Map,doc:e,keep:!0,mapAsMap:t===!0,mapKeyWarned:!1,maxAliasCount:typeof n=="number"?n:100},a=M(this,"",o);if(typeof i=="function")for(let{count:l,res:c}of o.anchors.values())i(c,l);return typeof r=="function"?fe(r,{"":a},"",a):a}};var ee=class extends ue{constructor(e){super(tt),this.source=e,Object.defineProperty(this,"tag",{set(){throw new Error("Alias nodes cannot have tags")}})}resolve(e,t){let n;t?.aliasResolveCache?n=t.aliasResolveCache:(n=[],Q(e,{Node:(r,o)=>{(x(o)||nt(o))&&n.push(o)}}),t&&(t.aliasResolveCache=n));let i;for(let r of n){if(r===this)break;r.anchor===this.source&&(i=r)}return i}toJSON(e,t){if(!t)return{source:this.source};let{anchors:n,doc:i,maxAliasCount:r}=t,o=this.resolve(i,t);if(!o){let l=`Unresolved alias (the anchor must be set before the alias): ${this.source}`;throw new ReferenceError(l)}let a=n.get(o);if(a||(M(o,null,t),a=n.get(o)),a?.res===void 0){let l="This should not happen: Alias anchor was not resolved?";throw new ReferenceError(l)}if(r>=0&&(a.count+=1,a.aliasCount===0&&(a.aliasCount=rt(i,o,n)),a.count*a.aliasCount>r)){let l="Excessive alias count indicates a resource exhaustion attack";throw new ReferenceError(l)}return a.res}toString(e,t,n){let i=`*${this.source}`;if(e){if(it(this.source),e.options.verifyAliasOrder&&!e.anchors.has(this.source)){let r=`Unresolved alias (the anchor must be set before the alias): ${this.source}`;throw new Error(r)}if(e.implicitKey)return`${i} `}return i}};function rt(s,e,t){if(x(e)){let n=e.resolve(s),i=t&&n&&t.get(n);return i?i.count*i.aliasCount:0}else if(L(e)){let n=0;for(let i of e.items){let r=rt(s,i,t);r>n&&(n=r)}return n}else if(T(e)){let n=rt(s,e.key,t),i=rt(s,e.value,t);return Math.max(n,i)}return 1}var ot=s=>!s||typeof s!="function"&&typeof s!="object",y=class extends ue{constructor(e){super(q),this.value=e}toJSON(e,t){return t?.keep?this.value:M(this.value,e,t)}toString(){return String(this.value)}};y.BLOCK_FOLDED="BLOCK_FOLDED";y.BLOCK_LITERAL="BLOCK_LITERAL";y.PLAIN="PLAIN";y.QUOTE_DOUBLE="QUOTE_DOUBLE";y.QUOTE_SINGLE="QUOTE_SINGLE";var mn="tag:yaml.org,2002:";function dn(s,e,t){if(e){let n=t.filter(r=>r.tag===e),i=n.find(r=>!r.format)??n[0];if(!i)throw new Error(`Tag ${e} not found`);return i}return t.find(n=>n.identify?.(s)&&!n.format)}function oe(s,e,t){if(R(s)&&(s=s.contents),I(s))return s;if(T(s)){let f=t.schema[Y].createNode?.(t.schema,null,t);return f.items.push(s),f}(s instanceof String||s instanceof Number||s instanceof Boolean||typeof BigInt<"u"&&s instanceof BigInt)&&(s=s.valueOf());let{aliasDuplicateObjects:n,onAnchor:i,onTagObj:r,schema:o,sourceObjects:a}=t,l;if(n&&s&&typeof s=="object"){if(l=a.get(s),l)return l.anchor??(l.anchor=i(s)),new ee(l.anchor);l={anchor:null,node:null},a.set(s,l)}e?.startsWith("!!")&&(e=mn+e.slice(2));let c=dn(s,e,o.tags);if(!c){if(s&&typeof s.toJSON=="function"&&(s=s.toJSON()),!s||typeof s!="object"){let f=new y(s);return l&&(l.node=f),f}c=s instanceof Map?o[Y]:Symbol.iterator in Object(s)?o[re]:o[Y]}r&&(r(c),delete t.onTagObj);let p=c?.createNode?c.createNode(t.schema,s,t):typeof c?.nodeClass?.from=="function"?c.nodeClass.from(t.schema,s,t):new y(s);return e?p.tag=e:c.default||(p.tag=c.tag),l&&(l.node=p),p}function qe(s,e,t){let n=t;for(let i=e.length-1;i>=0;--i){let r=e[i];if(typeof r=="number"&&Number.isInteger(r)&&r>=0){let o=[];o[r]=n,n=o}else n=new Map([[r,n]])}return oe(n,void 0,{aliasDuplicateObjects:!1,keepUndefined:!1,onAnchor:()=>{throw new Error("This should not happen, please report a bug.")},schema:s,sourceObjects:new Map})}var Ae=s=>s==null||typeof s=="object"&&!!s[Symbol.iterator]().next().done,Oe=class extends ue{constructor(e,t){super(e),Object.defineProperty(this,"schema",{value:t,configurable:!0,enumerable:!1,writable:!0})}clone(e){let t=Object.create(Object.getPrototypeOf(this),Object.getOwnPropertyDescriptors(this));return e&&(t.schema=e),t.items=t.items.map(n=>I(n)||T(n)?n.clone(e):n),this.range&&(t.range=this.range.slice()),t}addIn(e,t){if(Ae(e))this.add(t);else{let[n,...i]=e,r=this.get(n,!0);if(L(r))r.addIn(i,t);else if(r===void 0&&this.schema)this.set(n,qe(this.schema,i,t));else throw new Error(`Expected YAML collection at ${n}. Remaining path: ${i}`)}}deleteIn(e){let[t,...n]=e;if(n.length===0)return this.delete(t);let i=this.get(t,!0);if(L(i))return i.deleteIn(n);throw new Error(`Expected YAML collection at ${t}. Remaining path: ${n}`)}getIn(e,t){let[n,...i]=e,r=this.get(n,!0);return i.length===0?!t&&E(r)?r.value:r:L(r)?r.getIn(i,t):void 0}hasAllNullValues(e){return this.items.every(t=>{if(!T(t))return!1;let n=t.value;return n==null||e&&E(n)&&n.value==null&&!n.commentBefore&&!n.comment&&!n.tag})}hasIn(e){let[t,...n]=e;if(n.length===0)return this.has(t);let i=this.get(t,!0);return L(i)?i.hasIn(n):!1}setIn(e,t){let[n,...i]=e;if(i.length===0)this.set(n,t);else{let r=this.get(n,!0);if(L(r))r.setIn(i,t);else if(r===void 0&&this.schema)this.set(n,qe(this.schema,i,t));else throw new Error(`Expected YAML collection at ${n}. Remaining path: ${i}`)}}};var ws=s=>s.replace(/^(?!$)(?: $)?/gm,"#");function J(s,e){return/^\n+$/.test(s)?s.substring(1):e?s.replace(/^(?! *$)/gm,e):s}var te=(s,e,t)=>s.endsWith(`
@@ -64,6 +64,9 @@
64
64
  </script>
65
65
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
66
66
  <link rel="stylesheet" href="/css/theme.css">
67
+ <!-- Canonical styles for the shared <Markdown> component — load before views so it styles
68
+ rendered markdown everywhere the component is used (no per-view re-definition). -->
69
+ <link rel="stylesheet" href="/css/components/markdown.css">
67
70
  <!-- View CSS preloaded so they are ready before any view renders (prevents FOUC) -->
68
71
  <!-- hrefs are stamped with ?v=BUILD_ID at serve time by portal.ts serveSpa() -->
69
72
  <link rel="stylesheet" href="/css/views/portal.css">
@@ -71,6 +74,7 @@
71
74
  <link rel="stylesheet" href="/css/views/portal-classic.css">
72
75
  <link rel="stylesheet" href="/css/views/portal-dev.css">
73
76
  <link rel="stylesheet" href="/css/views/profile.css">
77
+ <link rel="stylesheet" href="/css/views/notebook.css">
74
78
  <link rel="stylesheet" href="/css/views/offers.css">
75
79
  <link rel="stylesheet" href="/css/views/foundry.css">
76
80
  <link rel="stylesheet" href="/css/views/calibrator.css">
@@ -157,6 +161,7 @@
157
161
  "/js/services/foundry-probe-reconcile.js": "/js/services/foundry-probe-reconcile.js",
158
162
  "/js/services/knowledge.js": "/js/services/knowledge.js",
159
163
  "/js/services/memory.js": "/js/services/memory.js",
164
+ "/js/services/notebook.js": "/js/services/notebook.js",
160
165
  "/js/services/nodes.js": "/js/services/nodes.js",
161
166
  "/js/services/organisms.js": "/js/services/organisms.js",
162
167
  "/js/services/offers.js": "/js/services/offers.js",
@@ -167,6 +172,8 @@
167
172
  "/js/services/wallet.js": "/js/services/wallet.js",
168
173
  "/js/services/work.js": "/js/services/work.js",
169
174
  "/js/services/messages.js": "/js/services/messages.js",
175
+ "/js/services/presence.js": "/js/services/presence.js",
176
+ "/js/presence-store.js": "/js/presence-store.js",
170
177
  "/js/components/auth-image.js": "/js/components/auth-image.js",
171
178
  "/js/components/tag-cloud.js": "/js/components/tag-cloud.js",
172
179
  "/js/components/tag-editor.js": "/js/components/tag-editor.js",
@@ -184,6 +191,7 @@
184
191
  "/components/ToggleSwitch.js": "/components/ToggleSwitch.js",
185
192
  "/components/Card.js": "/components/Card.js",
186
193
  "/components/StatusDot.js": "/components/StatusDot.js",
194
+ "/components/PresenceDot.js": "/components/PresenceDot.js",
187
195
  "/components/TagList.js": "/components/TagList.js",
188
196
  "/components/Pagination.js": "/components/Pagination.js",
189
197
  "/components/KeyValueRow.js": "/components/KeyValueRow.js",
@@ -208,7 +216,23 @@
208
216
  "/js/services/agent-integration.js": "/js/services/agent-integration.js",
209
217
  "/js/services/admin-agent-integration.js": "/js/services/admin-agent-integration.js",
210
218
  "/js/services/schedules.js": "/js/services/schedules.js",
211
- "/js/services/workflows.js": "/js/services/workflows.js"
219
+ "/js/services/workflows.js": "/js/services/workflows.js",
220
+ "/views/profile/shared.js": "/views/profile/shared.js",
221
+ "/views/profile/generator-settings.js": "/views/profile/generator-settings.js",
222
+ "/views/profile/organisms/helpers.js": "/views/profile/organisms/helpers.js",
223
+ "/views/profile/organisms/widgets.js": "/views/profile/organisms/widgets.js",
224
+ "/views/profile/organisms/panels.js": "/views/profile/organisms/panels.js",
225
+ "/views/profile/organisms/agents.js": "/views/profile/organisms/agents.js",
226
+ "/views/profile/organisms/members.js": "/views/profile/organisms/members.js",
227
+ "/views/profile/organisms/home.js": "/views/profile/organisms/home.js",
228
+ "/views/profile/organisms/workspace-list.js": "/views/profile/organisms/workspace-list.js",
229
+ "/views/profile/organisms/workspace.js": "/views/profile/organisms/workspace.js",
230
+ "/views/profile/organisms/activity-panel.js": "/views/profile/organisms/activity-panel.js",
231
+ "/views/profile/organisms/participants-panel.js": "/views/profile/organisms/participants-panel.js",
232
+ "/views/profile/organisms/workspace-comments.js": "/views/profile/organisms/workspace-comments.js",
233
+ "/views/profile/organisms/sources-panel.js": "/views/profile/organisms/sources-panel.js",
234
+ "/views/profile/organisms/schema-form.js": "/views/profile/organisms/schema-form.js",
235
+ "/views/profile/organisms/document.js": "/views/profile/organisms/document.js"
212
236
  }
213
237
  }
214
238
  </script>
@@ -223,6 +247,28 @@
223
247
  <!-- App root (Preact mounts here) -->
224
248
  <div id="app"></div>
225
249
 
250
+ <!-- SSO handoff: the desktop app already holds an authenticated owner JWT (from its inline
251
+ sign-in). It opens the portal with #handoff=<base64({owner,jwt})> so the browser is logged
252
+ in WITHOUT a second sign-in. Runs synchronously before the app reads the session. -->
253
+ <script>
254
+ (function () {
255
+ try {
256
+ var m = location.hash.match(/[#&]handoff=([^&]+)/);
257
+ if (!m) return;
258
+ var s = JSON.parse(atob(decodeURIComponent(m[1])));
259
+ if (s && s.jwt && s.owner) {
260
+ var node = '';
261
+ try { node = (JSON.parse(atob(s.jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))) || {}).node || ''; } catch (e) {}
262
+ localStorage.setItem('aimeat_session', JSON.stringify({
263
+ owner: s.owner, jwt: s.jwt, ghii: node ? (s.owner + '@' + node) : null, gaii: null,
264
+ }));
265
+ }
266
+ // Strip the token from the URL so it is not bookmarked or left in history.
267
+ history.replaceState(null, '', location.pathname + location.search);
268
+ } catch (e) { /* ignore a malformed handoff */ }
269
+ })();
270
+ </script>
271
+
226
272
  <script type="module">
227
273
  import { h, render, Component } from 'preact';
228
274
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -430,10 +476,24 @@
430
476
  }
431
477
 
432
478
  // ── Header Component ──
479
+ // Built-in public links. Operators can show/hide and reorder these via the admin
480
+ // Portal tab (persisted at /v1/site/header-nav). Auth/role-gated links (Apps, Profile,
481
+ // Admin) are NOT in this list — they stay forced by the session/role checks below.
482
+ const PUBLIC_NAV_LINKS = [
483
+ { id: 'try', path: '/v1/portal', key: 'nav.try' },
484
+ { id: 'howItWorks', path: '/v1/how-it-works', key: 'nav.howItWorks', fallback: 'How it works' },
485
+ { id: 'business', path: '/v1/business', key: 'nav.business', fallback: 'For your business' },
486
+ { id: 'devView', path: '/v1/portal?view=dev', key: 'nav.devView', matchActive: false },
487
+ { id: 'help', path: '/v1/help', key: 'nav.help' },
488
+ ];
489
+
433
490
  function Header({ navigate, locale }) {
434
491
  const [session, setSession] = useState(null);
435
492
  const [morsels, setMorsels] = useState(null);
436
493
  const [menuOpen, setMenuOpen] = useState(false);
494
+ // Header nav config: { order, hidden }. Defaults to all visible in declared order
495
+ // until the public /v1/site/header-nav fetch resolves (no flash of empty nav).
496
+ const [navConfig, setNavConfig] = useState({ order: PUBLIC_NAV_LINKS.map(l => l.id), hidden: [] });
437
497
 
438
498
  // Check auth state
439
499
  useEffect(() => {
@@ -451,6 +511,21 @@
451
511
  return () => window.removeEventListener('aimeat-auth-change', checkAuth);
452
512
  }, []);
453
513
 
514
+ // Load operator-configured header nav (public endpoint, no auth needed).
515
+ useEffect(() => {
516
+ let cancelled = false;
517
+ fetch('/v1/site/header-nav')
518
+ .then(r => r.json())
519
+ .then(d => {
520
+ if (cancelled || !d?.ok || !d.data) return;
521
+ const order = Array.isArray(d.data.order) ? d.data.order : PUBLIC_NAV_LINKS.map(l => l.id);
522
+ const hidden = Array.isArray(d.data.hidden) ? d.data.hidden : [];
523
+ setNavConfig({ order, hidden });
524
+ })
525
+ .catch(e => console.warn('Header nav fetch failed:', e.message));
526
+ return () => { cancelled = true; };
527
+ }, []);
528
+
454
529
  // Fetch morsels when session is available
455
530
  useEffect(() => {
456
531
  if (!session) { setMorsels(null); return; }
@@ -477,6 +552,13 @@
477
552
 
478
553
  const currentPath = location.pathname;
479
554
 
555
+ // Resolve the public links to render: config order, minus hidden, by declared id.
556
+ const linkById = Object.fromEntries(PUBLIC_NAV_LINKS.map(l => [l.id, l]));
557
+ const hiddenSet = new Set(navConfig.hidden);
558
+ const visibleLinks = navConfig.order
559
+ .map(id => linkById[id])
560
+ .filter(l => l && !hiddenSet.has(l.id));
561
+
480
562
  return html`
481
563
  <nav class="topnav">
482
564
  <div style="display:flex;align-items:center;gap:20px">
@@ -490,14 +572,14 @@
490
572
  <button class="topnav-burger" aria-label="Menu" aria-expanded=${menuOpen ? 'true' : 'false'}
491
573
  onClick=${() => setMenuOpen(o => !o)}>☰</button>
492
574
  <div class="topnav-menu ${menuOpen ? 'open' : ''}">
493
- <!-- "Try it" leads to the landing's path cards + app gallery — an empty
494
- Welcome Board on /v1/classic was the weakest possible first experience.
575
+ <!-- Public links are operator-configurable (visibility + order) via the admin
576
+ Portal tab. "Try it" leads to the landing's path cards + app gallery.
495
577
  Classic stays reachable deeper (landing card B "Start building"). -->
496
- <a href="/v1/portal" onClick=${(e) => { handleNav(e, '/v1/portal'); setMenuOpen(false); }} class="${currentPath === '/v1/portal' ? 'active' : ''}">${t('nav.try')}</a>
497
- <a href="/v1/how-it-works" onClick=${(e) => { handleNav(e, '/v1/how-it-works'); setMenuOpen(false); }} class="${currentPath === '/v1/how-it-works' ? 'active' : ''}">${t('nav.howItWorks') || 'How it works'}</a>
498
- <a href="/v1/business" onClick=${(e) => { handleNav(e, '/v1/business'); setMenuOpen(false); }} class="${currentPath === '/v1/business' ? 'active' : ''}">${t('nav.business') || 'For your business'}</a>
499
- <a href="/v1/portal?view=dev" onClick=${(e) => { handleNav(e, '/v1/portal?view=dev'); setMenuOpen(false); }}>${t('nav.devView')}</a>
500
- <a href="/v1/help" onClick=${(e) => { handleNav(e, '/v1/help'); setMenuOpen(false); }} class="${currentPath === '/v1/help' ? 'active' : ''}">${t('nav.help')}</a>
578
+ ${visibleLinks.map(link => html`
579
+ <a href="${link.path}" key=${link.id}
580
+ onClick=${(e) => { handleNav(e, link.path); setMenuOpen(false); }}
581
+ class="${link.matchActive !== false && currentPath === link.path ? 'active' : ''}">${t(link.key) || link.fallback || link.key}</a>
582
+ `)}
501
583
  ${session && html`
502
584
  <a href="/app-catalog.html" target="_blank" onClick=${() => setMenuOpen(false)} class="${currentPath === '/app-catalog.html' ? 'active' : ''}">${t('nav.apps') || 'Apps'}</a>
503
585
  <a href="/v1/profile" class="profile-link visible ${currentPath === '/v1/profile' ? 'active' : ''}" onClick=${(e) => { handleNav(e, '/v1/profile'); setMenuOpen(false); }}>
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file aimeat-globals.d.ts
3
+ * @description Ambient type declarations for the no-build frontend. Covers browser globals that
4
+ * arrive via <script> tags or build-time injection (not ES-module imports), plus the
5
+ * conventional fields the api.js fetch wrapper attaches to thrown Errors. This lets
6
+ * `tsc --noEmit` (checkJs) type-check public/ without these surfacing as "cannot find name"
7
+ * or "property does not exist" noise. It declares no runtime — types only.
8
+ * @usage Picked up automatically by tsconfig.frontend.json via its d.ts include glob.
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-19 — Initial ambient declarations for frontend type-checking
11
+ */
12
+
13
+ export {};
14
+
15
+ declare global {
16
+ /** The window.AIMEAT SDK (auth, data, ai, …) — loaded by aimeat-auth.js before the SPA. */
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const AIMEAT: any;
19
+ /** Chart.js — loaded via a <script> tag on admin pages. */
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ const Chart: any;
22
+ /** Build-time version string injected for cache-busting (see i18n.js). */
23
+ const __APP_VERSION__: string;
24
+
25
+ interface Window {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ AIMEAT: any;
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ Chart: any;
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ mermaid: any;
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ toastui: any;
34
+ showToast?: (message: string, isError?: boolean) => void;
35
+ /** Preact bundle marker set by the vendored preact build. */
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ __B?: any;
38
+ }
39
+
40
+ /**
41
+ * The api.js fetch wrapper attaches these fields to thrown Errors so callers can branch
42
+ * on the server error envelope (see public/js/api.js). Declared here so `err.code` /
43
+ * `err.status` / `err.response` type-check at every catch site.
44
+ */
45
+ interface Error {
46
+ code?: string;
47
+ status?: number;
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ response?: any;
50
+ source?: string;
51
+ }
52
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @file vendored-libs.d.ts
3
+ * @description Type stubs for the vendored, minified ESM libraries the frontend imports by bare
4
+ * specifier (preact, preact/hooks, htm). The browser resolves these via the importmap to
5
+ * public/lib/*.mjs at runtime; this stub exists only so `tsc --noEmit` (checkJs) resolves the
6
+ * imports without trying to type-check minified third-party bundles. Exports are intentionally
7
+ * loose (`any`) — htm template rendering is untyped regardless, so precise framework types add
8
+ * little here. tsconfig.frontend.json maps the bare specifiers to this file via "paths".
9
+ * @version-history
10
+ * v1.0.0 — 2026-06-19 — Initial stubs for preact / preact/hooks / htm
11
+ */
12
+
13
+ /* eslint-disable @typescript-eslint/no-explicit-any */
14
+
15
+ declare module 'preact' {
16
+ export const h: any;
17
+ export const Fragment: any;
18
+ export const render: any;
19
+ export const hydrate: any;
20
+ export const cloneElement: any;
21
+ export const createContext: any;
22
+ export const createRef: any;
23
+ export const isValidElement: any;
24
+ export const toChildArray: any;
25
+ export const options: any;
26
+ export class Component {
27
+ constructor(props?: any, context?: any);
28
+ setState(state?: any, callback?: () => void): void;
29
+ forceUpdate(callback?: () => void): void;
30
+ render(props?: any, state?: any, context?: any): any;
31
+ props: any;
32
+ state: any;
33
+ context: any;
34
+ base?: any;
35
+ }
36
+ const preact: any;
37
+ export default preact;
38
+ }
39
+
40
+ declare module 'preact/hooks' {
41
+ export const useState: any;
42
+ export const useEffect: any;
43
+ export const useLayoutEffect: any;
44
+ export const useRef: any;
45
+ export const useCallback: any;
46
+ export const useMemo: any;
47
+ export const useReducer: any;
48
+ export const useContext: any;
49
+ export const useErrorBoundary: any;
50
+ export const useId: any;
51
+ }
52
+
53
+ declare module 'htm' {
54
+ const htm: any;
55
+ export default htm;
56
+ }
@@ -13,8 +13,10 @@
13
13
  * 6. Create public/css/views/myview.css for view-specific styles
14
14
  * ─────────────────────────────────────────────────────────────────────────────
15
15
  */
16
- import { html } from 'htm/preact';
16
+ import { h } from 'preact';
17
+ import htm from 'htm';
17
18
  import { useState, useEffect } from 'preact/hooks';
19
+ const html = htm.bind(h);
18
20
  import { t } from '/js/i18n.js';
19
21
  import { escHtml } from '/js/utils.js';
20
22
  import { apiGet, apiPost } from '/js/api.js';
@@ -70,7 +70,7 @@ export default function AgentIntegrationTab({ data, session }) {
70
70
 
71
71
  return html`
72
72
  <div>
73
- ${renderPlatformRegistry(platforms, totalAgents, session, loadData)}
73
+ <${PlatformRegistry} platforms=${platforms} totalAgents=${totalAgents} session=${session} loadData=${loadData} />
74
74
  ${renderOnboardingOverview(onboarding, readiness, session, loadData)}
75
75
  ${renderSkillBundles(bundles, handleRegenerate, regenerating, session)}
76
76
  ${renderBundleTemplates()}
@@ -78,8 +78,9 @@ export default function AgentIntegrationTab({ data, session }) {
78
78
  `;
79
79
  }
80
80
 
81
- /* ── M8: Merged Platform column (name + ID as secondary text) ── */
82
- function renderPlatformRegistry(platforms, totalAgents, session, loadData) {
81
+ /* ── M8: Merged Platform column (name + ID as secondary text) ──
82
+ A real component (not a render-helper) so its useState obeys the Rules of Hooks. */
83
+ function PlatformRegistry({ platforms, totalAgents, session, loadData }) {
83
84
  const [showForm, setShowForm] = useState(false);
84
85
 
85
86
  return html`