@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
@@ -10,6 +10,8 @@ import {
10
10
  type CapabilityActionManifest,
11
11
  type CapabilityActionReviewResult,
12
12
  CapabilityActionReviewSchema,
13
+ type CapabilityCommandManifest,
14
+ CapabilityCommandManifestSchema,
13
15
  type CapabilityDefinitions,
14
16
  type CapabilityError,
15
17
  CapabilityErrorSchema,
@@ -190,12 +192,14 @@ const compileSchemaPresentation = (
190
192
 
191
193
  const compileOperationPresentation = (
192
194
  value: unknown,
193
- operation: CapabilityQueryManifest | CapabilityActionManifest,
195
+ operation: CapabilityQueryManifest | CapabilityActionManifest | CapabilityCommandManifest,
194
196
  label: string,
195
197
  ): CapabilityOperationPresentationTranslation => {
196
198
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
197
199
  const translation = value as Record<string, unknown>;
198
- const allowed = new Set(["title", "description", "input", "data", "searchTags"]);
200
+ const allowed = new Set(
201
+ "path" in operation ? ["title", "description", "input"] : ["title", "description", "input", "data", "searchTags"],
202
+ );
199
203
  const extra = Object.keys(translation).find((key) => !allowed.has(key));
200
204
  if (extra) throw new Error(`${label} contains unsupported field "${extra}"`);
201
205
  if (translation.title !== undefined) {
@@ -238,7 +242,7 @@ const compileOperationPresentation = (
238
242
  );
239
243
  }
240
244
  const input = compileSchemaPresentation(translation.input, operation.inputSchema, `${label}.input`);
241
- const data = compileSchemaPresentation(translation.data, operation.dataSchema, `${label}.data`);
245
+ const data = "dataSchema" in operation ? compileSchemaPresentation(translation.data, operation.dataSchema, `${label}.data`) : undefined;
242
246
  return {
243
247
  ...(typeof translation.title === "string" ? { title: translation.title.trim() } : {}),
244
248
  ...(typeof translation.description === "string" ? { description: translation.description.trim() } : {}),
@@ -263,6 +267,7 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
263
267
  const types = new Map(manifest.types.map((type) => [type.localId, type]));
264
268
  const queries = new Map(manifest.queries.map((operation) => [operation.localId, operation]));
265
269
  const actions = new Map(manifest.actions.map((operation) => [operation.localId, operation]));
270
+ const commands = new Map(manifest.commands.map((operation) => [operation.localId, operation]));
266
271
  const translations: Record<string, CapabilityPresentationTranslation> = {};
267
272
  for (const [locale, rawTranslation] of Object.entries(catalog.translations as Record<string, unknown>)) {
268
273
  const canonical = canonicalLocale(locale);
@@ -273,7 +278,7 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
273
278
  throw new Error(`Capability presentation translation ${canonical} must be an object`);
274
279
  }
275
280
  const raw = rawTranslation as Record<string, unknown>;
276
- const extra = Object.keys(raw).find((key) => key !== "types" && key !== "queries" && key !== "actions");
281
+ const extra = Object.keys(raw).find((key) => key !== "types" && key !== "queries" && key !== "actions" && key !== "commands");
277
282
  if (extra) throw new Error(`Capability presentation translation ${canonical} contains unsupported field "${extra}"`);
278
283
  const compileGroup = <T>(
279
284
  group: unknown,
@@ -311,10 +316,12 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
311
316
  });
312
317
  const translatedQueries = compileGroup(raw.queries, queries, "Query", compileOperationPresentation);
313
318
  const translatedActions = compileGroup(raw.actions, actions, "Action", compileOperationPresentation);
319
+ const translatedCommands = compileGroup(raw.commands, commands, "Command", compileOperationPresentation);
314
320
  translations[canonical] = {
315
321
  ...(translatedTypes ? { types: translatedTypes } : {}),
316
322
  ...(translatedQueries ? { queries: translatedQueries } : {}),
317
323
  ...(translatedActions ? { actions: translatedActions } : {}),
324
+ ...(translatedCommands ? { commands: translatedCommands } : {}),
318
325
  };
319
326
  }
320
327
  return { baseLocale, translations };
@@ -406,7 +413,7 @@ const normalizeSearchTags = (definition: CapabilityQueryDefinition, label: strin
406
413
  ...(aliases.length > 0 ? { aliases } : {}),
407
414
  };
408
415
  });
409
- return { tags };
416
+ return { tags, ...(definition.universalSearch.scopeTypes ? { scopeTypes: [...new Set(definition.universalSearch.scopeTypes)] } : {}) };
410
417
  };
411
418
 
412
419
  const compileOperationSchemas = (definition: CapabilityQueryDefinition | CapabilityActionDefinition, label: string) => {
@@ -426,7 +433,11 @@ const compileOperationSchemas = (definition: CapabilityQueryDefinition | Capabil
426
433
  inputSchema,
427
434
  dataSchema,
428
435
  resultZodSchema,
429
- schemaHash: capabilityHash({ inputSchema, dataSchema }),
436
+ schemaHash: capabilityHash({
437
+ inputSchema,
438
+ dataSchema,
439
+ ...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
440
+ }),
430
441
  };
431
442
  };
432
443
 
@@ -462,6 +473,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
462
473
  for (const [localId, definition] of Object.entries(definitions.queries ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
463
474
  registerLocalId(localId, "Query");
464
475
  const label = `Query ${localId}`;
476
+ if (
477
+ definition.stream &&
478
+ ((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
479
+ )
480
+ throw new Error(`${label}: write streams require an Action and a positive byte budget`);
481
+ if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
482
+ throw new Error(`${label}: write streams require idempotency`);
465
483
  const schemas = compileOperationSchemas(definition, label);
466
484
  if (definition.universalSearch) {
467
485
  const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
@@ -480,9 +498,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
480
498
  inputSchema: schemas.inputSchema,
481
499
  dataSchema: schemas.dataSchema,
482
500
  schemaHash: schemas.schemaHash,
501
+ ...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
483
502
  openWorld: definition.openWorld,
484
503
  universalSearch: normalizeSearchTags(definition, label),
485
504
  } satisfies CapabilityQueryManifest;
505
+ for (const type of manifest.universalSearch?.scopeTypes ?? []) {
506
+ if (!typeIds.has(qualifiedId(appId, type))) throw new Error(`${label} declares an unknown search scope type ${type}`);
507
+ }
486
508
  queries.set(localId, {
487
509
  definition,
488
510
  manifest,
@@ -499,12 +521,41 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
499
521
  for (const [localId, definition] of Object.entries(definitions.actions ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
500
522
  registerLocalId(localId, "Action");
501
523
  const label = `Action ${localId}`;
524
+ // Declaration rule, documented in docs/platform/capabilities.md:
525
+ // destructive means irreversible or externally visible. An irreversible or
526
+ // open-world effect must always be reviewable and safely repeatable, and
527
+ // must never be approved once and then replayed silently.
528
+ if (definition.destructive && !definition.review) {
529
+ throw new Error(`${label} is destructive and must declare review()`);
530
+ }
531
+ if (definition.destructive && definition.idempotency !== "required") {
532
+ throw new Error(`${label} is destructive and must declare idempotency: "required"`);
533
+ }
534
+ if (definition.openWorld && !definition.review) {
535
+ throw new Error(`${label} is open-world and must declare review()`);
536
+ }
537
+ if (definition.openWorld && definition.idempotency !== "required") {
538
+ throw new Error(`${label} is open-world and must declare idempotency: "required"`);
539
+ }
502
540
  if (definition.approval === "rememberable" && definition.openWorld) {
503
541
  throw new Error(`${label} cannot remember approval for an open-world effect`);
504
542
  }
543
+ if (definition.approval === "rememberable" && definition.destructive) {
544
+ throw new Error(`${label} cannot remember approval for a destructive effect`);
545
+ }
546
+ if (definition.approval === "none" && (definition.destructive || definition.openWorld)) {
547
+ throw new Error(`${label} cannot skip approval for destructive or open-world effects`);
548
+ }
505
549
  if (definition.approval === "rememberable" && !definition.review) {
506
550
  throw new Error(`${label} must provide a review before approval can be remembered`);
507
551
  }
552
+ if (
553
+ definition.stream &&
554
+ ((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
555
+ )
556
+ throw new Error(`${label}: write streams require an Action and a positive byte budget`);
557
+ if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
558
+ throw new Error(`${label}: write streams require idempotency`);
508
559
  const schemas = compileOperationSchemas(definition, label);
509
560
  const manifest = {
510
561
  localId,
@@ -513,6 +564,7 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
513
564
  inputSchema: schemas.inputSchema,
514
565
  dataSchema: schemas.dataSchema,
515
566
  schemaHash: schemas.schemaHash,
567
+ ...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
516
568
  destructive: definition.destructive,
517
569
  openWorld: definition.openWorld,
518
570
  idempotency: definition.idempotency,
@@ -526,12 +578,30 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
526
578
  });
527
579
  }
528
580
 
581
+ const commands = Object.entries(definitions.commands ?? {})
582
+ .sort(([a], [b]) => a.localeCompare(b))
583
+ .map(([localId, definition]) => {
584
+ registerLocalId(localId, "Command");
585
+ const inputSchema = projectSchema(definition.input, `Command ${localId} input`, "input");
586
+ assertClosedObjectInput(inputSchema, `Command ${localId} input`);
587
+ return CapabilityCommandManifestSchema.parse({
588
+ localId,
589
+ title: definition.title.trim(),
590
+ description: definition.description.trim(),
591
+ icon: definition.icon,
592
+ keywords: [...(definition.keywords ?? [])],
593
+ inputSchema,
594
+ path: definition.path,
595
+ });
596
+ });
597
+
529
598
  const manifestBase = {
530
599
  protocolVersion: CAPABILITY_PROTOCOL_VERSION,
531
600
  appId,
532
601
  types,
533
602
  queries: [...queries.values()].map((entry) => entry.manifest),
534
603
  actions: [...actions.values()].map((entry) => entry.manifest),
604
+ commands,
535
605
  };
536
606
  const manifest = CapabilityManifestSchema.parse({
537
607
  ...manifestBase,
@@ -614,6 +684,7 @@ export const resolveCapabilityManifestPresentation = (
614
684
  ...(operation.universalSearch
615
685
  ? {
616
686
  universalSearch: {
687
+ ...operation.universalSearch,
617
688
  tags: operation.universalSearch.tags.map((tag) => {
618
689
  const tagCopy = copy.searchTags?.[tag.tag];
619
690
  return tagCopy
@@ -625,6 +696,17 @@ export const resolveCapabilityManifestPresentation = (
625
696
  : {}),
626
697
  };
627
698
  }),
699
+ commands: current.commands.map((operation) => {
700
+ const copy = translation.commands?.[operation.localId];
701
+ return copy
702
+ ? {
703
+ ...operation,
704
+ title: copy.title ?? operation.title,
705
+ description: copy.description ?? operation.description,
706
+ inputSchema: applySchemaPresentation(operation.inputSchema, copy.input),
707
+ }
708
+ : operation;
709
+ }),
628
710
  actions: current.actions.map((operation) => {
629
711
  const copy = translation.actions?.[operation.localId];
630
712
  return copy
@@ -668,10 +750,20 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
668
750
  );
669
751
  }
670
752
  assertClosedObjectInput(inputSchema, `Operation ${operation.localId} input`);
671
- const expectedSchemaHash = capabilityHash({ inputSchema: operation.inputSchema, dataSchema: operation.dataSchema });
753
+ if (operation.stream?.direction === "write" && (!("idempotency" in operation) || operation.idempotency !== "required")) {
754
+ throw new Error(`Operation ${operation.localId} write streams require an idempotent Action`);
755
+ }
756
+ const expectedSchemaHash = capabilityHash({
757
+ inputSchema: operation.inputSchema,
758
+ dataSchema: operation.dataSchema,
759
+ ...(operation.stream ? { stream: operation.stream } : {}),
760
+ });
672
761
  if (operation.schemaHash !== expectedSchemaHash)
673
762
  throw new Error(`Operation ${operation.localId} schemaHash does not match its schemas`);
674
763
  if ("universalSearch" in operation && operation.universalSearch) {
764
+ for (const type of operation.universalSearch.scopeTypes ?? []) {
765
+ if (!manifest.types.some((entry) => entry.localId === type)) throw new Error(`Unknown search scope type ${type}`);
766
+ }
675
767
  const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
676
768
  const expectedData = projectSchema(UniversalSearchDataSchema, "Universal Search data", "output");
677
769
  if (
@@ -682,6 +774,11 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
682
774
  }
683
775
  }
684
776
  }
777
+ for (const command of manifest.commands) {
778
+ registerLocalId(command.localId, "Command");
779
+ z.fromJSONSchema(structuredClone(command.inputSchema));
780
+ assertClosedObjectInput(command.inputSchema, `Command ${command.localId} input`);
781
+ }
685
782
  const queries = new Map(manifest.queries.map((query) => [query.localId, query]));
686
783
  for (const type of manifest.types) {
687
784
  if (!type.reader) continue;
@@ -787,6 +884,12 @@ export const capabilityManifestEvolutionIssues = (previous: CapabilityManifest,
787
884
  else if (type.reader && current.reader !== type.reader) issues.push(`Type ${type.localId} reader changed`);
788
885
  }
789
886
 
887
+ const nextCommands = new Map(next.commands.map((command) => [command.localId, command]));
888
+ for (const command of previous.commands) {
889
+ const current = nextCommands.get(command.localId);
890
+ if (!current) issues.push(`Command ${command.localId} was removed`);
891
+ else issues.push(...schemaEvolutionIssues(command.inputSchema, current.inputSchema, `Command ${command.localId} input`, "input"));
892
+ }
790
893
  const nextQueries = new Map(next.queries.map((operation) => [operation.localId, operation]));
791
894
  const nextActions = new Map(next.actions.map((operation) => [operation.localId, operation]));
792
895
  for (const operation of previous.queries) {
@@ -982,11 +1085,32 @@ export const invokeCompiledCapability = async (params: {
982
1085
  },
983
1086
  };
984
1087
  }
1088
+ if ("universalSearch" in operation.manifest && operation.manifest.universalSearch) {
1089
+ const search = UniversalSearchInputSchema.parse(input.data);
1090
+ if (
1091
+ search.scope &&
1092
+ !(operation.manifest.universalSearch.scopeTypes ?? []).some(
1093
+ (type) => qualifiedId(params.compiled.manifest.appId, type) === search.scope!.type,
1094
+ )
1095
+ )
1096
+ return { ok: false, error: { code: "VALIDATION_FAILED", message: "Unsupported search scope", status: 400 } };
1097
+ }
985
1098
  try {
986
1099
  const invoked = await operation.definition.run(input.data, context);
987
1100
  if (!invoked.ok) {
988
1101
  return normalizeProviderError(invoked.error, "Capability returned an invalid error", params.onUnexpectedError);
989
1102
  }
1103
+ const stream = invoked.data.stream;
1104
+ if (
1105
+ stream &&
1106
+ (Buffer.byteLength(stream.id) > 2048 ||
1107
+ !operation.definition.stream ||
1108
+ stream.direction !== operation.definition.stream.direction ||
1109
+ stream.size > operation.definition.stream.maxBytes ||
1110
+ Date.parse(stream.expiresAt) <= Date.now() ||
1111
+ Date.parse(stream.expiresAt) > Date.now() + 86_400_000)
1112
+ )
1113
+ return invalidProviderError("Stream is outside its declared policy or 24 hour lifetime");
990
1114
  const validated = validateCapabilityResult(params.compiled, operation, invoked.data, params.onUnexpectedError);
991
1115
  return validated.ok
992
1116
  ? ({
@@ -0,0 +1,81 @@
1
+ import { isServiceError } from "@k2b/stdlib";
2
+ import { exactStream } from "../capabilities/stream-body";
3
+ import type { CapabilityExecutionContext } from "../contracts/capabilities";
4
+ import {
5
+ CAPABILITY_MAX_RESULT_BYTES,
6
+ CapabilityStreamSchema,
7
+ CapabilityStreamStatusSchema,
8
+ capabilityResultSchema,
9
+ } from "../contracts/capabilities";
10
+ import type { CompiledCapabilities } from "./capabilities";
11
+
12
+ const failure = (status: number, code: string, message: string) => Response.json({ code, message }, { status });
13
+ export async function invokeCapabilityStream(params: {
14
+ compiled: CompiledCapabilities;
15
+ kind: "queries" | "actions";
16
+ localId: string;
17
+ verb: string;
18
+ request: Request;
19
+ context: CapabilityExecutionContext;
20
+ }): Promise<Response> {
21
+ const operation = params.compiled[params.kind].get(params.localId);
22
+ const definition = operation?.definition.stream;
23
+ if (!operation || !definition) return failure(404, "STREAM_NOT_FOUND", "Stream operation not found");
24
+ const offer = params.request.headers.get("x-cloud-stream-offer");
25
+ if (!offer || offer.length > 24000) return failure(400, "STREAM_INVALID", "Invalid stream offer");
26
+ const receipt = (value: unknown) => {
27
+ const parsed = capabilityResultSchema(operation.definition.data).parse(value);
28
+ if (parsed.stream) throw new Error("Stream receipts cannot issue another stream");
29
+ return parsed;
30
+ };
31
+ try {
32
+ const stream = CapabilityStreamSchema.parse(JSON.parse(Buffer.from(offer, "base64url").toString()));
33
+ if (stream.direction !== definition.direction || stream.size > definition.maxBytes || Date.parse(stream.expiresAt) <= Date.now())
34
+ return failure(410, "STREAM_EXPIRED", "Stream expired or outside its byte budget");
35
+ if (definition.direction === "read") {
36
+ if (params.verb !== "read") return failure(403, "STREAM_DENIED", "Read-only stream");
37
+ const response = await definition.read(stream, params.context);
38
+ if (!response.ok || !response.body) {
39
+ await response.body?.cancel();
40
+ return failure(502, "STREAM_FAILED", "Source could not be read");
41
+ }
42
+ return new Response(exactStream(response.body, stream.size), {
43
+ headers: { "content-type": stream.mediaType, "cache-control": "no-store" },
44
+ });
45
+ }
46
+ let result: unknown;
47
+ if (params.verb === "write") {
48
+ const body = exactStream(
49
+ params.request.body ??
50
+ new ReadableStream({
51
+ start(c) {
52
+ c.close();
53
+ },
54
+ }),
55
+ stream.size,
56
+ );
57
+ try {
58
+ const value = await definition.write(stream, body, params.context);
59
+ result = receipt(value);
60
+ } finally {
61
+ if (!body.locked) await body.cancel().catch(() => undefined);
62
+ }
63
+ } else if (params.verb === "status") {
64
+ const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
65
+ if (status.state === "completed") receipt(status.result);
66
+ result = status;
67
+ } else if (params.verb === "abort") {
68
+ await definition.abort(stream, params.context);
69
+ const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
70
+ if (status.state === "completed") receipt(status.result);
71
+ result = status;
72
+ } else return failure(400, "STREAM_INVALID", "Unknown stream operation");
73
+ const json = JSON.stringify(result);
74
+ if (Buffer.byteLength(json) > CAPABILITY_MAX_RESULT_BYTES)
75
+ return failure(502, "STREAM_FAILED", "Stream receipt is too large; inspect status before retrying");
76
+ return new Response(json, { headers: { "content-type": "application/json", "cache-control": "no-store" } });
77
+ } catch (error) {
78
+ if (isServiceError(error)) return failure(error.status, error.code, error.message);
79
+ return failure(409, "STREAM_FAILED", "Transfer failed; inspect status before retrying a write");
80
+ }
81
+ }