aimeat 1.33.2 → 1.35.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 (328) hide show
  1. package/dist/locales/en.json +437 -15
  2. package/dist/locales/fi.json +437 -15
  3. package/dist/public/cortex-bundled/aimeat-charts.js +3 -3
  4. package/dist/public/cortex-bundled/aimeat-charts.yaml +3 -3
  5. package/dist/public/cortex-bundled/aimeat-i18n.js +123 -0
  6. package/dist/public/cortex-bundled/aimeat-i18n.yaml +64 -0
  7. package/dist/public/css/views/landing.css +2 -0
  8. package/dist/public/css/views/secretary.css +464 -2
  9. package/dist/public/js/i18n.js +9 -0
  10. package/dist/public/js/services/apps.js +4 -2
  11. package/dist/public/js/services/memory.js +9 -3
  12. package/dist/public/js/services/offers.js +1 -1
  13. package/dist/public/js/services/organisms.js +49 -17
  14. package/dist/public/js/services/secretary-helpers.js +266 -17
  15. package/dist/public/js/services/secretary-policy.js +28 -2
  16. package/dist/public/js/services/secretary-rules.js +70 -0
  17. package/dist/public/js/theme.js +20 -0
  18. package/dist/public/lib/VENDORED-STYLING.md +31 -0
  19. package/dist/public/lib/aimeat-daisyui-bridge.css +48 -0
  20. package/dist/public/lib/chartjs@4.js +14 -0
  21. package/dist/public/lib/daisyui@5.css +7 -0
  22. package/dist/public/lib/tailwindcss@4.js +8 -0
  23. package/dist/public/llms-template.txt +9 -6
  24. package/dist/public/showroom.html +104 -0
  25. package/dist/public/showroom.js +247 -0
  26. package/dist/public/spa.html +18 -16
  27. package/dist/public/views/aimeat-os.js +3 -2
  28. package/dist/public/views/landing.js +50 -6
  29. package/dist/public/views/profile/agents/agent-card.js +13 -4
  30. package/dist/public/views/profile/agents/state-detector.js +6 -0
  31. package/dist/public/views/profile/agents/tab-integration.js +11 -2
  32. package/dist/public/views/profile/apps-tab.js +46 -0
  33. package/dist/public/views/profile/extensions-tab.js +4 -0
  34. package/dist/public/views/profile/inbox-tab.js +1 -1
  35. package/dist/public/views/profile/landing-page.js +8 -3
  36. package/dist/public/views/profile/memory-tab.js +37 -6
  37. package/dist/public/views/profile/organisms/home.js +32 -0
  38. package/dist/public/views/profile/organisms/workspace-list.js +36 -5
  39. package/dist/public/views/profile/organisms/workspace.js +29 -4
  40. package/dist/public/views/profile/organisms-tab.js +61 -8
  41. package/dist/public/views/secretary/calendar.js +327 -0
  42. package/dist/public/views/secretary/cards-reach.js +16 -17
  43. package/dist/public/views/secretary/cards.js +217 -79
  44. package/dist/public/views/secretary/dashboard.js +617 -0
  45. package/dist/public/views/secretary/quality.js +156 -0
  46. package/dist/public/views/secretary/use-autonomy.js +17 -2
  47. package/dist/public/views/secretary/use-freshness.js +50 -0
  48. package/dist/public/views/secretary/use-intake.js +23 -4
  49. package/dist/public/views/secretary/use-layout.js +122 -0
  50. package/dist/public/views/secretary/use-quick-actions.js +98 -0
  51. package/dist/public/views/secretary/use-specialists.js +103 -0
  52. package/dist/public/views/secretary/use-triggers.js +159 -0
  53. package/dist/public/views/secretary/use-whats-next.js +340 -0
  54. package/dist/public/views/secretary/workflow-design.js +112 -0
  55. package/dist/public/views/secretary.js +836 -44
  56. package/dist/scripts/check-no-max-tokens.d.ts +2 -0
  57. package/dist/scripts/check-no-max-tokens.d.ts.map +1 -0
  58. package/dist/scripts/check-no-max-tokens.js +71 -0
  59. package/dist/scripts/check-no-max-tokens.js.map +1 -0
  60. package/dist/src/cli/connect/mcp/tools/organisms.d.ts +2 -0
  61. package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
  62. package/dist/src/cli/connect/mcp/tools/organisms.js +9 -1
  63. package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
  64. package/dist/src/cli/connect/onboarding-prompt.d.ts +3 -0
  65. package/dist/src/cli/connect/onboarding-prompt.d.ts.map +1 -1
  66. package/dist/src/cli/connect/onboarding-prompt.js +10 -3
  67. package/dist/src/cli/connect/onboarding-prompt.js.map +1 -1
  68. package/dist/src/data/aimeat-iam-package.d.ts +25 -0
  69. package/dist/src/data/aimeat-iam-package.d.ts.map +1 -0
  70. package/dist/src/data/aimeat-iam-package.js +312 -0
  71. package/dist/src/data/aimeat-iam-package.js.map +1 -0
  72. package/dist/src/data/aimeat-marketplace-package.d.ts +25 -0
  73. package/dist/src/data/aimeat-marketplace-package.d.ts.map +1 -0
  74. package/dist/src/data/aimeat-marketplace-package.js +231 -0
  75. package/dist/src/data/aimeat-marketplace-package.js.map +1 -0
  76. package/dist/src/data/app-templates.d.ts +48 -0
  77. package/dist/src/data/app-templates.d.ts.map +1 -0
  78. package/dist/src/data/app-templates.js +856 -0
  79. package/dist/src/data/app-templates.js.map +1 -0
  80. package/dist/src/data/example-packages.d.ts.map +1 -1
  81. package/dist/src/data/example-packages.js +3 -1
  82. package/dist/src/data/example-packages.js.map +1 -1
  83. package/dist/src/generated/api-types.d.ts +689 -7
  84. package/dist/src/generated/api-types.d.ts.map +1 -1
  85. package/dist/src/generated/prisma-postgres/edge.js +10 -3
  86. package/dist/src/generated/prisma-postgres/index-browser.js +7 -0
  87. package/dist/src/generated/prisma-postgres/index.d.ts +205 -2
  88. package/dist/src/generated/prisma-postgres/index.js +10 -3
  89. package/dist/src/generated/prisma-postgres/package.json +1 -1
  90. package/dist/src/generated/prisma-postgres/schema.prisma +23 -13
  91. package/dist/src/generated/prisma-postgres/wasm.js +10 -3
  92. package/dist/src/mcp/agent-onboarding.d.ts +4 -0
  93. package/dist/src/mcp/agent-onboarding.d.ts.map +1 -1
  94. package/dist/src/mcp/agent-onboarding.js +28 -2
  95. package/dist/src/mcp/agent-onboarding.js.map +1 -1
  96. package/dist/src/mcp/annotations.d.ts.map +1 -1
  97. package/dist/src/mcp/annotations.js +1 -0
  98. package/dist/src/mcp/annotations.js.map +1 -1
  99. package/dist/src/mcp/catalog/definitions.d.ts.map +1 -1
  100. package/dist/src/mcp/catalog/definitions.js +16 -1
  101. package/dist/src/mcp/catalog/definitions.js.map +1 -1
  102. package/dist/src/mcp/catalog/surfaces.js +3 -3
  103. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  104. package/dist/src/mcp/index.d.ts +3 -0
  105. package/dist/src/mcp/index.d.ts.map +1 -1
  106. package/dist/src/mcp/index.js +11 -0
  107. package/dist/src/mcp/index.js.map +1 -1
  108. package/dist/src/mcp/organisms.d.ts.map +1 -1
  109. package/dist/src/mcp/organisms.js +48 -8
  110. package/dist/src/mcp/organisms.js.map +1 -1
  111. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  112. package/dist/src/mcp/workspaces.js +6 -4
  113. package/dist/src/mcp/workspaces.js.map +1 -1
  114. package/dist/src/models/agent-onboarding-schemas.d.ts +47 -0
  115. package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -1
  116. package/dist/src/models/agent-onboarding-schemas.js +117 -0
  117. package/dist/src/models/agent-onboarding-schemas.js.map +1 -1
  118. package/dist/src/models/offer-schemas.d.ts +8 -8
  119. package/dist/src/routes/agent-onboarding.d.ts +5 -0
  120. package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
  121. package/dist/src/routes/agent-onboarding.js +21 -2
  122. package/dist/src/routes/agent-onboarding.js.map +1 -1
  123. package/dist/src/routes/ai.d.ts +8 -4
  124. package/dist/src/routes/ai.d.ts.map +1 -1
  125. package/dist/src/routes/ai.js +38 -14
  126. package/dist/src/routes/ai.js.map +1 -1
  127. package/dist/src/routes/app-grants.d.ts +2 -0
  128. package/dist/src/routes/app-grants.d.ts.map +1 -1
  129. package/dist/src/routes/app-grants.js +7 -1
  130. package/dist/src/routes/app-grants.js.map +1 -1
  131. package/dist/src/routes/app-templates.d.ts +16 -0
  132. package/dist/src/routes/app-templates.d.ts.map +1 -0
  133. package/dist/src/routes/app-templates.js +40 -0
  134. package/dist/src/routes/app-templates.js.map +1 -0
  135. package/dist/src/routes/apps.d.ts.map +1 -1
  136. package/dist/src/routes/apps.js +55 -5
  137. package/dist/src/routes/apps.js.map +1 -1
  138. package/dist/src/routes/auth.d.ts +3 -0
  139. package/dist/src/routes/auth.d.ts.map +1 -1
  140. package/dist/src/routes/auth.js +9 -4
  141. package/dist/src/routes/auth.js.map +1 -1
  142. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  143. package/dist/src/routes/bootstrap.js +1 -0
  144. package/dist/src/routes/bootstrap.js.map +1 -1
  145. package/dist/src/routes/generator.d.ts.map +1 -1
  146. package/dist/src/routes/generator.js +3 -2
  147. package/dist/src/routes/generator.js.map +1 -1
  148. package/dist/src/routes/lib-ai.d.ts +0 -1
  149. package/dist/src/routes/lib-ai.d.ts.map +1 -1
  150. package/dist/src/routes/lib-ai.js +13 -6
  151. package/dist/src/routes/lib-ai.js.map +1 -1
  152. package/dist/src/routes/lib-header.d.ts +2 -0
  153. package/dist/src/routes/lib-header.d.ts.map +1 -1
  154. package/dist/src/routes/lib-header.js +7 -16
  155. package/dist/src/routes/lib-header.js.map +1 -1
  156. package/dist/src/routes/lib-markdown.d.ts +18 -0
  157. package/dist/src/routes/lib-markdown.d.ts.map +1 -0
  158. package/dist/src/routes/lib-markdown.js +236 -0
  159. package/dist/src/routes/lib-markdown.js.map +1 -0
  160. package/dist/src/routes/libs.d.ts +22 -0
  161. package/dist/src/routes/libs.d.ts.map +1 -1
  162. package/dist/src/routes/libs.js +133 -10
  163. package/dist/src/routes/libs.js.map +1 -1
  164. package/dist/src/routes/memory.d.ts.map +1 -1
  165. package/dist/src/routes/memory.js +21 -2
  166. package/dist/src/routes/memory.js.map +1 -1
  167. package/dist/src/routes/openrouter.d.ts +4 -0
  168. package/dist/src/routes/openrouter.d.ts.map +1 -1
  169. package/dist/src/routes/openrouter.js +17 -0
  170. package/dist/src/routes/openrouter.js.map +1 -1
  171. package/dist/src/routes/organisms.d.ts.map +1 -1
  172. package/dist/src/routes/organisms.js +107 -7
  173. package/dist/src/routes/organisms.js.map +1 -1
  174. package/dist/src/routes/portal.d.ts +9 -0
  175. package/dist/src/routes/portal.d.ts.map +1 -1
  176. package/dist/src/routes/portal.js +1 -1
  177. package/dist/src/routes/portal.js.map +1 -1
  178. package/dist/src/routes/public-stats.d.ts +3 -0
  179. package/dist/src/routes/public-stats.d.ts.map +1 -1
  180. package/dist/src/routes/public-stats.js +9 -2
  181. package/dist/src/routes/public-stats.js.map +1 -1
  182. package/dist/src/routes/secretary.d.ts +34 -0
  183. package/dist/src/routes/secretary.d.ts.map +1 -0
  184. package/dist/src/routes/secretary.js +311 -0
  185. package/dist/src/routes/secretary.js.map +1 -0
  186. package/dist/src/routes/specialists.d.ts.map +1 -1
  187. package/dist/src/routes/specialists.js +21 -0
  188. package/dist/src/routes/specialists.js.map +1 -1
  189. package/dist/src/routes/subdomains.js +1 -1
  190. package/dist/src/routes/subdomains.js.map +1 -1
  191. package/dist/src/routes/workflows.d.ts +9 -3
  192. package/dist/src/routes/workflows.d.ts.map +1 -1
  193. package/dist/src/routes/workflows.js +9 -3
  194. package/dist/src/routes/workflows.js.map +1 -1
  195. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  196. package/dist/src/server-bootstrap/routes-loader.js +4 -0
  197. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  198. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  199. package/dist/src/server-bootstrap/service-init.js +7 -0
  200. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  201. package/dist/src/server-bootstrap/static-files.d.ts +4 -0
  202. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  203. package/dist/src/server-bootstrap/static-files.js +18 -0
  204. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  205. package/dist/src/services/archive.d.ts +74 -0
  206. package/dist/src/services/archive.d.ts.map +1 -0
  207. package/dist/src/services/archive.js +132 -0
  208. package/dist/src/services/archive.js.map +1 -0
  209. package/dist/src/services/cortex-seeder.d.ts +4 -1
  210. package/dist/src/services/cortex-seeder.d.ts.map +1 -1
  211. package/dist/src/services/cortex-seeder.js +25 -6
  212. package/dist/src/services/cortex-seeder.js.map +1 -1
  213. package/dist/src/services/directory.d.ts.map +1 -1
  214. package/dist/src/services/directory.js +3 -2
  215. package/dist/src/services/directory.js.map +1 -1
  216. package/dist/src/services/generator-prompt-seeds.d.ts.map +1 -1
  217. package/dist/src/services/generator-prompt-seeds.js +5 -4
  218. package/dist/src/services/generator-prompt-seeds.js.map +1 -1
  219. package/dist/src/services/message-send.d.ts.map +1 -1
  220. package/dist/src/services/message-send.js +14 -0
  221. package/dist/src/services/message-send.js.map +1 -1
  222. package/dist/src/services/onboarding-guide.d.ts +53 -0
  223. package/dist/src/services/onboarding-guide.d.ts.map +1 -0
  224. package/dist/src/services/onboarding-guide.js +81 -0
  225. package/dist/src/services/onboarding-guide.js.map +1 -0
  226. package/dist/src/services/organism-search.d.ts +6 -2
  227. package/dist/src/services/organism-search.d.ts.map +1 -1
  228. package/dist/src/services/organism-search.js +2 -2
  229. package/dist/src/services/organism-search.js.map +1 -1
  230. package/dist/src/services/owner-memory.d.ts +1 -0
  231. package/dist/src/services/owner-memory.d.ts.map +1 -1
  232. package/dist/src/services/owner-memory.js.map +1 -1
  233. package/dist/src/services/owner-session.d.ts +1 -0
  234. package/dist/src/services/owner-session.d.ts.map +1 -1
  235. package/dist/src/services/owner-session.js +10 -2
  236. package/dist/src/services/owner-session.js.map +1 -1
  237. package/dist/src/services/package-seeder.d.ts +22 -0
  238. package/dist/src/services/package-seeder.d.ts.map +1 -0
  239. package/dist/src/services/package-seeder.js +30 -0
  240. package/dist/src/services/package-seeder.js.map +1 -0
  241. package/dist/src/services/presence.d.ts +3 -0
  242. package/dist/src/services/presence.d.ts.map +1 -1
  243. package/dist/src/services/presence.js +10 -0
  244. package/dist/src/services/presence.js.map +1 -1
  245. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  246. package/dist/src/services/prompt-defaults.js +8 -3
  247. package/dist/src/services/prompt-defaults.js.map +1 -1
  248. package/dist/src/services/scheduler.d.ts +50 -2
  249. package/dist/src/services/scheduler.d.ts.map +1 -1
  250. package/dist/src/services/scheduler.js +454 -26
  251. package/dist/src/services/scheduler.js.map +1 -1
  252. package/dist/src/services/secretary-authoring.d.ts +71 -0
  253. package/dist/src/services/secretary-authoring.d.ts.map +1 -0
  254. package/dist/src/services/secretary-authoring.js +336 -0
  255. package/dist/src/services/secretary-authoring.js.map +1 -0
  256. package/dist/src/services/secretary-clarify.d.ts +57 -0
  257. package/dist/src/services/secretary-clarify.d.ts.map +1 -0
  258. package/dist/src/services/secretary-clarify.js +104 -0
  259. package/dist/src/services/secretary-clarify.js.map +1 -0
  260. package/dist/src/services/secretary-feed.d.ts +11 -0
  261. package/dist/src/services/secretary-feed.d.ts.map +1 -0
  262. package/dist/src/services/secretary-feed.js +28 -0
  263. package/dist/src/services/secretary-feed.js.map +1 -0
  264. package/dist/src/services/secretary-tick.d.ts +243 -3
  265. package/dist/src/services/secretary-tick.d.ts.map +1 -1
  266. package/dist/src/services/secretary-tick.js +273 -3
  267. package/dist/src/services/secretary-tick.js.map +1 -1
  268. package/dist/src/services/secretary-workbrief.d.ts +144 -0
  269. package/dist/src/services/secretary-workbrief.d.ts.map +1 -0
  270. package/dist/src/services/secretary-workbrief.js +315 -0
  271. package/dist/src/services/secretary-workbrief.js.map +1 -0
  272. package/dist/src/services/secretary-worktier.d.ts +118 -0
  273. package/dist/src/services/secretary-worktier.d.ts.map +1 -0
  274. package/dist/src/services/secretary-worktier.js +157 -0
  275. package/dist/src/services/secretary-worktier.js.map +1 -0
  276. package/dist/src/services/skill-bundle/generator.d.ts +3 -0
  277. package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
  278. package/dist/src/services/skill-bundle/generator.js +19 -0
  279. package/dist/src/services/skill-bundle/generator.js.map +1 -1
  280. package/dist/src/services/specialist-runner.d.ts +36 -0
  281. package/dist/src/services/specialist-runner.d.ts.map +1 -0
  282. package/dist/src/services/specialist-runner.js +91 -0
  283. package/dist/src/services/specialist-runner.js.map +1 -0
  284. package/dist/src/services/specialist.d.ts +9 -0
  285. package/dist/src/services/specialist.d.ts.map +1 -1
  286. package/dist/src/services/specialist.js +42 -0
  287. package/dist/src/services/specialist.js.map +1 -1
  288. package/dist/src/services/structure-overview.d.ts +13 -0
  289. package/dist/src/services/structure-overview.d.ts.map +1 -1
  290. package/dist/src/services/structure-overview.js +34 -11
  291. package/dist/src/services/structure-overview.js.map +1 -1
  292. package/dist/src/services/system-agent-responder.d.ts +36 -0
  293. package/dist/src/services/system-agent-responder.d.ts.map +1 -0
  294. package/dist/src/services/system-agent-responder.js +107 -0
  295. package/dist/src/services/system-agent-responder.js.map +1 -0
  296. package/dist/src/services/system-agent.d.ts +34 -1
  297. package/dist/src/services/system-agent.d.ts.map +1 -1
  298. package/dist/src/services/system-agent.js +73 -0
  299. package/dist/src/services/system-agent.js.map +1 -1
  300. package/dist/src/storage/interface.d.ts +33 -0
  301. package/dist/src/storage/interface.d.ts.map +1 -1
  302. package/dist/src/storage/interface.js.map +1 -1
  303. package/dist/src/storage/providers/mongodb/index.d.ts +37 -0
  304. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  305. package/dist/src/storage/providers/mongodb/index.js +132 -12
  306. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  307. package/dist/src/storage/providers/sqlite/index.d.ts +23 -1
  308. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  309. package/dist/src/storage/providers/sqlite/index.js +60 -8
  310. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  311. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +29 -1
  312. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  313. package/dist/src/storage/providers/sqlite/repos/memory.js +109 -10
  314. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  315. package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
  316. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  317. package/dist/src/storage/providers/sqlite/schema.js +64 -9
  318. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  319. package/dist/src/storage/repositories/app.repository.d.ts +15 -0
  320. package/dist/src/storage/repositories/app.repository.d.ts.map +1 -1
  321. package/dist/src/storage/repositories/memory.repository.d.ts +41 -1
  322. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  323. package/dist/src/storage/repositories/organism.repository.d.ts +2 -1
  324. package/dist/src/storage/repositories/organism.repository.d.ts.map +1 -1
  325. package/dist/static/app-catalog.html +469 -122
  326. package/package.json +2 -1
  327. package/prisma/schema.postgres.prisma +10 -0
  328. package/prisma/schema.prisma +12 -0
@@ -3,6 +3,8 @@
3
3
  * @description Agent card component with collapsed/expanded states,
4
4
  * Two-Zone Header (identity + state-dependent status), and tab bar.
5
5
  * @version-history
6
+ * v1.18.0 -- 2026-06-30 -- Onboarding step labels use tOr() so a missing agentOnboarding.steps.*
7
+ * key falls back to the server-provided step.title instead of rendering the raw key.
6
8
  * v1.17.0 -- 2026-06-24 -- Secretary P5 (S-A): render a "Specialist · <role>" badge for agents tagged
7
9
  * system:specialist, so the new specialist agent type is recognizable in the Agents tab.
8
10
  * v1.16.0 -- 2026-06-21 -- Unseen-change badges are now has-unseen DOTS (no count) — the
@@ -59,7 +61,7 @@
59
61
  import { h } from 'preact';
60
62
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
61
63
  import htm from 'htm';
62
- import { t } from '/js/i18n.js';
64
+ import { t, tOr } from '/js/i18n.js';
63
65
  import { apiGet, apiPatch } from '/js/api.js';
64
66
  import { timeAgo } from '/js/utils.js';
65
67
  import { detectAgentState, getDefaultTab, getStateColor } from './state-detector.js';
@@ -402,6 +404,10 @@ function renderPlatformBadge(onboarding) {
402
404
  const READINESS_RANKS = { none: 0, basic: 1, standard: 2, advanced: 3, full: 4 };
403
405
 
404
406
  function renderReadinessBadge(state, onboarding) {
407
+ if (state === 'system') {
408
+ // Internal (auto-provisioned) agent — no device-auth onboarding / readiness.
409
+ return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">${t('profile.agents.detail.state.internal')}</span>`;
410
+ }
405
411
  if (state === 'new') {
406
412
  return html`<span class="pf-agd-badge pf-agd-badge--readiness-none">--</span>`;
407
413
  }
@@ -433,7 +439,7 @@ function renderCollapsedStats(state, agent, onboarding) {
433
439
  return html`${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''} | ${t('profile.agents.detail.state.newSummary')}`;
434
440
  case 'onboarding': {
435
441
  const nextStep = onboarding?.steps?.find(s => s.status === 'pending');
436
- return html`${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''} ${nextStep ? `| ${t('profile.agents.detail.state.next')}: ${t('agentOnboarding.steps.' + nextStep.id) || nextStep.title || nextStep.id}` : ''}`;
442
+ return html`${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''} ${nextStep ? `| ${t('profile.agents.detail.state.next')}: ${tOr('agentOnboarding.steps.' + nextStep.id, nextStep.title || nextStep.id)}` : ''}`;
437
443
  }
438
444
  case 'problem':
439
445
  return html`${t('profile.agents.detail.state.problemSummary')} | ${agent.last_seen ? `${t('profile.agents.detail.lastSeen')}: ${timeAgo(agent.last_seen)}` : ''}`;
@@ -454,6 +460,9 @@ function renderCollapsedStats(state, agent, onboarding) {
454
460
 
455
461
  function renderZone2(state, agent, onboarding, setActiveTab, showToast) {
456
462
  switch (state) {
463
+ case 'system':
464
+ // Internal agents (Secretary / company Secretary / specialists) skip the Hello Integration banner.
465
+ return null;
457
466
  case 'new':
458
467
  return html`
459
468
  <div class="pf-agd-zone2 pf-agd-zone2--new">
@@ -476,7 +485,7 @@ function renderZone2(state, agent, onboarding, setActiveTab, showToast) {
476
485
  <div class="pf-agd-zone2 pf-agd-zone2--onboarding">
477
486
  <div class="pf-agd-zone2-title">
478
487
  ${t('profile.agents.detail.zone2.onboardingTitle')}: ${passed} / ${total}
479
- ${nextStep ? html`<span class="pf-agd-zone2-desc"> ${t('profile.agents.detail.state.next')}: ${t('agentOnboarding.steps.' + nextStep.id) || nextStep.title || nextStep.id}</span>` : ''}
488
+ ${nextStep ? html`<span class="pf-agd-zone2-desc"> ${t('profile.agents.detail.state.next')}: ${tOr('agentOnboarding.steps.' + nextStep.id, nextStep.title || nextStep.id)}</span>` : ''}
480
489
  </div>
481
490
  <div class="pf-agd-progress-bar">
482
491
  <div class="pf-agd-progress-fill" style="width: ${pct}%"></div>
@@ -484,7 +493,7 @@ function renderZone2(state, agent, onboarding, setActiveTab, showToast) {
484
493
  <div class="pf-agd-step-pills">
485
494
  ${steps.map(s => html`
486
495
  <span key=${s.id} class="pf-agd-step-pill pf-agd-step-pill--${s.status}">
487
- ${s.status === 'passed' ? '✓' : '○'} ${t('agentOnboarding.steps.' + s.id) || s.title || s.id}
496
+ ${s.status === 'passed' ? '✓' : '○'} ${tOr('agentOnboarding.steps.' + s.id, s.title || s.id)}
488
497
  </span>
489
498
  `)}
490
499
  </div>
@@ -16,6 +16,10 @@
16
16
  * Checked in priority order: new > onboarding > problem > idle > production.
17
17
  */
18
18
  export function detectAgentState(agent, onboarding) {
19
+ // System agents (Secretary, company Secretary, specialists) are auto-provisioned and never run the
20
+ // device-auth "Hello Integration" onboarding — so they must never show the 0/11 "onboarding not
21
+ // started" state. Any `system:*` tag marks an internal agent.
22
+ if ((agent.tags || []).some((tag) => typeof tag === 'string' && tag.startsWith('system:'))) return 'system';
19
23
  if (!onboarding || onboarding.status === 'pending' || onboarding.status === 'not_started') return 'new';
20
24
  if (onboarding.status === 'in_progress') return 'onboarding';
21
25
 
@@ -34,6 +38,7 @@ export function getDefaultTab(state) {
34
38
  case 'new': return 'integration';
35
39
  case 'onboarding': return 'integration';
36
40
  case 'problem': return 'integration';
41
+ case 'system': return 'directives'; // internal agents: brain, not onboarding
37
42
  case 'idle': return 'tasks';
38
43
  case 'production': return 'tasks';
39
44
  default: return 'tasks';
@@ -45,6 +50,7 @@ export function getStateColor(state) {
45
50
  case 'new': return 'var(--warning)';
46
51
  case 'onboarding': return 'var(--warning)';
47
52
  case 'problem': return 'var(--danger)';
53
+ case 'system': return 'var(--success)';
48
54
  case 'idle': return 'var(--text-muted)';
49
55
  case 'production': return 'var(--success)';
50
56
  default: return 'var(--text-muted)';
@@ -4,6 +4,9 @@
4
4
  * during onboarding or production status (connection, platform, readiness,
5
5
  * identity, delivery log) after completion.
6
6
  * @version-history
7
+ * v1.8.0 -- 2026-06-30 -- Onboarding checklist step labels use tOr() so a missing
8
+ * agentOnboarding.steps.* key falls back to the server-provided step.title (which carries
9
+ * the howTo-enriched onboarding payload) instead of rendering the raw i18n key.
7
10
  * v1.7.0 -- 2026-06-10 -- Webhook fields (URL, fail count, Test/Edit) hidden while delivery
8
11
  * is polling; a "+ Set up webhook" reveal opens the URL form for switching methods.
9
12
  * v1.6.0 -- 2026-06-02 -- Component unification (#1): the 3 copy buttons (agent prompt,
@@ -29,7 +32,7 @@ import { h } from 'preact';
29
32
  import { useState, useEffect, useRef } from 'preact/hooks';
30
33
  import htm from 'htm';
31
34
  import { onLiveUpdate } from '/lib/live-updates.js';
32
- import { t } from '/js/i18n.js';
35
+ import { t, tOr } from '/js/i18n.js';
33
36
  import { timeAgo } from '/js/utils.js';
34
37
  import { CopyButton } from '/components/CopyButton.js';
35
38
  import { detectAgentState } from './state-detector.js';
@@ -187,6 +190,12 @@ curl -H "Authorization: Bearer <jwt>" -o skill-bundle.zip "${skillBundleUrl}" &&
187
190
  return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
188
191
  }
189
192
 
193
+ // System agents (Secretary / company Secretary / specialists) are auto-provisioned and never run the
194
+ // device-auth "Hello Integration" onboarding — show a short note instead of the 0/11 checklist.
195
+ if (state === 'system') {
196
+ return html`<div class="pf-agd-empty">${t('profile.agents.detail.internalAgentNote')}</div>`;
197
+ }
198
+
190
199
  const isOnboarding = state === 'new' || state === 'onboarding';
191
200
 
192
201
  if (isOnboarding) {
@@ -235,7 +244,7 @@ function renderOnboardingView(onboarding, agentName, handleRerun, rerunning, cur
235
244
  <span class="pf-agd-step-icon">
236
245
  ${step.status === 'passed' ? '✓' : step.status === 'failed' ? '✗' : '○'}
237
246
  </span>
238
- <span class="pf-agd-step-name">${i + 1}. ${t('agentOnboarding.steps.' + step.id) || step.title || step.id}</span>
247
+ <span class="pf-agd-step-name">${i + 1}. ${tOr('agentOnboarding.steps.' + step.id, step.title || step.id)}</span>
239
248
  <span class="pf-agd-step-detail">
240
249
  ${step.validatedAt ? timeAgo(step.validatedAt) : ''}
241
250
  ${step.validationDetail ? ` -- ${step.validationDetail}` : ''}
@@ -2,6 +2,8 @@
2
2
  * @file apps-tab.js
3
3
  * @description Profile tab for HTML app management — upload, gallery, access code editing.
4
4
  * @version-history
5
+ * v1.4.0 — 2026-06-26 — Add "Edit details" (rename + edit description) to My Apps — updates the
6
+ * display name in place without re-publishing; the app link (owner/filename) stays the same.
5
7
  * v1.3.0 — 2026-06-24 — Show a "moderated by operator: hidden" badge + hint (with reason) on My Apps
6
8
  * when an operator has hidden the app; the owner sees it but cannot lift it.
7
9
  * v1.2.0 — 2026-06-20 — Add Park/Unpark control + Parked badge/hint to My Apps (hide an app
@@ -30,6 +32,9 @@ export default function AppsTab({ session, showToast, onStats }) {
30
32
  const [showAppUpload, setShowAppUpload] = useState(false);
31
33
  const [editingApp, setEditingApp] = useState(null);
32
34
  const [editCode, setEditCode] = useState('');
35
+ const [editingMeta, setEditingMeta] = useState(null);
36
+ const [editName, setEditName] = useState('');
37
+ const [editDesc, setEditDesc] = useState('');
33
38
 
34
39
  useEffect(() => {
35
40
  if (session) loadData();
@@ -78,6 +83,33 @@ export default function AppsTab({ session, showToast, onStats }) {
78
83
  setEditCode('');
79
84
  }
80
85
 
86
+ function startEditMeta(app) {
87
+ setEditingMeta(app.filename);
88
+ setEditName(app.manifest?.name || String(app.filename || app.name || '').replace(/\.html?$/i, ''));
89
+ setEditDesc(app.manifest?.description || '');
90
+ }
91
+
92
+ function cancelEditMeta() {
93
+ setEditingMeta(null);
94
+ setEditName('');
95
+ setEditDesc('');
96
+ }
97
+
98
+ async function handleSaveMeta(filename) {
99
+ const name = editName.trim();
100
+ const description = editDesc.trim();
101
+ if (!name) { showToast(t('profile.apps.nameRequired') || 'Name cannot be empty', true); return; }
102
+ if (!description) { showToast(t('profile.apps.descRequired') || 'Description cannot be empty', true); return; }
103
+ const resp = await patchApp(filename, { name, description });
104
+ if (resp.ok !== false) {
105
+ showToast(t('profile.apps.detailsUpdated') || 'App details updated');
106
+ cancelEditMeta();
107
+ loadData();
108
+ } else {
109
+ showToast(resp?.error?.message || t('profile.apps.updateFailed') || 'Update failed', true);
110
+ }
111
+ }
112
+
81
113
  async function handleTogglePark(app) {
82
114
  const next = !app.parked;
83
115
  const resp = await patchApp(app.filename || app.name, { parked: next });
@@ -158,10 +190,24 @@ export default function AppsTab({ session, showToast, onStats }) {
158
190
  data: { owner: a.owner || session.owner, filename: a.filename || a.name } });
159
191
  window.open(`/v1/apps/${encodeURIComponent(a.owner || session.owner)}/${encodeURIComponent(a.filename || a.name)}?mode=inline`, '_blank');
160
192
  }}>${t('profile.apps.launch') || 'Launch'}</button>
193
+ <button class="btn-sm" onClick=${() => startEditMeta(a)}>${t('profile.apps.editDetails') || 'Edit Details'}</button>
161
194
  <button class="btn-sm" onClick=${() => startEdit(a)}>${t('profile.apps.editAccess') || 'Edit Access Code'}</button>
162
195
  <button class=${a.parked ? 'btn-success btn-sm' : 'btn-outline btn-sm'} onClick=${() => handleTogglePark(a)}>${a.parked ? (t('profile.apps.unpark') || 'Unpark') : (t('profile.apps.park') || 'Park')}</button>
163
196
  <button class="btn-danger-solid btn-sm" onClick=${() => handleDelete(a.filename || a.name)}>${t('profile.apps.deleteBtn') || 'Delete'}</button>
164
197
  </div>
198
+ ${editingMeta === a.filename ? html`
199
+ <div class="pf-edit-panel">
200
+ <label class="text-meta mb-half">${t('profile.apps.nameLabel') || 'App Name'}</label>
201
+ <input class="input-field pf-flex-fill mb-half" maxLength="120" placeholder=${t('profile.apps.namePlaceholder') || 'Display name'} value=${editName} onInput=${e => setEditName(e.target.value)} />
202
+ <label class="text-meta mb-half">${t('profile.apps.descLabel') || 'Description'}</label>
203
+ <textarea class="input-field pf-flex-fill mb-half" rows="2" maxLength="2000" placeholder=${t('profile.apps.descPlaceholder') || 'What does this app do?'} value=${editDesc} onInput=${e => setEditDesc(e.target.value)}></textarea>
204
+ <div class="text-meta-sm mb-half">${t('profile.apps.renameHint') || 'The app link stays the same — only the displayed name and description change.'}</div>
205
+ <div class="flex-row">
206
+ <button class="btn-primary btn-sm" onClick=${() => handleSaveMeta(a.filename)}>${t('profile.apps.save') || 'Save'}</button>
207
+ <button class="btn-outline btn-sm" onClick=${cancelEditMeta}>${t('profile.cancel') || 'Cancel'}</button>
208
+ </div>
209
+ </div>
210
+ ` : ''}
165
211
  ${editingApp === a.filename ? html`
166
212
  <div class="pf-edit-panel">
167
213
  <label class="text-meta mb-half">${t('profile.apps.accessCodeLabel') || 'Access Code'}</label>
@@ -961,11 +961,13 @@ export default function ExtensionsTab({ session, showToast }) {
961
961
  <div class="ext-grid">
962
962
  ${nodeExts.map(ext => {
963
963
  const types = ext.component_types || [];
964
+ const isLib = types.includes('lib') && !types.some(ct => ['schema','action','board-template','ontology','seed-data'].includes(ct));
964
965
  return html`
965
966
  <div class="ext-card" onClick=${() => loadDetail(ext.name)}>
966
967
  <div class="ext-card-header">
967
968
  <span class="ext-card-name">${ext.name}</span>
968
969
  <span class="ext-card-version">v${ext.version || '?'}</span>
970
+ <span class="badge ${isLib ? 'badge-info' : 'badge-dim'}">${isLib ? t('profile.extensions.libraryBadge') : t('profile.extensions.serviceBadge')}</span>
969
971
  <span class="ext-visibility-badge public">${'\u{1F310}'}</span>
970
972
  </div>
971
973
  <div class="ext-card-desc">${ext.description || ''}</div>
@@ -985,6 +987,7 @@ export default function ExtensionsTab({ session, showToast }) {
985
987
  <div class="ext-grid">
986
988
  ${myExts.map(ext => {
987
989
  const types = ext.component_types || [];
990
+ const isLib = types.includes('lib') && !types.some(ct => ['schema','action','board-template','ontology','seed-data'].includes(ct));
988
991
  const isActive = ext.status === 'active';
989
992
  const vis = ext.visibility || 'private';
990
993
  return html`
@@ -992,6 +995,7 @@ export default function ExtensionsTab({ session, showToast }) {
992
995
  <div class="ext-card-header">
993
996
  <span class="ext-card-name">${ext.name}</span>
994
997
  <span class="ext-card-version">v${ext.version || '?'}</span>
998
+ <span class="badge ${isLib ? 'badge-info' : 'badge-dim'}">${isLib ? t('profile.extensions.libraryBadge') : t('profile.extensions.serviceBadge')}</span>
995
999
  <span class="ext-visibility-badge ${vis}">${vis === 'public' ? '\u{1F310}' : '\u{1F512}'}</span>
996
1000
  </div>
997
1001
  <div class="ext-card-desc">${ext.description || ''}</div>
@@ -164,7 +164,7 @@ function groupConversations(conversations) {
164
164
  }
165
165
 
166
166
  function Avatar({ seed, size = 36 }) {
167
- const svg = minidenticon(seed || 'user');
167
+ const svg = minidenticon(typeof seed === 'string' && seed ? seed : 'user');
168
168
  return html`<span class="inbox-avatar" style=${`width:${size}px;height:${size}px`}
169
169
  dangerouslySetInnerHTML=${{ __html: svg }}></span>`;
170
170
  }
@@ -230,7 +230,12 @@ function EditProfileModal({ session, onClose, onSaved, onChangePassword }) {
230
230
  const resp = await updateProfile(fields);
231
231
  if (resp && resp.data) {
232
232
  if (session && typeof fields.display_name === 'string') {
233
- session.displayName = fields.display_name;
233
+ // Persist + re-render the golden login pill live (so it shows the new name now).
234
+ if (window.AIMEAT?.auth?.updateSessionMeta) {
235
+ window.AIMEAT.auth.updateSessionMeta({ displayName: fields.display_name });
236
+ } else {
237
+ session.displayName = fields.display_name;
238
+ }
234
239
  }
235
240
  onSaved?.();
236
241
  } else {
@@ -732,7 +737,7 @@ function ProfileCard({ tier, stats, session, onEditProfile, switchTab }) {
732
737
  const NODE_URL = getNodeUrl();
733
738
  const isNew = tier === 'new';
734
739
  const isExperienced = tier === 'experienced';
735
- const avatarSvg = minidenticon(session.owner || 'user');
740
+ const avatarSvg = minidenticon(typeof session.owner === 'string' && session.owner ? session.owner : 'user');
736
741
 
737
742
  // Stats are NAVIGATION, not decoration \u2014 each one opens its own section.
738
743
  const stat = (icon, val, labelKey, tabId, green) => html`
@@ -1184,7 +1189,7 @@ export default function LandingPage({ tier, stats, session, navigate, showToast,
1184
1189
 
1185
1190
  <aside class="pf-sidebar">
1186
1191
  <div class="pf-side-identity">
1187
- <div class="pf-side-avatar" dangerouslySetInnerHTML=${{ __html: minidenticon(owner || 'user') }}></div>
1192
+ <div class="pf-side-avatar" dangerouslySetInnerHTML=${{ __html: minidenticon(typeof owner === 'string' && owner ? owner : 'user') }}></div>
1188
1193
  <div class="pf-side-id-name">${session.displayName || owner}</div>
1189
1194
  </div>
1190
1195
 
@@ -41,6 +41,12 @@
41
41
  * v2.3.1 — 2026-06-22 — Toolbar layout: data tools (Load all / Export / Import) grouped in their
42
42
  * own bordered .mem-tools-section up top; content-search + filter on one row; sort + "New memory"
43
43
  * moved to a bottom bar (New-memory pushed right).
44
+ * v2.4.0 — 2026-06-26 — Per-row "Copy URL" button in Files actions (canonical <CopyButton>) so a
45
+ * single file's URL can be grabbed for embedding without copying all URLs at once. Both the
46
+ * per-row and the bulk "Copy URLs" buttons now emit the PUBLIC no-auth URL
47
+ * (/v1/pub/:owner/:key, served when visibility==='public') instead of the authenticated,
48
+ * owner-scoped /v1/memory/files/:key — so a public file's link actually loads in a browser /
49
+ * <img>. Requires owner_gaii from the files list (added to GET /v1/memory/files).
44
50
  */
45
51
  import { h } from 'preact';
46
52
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -88,6 +94,7 @@ export default function MemoryTab({ session, showToast, onStats }) {
88
94
  const [mainTab, setMainTab] = useState('own'); // 'own' | 'discover' | 'remote'
89
95
  const [browseMode, setBrowseMode] = useState(null); // 'home' | 'remote' | 'discover' | null
90
96
  const [filterText, setFilterText] = useState('');
97
+ const [memArchived, setMemArchived] = useState(false); // false = active working set, true = archived-only view
91
98
  const [orgNames, setOrgNames] = useState({}); // organism uuid → display name
92
99
  const [collapsedGroups, setCollapsedGroups] = useState(() => {
93
100
  try { return new Set(JSON.parse(sessionStorage.getItem('aimeat.mem.groups.collapsed') || '[]')); } catch { return new Set(); }
@@ -145,7 +152,7 @@ export default function MemoryTab({ session, showToast, onStats }) {
145
152
 
146
153
  useEffect(() => {
147
154
  if (session) { loadMemories(); }
148
- }, [selectedAgent]);
155
+ }, [selectedAgent, memArchived]);
149
156
 
150
157
  async function loadFedConsents() {
151
158
  try {
@@ -231,16 +238,18 @@ export default function MemoryTab({ session, showToast, onStats }) {
231
238
  async function loadMemories() {
232
239
  try {
233
240
  const agentGaii = selectedAgent || undefined;
241
+ const memOpts = memArchived ? { archived: 'only' } : undefined;
234
242
  if (fullLoaded) {
235
- const list = await memoryService.listMemories(agentGaii);
243
+ const list = await memoryService.listMemories(agentGaii, memOpts);
236
244
  setMemories(Array.isArray(list) ? list : []);
237
- onStats?.({ memory: Array.isArray(list) ? list.length : 0 });
245
+ // Don't report the archived-only count as the headline memory stat (it's a filtered view).
246
+ if (!memArchived) onStats?.({ memory: Array.isArray(list) ? list.length : 0 });
238
247
  } else {
239
- const { items, quota } = await memoryService.listMemoriesMeta(agentGaii);
248
+ const { items, quota } = await memoryService.listMemoriesMeta(agentGaii, memOpts);
240
249
  setMemories(items);
241
250
  setMemQuota(quota);
242
251
  setValueCache({});
243
- onStats?.({ memory: items.length });
252
+ if (!memArchived) onStats?.({ memory: items.length });
244
253
  }
245
254
  } catch { setMemories([]); }
246
255
  }
@@ -830,6 +839,10 @@ export default function MemoryTab({ session, showToast, onStats }) {
830
839
  value=${filterText} onInput=${e => setFilterText(e.target.value)} />
831
840
  ${filterText && html`<button class="btn-ghost btn-sm" onClick=${() => setFilterText('')}>✕</button>`}
832
841
  </div>
842
+ <div class="search-bar">
843
+ <button class="btn-sm ${!memArchived ? 'btn-primary' : 'btn-outline'}" onClick=${() => setMemArchived(false)}>${t('profile.memory.viewActive') || 'Active'}</button>
844
+ <button class="btn-sm ${memArchived ? 'btn-primary' : 'btn-outline'}" onClick=${() => setMemArchived(true)}>${'🗄️ '}${t('profile.memory.viewArchived') || 'Archived'}</button>
845
+ </div>
833
846
  </div>
834
847
  <div class="action-bar mem-bottom-bar">
835
848
  <div class="mem-sort-bar">
@@ -917,7 +930,19 @@ export default function MemoryTab({ session, showToast, onStats }) {
917
930
  });
918
931
  };
919
932
 
920
- const fileUrls = filtered.map(f => `${NODE_URL}/v1/memory/files/${encodeURIComponent(f.key || f.name)}`).join('\n');
933
+ // Public, no-auth file URL (mirrors memory's /v1/memory/:gaii/:key public read):
934
+ // GET /v1/pub/:owner/:key serves the bytes when visibility==='public', so the copied
935
+ // link is embeddable anywhere (<img src>, another site, a chat). Keeps slashes in keys
936
+ // literal for the wildcard route; only encodes per-segment. Falls back to the legacy
937
+ // authenticated URL if the list response predates owner_gaii (server needs a restart).
938
+ const fileUrl = (f) => {
939
+ const owner = f.owner_gaii;
940
+ const keyPath = String(f.key || f.name).split('/').map(encodeURIComponent).join('/');
941
+ return owner
942
+ ? `${NODE_URL}/v1/pub/${encodeURIComponent(owner)}/${keyPath}`
943
+ : `${NODE_URL}/v1/memory/files/${encodeURIComponent(f.key || f.name)}`;
944
+ };
945
+ const fileUrls = filtered.map(fileUrl).join('\n');
921
946
 
922
947
  return html`
923
948
  <div class="action-bar">
@@ -965,6 +990,12 @@ export default function MemoryTab({ session, showToast, onStats }) {
965
990
  `}
966
991
  </div>
967
992
  <div class="file-actions">
993
+ <${CopyButton}
994
+ text=${fileUrl(f)}
995
+ label=${`\u{1F517} ${t('profile.files.copyUrl') || 'Copy URL'}`}
996
+ title=${t('profile.files.copyUrl') || 'Copy URL'}
997
+ className="btn-outline btn-sm"
998
+ onCopied=${() => showToast(t('profile.files.urlCopied') || 'URL copied to clipboard')} />
968
999
  <button class="btn-outline btn-sm" onClick=${() => handleDownloadFile(fKey)}>${t('profile.files.download')}</button>
969
1000
  <button class="btn-danger-solid btn-sm" onClick=${() => handleDeleteFile(fKey)}>${t('profile.files.delete')}</button>
970
1001
  </div>
@@ -194,6 +194,25 @@ export function OrganismHome({ org, ghii, showToast, initialSettings, onOpenWs,
194
194
  } catch { showToast(t('organisms.deleteError') || 'Failed to delete'); }
195
195
  }, { danger: true, title: t('organisms.deleteOrganismTitle') || 'Delete this organism' });
196
196
  };
197
+ // Archive / unarchive the whole organism (creator/admin) — read-only + hidden from AI materials,
198
+ // cascades to its workspaces, fully reversible (smart restore). Unlike delete, nothing is destroyed.
199
+ const doArchive = (archived) => {
200
+ confirm(
201
+ (archived
202
+ ? (t('organisms.confirmArchive') || 'Archive “{name}”? It becomes read-only and is hidden from AI operations until you unarchive it. Its workspaces are archived too.')
203
+ : (t('organisms.confirmUnarchive') || 'Unarchive “{name}”? It and the workspaces archived with it become active again.')
204
+ ).replace('{name}', org.name || org.id),
205
+ async () => {
206
+ try {
207
+ if (archived) await orgService.archiveContent(org.id, { level: 'organism' });
208
+ else await orgService.unarchiveContent(org.id, { level: 'organism' });
209
+ showToast(archived ? (t('organisms.organismArchived') || 'Organism archived') : (t('organisms.organismUnarchived') || 'Organism restored'));
210
+ onChanged?.();
211
+ } catch (e) { showToast((e && e.message) || 'Failed'); }
212
+ },
213
+ { title: archived ? (t('organisms.archive') || 'Archive') : (t('organisms.unarchive') || 'Unarchive') },
214
+ );
215
+ };
197
216
 
198
217
  const extraAdmins = (org.admins || []).filter(a => a !== org.creatorGhii);
199
218
  const visHint = t(`organisms.visHint.${form.visibility}`);
@@ -254,6 +273,19 @@ export function OrganismHome({ org, ghii, showToast, initialSettings, onOpenWs,
254
273
  ` : null}
255
274
  </div>
256
275
 
276
+ ${canEdit ? html`
277
+ <div class="pj-danger-box">
278
+ <div class="pj-danger-row">
279
+ <div class="pj-danger-text">
280
+ <div class="pj-danger-title">${org.archived ? (t('organisms.unarchiveOrganismTitle') || 'Unarchive this organism') : (t('organisms.archiveOrganismTitle') || 'Archive this organism')}</div>
281
+ <div class="pj-danger-sub">${org.archived
282
+ ? (t('organisms.unarchiveOrganismSub') || 'Make it active again. Workspaces archived together with it are restored.')
283
+ : (t('organisms.archiveOrganismSub') || 'Make it read-only and hide it (and its workspaces) from AI operations. Reversible — nothing is deleted.')}</div>
284
+ </div>
285
+ <button class="btn-outline btn-sm" onClick=${() => doArchive(!org.archived)}>${org.archived ? `♻️ ${t('organisms.unarchive') || 'Unarchive'}` : `🗄️ ${t('organisms.archive') || 'Archive'}`}</button>
286
+ </div>
287
+ </div>` : null}
288
+
257
289
  ${(isCreator || isMember) ? html`
258
290
  <div class="pj-danger pj-danger-box">
259
291
  ${isCreator ? html`
@@ -13,6 +13,8 @@
13
13
  * v1.1.0 — 2026-06-22 — Kill the per-workspace fetch storm: one discoverWorkspaces({include:'enrichment'})
14
14
  * replaces the 1 + 3N (getWorkspace+activity+participants) fan-out; pending-review counts come inline;
15
15
  * the 'organisms' live refresh is debounced (1.5s) so an agent-driven event burst is one reload.
16
+ * v1.2.0 — 2026-06-26 — Archive/unarchive a workspace (creator/admin) via the kebab menu + an
17
+ * "archived" badge; archived workspaces are read-only and hidden from AI materials.
16
18
  */
17
19
  import { h } from 'preact';
18
20
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
@@ -45,6 +47,7 @@ export function WorkspaceList({ org, showToast, onOpen, onCount }) {
45
47
  const [openReqWs, setOpenReqWs] = useState(null); // which owned workspace's request-inbox is open
46
48
  const [reqInbox, setReqInbox] = useState([]); // access requests for openReqWs
47
49
  const [openPeopleWs, setOpenPeopleWs] = useState(null); // which workspace's inline participants list is open
50
+ const [archivedOpen, setArchivedOpen] = useState(false); // collapsible "Archived workspaces" section
48
51
  const [wsStats, setWsStats] = useState({}); // wsId → { recs, docs, lastEv, hasManifest }
49
52
  const [apprByWs, setApprByWs] = useState({}); // wsId → pending review count
50
53
 
@@ -142,6 +145,19 @@ export function WorkspaceList({ org, showToast, onOpen, onCount }) {
142
145
  );
143
146
  };
144
147
 
148
+ // Archive / unarchive a whole workspace (creator/admin). Archived workspaces become read-only and
149
+ // drop out of AI materials (overview/read/search) but stay searchable in the archive + restorable.
150
+ const setArchived = async (w, archived) => {
151
+ setBusy(true);
152
+ try {
153
+ const target = { level: 'workspace', ws: w.id };
154
+ if (archived) await orgService.archiveContent(orgId, target); else await orgService.unarchiveContent(orgId, target);
155
+ await load();
156
+ showToast(archived ? (t('organisms.workspaceArchived') || 'Workspace archived') : (t('organisms.workspaceUnarchived') || 'Workspace restored'));
157
+ } catch (e) { showToast((e && e.message) || 'Failed'); }
158
+ finally { setBusy(false); }
159
+ };
160
+
145
161
  // ── Export (download ZIP backup) + Import (upload a ZIP as a new workspace) ──
146
162
  const jwt = () => { try { return window.AIMEAT?.auth?.getSession?.()?.jwt || ''; } catch { return ''; } };
147
163
  const fileRef = useRef(null);
@@ -207,14 +223,16 @@ export function WorkspaceList({ org, showToast, onOpen, onCount }) {
207
223
 
208
224
  ${list === null ? html`<${Spinner} />`
209
225
  : list.length === 0 ? html`<${EmptyState} icon="🗂️" text=${t('organisms.noWorkspaces') || 'No workspaces yet — create one to get started.'} />`
210
- : html`
211
- <div class="pj-org-list">
212
- ${list.map(w => {
226
+ : (() => {
227
+ const renderWsRow = (w) => {
213
228
  const locked = w.access === 'none';
214
229
  const reviews = apprByWs[w.id] || 0;
215
230
  const menuItems = w.access === 'owner' ? [
216
231
  { label: t('organisms.requests') || 'Access requests', icon: '👥', onClick: () => toggleInbox(w) },
217
232
  { label: t('organisms.export') || 'Export backup (.zip)', icon: '⬇', onClick: () => doExport(w) },
233
+ w.archived
234
+ ? { label: t('organisms.unarchive') || 'Unarchive', icon: '♻️', onClick: () => setArchived(w, false) }
235
+ : { label: t('organisms.archive') || 'Archive', icon: '🗄️', onClick: () => setArchived(w, true) },
218
236
  { label: t('organisms.delete') || 'Delete', danger: true, onClick: () => remove(w.id, w.name || w.id) },
219
237
  ] : [];
220
238
  return html`
@@ -226,6 +244,7 @@ export function WorkspaceList({ org, showToast, onOpen, onCount }) {
226
244
  <div class="pj-org-titlerow">
227
245
  ${locked ? html`<span class="pj-org-lock">${'🔒'}</span>` : null}
228
246
  <span class="pj-org-name">${(w.name || w.id)}</span>
247
+ ${w.archived ? html`<span class="pj-tab-pill" title=${t('organisms.archivedHint') || 'Archived — read-only, hidden from AI operations'}>${'🗄️ '}${t('organisms.archived') || 'archived'}</span>` : null}
229
248
  ${reviews > 0 ? html`<span class="pj-tab-pill">${'📨 '}${(t('organisms.toReview') || '{n} to review').replace('{n}', String(reviews))}</span>` : null}
230
249
  </div>
231
250
  <div class="pj-org-desc">${locked
@@ -265,8 +284,20 @@ export function WorkspaceList({ org, showToast, onOpen, onCount }) {
265
284
  </div>`)}
266
285
  </div>` : null}
267
286
  </div>`;
268
- })}
269
- </div>`}
287
+ };
288
+ // Active workspaces in the main list; archived ones collapse into a section below (restorable).
289
+ const active = list.filter(w => !w.archived);
290
+ const archived = list.filter(w => w.archived);
291
+ return html`
292
+ <div class="pj-org-list">${active.map(renderWsRow)}</div>
293
+ ${archived.length > 0 ? html`
294
+ <button class="pj-struct-toggle section-title-spaced" aria-expanded=${archivedOpen} onClick=${() => setArchivedOpen(o => !o)}>
295
+ <span class="pj-struct-caret">${archivedOpen ? '▾' : '▸'}</span>
296
+ <span>${'🗄️ '}${(t('organisms.archivedWorkspacesSection') || 'Archived workspaces ({n})').replace('{n}', String(archived.length))}</span>
297
+ </button>
298
+ ${archivedOpen ? html`<div class="pj-org-list">${archived.map(renderWsRow)}</div>` : null}` : null}
299
+ `;
300
+ })()}
270
301
 
271
302
  ${overview && showOverview ? html`
272
303
  <div class="pj-chart">
@@ -151,6 +151,7 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
151
151
  const [editingSec, setEditingSec] = useState(null); // section id currently being renamed inline
152
152
  const draggedDoc = useRef(null); // { type, id } of the doc being dragged
153
153
  const [showRegenerate, setShowRegenerate] = useState(false);
154
+ const [showArchived, setShowArchived] = useState(false); // false = active records, true = archived-only view
154
155
  const [delConfirm, setDelConfirm] = useState(''); // typed-name confirmation for delete
155
156
  const [newSpaceName, setNewSpaceName] = useState('');
156
157
  const [addingInitial, setAddingInitial] = useState(null); // record being edited (null = new draft)
@@ -196,7 +197,8 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
196
197
  }, [seen, wsEvents]);
197
198
 
198
199
  const load = useCallback(async () => {
199
- const w = await orgService.getWorkspace(orgId, wsId).catch(() => null);
200
+ // In the archived view, request ONLY archived content (read-only); otherwise the normal active set.
201
+ const w = await orgService.getWorkspace(orgId, wsId, showArchived ? { archived: 'only' } : undefined).catch(() => null);
200
202
  if (w && w.manifest) {
201
203
  const [ap, cfg, secs, act, cols] = await Promise.all([
202
204
  orgService.listApprovals(orgId, 'pending').catch(() => []),
@@ -209,7 +211,7 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
209
211
  setWsEvents(act?.events || []); setColorsByType(cols);
210
212
  }
211
213
  setWs(w && w.manifest ? w : null);
212
- }, [orgId, wsId]);
214
+ }, [orgId, wsId, showArchived]);
213
215
 
214
216
  useEffect(() => { load(); }, [load]);
215
217
 
@@ -575,6 +577,20 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
575
577
  finally { setBusy(false); }
576
578
  }, [orgId, wsId, showToast, load]);
577
579
 
580
+ // Archive / unarchive ONE record or document instance (read-only + hidden from AI; restorable). The
581
+ // record level archives the whole instance subtree (bare + .draft/.latest/.version.*) by its base key.
582
+ const setRecordArchived = useCallback(async (ot, instanceId, archived) => {
583
+ setBusy(true);
584
+ try {
585
+ const key = `organism.${orgId}.w.${wsId}.${ot.namespace}.${instanceId}`;
586
+ if (archived) await orgService.archiveContent(orgId, { level: 'record', ws: wsId, key });
587
+ else await orgService.unarchiveContent(orgId, { level: 'record', ws: wsId, key });
588
+ showToast(archived ? (t('organisms.recordArchived') || 'Archived') : (t('organisms.recordUnarchived') || 'Restored'));
589
+ await load();
590
+ } catch (e) { showToast((e && e.message) || 'Failed'); }
591
+ finally { setBusy(false); }
592
+ }, [orgId, wsId, showToast, load]);
593
+
578
594
  const resolve = useCallback(async (aid, decision) => {
579
595
  setBusy(true);
580
596
  try { await orgService.resolveApproval(orgId, aid, decision); showToast(decision === 'approve' ? (t('organisms.approved') || 'Approved') : (t('organisms.rejected') || 'Rejected')); await load(); }
@@ -918,6 +934,9 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
918
934
  <button class="pj-doc-link" onClick=${() => setActiveDoc({ type: ot.name, mode: 'view', page: d })}>
919
935
  ${d._draft ? html`<span class="badge badge-warn pj-mini">${t('organisms.draft') || 'draft'}</span> ` : ''}${d.title || d.id}
920
936
  </button>
937
+ ${showArchived
938
+ ? html`<button class="pj-icon-btn" title=${t('organisms.unarchive') || 'Unarchive'} disabled=${busy} onClick=${() => setRecordArchived(ot, d.id, false)}>♻️</button>`
939
+ : html`<button class="pj-icon-btn" title=${t('organisms.archive') || 'Archive'} disabled=${busy} onClick=${() => setRecordArchived(ot, d.id, true)}>🗄️</button>`}
921
940
  <button class="pj-icon-btn pj-doc-del" title=${t('organisms.delete') || 'Delete'} disabled=${busy} onClick=${() => removeObject(ot.namespace, d.id, d.title || d.id)}>🗑</button>
922
941
  </div>`;
923
942
 
@@ -1051,8 +1070,11 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
1051
1070
  <${ColorPicker} value=${itemColor(ot.name, o.id)} onPick=${(c) => setItemColor(ot.name, o.id, c)} />
1052
1071
  <button class="pj-rec-title" onClick=${() => toggleExpand(ot, o.id)}>${String(o[PRIMARY_FIELD[ot.name] || 'title'] || o.summary || o.id || '')}</button>
1053
1072
  ${o.status ? html`<span class="badge badge-info">${(o.status)}</span>` : null}
1054
- ${!draftsFor(ot.name).some(dr => dr.id === o.id) ? html`
1073
+ ${!showArchived && !draftsFor(ot.name).some(dr => dr.id === o.id) ? html`
1055
1074
  <button class="btn-ghost btn-sm" title=${t('organisms.reopenEditHint') || 'Reopen for editing — creates an editable draft from the published version'} disabled=${busy} onClick=${() => reopen(ot, o.id)}>${t('organisms.edit') || 'Edit'}</button>` : null}
1075
+ ${showArchived
1076
+ ? html`<button class="btn-ghost btn-sm" title=${t('organisms.unarchive') || 'Unarchive'} disabled=${busy} onClick=${() => setRecordArchived(ot, o.id, false)}>${'♻️ '}${t('organisms.unarchive') || 'Unarchive'}</button>`
1077
+ : html`<button class="pj-icon-btn" title=${t('organisms.archive') || 'Archive'} disabled=${busy} onClick=${() => setRecordArchived(ot, o.id, true)}>🗄️</button>`}
1056
1078
  <button class="pj-icon-btn" title=${t('organisms.delete') || 'Delete'} disabled=${busy} onClick=${() => removeObject(ot.namespace, o.id, String(o[PRIMARY_FIELD[ot.name] || 'title'] || o.id))}>🗑</button>
1057
1079
  </div>
1058
1080
  ${expandedRec[ot.name + ':' + o.id] ? html`<div class="pj-rec-fields">${recordFields(ot, o)}</div><${WorkspaceComments} orgId=${orgId} ws=${wsId} space=${ot.name} instanceId=${o.id} showToast=${showToast} batched=${true} initialComments=${commentsByKey[cKey(wsId, ot.name, o.id)]} onReload=${reloadComments} />` : null}
@@ -1364,8 +1386,11 @@ export function Workspace({ org, wsId, showToast, onBack, onBackToList, initialS
1364
1386
  ${back}
1365
1387
  <div class="pj-ws-titlerow">
1366
1388
  <span class="section-title pj-ws-title">${(ws.manifest?.name || org.name || 'Workspace')}</span>
1367
- <span class="badge badge-success">${(ws.manifest?.status || 'active')}</span>
1389
+ ${showArchived
1390
+ ? html`<span class="pj-tab-pill" title=${t('organisms.archivedViewHint') || 'Showing archived (read-only) records — restore one with ♻️'}>${'🗄️ '}${t('organisms.archivedView') || 'Archived view'}</span>`
1391
+ : html`<span class="badge badge-success">${(ws.manifest?.status || 'active')}</span>`}
1368
1392
  <span class="pj-ws-head-actions">
1393
+ <button class="btn-outline btn-sm ${showArchived ? 'pj-org-btn-active' : ''}" title=${t('organisms.toggleArchivedHint') || 'View/hide archived records'} onClick=${() => setShowArchived(s => !s)}>${showArchived ? `↩ ${t('organisms.viewActive') || 'Active'}` : `🗄️ ${t('organisms.viewArchived') || 'Archived'}`}</button>
1369
1394
  <${KebabMenu} label=${t('organisms.forAgentsHint') || 'Copy a ready prompt that teaches an AI to use this workspace'}
1370
1395
  btnClass="btn-outline btn-sm" trigger=${'🤖 ' + (t('organisms.agentAccess') || 'Agent access') + ' ▾'} items=${agentMenuItems} />
1371
1396
  <button class="btn-outline btn-sm ${showSettings ? 'pj-org-btn-active' : ''}" onClick=${() => guardWsDirty(() => setShowSettings(s => !s))}>${'⚙ '}${t('organisms.settings') || 'Settings'}</button>