@k2b/cloud 0.6.0 → 0.8.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 (349) hide show
  1. package/README.md +2 -2
  2. package/package.json +22 -11
  3. package/scripts/README.md +10 -0
  4. package/scripts/browser-performance.ts +14 -0
  5. package/scripts/build-pdf-renderer.ts +39 -0
  6. package/scripts/build.ts +20 -35
  7. package/scripts/preload.ts +16 -12
  8. package/scripts/sync-recovery-smoke.ts +22 -6
  9. package/src/_internal/app-presentation.ts +14 -2
  10. package/src/_internal/build-metadata.ts +8 -1
  11. package/src/_internal/capabilities.ts +131 -7
  12. package/src/_internal/capability-streams.ts +81 -0
  13. package/src/_internal/define-app.ts +80 -37
  14. package/src/_internal/heartbeat.ts +4 -0
  15. package/src/_internal/help-catalog.ts +27 -172
  16. package/src/_internal/help.ts +13 -76
  17. package/src/_internal/page-responses.ts +2 -0
  18. package/src/_internal/postgres-application-name.ts +30 -0
  19. package/src/_internal/process-identity.ts +15 -0
  20. package/src/_internal/process-sync.ts +1 -1
  21. package/src/_internal/readiness.ts +4 -1
  22. package/src/_internal/registry-validation.ts +29 -25
  23. package/src/_internal/registry.ts +2 -53
  24. package/src/_internal/runtime-context.ts +5 -16
  25. package/src/_internal/server-timing.ts +13 -0
  26. package/src/_internal/status-preserving-ssr.ts +18 -6
  27. package/src/_internal/web-vitals-asset.ts +10 -0
  28. package/src/access/PermissionEditor.tsx +18 -151
  29. package/src/access/PrincipalPicker.tsx +110 -0
  30. package/src/access/ResourceApiKeys.tsx +1 -1
  31. package/src/access/ui.ts +1 -0
  32. package/src/ai/admin.ts +14 -13
  33. package/src/ai/assistant-models.ts +12 -1
  34. package/src/ai/audio-format.ts +34 -0
  35. package/src/ai/audio-tool.ts +90 -0
  36. package/src/ai/browser-code-contracts.ts +201 -0
  37. package/src/ai/browser.ts +4 -0
  38. package/src/ai/capabilities.ts +55 -36
  39. package/src/ai/capability-execution.ts +47 -12
  40. package/src/ai/chat/blocks.tsx +229 -90
  41. package/src/ai/chat/builtin-tools.tsx +2 -4
  42. package/src/ai/chat/capability-result.ts +15 -0
  43. package/src/ai/chat/capability-table.tsx +2 -10
  44. package/src/ai/chat/composer-adapter.ts +83 -3
  45. package/src/ai/chat/message-actions.tsx +8 -7
  46. package/src/ai/chat/message-utils.ts +25 -1
  47. package/src/ai/chat/messages.ts +2 -0
  48. package/src/ai/chat/presentation.tsx +31 -47
  49. package/src/ai/chat/primitives.tsx +43 -1
  50. package/src/ai/chat/tool-disclosure.tsx +21 -2
  51. package/src/ai/chat/tool-groups.ts +58 -0
  52. package/src/ai/chat-quotas.ts +25 -0
  53. package/src/ai/chat-task-contracts.ts +14 -10
  54. package/src/ai/chat-tasks.ts +297 -43
  55. package/src/ai/client/controller.ts +165 -39
  56. package/src/ai/client/file-source.ts +12 -6
  57. package/src/ai/client/live-connection.ts +1 -0
  58. package/src/ai/client/projection.ts +29 -11
  59. package/src/ai/client/transport.ts +6 -0
  60. package/src/ai/code-capability-routes.ts +105 -0
  61. package/src/ai/code-capability-transport.ts +39 -0
  62. package/src/ai/code-execution.ts +53 -0
  63. package/src/ai/code-mode-skill.ts +118 -0
  64. package/src/ai/code-runtime-tools.ts +179 -0
  65. package/src/ai/code-source-contracts.ts +270 -0
  66. package/src/ai/code-source-tools.ts +188 -0
  67. package/src/ai/compaction.ts +27 -6
  68. package/src/ai/data-analysis-skill.ts +10 -0
  69. package/src/ai/default-tools.ts +99 -3
  70. package/src/ai/dictation-runtime.ts +171 -0
  71. package/src/ai/dictations.ts +188 -0
  72. package/src/ai/draft-content.ts +49 -0
  73. package/src/ai/enrich.ts +6 -0
  74. package/src/ai/executor.ts +189 -33
  75. package/src/ai/fetch-file-tool.ts +2 -2
  76. package/src/ai/file-content-version.ts +27 -0
  77. package/src/ai/file-context.ts +2 -1
  78. package/src/ai/file-media-type.ts +12 -0
  79. package/src/ai/file-reference-contracts.ts +15 -0
  80. package/src/ai/file-tools.ts +8 -1
  81. package/src/ai/files-store.ts +212 -71
  82. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  83. package/src/ai/grids-skill.ts +42 -10
  84. package/src/ai/http.ts +26 -8
  85. package/src/ai/index.ts +26 -3
  86. package/src/ai/inference-calls.ts +197 -0
  87. package/src/ai/live-events.ts +5 -1
  88. package/src/ai/live.ts +1 -1
  89. package/src/ai/markdown-pdf-tool.ts +2 -2
  90. package/src/ai/memories.ts +30 -27
  91. package/src/ai/memory-learning-runs.ts +1 -1
  92. package/src/ai/memory-learning.ts +64 -32
  93. package/src/ai/memory-tool.ts +1 -5
  94. package/src/ai/memory-workflow-evidence.ts +9 -4
  95. package/src/ai/message-queue.ts +178 -0
  96. package/src/ai/migrate.ts +189 -11
  97. package/src/ai/model-pricing.ts +43 -0
  98. package/src/ai/pdf-render-worker.ts +75 -0
  99. package/src/ai/pdf-render.fixture.ts +28 -0
  100. package/src/ai/pdf-render.ts +77 -0
  101. package/src/ai/projects-routes.ts +18 -43
  102. package/src/ai/projects.ts +33 -14
  103. package/src/ai/protocol.ts +3 -0
  104. package/src/ai/provider.ts +1 -8
  105. package/src/ai/quota-provider.ts +192 -0
  106. package/src/ai/quota-report.ts +152 -0
  107. package/src/ai/quotas-migrate.ts +45 -0
  108. package/src/ai/quotas.ts +168 -0
  109. package/src/ai/routes.ts +241 -11
  110. package/src/ai/run-timeout.ts +12 -0
  111. package/src/ai/runtime-tools.ts +7 -5
  112. package/src/ai/runtime.ts +74 -17
  113. package/src/ai/settings.ts +36 -8
  114. package/src/ai/short-id.ts +4 -5
  115. package/src/ai/skill-catalog.ts +0 -10
  116. package/src/ai/skill-search.ts +58 -0
  117. package/src/ai/skill-seeds.ts +114 -20
  118. package/src/ai/skill-tool.ts +21 -5
  119. package/src/ai/skills-routes.ts +49 -4
  120. package/src/ai/skills.ts +358 -163
  121. package/src/ai/store.ts +416 -201
  122. package/src/ai/stream.ts +5 -0
  123. package/src/ai/structured-runs.ts +4 -3
  124. package/src/ai/structured.ts +12 -1
  125. package/src/ai/system-prompt.ts +14 -2
  126. package/src/ai/task-contracts.ts +84 -0
  127. package/src/ai/task-execution.ts +92 -0
  128. package/src/ai/timeline.ts +2 -5
  129. package/src/ai/todo-contracts.ts +32 -0
  130. package/src/ai/todo-tool.ts +19 -0
  131. package/src/ai/tool-audit.ts +30 -40
  132. package/src/ai/tools.ts +6 -0
  133. package/src/ai/transcription.ts +202 -0
  134. package/src/ai/turn-timing.ts +114 -0
  135. package/src/ai/types.ts +58 -10
  136. package/src/ai/ui.tsx +3 -0
  137. package/src/ai/usage.ts +118 -118
  138. package/src/ai/vision-tool.ts +60 -10
  139. package/src/api/admin-ai-projects.ts +2 -2
  140. package/src/api/admin-ai-quotas.ts +80 -0
  141. package/src/api/admin-ai-skills.ts +48 -5
  142. package/src/api/admin-ai-usage.ts +2 -2
  143. package/src/api/admin-core-settings.ts +25 -0
  144. package/src/api/admin-identity.ts +91 -51
  145. package/src/api/admin-linux-identities.ts +10 -4
  146. package/src/api/admin-rail.ts +58 -0
  147. package/src/api/announcements.ts +14 -0
  148. package/src/api/app-approval.ts +2 -2
  149. package/src/api/auth.ts +1 -1
  150. package/src/api/capabilities.ts +324 -43
  151. package/src/api/capability-streams.ts +262 -0
  152. package/src/api/help.ts +8 -44
  153. package/src/api/index.ts +8 -2
  154. package/src/api/mcp.ts +45 -34
  155. package/src/api/me-web-vitals.ts +35 -0
  156. package/src/api/me.ts +2 -0
  157. package/src/api/search/schemas.ts +26 -15
  158. package/src/api/search.bench.ts +1 -1
  159. package/src/api/search.ts +54 -17
  160. package/src/browser/CloudResourceSearch.tsx +640 -702
  161. package/src/browser/app-approval-vault.ts +19 -7
  162. package/src/browser/command-bridge.ts +109 -0
  163. package/src/browser/command-shortcuts.ts +76 -0
  164. package/src/browser/commands.ts +67 -0
  165. package/src/browser/navigation-search.ts +48 -0
  166. package/src/browser/notifications.ts +1 -1
  167. package/src/browser/resource-picker.tsx +5 -18
  168. package/src/browser/resource-search-dialog.ts +9 -0
  169. package/src/browser/resource-search-input.ts +46 -0
  170. package/src/browser/resource-search-messages.ts +76 -40
  171. package/src/browser/resource-search.ts +7 -0
  172. package/src/browser/search-bridge.ts +102 -0
  173. package/src/browser/search-commands.ts +59 -0
  174. package/src/browser/search.ts +13 -0
  175. package/src/browser/spotlight-position.ts +208 -0
  176. package/src/browser/testing.ts +6 -0
  177. package/src/browser/web-vitals.ts +25 -0
  178. package/src/capabilities/claims.ts +179 -0
  179. package/src/capabilities/client.ts +34 -0
  180. package/src/capabilities/command-link.ts +16 -0
  181. package/src/capabilities/executions.ts +298 -0
  182. package/src/capabilities/migrate.ts +81 -0
  183. package/src/capabilities/server.ts +116 -35
  184. package/src/capabilities/stream-body.ts +16 -0
  185. package/src/capabilities/streams.ts +28 -0
  186. package/src/cli/admin/ai-quotas.ts +161 -0
  187. package/src/cli/admin/ai-skills.ts +64 -0
  188. package/src/cli/admin/ai-usage.ts +53 -30
  189. package/src/cli/admin/app-credentials.ts +77 -0
  190. package/src/cli/admin/data.ts +17 -0
  191. package/src/cli/admin/index.ts +10 -1
  192. package/src/cli/admin/instance.ts +2 -2
  193. package/src/cli/admin/linux.ts +4 -2
  194. package/src/cli/admin/sync.ts +1 -1
  195. package/src/cli/capabilities.ts +42 -0
  196. package/src/cli/capability-streams.ts +25 -0
  197. package/src/cli/index.ts +3 -2
  198. package/src/config/define-env.ts +97 -0
  199. package/src/config/env.ts +147 -51
  200. package/src/config/index.ts +3 -1
  201. package/src/contracts/app.ts +23 -3
  202. package/src/contracts/capabilities.ts +75 -5
  203. package/src/contracts/capability-streams.ts +46 -0
  204. package/src/contracts/commands.ts +49 -0
  205. package/src/contracts/index.ts +5 -2
  206. package/src/contracts/rail-admin.ts +38 -0
  207. package/src/contracts/rail-preferences.ts +16 -0
  208. package/src/contracts/registry.ts +5 -27
  209. package/src/contracts/settings-types.ts +1 -1
  210. package/src/contracts/shared.ts +18 -5
  211. package/src/contracts/web-vitals.ts +27 -0
  212. package/src/desktop/index.ts +1 -0
  213. package/src/desktop/solid.tsx +1 -1
  214. package/src/index.ts +8 -6
  215. package/src/server/index.ts +3 -0
  216. package/src/server/middleware/auth.ts +3 -2
  217. package/src/server/middleware/route-template.ts +9 -3
  218. package/src/server/middleware/runtime.ts +2 -1
  219. package/src/server/middleware/settings.ts +17 -11
  220. package/src/server/ratelimit.ts +1 -1
  221. package/src/server/services/access-revision.ts +10 -0
  222. package/src/server/services/access.ts +3 -1
  223. package/src/server/services/index.ts +1 -0
  224. package/src/services/account-category-policy.ts +9 -0
  225. package/src/services/account-lifecycle/audit.ts +1 -1
  226. package/src/services/accounts/app.ts +12 -2
  227. package/src/services/accounts/email-write.ts +23 -0
  228. package/src/services/accounts/groups.ts +15 -4
  229. package/src/services/accounts/identities.ts +249 -0
  230. package/src/services/accounts/identity-reconciliation.ts +87 -0
  231. package/src/services/accounts/ipa-data.ts +2 -2
  232. package/src/services/accounts/local-groups.ts +2 -2
  233. package/src/services/accounts/notification-sender.ts +12 -2
  234. package/src/services/accounts/posix.ts +45 -29
  235. package/src/services/accounts/users.ts +2 -2
  236. package/src/services/announcements/index.ts +69 -8
  237. package/src/services/app-approval.ts +11 -10
  238. package/src/services/audit/index.ts +1 -1
  239. package/src/services/cache-fill.ts +35 -0
  240. package/src/services/help/index.ts +109 -0
  241. package/src/services/help/maintenance.ts +25 -0
  242. package/src/services/help/store.ts +180 -0
  243. package/src/services/help/types.ts +46 -0
  244. package/src/services/identity/invocation-actor.ts +2 -2
  245. package/src/services/identity/key-config.ts +8 -5
  246. package/src/services/identity/key-ring.ts +1 -1
  247. package/src/services/identity/runtime-config.ts +6 -5
  248. package/src/services/index.ts +15 -0
  249. package/src/services/ipa/posix.ts +1 -1
  250. package/src/services/ipa/sync.ts +1 -1
  251. package/src/services/ipa/users.ts +268 -221
  252. package/src/services/legal-consent.ts +1 -1
  253. package/src/services/logging/index.ts +15 -13
  254. package/src/services/logging/trace.ts +2 -1
  255. package/src/services/logging/web-vitals.ts +81 -0
  256. package/src/services/mandates/index.ts +21 -4
  257. package/src/services/mandates/policy.ts +165 -2
  258. package/src/services/notifications/batches.ts +125 -57
  259. package/src/services/notifications/browser.ts +12 -0
  260. package/src/services/notifications/index.ts +0 -2
  261. package/src/services/notifications/platform.ts +0 -4
  262. package/src/services/oauth-tokens.ts +3 -3
  263. package/src/services/pdf/gotenberg.ts +131 -72
  264. package/src/services/pdf/index.ts +11 -8
  265. package/src/services/providers/local/users.ts +25 -16
  266. package/src/services/public-http.ts +101 -0
  267. package/src/services/rail-shortcuts.ts +95 -0
  268. package/src/services/rail-snapshot.ts +53 -0
  269. package/src/services/request-cache-redis.ts +29 -0
  270. package/src/services/session/index.ts +15 -2
  271. package/src/services/session/user.ts +62 -37
  272. package/src/services/settings/app.ts +5 -1
  273. package/src/services/settings/core-settings.ts +26 -35
  274. package/src/services/settings/defaults.ts +5 -0
  275. package/src/services/settings/index.ts +3 -3
  276. package/src/services/settings/store.ts +58 -84
  277. package/src/services/webauthn.ts +1 -1
  278. package/src/shared/ai-costs.ts +42 -0
  279. package/src/shared/ai-platform-prompt.ts +1 -1
  280. package/src/shared/ai-quotas.ts +136 -0
  281. package/src/shared/ai-usage.ts +6 -16
  282. package/src/shared/app-presentation.ts +21 -15
  283. package/src/shared/capability-messages.ts +14 -0
  284. package/src/shared/help.ts +6 -42
  285. package/src/shared/index.ts +3 -0
  286. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  287. package/src/ssr/AdminLayout.tsx +7 -5
  288. package/src/ssr/AdminSidebar.tsx +30 -16
  289. package/src/ssr/AppLaunchpad.island.tsx +33 -136
  290. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  291. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  292. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  293. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  294. package/src/ssr/Layout.tsx +17 -6
  295. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  296. package/src/ssr/LayoutFooter.tsx +1 -1
  297. package/src/ssr/LayoutHeader.tsx +46 -14
  298. package/src/ssr/LayoutHelp.tsx +22 -23
  299. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  300. package/src/ssr/LayoutRail.tsx +17 -9
  301. package/src/ssr/MinimalLayout.tsx +1 -1
  302. package/src/ssr/MobileNavigation.tsx +71 -0
  303. package/src/ssr/MobileProfileActions.tsx +49 -0
  304. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  305. package/src/ssr/RailApps.island.tsx +2 -2
  306. package/src/ssr/RailEditor.tsx +17 -2
  307. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  308. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  309. package/src/ssr/admin-navigation.ts +11 -1
  310. package/src/ssr/help.ts +29 -0
  311. package/src/ssr/islands/SearchBar.tsx +67 -0
  312. package/src/ssr/islands/index.ts +3 -1
  313. package/src/ssr/layout-context.ts +2 -2
  314. package/src/ssr/layout-help-search.ts +15 -0
  315. package/src/ssr/mobile-menu-history.ts +70 -0
  316. package/src/ssr/platform-messages.ts +16 -8
  317. package/src/ssr/profile-actions.ts +42 -0
  318. package/src/ssr/rail-context.ts +8 -4
  319. package/src/ssr/rail-messages.ts +2 -0
  320. package/src/ssr/rail-navigation.ts +3 -3
  321. package/src/ssr/workspace-navigation.ts +75 -0
  322. package/src/styles/effects.css +6 -6
  323. package/src/styles/global.css +54 -0
  324. package/src/styles/input.css +43 -41
  325. package/src/styles/resource-search.css +505 -0
  326. package/src/styles/utilities-feedback.css +13 -0
  327. package/src/styles/utilities-layout.css +1 -5
  328. package/src/styles/utilities-navigation.css +3 -39
  329. package/src/types/ambient.d.ts +5 -0
  330. package/src/workflows/ai/index.ts +2 -2
  331. package/src/workflows/ai/runtime.ts +19 -90
  332. package/src/workflows/ai/store.ts +2 -0
  333. package/src/workflows/ai/types.ts +6 -82
  334. package/src/workflows/ai-actions.ts +16 -13
  335. package/src/workflows/definition.ts +10 -5
  336. package/src/workflows/store/actions.ts +44 -20
  337. package/src/workflows/store/index.ts +4 -2
  338. package/src/workflows/store/runs.ts +36 -0
  339. package/src/workflows/store/worker-pool.ts +55 -0
  340. package/src/workflows/store/worker-runtime.ts +90 -0
  341. package/src/ai/kit-skill.ts +0 -22
  342. package/src/ai/usage-migrate.ts +0 -81
  343. package/src/browser/notification-target.ts +0 -27
  344. package/src/contracts/notification-live.ts +0 -66
  345. package/src/server/services/freeipa/test-certificates.ts +0 -44
  346. package/src/services/notifications/live.ts +0 -50
  347. package/src/services/session/test-fixture.ts +0 -18
  348. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  349. package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ai/http.ts CHANGED
@@ -4,7 +4,9 @@ import { z } from "zod";
4
4
  import { CapabilityAppIdSchema } from "../contracts/capabilities";
5
5
  import { type AuthContext, err, fail, respond } from "../server";
6
6
  import { aiAttachmentMarker } from "./attachments";
7
+ import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
7
8
  import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
9
+ import { AiQuotaError } from "./quotas";
8
10
  import { AiResourceMarkerSchema } from "./resource-markers";
9
11
  import { AI_SHORT_ID_PATTERN } from "./short-id";
10
12
  import { AI_SKILL_NAME_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
@@ -19,21 +21,25 @@ import { isAiSettingsError } from "./validate";
19
21
 
20
22
  export const AiDraftContentPartSchema = z.discriminatedUnion("type", [
21
23
  z.object({ type: z.literal("text"), text: z.string().max(20_000) }),
22
- AiResourceMarkerSchema.extend({ type: z.literal("resource") }),
24
+ AiResourceMarkerSchema.extend({ type: z.literal("resource"), inline: z.boolean().optional() }),
23
25
  z.object({
24
26
  type: z.literal("file"),
25
27
  path: z.string().trim().min(1).max(500),
26
28
  mediaType: z.string().trim().min(1).max(120),
27
29
  size: z.number().int().min(0),
28
30
  version: z.number().int().min(1),
31
+ inline: z.boolean().optional(),
29
32
  }),
33
+ z.object({ type: z.literal("project-file"), path: z.string().min(1).max(500).startsWith("/project/"), inline: z.boolean().optional() }),
30
34
  ]);
31
35
 
32
36
  export const AiConversationDraftInputSchema = z
33
37
  .object({
34
- content: z.array(AiDraftContentPartSchema).max(20),
38
+ content: z.array(AiDraftContentPartSchema).max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
35
39
  })
36
40
  .superRefine((value, context) => {
41
+ if (value.content.reduce((length, part) => length + (part.type === "text" ? part.text.length : 0), 0) > 20_000)
42
+ context.addIssue({ code: "custom", path: ["content"], message: "Draft text exceeds 20000 characters" });
37
43
  if (value.content.filter((part) => part.type !== "text").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
38
44
  context.addIssue({ code: "custom", path: ["content"], message: `A draft can attach at most ${AI_TURN_ATTACHMENT_MAX_ITEMS} items` });
39
45
  }
@@ -41,7 +47,9 @@ export const AiConversationDraftInputSchema = z
41
47
 
42
48
  export const AiInitialConversationDraftInputSchema = z
43
49
  .object({
44
- content: z.array(z.union([AiDraftContentPartSchema.options[0], AiDraftContentPartSchema.options[1]])).max(20),
50
+ content: z
51
+ .array(z.union([AiDraftContentPartSchema.options[0], AiDraftContentPartSchema.options[1]]))
52
+ .max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
45
53
  })
46
54
  .superRefine((value, context) => {
47
55
  if (value.content.filter((part) => part.type === "resource").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
@@ -112,7 +120,7 @@ export const AiUserContentPartSchema = z.union([
112
120
 
113
121
  export type AiTurnContentPart = z.infer<typeof AiUserContentPartSchema>;
114
122
 
115
- export const AiClientToolIdSchema = z.enum(["local_bash"] satisfies [AiClientToolId]);
123
+ export const AiClientToolIdSchema = z.enum(["local_bash", ...CODE_RUNTIME_TOOL_NAMES] satisfies [AiClientToolId, ...AiClientToolId[]]);
116
124
 
117
125
  export const AiTurnInputSchema = z
118
126
  .object({
@@ -120,10 +128,14 @@ export const AiTurnInputSchema = z
120
128
  content: z
121
129
  .array(AiUserContentPartSchema)
122
130
  .min(1)
123
- .max(AI_TURN_ATTACHMENT_MAX_ITEMS + 1)
131
+ .max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
124
132
  .optional(),
125
133
  modelProfileId: z.string().trim().min(1).optional(),
126
- clientToolIds: z.array(AiClientToolIdSchema).max(1).optional(),
134
+ clientToolIds: z
135
+ .array(AiClientToolIdSchema)
136
+ .max(1 + CODE_RUNTIME_TOOL_NAMES.length)
137
+ .refine((ids) => new Set(ids).size === ids.length, "Client tool IDs must be unique")
138
+ .optional(),
127
139
  })
128
140
  .refine((input) => Boolean(input.message?.trim() || input.content?.length), {
129
141
  message: "Message or content is required.",
@@ -141,9 +153,14 @@ export const AiTurnInputSchema = z
141
153
  export type AiTurnInput = z.infer<typeof AiTurnInputSchema>;
142
154
 
143
155
  export const AiSubmitConversationDraftInputSchema = z.object({
156
+ queueId: z.uuid().optional(),
144
157
  draftRevision: z.number().int().min(1),
145
158
  modelProfileId: z.string().trim().min(1).optional(),
146
- clientToolIds: z.array(AiClientToolIdSchema).max(1).optional(),
159
+ clientToolIds: z
160
+ .array(AiClientToolIdSchema)
161
+ .max(1 + CODE_RUNTIME_TOOL_NAMES.length)
162
+ .refine((ids) => new Set(ids).size === ids.length, "Client tool IDs must be unique")
163
+ .optional(),
147
164
  });
148
165
 
149
166
  export const AiSteerInputSchema = z.object({
@@ -166,7 +183,7 @@ export const AiMessageRetryInputSchema = z
166
183
  content: z
167
184
  .array(AiUserContentPartSchema)
168
185
  .min(1)
169
- .max(AI_TURN_ATTACHMENT_MAX_ITEMS + 1)
186
+ .max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
170
187
  .optional(),
171
188
  modelProfileId: z.string().trim().min(1).optional(),
172
189
  })
@@ -245,6 +262,7 @@ const aiSettingsServiceError = (error: AiSettingsError) => {
245
262
  };
246
263
 
247
264
  export const toAiErrorResponse = (c: Context<AuthContext>, error: unknown) => {
265
+ if (error instanceof AiQuotaError) return c.json({ ok: false, error: error.message, message: error.message, code: error.code }, 429);
248
266
  if (isAiSettingsError(error)) {
249
267
  return respond(c, fail(aiSettingsServiceError(error.aiError)));
250
268
  }
package/src/ai/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { assistantAiSettingsState, listAssistantAiModels } from "./assistant-models";
2
1
  export { type AiApprovalPreferenceRoutes, type AiApprovalPreferenceView, createAiApprovalPreferenceRoutes } from "./approval-routes";
3
2
  export {
4
3
  type AiToolApprovalContext,
@@ -12,12 +11,17 @@ export {
12
11
  rememberAiToolApproval,
13
12
  revokeAiToolApprovalPreference,
14
13
  } from "./approvals";
14
+ export { assistantAiSettingsState, listAssistantAiModels, selectAssistantAiModelId } from "./assistant-models";
15
15
  export { type AiAttachmentRef, aiAttachmentMarker, formatAiFileSize, parseAiAttachmentMarkers } from "./attachments";
16
+ export { CloudAiTranscribeAudioInputSchema, CloudAiTranscribeAudioOutputSchema, createCloudAiTranscribeAudioTool } from "./audio-tool";
17
+ export { CODE_RUNTIME_TOOL_NAMES, CodeResourceId, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
16
18
  export { aiCapabilityId, aiCapabilityToolName } from "./capabilities";
19
+ export { getAiChatQuotas } from "./chat-quotas";
17
20
  export {
18
21
  type AiChatTaskOccurrenceView,
19
22
  type AiChatTaskView,
20
23
  AiConversationIdSchema,
24
+ ChatTaskGrantsSchema,
21
25
  ChatTaskIdSchema,
22
26
  ChatTaskOccurrenceIdSchema,
23
27
  ChatTaskScheduleInputSchema,
@@ -37,6 +41,10 @@ export {
37
41
  aiChatTasks,
38
42
  } from "./chat-tasks";
39
43
  export { parseAiSse } from "./client/transport";
44
+ // Framework-owned managed execution host transport.
45
+ export { CODE_CAPABILITY_TOKEN_HEADER, createCodeCapabilityTransport } from "./code-capability-transport";
46
+ export { authorizeCodeExecution } from "./code-execution";
47
+ export { CODE_SOURCE_TOOLS, type CodeSourceToolName } from "./code-source-contracts";
40
48
  export { listAiCredentialProfileIds } from "./credentials";
41
49
  export {
42
50
  CLOUD_AI_TEXT_EDITOR_MAX_CHARS,
@@ -66,13 +74,18 @@ export {
66
74
  shouldApplyEnrichedDescription,
67
75
  shouldApplyEnrichedTitle,
68
76
  } from "./enrich";
77
+ export { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
78
+ export { AiFileLocation, AiFileReference } from "./file-reference-contracts";
69
79
  export {
70
80
  AI_FILES_MAX_CONVERSATION_BYTES_DEFAULT,
71
81
  AI_FILES_MAX_FILE_BYTES_DEFAULT,
72
82
  type AiFileStat,
83
+ createAiConversationArtifact,
73
84
  guessAiMediaType,
74
85
  listAiConversationFiles,
75
86
  normalizeAiFilePath,
87
+ readAiConversationFile,
88
+ writeAiConversationFile,
76
89
  } from "./files-store";
77
90
  export {
78
91
  AI_FIRECRAWL_API_KEY_SETTING_KEY,
@@ -202,6 +215,7 @@ export {
202
215
  isNewerWireEvent,
203
216
  } from "./protocol";
204
217
  export { createAiProvider } from "./provider";
218
+ export { AiQuotaError } from "./quotas";
205
219
  export { isConversationResourceCursor } from "./resource-refs";
206
220
  export type { AiRoutes } from "./routes";
207
221
  export {
@@ -214,7 +228,7 @@ export {
214
228
  selectAiModelProfile,
215
229
  toPublicAiSettingsState,
216
230
  } from "./settings";
217
- export { AI_SHORT_ID_PATTERN, createAiShortId } from "./short-id";
231
+ export { AI_SHORT_ID_PATTERN, createAiShortId, withAiShortIdForDb } from "./short-id";
218
232
  export {
219
233
  AI_SKILL_DESCRIPTION_MAX_CHARS,
220
234
  AI_SKILL_EXTRA_FRONTMATTER_MAX_CHARS,
@@ -237,7 +251,6 @@ export {
237
251
  validateAiSkillName,
238
252
  validateAiSkillReferences,
239
253
  } from "./skill-format";
240
- export { seedCloudAiSkills } from "./skill-seeds";
241
254
  export {
242
255
  type AiLoadedSkillSnapshot,
243
256
  type AiSkill,
@@ -251,6 +264,7 @@ export {
251
264
  AiSkillRevisionConflictError,
252
265
  type AiSkillSummary,
253
266
  aiSkills,
267
+ seedCloudAiSkills,
254
268
  } from "./skills";
255
269
  export { type AiSkillsRoutes, aiSkillsRoutes } from "./skills-routes";
256
270
  export { aiConversations } from "./store";
@@ -273,8 +287,17 @@ export {
273
287
  runAiStructured,
274
288
  } from "./structured";
275
289
  export { aiGlobalInstructionsContext, composeAiSystemPrompt, renderAiGlobalInstructions } from "./system-prompt";
290
+ export { type AiTaskRequest, type AiTaskRequestInput, AiTaskRequestSchema } from "./task-contracts";
291
+ export { executeAiTask } from "./task-execution";
276
292
  export { type AiToolApprovalState, type AiToolCallLocation, aiToolAudit } from "./tool-audit";
277
293
  export { defineAiTool, isFrontendToolMode, type PreparedAiTools, prepareAiTools } from "./tools";
294
+ export {
295
+ type AiResolvedAudioModel,
296
+ createAiTranscriptionProvider,
297
+ type RunAiTranscriptionInput,
298
+ resolveAiAudioModel,
299
+ runAiTranscription,
300
+ } from "./transcription";
278
301
  export type {
279
302
  AiAccessResult,
280
303
  AiCapabilityToolPresentation,
@@ -0,0 +1,197 @@
1
+ import { type SQL, sql } from "bun";
2
+ import type { AccessSubject } from "../server/services/access";
3
+ import { aiBudgetUnits, aiCostDecimal, aiCostUnits, aiDecimalUnits, hasBillableAiPricing } from "../shared/ai-costs";
4
+ import { AiQuotaError, aiQuotas, quotaWindow } from "./quotas";
5
+ import type { AiModelProfile } from "./types";
6
+
7
+ export class AiBackgroundCostError extends Error {
8
+ readonly code = "ai_background_cost_stop";
9
+ constructor() {
10
+ super("Background AI is paused by the cost safety limit. An administrator must review Usage rules.");
11
+ }
12
+ }
13
+
14
+ /** Admission can fail without latching the instance-wide emergency stop. */
15
+ export class AiBackgroundAdmissionError extends Error {
16
+ readonly code: string;
17
+ constructor(readonly retryable: boolean) {
18
+ super(
19
+ retryable
20
+ ? "Background AI budget is reserved by running calls. Try again after they finish."
21
+ : "This call cannot fit in the remaining background AI budget.",
22
+ );
23
+ this.code = retryable ? "ai_background_budget_reserved" : "ai_background_budget_insufficient";
24
+ }
25
+ }
26
+
27
+ export type AiCallContext = {
28
+ kind: "chat" | "background";
29
+ task: string;
30
+ subject?: AccessSubject | null;
31
+ userId?: string;
32
+ conversationId?: string;
33
+ turnId?: string;
34
+ workflowRunId?: string;
35
+ stepKey?: string;
36
+ traceId?: string;
37
+ appId?: string;
38
+ };
39
+
40
+ export async function backgroundCostState(db: SQL = sql) {
41
+ const [row] = await db<
42
+ { used: number; reserved: number; usedExact: string; reservedExact: string; unknown: number; stoppedAt: Date | null; unit: string }[]
43
+ >`
44
+ SELECT COALESCE(sum(cost),0)::text AS "usedExact",
45
+ COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0)::text AS "reservedExact",
46
+ COALESCE(sum(cost),0)::float8 AS used,
47
+ COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0)::float8 AS reserved,
48
+ count(*) FILTER(WHERE (COALESCE((pricing->>'inputPerMillion')::numeric,0)>0 OR COALESCE((pricing->>'outputPerMillion')::numeric,0)>0) AND cost IS NULL AND (finished_at IS NOT NULL OR lease_expires_at<=now()))::int AS unknown,
49
+ (SELECT background_stopped_at FROM ai.cost_config WHERE singleton) AS "stoppedAt",
50
+ (SELECT unit FROM ai.cost_config WHERE singleton) AS unit
51
+ FROM ai.inference_calls WHERE kind='background' AND started_at>=now()-interval '24 hours'`;
52
+ return row!;
53
+ }
54
+
55
+ /** Called under the configuration lock; persist transitions before notification delivery. */
56
+ async function checkBackgroundBudget(db: SQL) {
57
+ const config = await aiQuotas.config(db);
58
+ const state = await backgroundCostState(db);
59
+ if (!config.background?.enabled) return state;
60
+ const [flags] = await db<{ background_warned: boolean }[]>`SELECT background_warned FROM ai.cost_config WHERE singleton`;
61
+ const warning = config.background.warnAt !== null && aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.warnAt);
62
+ const stop = aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown > 0;
63
+ for (const kind of [...(warning && !flags!.background_warned ? ["warning"] : []), ...(stop && !state.stoppedAt ? ["stop"] : [])])
64
+ await db`INSERT INTO ai.cost_alerts(id,kind,cost,unit) VALUES(${crypto.randomUUID()}::uuid,${kind},${state.usedExact}::numeric,${config.unit ?? "EUR"})`;
65
+ await db`UPDATE ai.cost_config SET background_warned=${warning},
66
+ background_stopped_at=CASE WHEN ${stop} THEN COALESCE(background_stopped_at,clock_timestamp()) ELSE background_stopped_at END WHERE singleton`;
67
+ return backgroundCostState(db);
68
+ }
69
+
70
+ /** Admission and reservation share the settings lock across all worker processes. */
71
+ export async function beginAiCall(
72
+ profile: AiModelProfile,
73
+ context: AiCallContext,
74
+ inputTokens: number,
75
+ requestedOutput?: number,
76
+ outputBound?: number,
77
+ ) {
78
+ const result = await sql.begin(async (db) => {
79
+ await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
80
+ const config = await aiQuotas.config(db);
81
+ let remaining: bigint | undefined;
82
+ let backgroundHeadroom: bigint | undefined;
83
+ if (hasBillableAiPricing(profile.pricing) && context.kind === "background" && config.background?.enabled) {
84
+ const state = await checkBackgroundBudget(db);
85
+ if (state.stoppedAt || aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown) {
86
+ await db`UPDATE ai.cost_config SET background_stopped_at=COALESCE(background_stopped_at,clock_timestamp()) WHERE singleton`;
87
+ return { error: new AiBackgroundCostError() };
88
+ }
89
+ backgroundHeadroom = aiBudgetUnits(config.background.stopAt) - aiDecimalUnits(state.usedExact);
90
+ remaining = backgroundHeadroom - aiDecimalUnits(state.reservedExact);
91
+ }
92
+ if (hasBillableAiPricing(profile.pricing) && context.kind === "chat" && context.subject && config.enabled) {
93
+ const now = new Date();
94
+ const snapshot = await aiQuotas.snapshot(context.subject, profile.id, now, db);
95
+ for (const balance of snapshot.balances) {
96
+ if (balance.bypassed || balance.limit === null) continue;
97
+ if (balance.unknown)
98
+ return {
99
+ error: new AiQuotaError(
100
+ "quota_usage_unknown",
101
+ "Chat costs could not be measured. Reset the allowance or wait for the next window.",
102
+ ),
103
+ };
104
+ const rule = config.rules.find((rule) => rule.scope === balance.scope)!;
105
+ const window = quotaWindow(rule.anchor, rule.hours, now);
106
+ const user = context.subject.type === "user" ? context.subject.userId : null;
107
+ const service = context.subject.type === "service_account" ? context.subject.serviceAccountId : null;
108
+ const [pending] = await db<{ committed: string }[]>`
109
+ SELECT (COALESCE(sum(cost),0)+COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0))::text AS committed FROM ai.inference_calls c
110
+ WHERE kind='chat'
111
+ AND user_id IS NOT DISTINCT FROM ${user}::uuid AND service_account_id IS NOT DISTINCT FROM ${service}::uuid
112
+ AND (${balance.scope}='*' OR model_profile_id=${balance.scope}) AND started_at>=${window.from} AND started_at<${window.until}
113
+ AND started_at>COALESCE((SELECT max(created_at) FROM ai.cost_resets r
114
+ WHERE r.user_id IS NOT DISTINCT FROM ${user}::uuid AND r.service_account_id IS NOT DISTINCT FROM ${service}::uuid AND r.scope=${balance.scope}),'-infinity'::timestamptz)`;
115
+ const available = aiBudgetUnits(balance.limit) - aiDecimalUnits(pending?.committed ?? "0");
116
+ remaining = remaining === undefined || available < remaining ? available : remaining;
117
+ }
118
+ }
119
+ let maxOutputTokens = requestedOutput;
120
+ let reserved = "0";
121
+ if (profile.pricing && remaining !== undefined) {
122
+ const inputCost = aiCostUnits(profile.pricing, inputTokens, 0);
123
+ const outputPrice = aiCostUnits(profile.pricing, 0, 1);
124
+ const affordable = outputPrice > 0n ? Number((remaining - inputCost) / outputPrice) : Infinity;
125
+ if (inputCost > remaining || affordable < 1)
126
+ return {
127
+ error:
128
+ context.kind === "background"
129
+ ? new AiBackgroundAdmissionError(backgroundHeadroom !== undefined && inputCost + outputPrice <= backgroundHeadroom)
130
+ : new AiQuotaError("quota_exhausted", "Chat cost limit reached."),
131
+ };
132
+ const reserveOutput = Math.min(requestedOutput ?? outputBound ?? affordable, affordable);
133
+ // Preserve adapter defaults unless an explicit maximum or budget requires a cap.
134
+ if (requestedOutput !== undefined || reserveOutput < (outputBound ?? Infinity)) maxOutputTokens = reserveOutput;
135
+ reserved = aiCostDecimal(aiCostUnits(profile.pricing, inputTokens, Number.isFinite(reserveOutput) ? reserveOutput : 0));
136
+ }
137
+ const workflow = context.workflowRunId
138
+ ? (
139
+ await db<{ id: string; name: string; app: string; revision: number }[]>`
140
+ SELECT w.id,w.name,r.app_id AS app,v.revision FROM workflows.run r
141
+ JOIN workflows.workflow w ON w.id=r.workflow_id JOIN workflows.version v ON v.id=r.workflow_version_id
142
+ WHERE r.id=${context.workflowRunId}::uuid`
143
+ )[0]
144
+ : undefined;
145
+ const id = crypto.randomUUID();
146
+ const userId = context.subject?.type === "user" ? context.subject.userId : context.userId;
147
+ const serviceId = context.subject?.type === "service_account" ? context.subject.serviceAccountId : null;
148
+ await db`INSERT INTO ai.inference_calls(id,user_id,service_account_id,model_profile_id,provider_model,kind,task,
149
+ turn_id,turn_attempt,conversation_id,workflow_run_id,step_key,trace_id,app_id,workflow_id,workflow_name,workflow_version,pricing,reserved,lease_expires_at)
150
+ VALUES(${id}::uuid,CASE WHEN ${serviceId}::uuid IS NULL THEN COALESCE(${userId ?? null}::uuid,(SELECT created_by_user_id FROM ai.conversations WHERE id=${context.conversationId ?? null}::uuid)) ELSE NULL END,
151
+ ${serviceId}::uuid,${profile.id},${profile.model},${context.kind},${context.task},${context.turnId ?? null}::uuid,
152
+ (SELECT attempt FROM ai.turns WHERE id=${context.turnId ?? null}::uuid),${context.conversationId ?? null}::uuid,
153
+ ${context.workflowRunId ?? null}::uuid,${context.stepKey ?? null},${context.traceId ?? null},COALESCE(${workflow?.app ?? context.appId ?? null},(SELECT launched_by_app_id FROM ai.conversations WHERE id=${context.conversationId ?? null}::uuid),'core'),${workflow?.id ?? null}::uuid,${workflow?.name ?? null},${workflow?.revision ?? null},
154
+ (${profile.pricing ? JSON.stringify(profile.pricing) : null}::text)::jsonb,${reserved}::numeric,now()+interval '2 minutes')`;
155
+ return { id, maxOutputTokens };
156
+ });
157
+ if (result.error) throw result.error;
158
+ return { id: result.id!, maxOutputTokens: result.maxOutputTokens };
159
+ }
160
+
161
+ export async function finishAiCall(
162
+ id: string,
163
+ usage: { input: number; output: number; estimated?: boolean } | undefined,
164
+ status: "ok" | "failed",
165
+ ) {
166
+ await sql.begin(async (db) => {
167
+ await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
168
+ const [call] = await db<
169
+ { pricing: AiModelProfile["pricing"]; kind: string }[]
170
+ >`SELECT pricing,kind FROM ai.inference_calls WHERE id=${id}::uuid AND finished_at IS NULL FOR UPDATE`;
171
+ if (!call) return;
172
+ const cost =
173
+ call.pricing?.inputPerMillion === 0 && call.pricing.outputPerMillion === 0
174
+ ? "0"
175
+ : call.pricing && usage
176
+ ? aiCostDecimal(aiCostUnits(call.pricing, usage.input, usage.output))
177
+ : null;
178
+ await db`UPDATE ai.inference_calls SET input=${usage?.input ?? null},output=${usage?.output ?? null},estimated=${usage?.estimated ?? false},
179
+ cost=${cost}::numeric,reserved=0,finished_at=clock_timestamp(),status=${status},error_code=${status === "failed" ? "ai_provider_call_failed" : null} WHERE id=${id}::uuid`;
180
+ if (call.kind === "background") await checkBackgroundBudget(db);
181
+ });
182
+ }
183
+
184
+ export async function releaseBackgroundCostStop(actorId: string) {
185
+ return sql.begin(async (db) => {
186
+ await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
187
+ const config = await aiQuotas.config(db);
188
+ const state = await backgroundCostState(db);
189
+ if (config.background?.enabled && (aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown))
190
+ throw new AiBackgroundCostError();
191
+ if (!state.stoppedAt) return state;
192
+ await db`UPDATE ai.cost_config SET background_stopped_at=NULL,background_warned=false,revision=revision+1 WHERE singleton`;
193
+ const next = await aiQuotas.config(db);
194
+ await db`INSERT INTO ai.cost_changes(revision,actor_id,config) VALUES(${next.revision},${actorId}::uuid,(${JSON.stringify({ ...next, action: "release-background-stop" })}::text)::jsonb)`;
195
+ return backgroundCostState(db);
196
+ });
197
+ }
@@ -7,6 +7,7 @@ export const AI_INVALIDATION_DOMAINS = [
7
7
  "conversation-sources",
8
8
  "conversation-files",
9
9
  "conversation-tasks",
10
+ "conversation-dictations",
10
11
  "project-list",
11
12
  "project-detail",
12
13
  "project-context",
@@ -129,7 +130,10 @@ const AiConversationSchema = z
129
130
  keywords: z.array(z.string()),
130
131
  pinnedAt: z.string().nullable(),
131
132
  archivedAt: z.string().nullable(),
132
- runStatus: z.enum(["idle", "queued", "running", "needs_attention", "failed"]),
133
+ done: z.boolean().nullable(),
134
+ isDone: z.boolean(),
135
+ lastUsedAt: z.string(),
136
+ runStatus: z.enum(["idle", "queued", "running", "needs_attention", "waiting_for_browser", "failed"]),
133
137
  runError: z.string().nullable(),
134
138
  unreadCompletion: z.boolean(),
135
139
  projectId: z.string().nullable(),
package/src/ai/live.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { latestAiInvalidationCursor } from "./live-outbox";
2
- export { aiLiveRoutes, type AiLiveRoutes, type AiLiveRoutesConfig } from "./live-routes";
2
+ export { type AiLiveRoutes, type AiLiveRoutesConfig, aiLiveRoutes } from "./live-routes";
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import {
2
3
  MARKDOWN_PDF_MAX_CUSTOM_CSS_BYTES,
3
4
  MARKDOWN_PDF_MAX_MARKDOWN_BYTES,
@@ -5,9 +6,8 @@ import {
5
6
  type RenderMarkdownToPdfInput,
6
7
  renderMarkdownToPdf,
7
8
  } from "../services/pdf";
8
- import { z } from "zod";
9
9
  import { aiProjectFilePathFromMount } from "./file-mount";
10
- import { aiFileStore, normalizeAiFilePath, type AiFileContent } from "./files-store";
10
+ import { type AiFileContent, aiFileStore, normalizeAiFilePath } from "./files-store";
11
11
  import { defineAiTool } from "./tools";
12
12
 
13
13
  export const CloudAiMarkdownToPdfInputSchema = z
@@ -459,9 +459,7 @@ export const aiMemories = {
459
459
  action: "retired" as const,
460
460
  kind: memory.kind,
461
461
  content: memory.content,
462
- ...(memory.resource_type && memory.resource_id
463
- ? { resourceRef: { type: memory.resource_type, id: memory.resource_id } }
464
- : {}),
462
+ ...(memory.resource_type && memory.resource_id ? { resourceRef: { type: memory.resource_type, id: memory.resource_id } } : {}),
465
463
  }));
466
464
  }
467
465
 
@@ -509,19 +507,20 @@ export const aiMemories = {
509
507
  );
510
508
  const created = rows[0];
511
509
  return created
512
- ? [{
513
- action: "added",
514
- kind: created.kind,
515
- content: created.content,
516
- ...(resourceRef ? { resourceRef } : {}),
517
- }]
510
+ ? [
511
+ {
512
+ action: "added",
513
+ kind: created.kind,
514
+ content: created.content,
515
+ ...(resourceRef ? { resourceRef } : {}),
516
+ },
517
+ ]
518
518
  : [];
519
519
  }
520
520
 
521
521
  if (duplicate && !targets.some((memory) => memory.id === duplicate.id)) {
522
522
  const duplicateResourceMatches =
523
- proposal.kind !== "workflow" ||
524
- (duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
523
+ proposal.kind !== "workflow" || (duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
525
524
  if (duplicate.kind !== proposal.kind || !duplicateResourceMatches) return [];
526
525
  await tx`
527
526
  UPDATE ai.memories old
@@ -532,15 +531,17 @@ export const aiMemories = {
532
531
  AND old.source = 'background' AND old.priority = 'normal'
533
532
  AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
534
533
  `;
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
- }];
534
+ return [
535
+ {
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
+ ];
544
545
  }
545
546
 
546
547
  const destination = targets[0]!;
@@ -565,13 +566,15 @@ export const aiMemories = {
565
566
  AND deleted_at IS NULL AND superseded_by_id IS NULL
566
567
  `;
567
568
  }
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
- }];
569
+ return [
570
+ {
571
+ action: proposal.action === "merge" ? "merged" : "updated",
572
+ kind: proposal.kind,
573
+ content,
574
+ previousContent: targets.map((memory) => memory.content).join(" · "),
575
+ ...(resourceRef ? { resourceRef } : {}),
576
+ },
577
+ ];
575
578
  });
576
579
  },
577
580
 
@@ -1,7 +1,7 @@
1
1
  import { sql } from "bun";
2
2
  import type { CloudResourceRef } from "../contracts/capabilities";
3
- import { withAiShortId } from "./short-id";
4
3
  import type { AiMemoryKind } from "./memories";
4
+ import { withAiShortId } from "./short-id";
5
5
 
6
6
  export type AiMemoryLearningRunStatus = "running" | "ok" | "skipped" | "failed";
7
7
  export type AiMemoryLearningRunKind = "turn" | "workflow";