@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,605 @@
1
+ import { sql } from "bun";
2
+ import { type CloudResourceRef, CloudResourceRefSchema } from "../contracts/capabilities";
3
+ import { logger } from "../services/logging";
4
+ import { withAiShortId } from "./short-id";
5
+
6
+ export const AI_MEMORY_CONTENT_MAX_CHARS = 500;
7
+ export const AI_MEMORY_HOT_MAX_ITEMS = 20;
8
+ export const AI_MEMORY_HOT_MAX_CHARS = 6_000;
9
+
10
+ export type AiMemoryKind = "fact" | "preference" | "workflow";
11
+ export type AiMemoryPriority = "normal" | "pinned";
12
+ export type AiMemorySource = "user" | "agent" | "background";
13
+
14
+ export type AiBackgroundMemoryProposal = {
15
+ action: "add" | "replace" | "merge" | "retire";
16
+ kind: AiMemoryKind;
17
+ content: string;
18
+ memoryIds: string[];
19
+ resourceRef: CloudResourceRef | null;
20
+ };
21
+
22
+ export type AiBackgroundMemoryChange = {
23
+ action: "added" | "updated" | "merged" | "retired";
24
+ kind: AiMemoryKind;
25
+ content: string;
26
+ previousContent?: string;
27
+ resourceRef?: CloudResourceRef;
28
+ };
29
+
30
+ export type AiMemory = {
31
+ id: string;
32
+ shortId: string;
33
+ userId: string;
34
+ kind: AiMemoryKind;
35
+ content: string;
36
+ priority: AiMemoryPriority;
37
+ source: AiMemorySource;
38
+ sourceConversationId: string | null;
39
+ sourceMessageId: string | null;
40
+ resourceRef: CloudResourceRef | null;
41
+ createdAt: string;
42
+ updatedAt: string;
43
+ };
44
+
45
+ type MemoryRow = {
46
+ id: string;
47
+ short_id: string;
48
+ user_id: string;
49
+ kind: AiMemoryKind;
50
+ content: string;
51
+ priority: AiMemoryPriority;
52
+ source: AiMemorySource;
53
+ source_conversation_id: string | null;
54
+ source_message_id: string | null;
55
+ resource_type: string | null;
56
+ resource_id: string | null;
57
+ created_at: string | Date;
58
+ updated_at: string | Date;
59
+ };
60
+
61
+ type SearchBackend = "native" | "bm25";
62
+
63
+ const BM25_INDEX = "ai.memories_search_bm25_idx";
64
+ const BM25_CAPABILITY_ERROR_CODES = new Set(["0A000", "42704", "42883", "55000"]);
65
+ const log = logger("ai:memories");
66
+
67
+ let backendPromise: Promise<SearchBackend> | null = null;
68
+
69
+ const toMemory = (row: MemoryRow): AiMemory => ({
70
+ id: row.id,
71
+ shortId: row.short_id,
72
+ userId: row.user_id,
73
+ kind: row.kind,
74
+ content: row.content,
75
+ priority: row.priority,
76
+ source: row.source,
77
+ sourceConversationId: row.source_conversation_id,
78
+ sourceMessageId: row.source_message_id,
79
+ resourceRef: row.resource_type && row.resource_id ? { type: row.resource_type, id: row.resource_id } : null,
80
+ createdAt: new Date(row.created_at).toISOString(),
81
+ updatedAt: new Date(row.updated_at).toISOString(),
82
+ });
83
+
84
+ const normalizeContent = (content: string): string => content.replace(/\s+/g, " ").trim().slice(0, AI_MEMORY_CONTENT_MAX_CHARS);
85
+
86
+ const detectSearchBackend = async (): Promise<SearchBackend> => {
87
+ const [row] = await sql<{ available: boolean }[]>`
88
+ SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_textsearch')
89
+ AND EXISTS (
90
+ SELECT 1
91
+ FROM pg_class index_class
92
+ JOIN pg_am access_method ON access_method.oid = index_class.relam
93
+ WHERE index_class.oid = to_regclass(${BM25_INDEX})
94
+ AND access_method.amname = 'bm25'
95
+ ) AS available
96
+ `;
97
+ const backend = row?.available ? "bm25" : "native";
98
+ log.info("Memory search backend active", { backend });
99
+ return backend;
100
+ };
101
+
102
+ export const getAiMemorySearchBackend = (): Promise<SearchBackend> => {
103
+ backendPromise ??= detectSearchBackend().catch((error) => {
104
+ log.warn("Memory search backend detection failed; using native PostgreSQL FTS", {
105
+ error: error instanceof Error ? error.message : String(error),
106
+ });
107
+ return "native";
108
+ });
109
+ return backendPromise;
110
+ };
111
+
112
+ /** Test and migration hook. Runtime callers should use getAiMemorySearchBackend(). */
113
+ export const resetAiMemorySearchBackend = (): void => {
114
+ backendPromise = null;
115
+ };
116
+
117
+ export const isAiMemoryBm25CapabilityError = (error: unknown): boolean => {
118
+ if (typeof error !== "object" || error === null || !("code" in error)) return false;
119
+ return BM25_CAPABILITY_ERROR_CODES.has(String(error.code));
120
+ };
121
+
122
+ const listActive = async (userId: string, limit: number): Promise<AiMemory[]> => {
123
+ const rows = await sql<MemoryRow[]>`
124
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
125
+ resource_type, resource_id, created_at, updated_at
126
+ FROM ai.memories
127
+ WHERE user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
128
+ ORDER BY (priority = 'pinned') DESC, updated_at DESC, id ASC
129
+ LIMIT ${limit}
130
+ `;
131
+ return rows.map(toMemory);
132
+ };
133
+
134
+ const searchRows = async (input: { userId: string; query: string; limit: number; backend: SearchBackend }): Promise<AiMemory[]> => {
135
+ const query = input.query.trim();
136
+ if (!query) return listActive(input.userId, input.limit);
137
+
138
+ const rows =
139
+ input.backend === "bm25"
140
+ ? await sql<MemoryRow[]>`
141
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
142
+ resource_type, resource_id, created_at, updated_at
143
+ FROM ai.memories
144
+ WHERE user_id = ${input.userId}::uuid
145
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
146
+ AND search_document @@ websearch_to_tsquery('simple', ${query})
147
+ ORDER BY
148
+ (priority = 'pinned') DESC,
149
+ content <@> to_bm25query(${query}, ${BM25_INDEX}),
150
+ updated_at DESC,
151
+ id ASC
152
+ LIMIT ${input.limit}
153
+ `
154
+ : await sql<MemoryRow[]>`
155
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
156
+ resource_type, resource_id, created_at, updated_at
157
+ FROM ai.memories
158
+ WHERE user_id = ${input.userId}::uuid
159
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
160
+ AND search_document @@ websearch_to_tsquery('simple', ${query})
161
+ ORDER BY
162
+ (priority = 'pinned') DESC,
163
+ ts_rank_cd(search_document, websearch_to_tsquery('simple', ${query})) DESC,
164
+ updated_at DESC,
165
+ id ASC
166
+ LIMIT ${input.limit}
167
+ `;
168
+ return rows.map(toMemory);
169
+ };
170
+
171
+ const search = async (input: { userId: string; query?: string; limit?: number }): Promise<AiMemory[]> => {
172
+ const query = input.query?.trim() ?? "";
173
+ const limit = Math.min(Math.max(input.limit ?? 20, 1), 50);
174
+ const backend = query ? await getAiMemorySearchBackend() : "native";
175
+ try {
176
+ return await searchRows({ userId: input.userId, query, limit, backend });
177
+ } catch (error) {
178
+ if (backend !== "bm25" || !isAiMemoryBm25CapabilityError(error)) throw error;
179
+ log.warn("Memory BM25 query failed; falling back to native PostgreSQL FTS", {
180
+ error: error instanceof Error ? error.message : String(error),
181
+ });
182
+ backendPromise = Promise.resolve("native");
183
+ return searchRows({ userId: input.userId, query, limit, backend: "native" });
184
+ }
185
+ };
186
+
187
+ export const formatAiMemories = (
188
+ memories: AiMemory[],
189
+ maxChars = AI_MEMORY_HOT_MAX_CHARS,
190
+ ): { text: string; included: AiMemory[]; truncated: boolean } => {
191
+ const lines: string[] = [];
192
+ const included: AiMemory[] = [];
193
+ let used = 0;
194
+ for (const memory of memories) {
195
+ const date = memory.updatedAt.slice(0, 10);
196
+ const resource = memory.resourceRef ? ` [Cloud resource ${memory.resourceRef.type}:${memory.resourceRef.id}]` : "";
197
+ const line = `- [${date}] ${memory.kind}: ${memory.content}${resource}`;
198
+ if (used + line.length + (lines.length > 0 ? 1 : 0) > maxChars) break;
199
+ lines.push(line);
200
+ included.push(memory);
201
+ used += line.length + (lines.length > 1 ? 1 : 0);
202
+ }
203
+ return { text: lines.join("\n"), included, truncated: included.length < memories.length };
204
+ };
205
+
206
+ export const aiMemories = {
207
+ list: search,
208
+
209
+ async resolveSourceConversationShortIds(userId: string, conversationIds: string[]): Promise<Map<string, string>> {
210
+ if (conversationIds.length === 0) return new Map();
211
+ const rows = await sql<{ id: string; short_id: string }[]>`
212
+ SELECT id, short_id
213
+ FROM ai.conversations
214
+ WHERE created_by_user_id = ${userId}::uuid AND id IN ${sql(conversationIds)}
215
+ `;
216
+ return new Map(rows.map((row) => [row.id, row.short_id]));
217
+ },
218
+
219
+ async wasDeleted(userId: string, content: string): Promise<boolean> {
220
+ const normalized = normalizeContent(content);
221
+ if (!normalized) return false;
222
+ const [row] = await sql<{ exists: boolean }[]>`
223
+ SELECT EXISTS (
224
+ SELECT 1 FROM ai.memories
225
+ WHERE user_id = ${userId}::uuid AND lower(content) = lower(${normalized}) AND deleted_at IS NOT NULL
226
+ ) AS exists
227
+ `;
228
+ return Boolean(row?.exists);
229
+ },
230
+
231
+ async get(userId: string, memoryId: string): Promise<AiMemory | null> {
232
+ const rows = await sql<MemoryRow[]>`
233
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
234
+ resource_type, resource_id, created_at, updated_at
235
+ FROM ai.memories
236
+ WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
237
+ `;
238
+ return rows[0] ? toMemory(rows[0]) : null;
239
+ },
240
+
241
+ async getByShortId(userId: string, shortId: string): Promise<AiMemory | null> {
242
+ const rows = await sql<MemoryRow[]>`
243
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
244
+ resource_type, resource_id, created_at, updated_at
245
+ FROM ai.memories
246
+ WHERE short_id = ${shortId} AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
247
+ `;
248
+ return rows[0] ? toMemory(rows[0]) : null;
249
+ },
250
+
251
+ async create(input: {
252
+ userId: string;
253
+ kind: AiMemoryKind;
254
+ content: string;
255
+ priority?: AiMemoryPriority;
256
+ source?: AiMemorySource;
257
+ sourceConversationId?: string;
258
+ sourceMessageId?: string;
259
+ resourceRef?: CloudResourceRef;
260
+ }): Promise<AiMemory> {
261
+ const content = normalizeContent(input.content);
262
+ if (!content) throw new Error("Memory content is required.");
263
+ if (input.sourceMessageId && !input.sourceConversationId) {
264
+ throw new Error("A memory source message requires its source conversation.");
265
+ }
266
+ const resourceRef = input.resourceRef ? CloudResourceRefSchema.parse(input.resourceRef) : null;
267
+ if ((input.kind === "workflow") !== Boolean(resourceRef)) throw new Error("Workflow memories require exactly one Cloud resource.");
268
+ const rows = await withAiShortId(
269
+ "idx_ai_memories_short_id",
270
+ (shortId) => sql<MemoryRow[]>`
271
+ INSERT INTO ai.memories (
272
+ short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id, resource_type, resource_id
273
+ )
274
+ SELECT
275
+ ${shortId},
276
+ ${input.userId}::uuid,
277
+ ${input.kind},
278
+ ${content},
279
+ ${input.priority ?? "normal"},
280
+ ${input.source ?? "user"},
281
+ ${input.sourceConversationId ?? null}::uuid,
282
+ ${input.sourceMessageId ?? null}::uuid,
283
+ ${resourceRef?.type ?? null},
284
+ ${resourceRef?.id ?? null}
285
+ WHERE ${input.sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
286
+ SELECT 1 FROM ai.conversations c
287
+ WHERE c.id = ${input.sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${input.userId}::uuid
288
+ AND (${input.sourceMessageId ?? null}::uuid IS NULL OR EXISTS (
289
+ SELECT 1 FROM ai.messages m
290
+ WHERE m.id = ${input.sourceMessageId ?? null}::uuid AND m.conversation_id = c.id
291
+ ))
292
+ )
293
+ ON CONFLICT (user_id, lower(content)) WHERE deleted_at IS NULL AND superseded_by_id IS NULL
294
+ DO UPDATE SET updated_at = ai.memories.updated_at
295
+ RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
296
+ resource_type, resource_id, created_at, updated_at
297
+ `,
298
+ );
299
+ if (!rows[0]) throw new Error("Memory source conversation is unavailable.");
300
+ return toMemory(rows[0]!);
301
+ },
302
+
303
+ async update(
304
+ userId: string,
305
+ memoryId: string,
306
+ patch: {
307
+ kind?: AiMemoryKind;
308
+ content?: string;
309
+ priority?: AiMemoryPriority;
310
+ source?: AiMemorySource;
311
+ sourceConversationId?: string;
312
+ resourceRef?: CloudResourceRef | null;
313
+ },
314
+ ): Promise<AiMemory | null> {
315
+ const content = patch.content === undefined ? null : normalizeContent(patch.content);
316
+ if (patch.content !== undefined && !content) throw new Error("Memory content is required.");
317
+ const resourceRef = patch.resourceRef ? CloudResourceRefSchema.parse(patch.resourceRef) : null;
318
+ const rows = await sql<MemoryRow[]>`
319
+ UPDATE ai.memories
320
+ SET kind = COALESCE(${patch.kind ?? null}, kind),
321
+ content = COALESCE(${content}, content),
322
+ priority = COALESCE(${patch.priority ?? null}, priority),
323
+ source = COALESCE(${patch.source ?? null}, source),
324
+ source_conversation_id = COALESCE(${patch.sourceConversationId ?? null}::uuid, source_conversation_id),
325
+ resource_type = CASE WHEN ${patch.resourceRef === undefined} THEN resource_type ELSE ${resourceRef?.type ?? null} END,
326
+ resource_id = CASE WHEN ${patch.resourceRef === undefined} THEN resource_id ELSE ${resourceRef?.id ?? null} END,
327
+ updated_at = now()
328
+ WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
329
+ AND (${patch.sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
330
+ SELECT 1 FROM ai.conversations c
331
+ WHERE c.id = ${patch.sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${userId}::uuid
332
+ ))
333
+ RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
334
+ resource_type, resource_id, created_at, updated_at
335
+ `;
336
+ return rows[0] ? toMemory(rows[0]) : null;
337
+ },
338
+
339
+ async updateByShortId(
340
+ userId: string,
341
+ shortId: string,
342
+ patch: {
343
+ kind?: AiMemoryKind;
344
+ content?: string;
345
+ priority?: AiMemoryPriority;
346
+ source?: AiMemorySource;
347
+ sourceConversationId?: string;
348
+ resourceRef?: CloudResourceRef | null;
349
+ },
350
+ ): Promise<AiMemory | null> {
351
+ const memory = await this.getByShortId(userId, shortId);
352
+ return memory ? this.update(userId, memory.id, patch) : null;
353
+ },
354
+
355
+ async delete(userId: string, memoryId: string): Promise<boolean> {
356
+ const rows = await sql<{ id: string }[]>`
357
+ UPDATE ai.memories SET deleted_at = now(), updated_at = now()
358
+ WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
359
+ RETURNING id
360
+ `;
361
+ return Boolean(rows[0]);
362
+ },
363
+
364
+ async deleteByShortId(userId: string, shortId: string): Promise<boolean> {
365
+ const rows = await sql<{ id: string }[]>`
366
+ UPDATE ai.memories SET deleted_at = now(), updated_at = now()
367
+ WHERE short_id = ${shortId} AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
368
+ RETURNING id
369
+ `;
370
+ return Boolean(rows[0]);
371
+ },
372
+
373
+ async supersede(userId: string, memoryId: string, supersededById: string, sourceConversationId?: string): Promise<boolean> {
374
+ if (memoryId === supersededById) return false;
375
+ const rows = await sql<{ id: string }[]>`
376
+ UPDATE ai.memories old
377
+ SET superseded_by_id = newer.id,
378
+ source = 'background',
379
+ source_conversation_id = COALESCE(${sourceConversationId ?? null}::uuid, old.source_conversation_id),
380
+ updated_at = now()
381
+ FROM ai.memories newer
382
+ WHERE old.id = ${memoryId}::uuid
383
+ AND old.user_id = ${userId}::uuid
384
+ AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
385
+ AND newer.id = ${supersededById}::uuid
386
+ AND newer.user_id = old.user_id
387
+ AND newer.deleted_at IS NULL AND newer.superseded_by_id IS NULL
388
+ AND (${sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
389
+ SELECT 1 FROM ai.conversations c
390
+ WHERE c.id = ${sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${userId}::uuid
391
+ ))
392
+ RETURNING old.id
393
+ `;
394
+ return Boolean(rows[0]);
395
+ },
396
+
397
+ /**
398
+ * Apply one model proposal as an ownership-checked transaction. Background
399
+ * learning may only mutate normal background memories; user, agent, and
400
+ * pinned entries remain authoritative.
401
+ */
402
+ async applyBackgroundProposal(input: {
403
+ userId: string;
404
+ sourceConversationId: string;
405
+ sourceMessageId?: string;
406
+ proposal: AiBackgroundMemoryProposal;
407
+ }): Promise<AiBackgroundMemoryChange[]> {
408
+ const proposal = input.proposal;
409
+ const content = normalizeContent(proposal.content);
410
+ const memoryIds = [...new Set(proposal.memoryIds.map((id) => id.trim()).filter(Boolean))].slice(0, 5);
411
+ const resourceRef = proposal.resourceRef ? CloudResourceRefSchema.parse(proposal.resourceRef) : null;
412
+ const needsContent = proposal.action !== "retire";
413
+ if (needsContent && !content) return [];
414
+ if ((proposal.kind === "workflow") !== Boolean(resourceRef)) return [];
415
+ if (proposal.action === "add" && memoryIds.length > 0) return [];
416
+ if (proposal.action === "replace" && memoryIds.length !== 1) return [];
417
+ if (proposal.action === "merge" && memoryIds.length < 2) return [];
418
+ if (proposal.action === "retire" && memoryIds.length < 1) return [];
419
+
420
+ return sql.begin(async (tx) => {
421
+ const [source] = await tx<{ id: string }[]>`
422
+ SELECT c.id
423
+ FROM ai.conversations c
424
+ WHERE c.id = ${input.sourceConversationId}::uuid
425
+ AND c.created_by_user_id = ${input.userId}::uuid
426
+ AND (${input.sourceMessageId ?? null}::uuid IS NULL OR EXISTS (
427
+ SELECT 1 FROM ai.messages m
428
+ WHERE m.id = ${input.sourceMessageId ?? null}::uuid AND m.conversation_id = c.id
429
+ ))
430
+ FOR UPDATE
431
+ `;
432
+ if (!source) return [];
433
+
434
+ const targets = memoryIds.length
435
+ ? await tx<MemoryRow[]>`
436
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
437
+ resource_type, resource_id, created_at, updated_at
438
+ FROM ai.memories
439
+ WHERE user_id = ${input.userId}::uuid
440
+ AND short_id IN ${tx(memoryIds)}
441
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
442
+ FOR UPDATE
443
+ `
444
+ : [];
445
+ if (targets.length !== memoryIds.length || targets.some((memory) => memory.source !== "background" || memory.priority !== "normal")) {
446
+ return [];
447
+ }
448
+ if (targets.some((memory) => memory.kind !== proposal.kind)) return [];
449
+
450
+ if (proposal.action === "retire") {
451
+ await tx`
452
+ UPDATE ai.memories
453
+ SET deleted_at = now(), source_conversation_id = ${input.sourceConversationId}::uuid, updated_at = now()
454
+ WHERE user_id = ${input.userId}::uuid AND id IN ${tx(targets.map((memory) => memory.id))}
455
+ AND source = 'background' AND priority = 'normal'
456
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
457
+ `;
458
+ return targets.map((memory) => ({
459
+ action: "retired" as const,
460
+ kind: memory.kind,
461
+ content: memory.content,
462
+ ...(memory.resource_type && memory.resource_id
463
+ ? { resourceRef: { type: memory.resource_type, id: memory.resource_id } }
464
+ : {}),
465
+ }));
466
+ }
467
+
468
+ const [duplicate] = await tx<MemoryRow[]>`
469
+ SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
470
+ resource_type, resource_id, created_at, updated_at
471
+ FROM ai.memories
472
+ WHERE user_id = ${input.userId}::uuid AND lower(content) = lower(${content})
473
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
474
+ FOR UPDATE
475
+ `;
476
+
477
+ if (proposal.action === "add") {
478
+ if (duplicate) {
479
+ if (duplicate.source === "background" && duplicate.priority === "normal") {
480
+ await tx`
481
+ UPDATE ai.memories
482
+ SET updated_at = now()
483
+ WHERE id = ${duplicate.id}::uuid AND user_id = ${input.userId}::uuid
484
+ `;
485
+ }
486
+ return [];
487
+ }
488
+ const [deleted] = await tx<{ exists: boolean }[]>`
489
+ SELECT EXISTS (
490
+ SELECT 1 FROM ai.memories
491
+ WHERE user_id = ${input.userId}::uuid AND lower(content) = lower(${content}) AND deleted_at IS NOT NULL
492
+ ) AS exists
493
+ `;
494
+ if (deleted?.exists) return [];
495
+ const rows = await withAiShortId(
496
+ "idx_ai_memories_short_id",
497
+ (shortId) => tx<MemoryRow[]>`
498
+ INSERT INTO ai.memories (
499
+ short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
500
+ resource_type, resource_id
501
+ ) VALUES (
502
+ ${shortId}, ${input.userId}::uuid, ${proposal.kind}, ${content}, 'normal', 'background',
503
+ ${input.sourceConversationId}::uuid, ${input.sourceMessageId ?? null}::uuid,
504
+ ${resourceRef?.type ?? null}, ${resourceRef?.id ?? null}
505
+ )
506
+ RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
507
+ resource_type, resource_id, created_at, updated_at
508
+ `,
509
+ );
510
+ const created = rows[0];
511
+ return created
512
+ ? [{
513
+ action: "added",
514
+ kind: created.kind,
515
+ content: created.content,
516
+ ...(resourceRef ? { resourceRef } : {}),
517
+ }]
518
+ : [];
519
+ }
520
+
521
+ if (duplicate && !targets.some((memory) => memory.id === duplicate.id)) {
522
+ const duplicateResourceMatches =
523
+ proposal.kind !== "workflow" ||
524
+ (duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
525
+ if (duplicate.kind !== proposal.kind || !duplicateResourceMatches) return [];
526
+ await tx`
527
+ UPDATE ai.memories old
528
+ SET superseded_by_id = ${duplicate.id}::uuid,
529
+ source_conversation_id = ${input.sourceConversationId}::uuid,
530
+ updated_at = now()
531
+ WHERE old.user_id = ${input.userId}::uuid AND old.id IN ${tx(targets.map((memory) => memory.id))}
532
+ AND old.source = 'background' AND old.priority = 'normal'
533
+ AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
534
+ `;
535
+ return [{
536
+ action: "merged",
537
+ kind: duplicate.kind,
538
+ content: duplicate.content,
539
+ previousContent: targets.map((memory) => memory.content).join(" · "),
540
+ ...(duplicate.resource_type && duplicate.resource_id
541
+ ? { resourceRef: { type: duplicate.resource_type, id: duplicate.resource_id } }
542
+ : {}),
543
+ }];
544
+ }
545
+
546
+ const destination = targets[0]!;
547
+ await tx`
548
+ UPDATE ai.memories
549
+ SET kind = ${proposal.kind}, content = ${content}, resource_type = ${resourceRef?.type ?? null},
550
+ resource_id = ${resourceRef?.id ?? null}, source_conversation_id = ${input.sourceConversationId}::uuid,
551
+ source_message_id = COALESCE(${input.sourceMessageId ?? null}::uuid, source_message_id),
552
+ updated_at = now()
553
+ WHERE id = ${destination.id}::uuid AND user_id = ${input.userId}::uuid
554
+ AND source = 'background' AND priority = 'normal'
555
+ `;
556
+ const superseded = targets.slice(1);
557
+ if (superseded.length > 0) {
558
+ await tx`
559
+ UPDATE ai.memories
560
+ SET superseded_by_id = ${destination.id}::uuid,
561
+ source_conversation_id = ${input.sourceConversationId}::uuid,
562
+ updated_at = now()
563
+ WHERE user_id = ${input.userId}::uuid AND id IN ${tx(superseded.map((memory) => memory.id))}
564
+ AND source = 'background' AND priority = 'normal'
565
+ AND deleted_at IS NULL AND superseded_by_id IS NULL
566
+ `;
567
+ }
568
+ return [{
569
+ action: proposal.action === "merge" ? "merged" : "updated",
570
+ kind: proposal.kind,
571
+ content,
572
+ previousContent: targets.map((memory) => memory.content).join(" · "),
573
+ ...(resourceRef ? { resourceRef } : {}),
574
+ }];
575
+ });
576
+ },
577
+
578
+ async selectHot(userId: string, query: string): Promise<{ text: string; memories: AiMemory[]; truncated: boolean }> {
579
+ const [countRow] = await sql<{ count: number }[]>`
580
+ SELECT COUNT(*)::int AS count
581
+ FROM ai.memories
582
+ WHERE user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
583
+ `;
584
+ const count = countRow?.count ?? 0;
585
+ if (count <= AI_MEMORY_HOT_MAX_ITEMS) {
586
+ const memories = await listActive(userId, AI_MEMORY_HOT_MAX_ITEMS);
587
+ const formatted = formatAiMemories(memories);
588
+ return { text: formatted.text, memories: formatted.included, truncated: formatted.truncated };
589
+ }
590
+
591
+ const [pinned, relevant, recent] = await Promise.all([
592
+ listActive(userId, 50).then((items) => items.filter((item) => item.priority === "pinned")),
593
+ search({ userId, query, limit: 40 }),
594
+ listActive(userId, 40),
595
+ ]);
596
+ const seen = new Set<string>();
597
+ const ranked = [...pinned, ...relevant, ...recent].filter((memory) => {
598
+ if (seen.has(memory.id)) return false;
599
+ seen.add(memory.id);
600
+ return true;
601
+ });
602
+ const formatted = formatAiMemories(ranked.slice(0, AI_MEMORY_HOT_MAX_ITEMS));
603
+ return { text: formatted.text, memories: formatted.included, truncated: count > formatted.included.length };
604
+ },
605
+ };