@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,619 @@
1
+ import { sql } from "bun";
2
+ import { createHash } from "node:crypto";
3
+ import { z } from "zod";
4
+ import { CloudResourceRefSchema } from "../contracts/capabilities";
5
+ import { coreSettings } from "../services";
6
+ import { logger } from "../services/logging";
7
+ import { normalizeLocale } from "../shared/locale";
8
+ import { parseAiAttachmentMarkers } from "./attachments";
9
+ import { type AiMemoryLearningChange, aiMemoryLearningRuns } from "./memory-learning-runs";
10
+ import {
11
+ listAiPendingWorkflowPatterns,
12
+ listAiTurnWorkflowEvidence,
13
+ markAiWorkflowPatternReviewed,
14
+ type AiMemoryWorkflowEvidence,
15
+ type AiMemoryWorkflowPattern,
16
+ } from "./memory-workflow-evidence";
17
+ import { type AiBackgroundMemoryProposal, aiMemories } from "./memories";
18
+ import { aiConversations } from "./store";
19
+ import type { RunAiStructuredInput, RunAiStructuredResult } from "./structured";
20
+ import { resolveAiBackgroundModel, runAiStructured } from "./structured";
21
+ import { AI_MEMORY_LEARNING_INSTRUCTIONS_SETTING_KEY, buildAiTaskPrompt } from "./task-prompt";
22
+ import type { AiResolvedModel, AiStoredMessage } from "./types";
23
+
24
+ const DEFAULT_BATCH_LIMIT = 10;
25
+ const WORKFLOW_PATTERN_LIMIT = 3;
26
+ const USER_TEXT_MAX_CHARS = 6_000;
27
+ const FINAL_ASSISTANT_MAX_CHARS = 1_500;
28
+ const WORKFLOW_EXAMPLE_MAX_CHARS = 2_000;
29
+ const MAX_OUTPUT_TOKENS = 700;
30
+ export const AI_MEMORY_LEARNING_MONTHLY_TOKEN_BUDGET_SETTING_KEY = "ai.memory_learning_monthly_token_budget";
31
+ export const AI_MEMORY_LEARNING_DEFAULT_MONTHLY_TOKEN_BUDGET = 100_000;
32
+
33
+ export const AiMemoryLearningChangeSchema = z.object({
34
+ action: z.enum(["add", "replace", "merge", "retire"]),
35
+ kind: z.enum(["fact", "preference", "workflow"]),
36
+ content: z.string().max(500),
37
+ memoryIds: z.array(z.string().min(1).max(12)).max(5),
38
+ resourceRef: CloudResourceRefSchema.nullable(),
39
+ });
40
+
41
+ export const AiLearnedMemoriesSchema = z.object({ changes: z.array(AiMemoryLearningChangeSchema).max(5) });
42
+ export type AiLearnedMemories = z.infer<typeof AiLearnedMemoriesSchema>;
43
+
44
+ const AiWorkflowPatternSchema = z.object({
45
+ workflow: z
46
+ .object({
47
+ content: z.string().min(1).max(500),
48
+ memoryIds: z.array(z.string().min(1).max(12)).max(5),
49
+ })
50
+ .nullable(),
51
+ });
52
+
53
+ type Candidate = {
54
+ conversationId: string;
55
+ turnId: string;
56
+ userId: string;
57
+ completedAsOf: string;
58
+ failCount: number;
59
+ locale?: string | null;
60
+ };
61
+
62
+ type TurnEvidence = {
63
+ candidate: Candidate;
64
+ messages: AiStoredMessage[];
65
+ userText: string;
66
+ finalAssistantText: string;
67
+ userMessageId?: string;
68
+ actions: AiMemoryWorkflowEvidence[];
69
+ };
70
+
71
+ type MemoryLearningDeps = {
72
+ structured?: <TOutput extends z.ZodType>(input: RunAiStructuredInput<TOutput>) => Promise<RunAiStructuredResult<TOutput>>;
73
+ resolveModel?: () => Promise<AiResolvedModel>;
74
+ listCandidates?: (limit: number, monthlyTokenBudget: number) => Promise<Candidate[]>;
75
+ readAdditionalInstructions?: () => Promise<string>;
76
+ listWorkflowPatterns?: (limit: number) => Promise<AiMemoryWorkflowPattern[]>;
77
+ monthlyTokenBudget?: number;
78
+ readMonthlyAccountedTokens?: (userId: string) => Promise<number>;
79
+ readDefaultLocale?: () => Promise<string>;
80
+ };
81
+
82
+ export type AiMemoryLearningRunSummary = {
83
+ scanned: number;
84
+ learned: number;
85
+ updated: number;
86
+ retired: number;
87
+ skipped: number;
88
+ failed: number;
89
+ };
90
+
91
+ const log = logger("ai:memory-learning");
92
+
93
+ export const MEMORY_LEARNING_PROMPT = [
94
+ "Maintain a small, useful personal profile from one newly completed private chat turn.",
95
+ "The normal result is no changes. Keep only durable information likely to help in multiple future chats.",
96
+ "Facts and preferences require an explicit statement in NEW USER TEXT. Do not infer them from Assistant text or actions.",
97
+ "A workflow links a recurring request type to one Cloud resource. Add one from this turn only when the user explicitly establishes a lasting routing rule and the matching resource appears in SUCCESSFUL ACTIONS.",
98
+ "FINAL ASSISTANT MARKDOWN may clarify what happened, but it is model-written context and never evidence by itself.",
99
+ "Exclude secrets, credentials, sensitive inferences, temporary plans, task details, quoted or attached content, and facts already owned by Contacts, Spaces, Notebooks, or another Cloud resource.",
100
+ "Use replace for one clearly corrected mutable memory, merge for two or more overlapping mutable memories, and retire only when the user explicitly makes mutable background information obsolete.",
101
+ "Protected memories are context only. Never replace, merge, or retire them.",
102
+ "Examples: 'Always answer briefly in German' adds a preference. 'Use Accounting for invoice mail' plus a successful mail.mailbox ref adds a workflow. 'Find this invoice in Accounting' does not establish a lasting workflow.",
103
+ ].join("\n");
104
+
105
+ export const MEMORY_LEARNING_OUTPUT_CONTRACT = [
106
+ "Return exactly {changes:[...]} with at most five changes.",
107
+ "add uses no memoryIds; replace uses one; merge uses two or more; retire uses one or more and empty content.",
108
+ "fact and preference use resourceRef null. workflow uses exactly one resourceRef from SUCCESSFUL ACTIONS.",
109
+ "Use only mutable memory ids shown in the input. Return an empty changes array when uncertain.",
110
+ ].join("\n");
111
+
112
+ export const WORKFLOW_PATTERN_PROMPT = [
113
+ "Decide whether three successful uses of the same Cloud capability and resource reveal one reusable workflow default.",
114
+ "Return a workflow only when the user requests share a clear recurring category and defaulting to this resource would avoid a future search or clarification.",
115
+ "Do not generalize from unrelated requests, resource contents, or the Assistant wording. Uncertainty returns null.",
116
+ "If an existing mutable workflow is corrected, return its id. Return multiple ids only when one new workflow genuinely consolidates them. Protected memories cannot be changed.",
117
+ ].join("\n");
118
+
119
+ export const WORKFLOW_PATTERN_OUTPUT_CONTRACT =
120
+ "Return exactly {workflow:null} or {workflow:{content,memoryIds}}. Content is one short reusable rule; memoryIds contains only shown mutable ids.";
121
+
122
+ const boundedText = (value: string, maxChars: number): string => {
123
+ const text = value.trim();
124
+ if (text.length <= maxChars) return text;
125
+ const half = Math.floor((maxChars - 30) / 2);
126
+ return `${text.slice(0, half).trimEnd()}\n[...truncated...]\n${text.slice(-half).trimStart()}`;
127
+ };
128
+
129
+ export const listAiMemoryLearningCandidates = async (limit: number, monthlyTokenBudget: number): Promise<Candidate[]> => {
130
+ return sql<Candidate[]>`
131
+ WITH monthly_usage AS (
132
+ SELECT run.user_id, COALESCE(sum(run.accounted_tokens), 0)::bigint AS used_tokens
133
+ FROM ai.memory_learning_runs run
134
+ WHERE run.created_at >= date_trunc('month', now())
135
+ GROUP BY run.user_id
136
+ ), eligible AS (
137
+ SELECT
138
+ conversation.id AS conversation_id,
139
+ turn.id AS turn_id,
140
+ conversation.created_by_user_id AS user_id,
141
+ turn.completed_at,
142
+ turn.memory_learn_fail_count,
143
+ turn.run_config->>'locale' AS locale,
144
+ row_number() OVER (
145
+ PARTITION BY conversation.created_by_user_id
146
+ ORDER BY turn.completed_at ASC, turn.id ASC
147
+ ) AS user_rank
148
+ FROM ai.turns turn
149
+ JOIN ai.conversations conversation ON conversation.id = turn.conversation_id
150
+ JOIN ai.user_prefs prefs ON prefs.user_id = conversation.created_by_user_id
151
+ LEFT JOIN monthly_usage usage ON usage.user_id = conversation.created_by_user_id
152
+ WHERE conversation.created_by_user_id IS NOT NULL
153
+ AND conversation.archived_at IS NULL
154
+ AND prefs.memory_learning_enabled = TRUE
155
+ AND turn.status = 'completed'
156
+ AND COALESCE(turn.run_config->>'kind', 'chat') = 'chat'
157
+ AND turn.memory_learned_at IS NULL
158
+ AND COALESCE(usage.used_tokens, 0) < ${monthlyTokenBudget}
159
+ AND (
160
+ turn.memory_learn_failed_at IS NULL
161
+ OR turn.memory_learn_failed_at + (interval '5 minutes' * pow(2, LEAST(turn.memory_learn_fail_count, 7))) < now()
162
+ )
163
+ )
164
+ SELECT
165
+ conversation_id AS "conversationId",
166
+ turn_id AS "turnId",
167
+ user_id AS "userId",
168
+ completed_at::text AS "completedAsOf",
169
+ memory_learn_fail_count AS "failCount",
170
+ locale
171
+ FROM eligible
172
+ WHERE user_rank <= 5
173
+ ORDER BY completed_at ASC, turn_id ASC
174
+ LIMIT ${Math.min(Math.max(limit, 1), 100)}
175
+ `;
176
+ };
177
+
178
+ const markLearned = async (candidate: Candidate): Promise<void> => {
179
+ await sql`
180
+ UPDATE ai.turns
181
+ SET memory_learned_at = ${candidate.completedAsOf}::timestamptz,
182
+ memory_learn_failed_at = NULL,
183
+ memory_learn_fail_count = 0
184
+ WHERE id = ${candidate.turnId}::uuid
185
+ AND conversation_id = ${candidate.conversationId}::uuid
186
+ `;
187
+ };
188
+
189
+ const markFailed = async (candidate: Candidate): Promise<void> => {
190
+ await sql`
191
+ UPDATE ai.turns
192
+ SET memory_learn_failed_at = now(), memory_learn_fail_count = memory_learn_fail_count + 1
193
+ WHERE id = ${candidate.turnId}::uuid
194
+ AND conversation_id = ${candidate.conversationId}::uuid
195
+ `;
196
+ };
197
+
198
+ const learningEnabled = async (userId: string): Promise<boolean> => {
199
+ const [row] = await sql<{ enabled: boolean }[]>`
200
+ SELECT COALESCE(memory_learning_enabled, FALSE) AS enabled
201
+ FROM ai.user_prefs
202
+ WHERE user_id = ${userId}::uuid
203
+ `;
204
+ return Boolean(row?.enabled);
205
+ };
206
+
207
+ const monthlyAccountedTokens = async (userId: string): Promise<number> => {
208
+ const [row] = await sql<{ tokens: number }[]>`
209
+ SELECT COALESCE(sum(accounted_tokens), 0)::int AS tokens
210
+ FROM ai.memory_learning_runs
211
+ WHERE user_id = ${userId}::uuid AND created_at >= date_trunc('month', now())
212
+ `;
213
+ return row?.tokens ?? 0;
214
+ };
215
+
216
+ export const buildMemoryLearningTranscript = (messages: AiStoredMessage[]): string =>
217
+ messages
218
+ .flatMap((stored) => {
219
+ const message = stored.message;
220
+ if (
221
+ stored.kind !== "message" ||
222
+ message.role !== "user" ||
223
+ stored.meta?.agentMessage ||
224
+ stored.meta?.scheduledTask
225
+ )
226
+ return [];
227
+ return message.content.flatMap((part) => {
228
+ if (typeof part === "string") return part.trim() ? [part.trim()] : [];
229
+ if (part.type !== "text") return [];
230
+ const text = parseAiAttachmentMarkers(part.text).text.trim();
231
+ if (!text || text.startsWith("[Attached Cloud resource ")) return [];
232
+ return [text];
233
+ });
234
+ })
235
+ .join("\n\n");
236
+
237
+ export const buildFinalAssistantMarkdown = (messages: AiStoredMessage[]): string => {
238
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
239
+ const message = messages[index]?.message;
240
+ if (message?.role !== "assistant") continue;
241
+ const text = message.content.flatMap((part) => (part.type === "text" && part.text.trim() ? [part.text.trim()] : [])).join("\n\n");
242
+ if (text) return text;
243
+ }
244
+ return "";
245
+ };
246
+
247
+ const loadTurnEvidence = async (candidate: Candidate): Promise<TurnEvidence> => {
248
+ const [messages, actions] = await Promise.all([
249
+ aiConversations.listTurnMessages({ conversationId: candidate.conversationId, loopId: candidate.turnId, includeCompacted: true }),
250
+ listAiTurnWorkflowEvidence(candidate.userId, candidate.turnId),
251
+ ]);
252
+ const firstUser = messages.find(
253
+ (message) => message.message.role === "user" && !message.meta?.agentMessage && !message.meta?.scheduledTask,
254
+ );
255
+ return {
256
+ candidate,
257
+ messages,
258
+ userText: boundedText(buildMemoryLearningTranscript(messages), USER_TEXT_MAX_CHARS),
259
+ finalAssistantText: boundedText(buildFinalAssistantMarkdown(messages), FINAL_ASSISTANT_MAX_CHARS),
260
+ userMessageId: firstUser?.id,
261
+ actions,
262
+ };
263
+ };
264
+
265
+ const memoryLines = (memories: Awaited<ReturnType<typeof aiMemories.list>>): string =>
266
+ memories.length > 0
267
+ ? memories
268
+ .map((memory) => {
269
+ const mutability = memory.source === "background" && memory.priority === "normal" ? "mutable" : "protected";
270
+ const resource = memory.resourceRef ? ` | ${memory.resourceRef.type}:${memory.resourceRef.id}` : "";
271
+ return `- ${memory.shortId} | ${mutability} | ${memory.kind} | ${memory.content}${resource}`;
272
+ })
273
+ .join("\n")
274
+ : "(none)";
275
+
276
+ const actionLines = (actions: AiMemoryWorkflowEvidence[]): string =>
277
+ actions.length > 0
278
+ ? actions
279
+ .map(
280
+ (action) =>
281
+ `- ${action.capabilityId} | ${action.resourceRef.type}:${action.resourceRef.id}${
282
+ action.resourceTitle ? ` | ${action.resourceTitle}` : ""
283
+ }`,
284
+ )
285
+ .join("\n")
286
+ : "(none)";
287
+
288
+ const learningInput = (evidence: TurnEvidence, memories: Awaited<ReturnType<typeof aiMemories.list>>): string =>
289
+ [
290
+ "NEW USER TEXT:",
291
+ evidence.userText || "(none)",
292
+ "",
293
+ "SUCCESSFUL ACTIONS:",
294
+ actionLines(evidence.actions),
295
+ "",
296
+ "FINAL ASSISTANT MARKDOWN (context only):",
297
+ evidence.finalAssistantText || "(none)",
298
+ "",
299
+ "ACTIVE MEMORIES:",
300
+ memoryLines(memories),
301
+ ].join("\n");
302
+
303
+ const estimatedTokens = (systemPrompt: string, taskInput: string): number =>
304
+ new TextEncoder().encode(systemPrompt + taskInput).byteLength + MAX_OUTPUT_TOKENS;
305
+
306
+ const workflowEvidenceKey = (pattern: AiMemoryWorkflowPattern): string =>
307
+ createHash("sha256")
308
+ .update(JSON.stringify([pattern.userId, pattern.capabilityId, pattern.resourceRef.type, pattern.resourceRef.id, ...[...pattern.turnIds].sort()]))
309
+ .digest("hex");
310
+
311
+ const toProposal = (change: z.infer<typeof AiMemoryLearningChangeSchema>): AiBackgroundMemoryProposal => ({
312
+ action: change.action,
313
+ kind: change.kind,
314
+ content: change.content,
315
+ memoryIds: change.memoryIds,
316
+ resourceRef: change.resourceRef,
317
+ });
318
+
319
+ const applyChanges = async (
320
+ evidence: TurnEvidence,
321
+ changes: z.infer<typeof AiMemoryLearningChangeSchema>[],
322
+ mutableMemoryIds: ReadonlySet<string>,
323
+ ): Promise<AiMemoryLearningChange[]> => {
324
+ const applied: AiMemoryLearningChange[] = [];
325
+ for (const change of changes) {
326
+ if (change.memoryIds.some((memoryId) => !mutableMemoryIds.has(memoryId))) continue;
327
+ if (
328
+ change.resourceRef &&
329
+ !evidence.actions.some(
330
+ (action) => action.resourceRef.type === change.resourceRef?.type && action.resourceRef.id === change.resourceRef.id,
331
+ )
332
+ ) continue;
333
+ const results = await aiMemories.applyBackgroundProposal({
334
+ userId: evidence.candidate.userId,
335
+ sourceConversationId: evidence.candidate.conversationId,
336
+ sourceMessageId: evidence.userMessageId,
337
+ proposal: toProposal(change),
338
+ });
339
+ applied.push(...results);
340
+ }
341
+ return applied;
342
+ };
343
+
344
+ const workflowPatternInput = async (
345
+ pattern: AiMemoryWorkflowPattern,
346
+ ): Promise<{ input: string; source: TurnEvidence | null; mutableMemoryIds: Set<string> }> => {
347
+ const sources = await Promise.all(
348
+ pattern.turnIds.map(async (turnId) => {
349
+ const [row] = await sql<{ conversation_id: string; completed_as_of: string; fail_count: number }[]>`
350
+ SELECT turn.conversation_id, turn.completed_at::text AS completed_as_of, turn.memory_learn_fail_count AS fail_count
351
+ FROM ai.turns turn
352
+ JOIN ai.conversations conversation ON conversation.id = turn.conversation_id
353
+ WHERE turn.id = ${turnId}::uuid AND conversation.created_by_user_id = ${pattern.userId}::uuid
354
+ `;
355
+ if (!row) return null;
356
+ return loadTurnEvidence({
357
+ conversationId: row.conversation_id,
358
+ turnId,
359
+ userId: pattern.userId,
360
+ completedAsOf: row.completed_as_of,
361
+ failCount: row.fail_count,
362
+ });
363
+ }),
364
+ );
365
+ const evidence = sources.filter((source): source is TurnEvidence => Boolean(source));
366
+ const query = evidence.map((source) => source.userText).join(" ");
367
+ const selected = await aiMemories.selectHot(pattern.userId, query);
368
+ const examples = evidence
369
+ .map(
370
+ (source, index) =>
371
+ `EXAMPLE ${index + 1}\nUser: ${boundedText(source.userText, WORKFLOW_EXAMPLE_MAX_CHARS)}\nFinal Assistant context: ${
372
+ boundedText(source.finalAssistantText, 500) || "(none)"
373
+ }`,
374
+ )
375
+ .join("\n\n");
376
+ return {
377
+ source: evidence[0] ?? null,
378
+ mutableMemoryIds: new Set(
379
+ selected.memories
380
+ .filter((memory) => memory.source === "background" && memory.priority === "normal")
381
+ .map((memory) => memory.shortId),
382
+ ),
383
+ input: [
384
+ `CAPABILITY: ${pattern.capabilityId}`,
385
+ `RESOURCE: ${pattern.resourceRef.type}:${pattern.resourceRef.id}${pattern.resourceTitle ? ` | ${pattern.resourceTitle}` : ""}`,
386
+ "",
387
+ examples,
388
+ "",
389
+ "ACTIVE MEMORIES:",
390
+ memoryLines(selected.memories),
391
+ ].join("\n"),
392
+ };
393
+ };
394
+
395
+ const usageNumbers = (usage: RunAiStructuredResult<z.ZodType>["usage"], reserved: number) => {
396
+ const inputTokens = usage?.input;
397
+ const outputTokens = usage?.output;
398
+ const total = usage?.total ?? (inputTokens !== undefined || outputTokens !== undefined ? (inputTokens ?? 0) + (outputTokens ?? 0) : undefined);
399
+ return { inputTokens, outputTokens, accountedTokens: total ?? reserved };
400
+ };
401
+
402
+ export const learnAiMemoriesFromPrivateChats = async (
403
+ input: { limit?: number; signal?: AbortSignal; heartbeat?: () => Promise<void>; deps?: MemoryLearningDeps } = {},
404
+ ): Promise<AiMemoryLearningRunSummary> => {
405
+ const summary: AiMemoryLearningRunSummary = { scanned: 0, learned: 0, updated: 0, retired: 0, skipped: 0, failed: 0 };
406
+ let resolved: AiResolvedModel;
407
+ try {
408
+ resolved = await (input.deps?.resolveModel ?? resolveAiBackgroundModel)();
409
+ } catch (error) {
410
+ log.info("Memory learning skipped: no background model available", {
411
+ error: error instanceof Error ? error.message : String(error),
412
+ });
413
+ return summary;
414
+ }
415
+
416
+ const configuredBudget =
417
+ input.deps?.monthlyTokenBudget ??
418
+ Number(await coreSettings.get<number>(AI_MEMORY_LEARNING_MONTHLY_TOKEN_BUDGET_SETTING_KEY));
419
+ const monthlyTokenBudget =
420
+ Number.isFinite(configuredBudget) && configuredBudget > 0
421
+ ? Math.floor(configuredBudget)
422
+ : AI_MEMORY_LEARNING_DEFAULT_MONTHLY_TOKEN_BUDGET;
423
+ const batchLimit = Math.min(Math.max(input.limit ?? DEFAULT_BATCH_LIMIT, 1), 100);
424
+ const candidateReadLimit = Math.min(batchLimit * 5, 100);
425
+ const candidates = await (input.deps?.listCandidates ?? listAiMemoryLearningCandidates)(candidateReadLimit, monthlyTokenBudget);
426
+ const readMonthlyUsage = input.deps?.readMonthlyAccountedTokens ?? monthlyAccountedTokens;
427
+ const structured = input.deps?.structured ?? runAiStructured;
428
+ const additionalInstructions = await (
429
+ input.deps?.readAdditionalInstructions ??
430
+ (() => coreSettings.get<string>(AI_MEMORY_LEARNING_INSTRUCTIONS_SETTING_KEY).then((value) => value ?? ""))
431
+ )();
432
+ let defaultLocale: Promise<string> | undefined;
433
+ const resolveLearningLocale = (locale?: string | null): Promise<string> => {
434
+ if (locale) return Promise.resolve(normalizeLocale(locale));
435
+ defaultLocale ??= (input.deps?.readDefaultLocale ?? (() => coreSettings.get<string>("app.locale")))().then(
436
+ normalizeLocale,
437
+ );
438
+ return defaultLocale;
439
+ };
440
+
441
+ let processedCandidates = 0;
442
+ for (const candidate of candidates) {
443
+ if (processedCandidates >= batchLimit) break;
444
+ if (input.signal?.aborted) break;
445
+ summary.scanned += 1;
446
+ const startedAt = Date.now();
447
+ let runId: string | null = null;
448
+ let changes: AiMemoryLearningChange[] = [];
449
+ try {
450
+ if (!(await learningEnabled(candidate.userId))) continue;
451
+ const evidence = await loadTurnEvidence(candidate);
452
+ if (!evidence.userText) {
453
+ await markLearned(candidate);
454
+ summary.skipped += 1;
455
+ processedCandidates += 1;
456
+ continue;
457
+ }
458
+ const selected = await aiMemories.selectHot(candidate.userId, evidence.userText);
459
+ const taskInput = learningInput(evidence, selected.memories);
460
+ const locale = await resolveLearningLocale(candidate.locale);
461
+ const systemPrompt = buildAiTaskPrompt({
462
+ baseInstructions: `${MEMORY_LEARNING_PROMPT}\nWrite new or changed memory content in the language identified by locale ${locale}.`,
463
+ additionalInstructions,
464
+ outputContract: MEMORY_LEARNING_OUTPUT_CONTRACT,
465
+ });
466
+ const reservedTokens = estimatedTokens(systemPrompt, taskInput);
467
+ if ((await readMonthlyUsage(candidate.userId)) + reservedTokens > monthlyTokenBudget) continue;
468
+ runId = await aiMemoryLearningRuns.start({
469
+ userId: candidate.userId,
470
+ conversationId: candidate.conversationId,
471
+ turnId: candidate.turnId,
472
+ runKind: "turn",
473
+ modelProfileId: resolved.profile.id,
474
+ accountedTokens: reservedTokens,
475
+ });
476
+ if (!runId) continue;
477
+ const result = await structured({
478
+ task: "memory-learn-turn",
479
+ attribution: { userId: candidate.userId, conversationId: candidate.conversationId, turnId: candidate.turnId },
480
+ appId: "ai",
481
+ systemPrompt,
482
+ input: taskInput,
483
+ output: AiLearnedMemoriesSchema,
484
+ outputName: "personalization_changes",
485
+ maxOutputTokens: MAX_OUTPUT_TOKENS,
486
+ signal: input.signal,
487
+ resolveModel: async () => resolved,
488
+ });
489
+ if (!(await learningEnabled(candidate.userId))) {
490
+ await markLearned(candidate);
491
+ } else {
492
+ changes = await applyChanges(
493
+ evidence,
494
+ result.output.changes,
495
+ new Set(
496
+ selected.memories
497
+ .filter((memory) => memory.source === "background" && memory.priority === "normal")
498
+ .map((memory) => memory.shortId),
499
+ ),
500
+ );
501
+ await markLearned(candidate);
502
+ }
503
+ const status = changes.length > 0 ? "ok" : "skipped";
504
+ await aiMemoryLearningRuns.finish({
505
+ runId,
506
+ status,
507
+ durationMs: Date.now() - startedAt,
508
+ changes,
509
+ ...usageNumbers(result.usage, reservedTokens),
510
+ });
511
+ summary.learned += changes.filter((change) => change.action === "added").length;
512
+ summary.updated += changes.filter((change) => change.action === "updated" || change.action === "merged").length;
513
+ summary.retired += changes.filter((change) => change.action === "retired").length;
514
+ if (status === "skipped") summary.skipped += 1;
515
+ processedCandidates += 1;
516
+ } catch (error) {
517
+ summary.failed += 1;
518
+ processedCandidates += 1;
519
+ await markFailed(candidate).catch(() => undefined);
520
+ if (runId) {
521
+ await aiMemoryLearningRuns
522
+ .fail({ runId, durationMs: Date.now() - startedAt, error: error instanceof Error ? error.message : String(error), changes })
523
+ .catch(() => undefined);
524
+ }
525
+ log.warn("Memory turn learning failed", {
526
+ conversationId: candidate.conversationId,
527
+ turnId: candidate.turnId,
528
+ failCount: candidate.failCount + 1,
529
+ error: error instanceof Error ? error.message : String(error),
530
+ });
531
+ }
532
+ await input.heartbeat?.();
533
+ }
534
+
535
+ const patterns = await (input.deps?.listWorkflowPatterns ?? listAiPendingWorkflowPatterns)(WORKFLOW_PATTERN_LIMIT);
536
+ for (const pattern of patterns) {
537
+ if (input.signal?.aborted) break;
538
+ let runId: string | null = null;
539
+ const startedAt = Date.now();
540
+ try {
541
+ if (!(await learningEnabled(pattern.userId))) continue;
542
+ const context = await workflowPatternInput(pattern);
543
+ if (!context.source) {
544
+ await markAiWorkflowPatternReviewed(pattern);
545
+ continue;
546
+ }
547
+ const locale = await resolveLearningLocale(context.source.candidate.locale);
548
+ const workflowPrompt = buildAiTaskPrompt({
549
+ baseInstructions: `${WORKFLOW_PATTERN_PROMPT}\nWrite new or changed workflow content in the language identified by locale ${locale}.`,
550
+ additionalInstructions,
551
+ outputContract: WORKFLOW_PATTERN_OUTPUT_CONTRACT,
552
+ });
553
+ const reservedTokens = estimatedTokens(workflowPrompt, context.input);
554
+ if ((await readMonthlyUsage(pattern.userId)) + reservedTokens > monthlyTokenBudget) continue;
555
+ runId = await aiMemoryLearningRuns.start({
556
+ userId: pattern.userId,
557
+ conversationId: context.source.candidate.conversationId,
558
+ turnId: context.source.candidate.turnId,
559
+ runKind: "workflow",
560
+ evidenceKey: workflowEvidenceKey(pattern),
561
+ modelProfileId: resolved.profile.id,
562
+ accountedTokens: reservedTokens,
563
+ });
564
+ if (!runId) continue;
565
+ const result = await structured({
566
+ task: "memory-learn-workflow",
567
+ attribution: { userId: pattern.userId, conversationId: context.source.candidate.conversationId, turnId: context.source.candidate.turnId },
568
+ appId: "ai",
569
+ systemPrompt: workflowPrompt,
570
+ input: context.input,
571
+ output: AiWorkflowPatternSchema,
572
+ outputName: "workflow_pattern",
573
+ maxOutputTokens: 300,
574
+ signal: input.signal,
575
+ resolveModel: async () => resolved,
576
+ });
577
+ let changes: AiMemoryLearningChange[] = [];
578
+ if (result.output.workflow && (await learningEnabled(pattern.userId))) {
579
+ const memoryIds = result.output.workflow.memoryIds;
580
+ const action = memoryIds.length > 1 ? "merge" : memoryIds.length === 1 ? "replace" : "add";
581
+ changes = await applyChanges(context.source, [
582
+ {
583
+ action,
584
+ kind: "workflow",
585
+ content: result.output.workflow.content,
586
+ memoryIds,
587
+ resourceRef: pattern.resourceRef,
588
+ },
589
+ ], context.mutableMemoryIds);
590
+ }
591
+ await markAiWorkflowPatternReviewed(pattern);
592
+ await aiMemoryLearningRuns.finish({
593
+ runId,
594
+ status: changes.length > 0 ? "ok" : "skipped",
595
+ durationMs: Date.now() - startedAt,
596
+ changes,
597
+ ...usageNumbers(result.usage, reservedTokens),
598
+ });
599
+ summary.learned += changes.filter((change) => change.action === "added").length;
600
+ summary.updated += changes.filter((change) => change.action === "updated" || change.action === "merged").length;
601
+ if (changes.length === 0) summary.skipped += 1;
602
+ } catch (error) {
603
+ summary.failed += 1;
604
+ if (runId) {
605
+ await aiMemoryLearningRuns
606
+ .fail({ runId, durationMs: Date.now() - startedAt, error: error instanceof Error ? error.message : String(error), changes: [] })
607
+ .catch(() => undefined);
608
+ }
609
+ log.warn("Memory workflow learning failed", {
610
+ capabilityId: pattern.capabilityId,
611
+ resourceType: pattern.resourceRef.type,
612
+ error: error instanceof Error ? error.message : String(error),
613
+ });
614
+ }
615
+ await input.heartbeat?.();
616
+ }
617
+
618
+ return summary;
619
+ };