@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/cli/index.ts CHANGED
@@ -32,7 +32,7 @@ export type CloudCliContext = {
32
32
  setDefault: (key: string, value: string | undefined) => Promise<void>;
33
33
  createApiClient: <TApi extends Hono<any, any, any>>(basePath: string) => CloudApiClient<TApi>;
34
34
  fetch: (path: string, init?: RequestInit) => Promise<Response>;
35
- readJson: <T>(response: Response) => Promise<T>;
35
+ readJson: <T>(response: Pick<Response, "json" | "text" | "ok" | "status" | "statusText">) => Promise<T>;
36
36
  print: (value?: string) => void;
37
37
  /** Write a stdout chunk without adding a newline. */
38
38
  /** Resolves once the value is flushed; await it when writing large output. */
@@ -51,7 +51,8 @@ export type CloudCliModule = {
51
51
  booleanFlags?: readonly string[];
52
52
  requiresCloud?: boolean;
53
53
  requiresCloudFor?: (args: string[], flags: CloudCliFlags) => boolean;
54
- help?: () => string;
54
+ /** Render module help in the request locale; command syntax stays unchanged. */
55
+ help?: (locale?: string) => string;
55
56
  run: (context: CloudCliContext) => Promise<number | void> | number | void;
56
57
  };
57
58
 
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Environment registry factory.
3
+ *
4
+ * Every runtime environment variable is declared once with a schema, a
5
+ * default, a scope and a one-sentence doc. Registries read `process.env`
6
+ * lazily: each key is parsed on access, so tests can change variables and
7
+ * consumers never depend on an import-time snapshot. `.env.example`,
8
+ * `.env.prod.example` and the configuration reference are generated from the
9
+ * `$specs` of every registry by `scripts/generate-config-docs.ts`.
10
+ */
11
+ import { z } from "zod";
12
+
13
+ /**
14
+ * `runtime` is read by deployed processes, `secret` is a runtime credential,
15
+ * `development` only matters for host-run development, builds or tooling.
16
+ */
17
+ export type EnvScope = "runtime" | "secret" | "development";
18
+
19
+ export type EnvSpec<T> = {
20
+ /** Parses the trimmed raw value. Empty and unset values never reach the schema. */
21
+ schema: z.ZodType<T, unknown>;
22
+ /** Used when the variable is unset or empty; a function is evaluated on every access. */
23
+ default?: T | (() => T);
24
+ /** One sentence for operators; rendered into the generated files. */
25
+ doc: string;
26
+ /** Default `runtime`. */
27
+ scope?: EnvScope;
28
+ /** Host-run development value written to `.env.example`. */
29
+ example?: string;
30
+ /** Throw on access when unset and no default exists. */
31
+ required?: boolean;
32
+ /** Alternative variable names, consulted after the canonical key. */
33
+ aliases?: string[];
34
+ };
35
+
36
+ export type EnvSpecs = Record<string, EnvSpec<unknown>>;
37
+
38
+ type EnvValue<S extends EnvSpec<unknown>> = S extends { default: unknown } | { required: true }
39
+ ? z.output<S["schema"]>
40
+ : z.output<S["schema"]> | undefined;
41
+
42
+ export type EnvRegistry<S extends EnvSpecs> = { readonly [K in keyof S]: EnvValue<S[K]> } & {
43
+ readonly $specs: S;
44
+ readonly $keys: readonly string[];
45
+ };
46
+
47
+ const rawValue = (key: string, aliases: string[] = []): string | undefined => {
48
+ for (const name of [key, ...aliases]) {
49
+ const value = process.env[name]?.trim();
50
+ if (value) return value;
51
+ }
52
+ return undefined;
53
+ };
54
+
55
+ const isFactory = <T>(value: T | (() => T)): value is () => T => typeof value === "function";
56
+
57
+ export const readEnv = <T>(key: string, spec: EnvSpec<T>): T | undefined => {
58
+ const raw = rawValue(key, spec.aliases);
59
+ if (raw === undefined) {
60
+ if (spec.default !== undefined) return isFactory(spec.default) ? spec.default() : spec.default;
61
+ if (spec.required) throw new Error(`${key} is required`);
62
+ return undefined;
63
+ }
64
+ const parsed = spec.schema.safeParse(raw);
65
+ if (!parsed.success) {
66
+ const reason = parsed.error.issues.map((issue) => issue.message).join("; ") || "is invalid";
67
+ throw new Error(`${key} ${reason}`);
68
+ }
69
+ return parsed.data;
70
+ };
71
+
72
+ export const defineEnv = <S extends EnvSpecs>(specs: S): EnvRegistry<S> => {
73
+ const keys = Object.freeze(Object.keys(specs));
74
+ const registry: Record<string, unknown> = { $specs: specs, $keys: keys };
75
+ for (const key of keys) {
76
+ Object.defineProperty(registry, key, { enumerable: true, configurable: true, get: () => readEnv(key, specs[key]!) });
77
+ }
78
+ // The getters above are the only way to build a typed lazy object.
79
+ return registry as EnvRegistry<S>;
80
+ };
81
+
82
+ /** Any non-empty string; empty values are treated as unset before parsing. */
83
+ export const envString = z.string();
84
+
85
+ /** `true` or `1` (case-insensitive) is true; everything else is false. */
86
+ export const envBoolean = z.string().transform((value) => {
87
+ const normalized = value.toLowerCase();
88
+ return normalized === "true" || normalized === "1";
89
+ });
90
+
91
+ /** Comma-separated entries; whitespace and empty entries are dropped. */
92
+ export const envList = z.string().transform((value) =>
93
+ value
94
+ .split(",")
95
+ .map((entry) => entry.trim())
96
+ .filter(Boolean),
97
+ );
package/src/config/env.ts CHANGED
@@ -1,54 +1,150 @@
1
1
  /**
2
- * Environment variable parsing.
3
- * All env vars are parsed and validated here, then exported as typed `env` object.
2
+ * Platform environment registry.
3
+ * Every variable a Cloud process reads is declared here or in an application's
4
+ * `src/env.ts`; values are parsed lazily on access (see `define-env.ts`).
4
5
  */
6
+ import { z } from "zod";
7
+ import { defineEnv, envBoolean, envList } from "./define-env";
5
8
 
6
- const str = (key: string, fallback: string = ""): string => process.env[key] ?? fallback;
7
-
8
- const optional = (key: string): string | undefined => process.env[key]?.trim() || undefined;
9
-
10
- const int = (key: string, fallback: number): number => {
11
- const value = process.env[key];
12
- const parsed = parseInt(value ?? "", 10);
13
- return isNaN(parsed) ? fallback : parsed;
14
- };
15
-
16
- const bool = (key: string, fallback: boolean): boolean => {
17
- const value = process.env[key]?.trim().toLowerCase();
18
- if (!value) return fallback;
19
- return value === "true" || value === "1";
20
- };
21
-
22
- const list = (key: string): string[] =>
23
- (process.env[key] ?? "")
24
- .split(",")
25
- .map((entry) => entry.trim())
26
- .filter(Boolean);
27
-
28
- export const env = {
29
- // Application (infrastructure — not changeable at runtime)
30
- APP_SECRET: str("APP_SECRET"),
31
- PORT: int("PORT", 3000),
32
- IS_DEVELOPMENT: process.env.NODE_ENV === "development",
33
-
34
- // Admin login (local emergency access — token-based, no IPA required)
35
- ADMIN_LOGIN_TOKEN: str("ADMIN_LOGIN_TOKEN"),
36
-
37
- // NATS / @k2b/sync (Postgres and Redis are read by Bun from DATABASE_URL / REDIS_URL)
38
- /** Bootstrap servers, comma-separated `nats://host:port` URLs. Required by `connectNats()`. */
39
- NATS_SERVERS: list("NATS_SERVERS"),
40
- /** Sync namespace shared by every app of one Cloud installation (e.g. `dev`, `prod`). Required. */
41
- SYNC_NAMESPACE: str("SYNC_NAMESPACE"),
42
- /** Path to a mounted NATS `.creds` file (JWT + NKey). Unset = server-side no-auth. */
43
- NATS_CREDS_FILE: optional("NATS_CREDS_FILE"),
44
- /** Path to a mounted CA certificate; enables TLS with that CA. Unset = plain TCP. */
45
- NATS_TLS_CA_FILE: optional("NATS_TLS_CA_FILE"),
46
- /**
47
- * Default true: only dial the servers listed in NATS_SERVERS. Clustered
48
- * nodes gossip their peers' advertised addresses, which are Docker-internal
49
- * hostnames (`ipa_nats_2`) that host-side clients (tests, `dev:cld` tooling)
50
- * cannot resolve. Set to `false` when clients can reach every advertised
51
- * address and should learn new nodes at runtime.
52
- */
53
- NATS_IGNORE_CLUSTER_UPDATES: bool("NATS_IGNORE_CLUSTER_UPDATES", true),
54
- } as const;
9
+ const syncReplicas = z
10
+ .enum(["1", "3", "5"], { error: "must be 1, 3, or 5" })
11
+ .transform((value): 1 | 3 | 5 => (value === "1" ? 1 : value === "3" ? 3 : 5));
12
+
13
+ const registry = defineEnv({
14
+ // Process
15
+ NODE_ENV: {
16
+ schema: z.string(),
17
+ doc: "Release images set `production`; `development` enables development-only behavior such as insecure cookies and HTTP issuers.",
18
+ },
19
+ HOSTNAME: {
20
+ schema: z.string(),
21
+ doc: "Container or host name; the default instance identity in worker leases and gateway registration.",
22
+ },
23
+
24
+ // Infrastructure
25
+ DATABASE_URL: {
26
+ schema: z.string(),
27
+ example: "postgresql://ipa:ipa@localhost:5432/ipa",
28
+ doc: "Postgres connection read by Bun SQL and by application-owned pools.",
29
+ },
30
+ REDIS_URL: {
31
+ schema: z.string(),
32
+ aliases: ["VALKEY_URL"],
33
+ example: "redis://localhost:6379",
34
+ doc: "Valkey connection used by Bun Redis for caches and rate limits.",
35
+ },
36
+ NATS_SERVERS: {
37
+ schema: envList,
38
+ default: [],
39
+ example: "nats://localhost:4222",
40
+ doc: "Comma-separated NATS JetStream bootstrap servers; the client dials all of them.",
41
+ },
42
+ SYNC_NAMESPACE: {
43
+ schema: z.string(),
44
+ default: "",
45
+ example: "dev",
46
+ doc: "Sync namespace shared by every app of one installation; isolates its NATS resources from other installations on the same cluster.",
47
+ },
48
+ SYNC_REPLICAS: {
49
+ schema: syncReplicas,
50
+ default: 3,
51
+ example: "1",
52
+ doc: "Persistent JetStream replica count (1, 3 or 5); keep it consistent across the installation and migrate existing resources before changing it.",
53
+ },
54
+ NATS_CREDS_FILE: {
55
+ schema: z.string(),
56
+ doc: "Path to a mounted NATS `.creds` file (JWT and NKey); unset means server-side no-auth.",
57
+ },
58
+ NATS_TLS_CA_FILE: {
59
+ schema: z.string(),
60
+ doc: "Path to a mounted CA certificate that enables TLS to NATS; unset means plain TCP.",
61
+ },
62
+ NATS_IGNORE_CLUSTER_UPDATES: {
63
+ schema: envBoolean,
64
+ default: true,
65
+ example: "true",
66
+ doc: "Only dial the listed servers and ignore cluster-advertised peer addresses; set to `false` when every advertised address is reachable.",
67
+ },
68
+
69
+ // Installation
70
+ APP_URL: {
71
+ schema: z.string(),
72
+ example: "localhost:3000",
73
+ doc: "Public Cloud URL used to bootstrap the `app.url` setting; the stored setting wins once written.",
74
+ },
75
+ APP_SECRET: {
76
+ schema: z.string(),
77
+ default: "",
78
+ scope: "secret",
79
+ example: "change-me-generate-with-openssl-rand-hex-32",
80
+ doc: "Deployment-wide secret for at-rest settings encryption and signed cursors; generate once with `openssl rand -hex 32` and never change it after data has been written.",
81
+ },
82
+ ADMIN_LOGIN_TOKEN: {
83
+ schema: z.string(),
84
+ default: "",
85
+ scope: "secret",
86
+ doc: "Core only: temporary first-administrator or recovery login token; remove it after normal administrator sign-in works.",
87
+ },
88
+
89
+ // Identity and OAuth
90
+ CLOUD_IDENTITY_KEY_ENCRYPTION_KEY: {
91
+ schema: z.string(),
92
+ scope: "secret",
93
+ example: "change-me-generate-with-openssl-rand-hex-32",
94
+ doc: "Core only: 64-hex KEK for platform identity signing keys stored in Postgres; generate once with `openssl rand -hex 32` and never distribute it to other apps.",
95
+ },
96
+ CLOUD_IDENTITY_NEXT_KEY: {
97
+ schema: z.string(),
98
+ scope: "secret",
99
+ doc: "Core only: pre-distributed next KEK before promoting it to current; cannot be combined with `CLOUD_IDENTITY_PREVIOUS_KEY`.",
100
+ },
101
+ CLOUD_IDENTITY_PREVIOUS_KEY: {
102
+ schema: z.string(),
103
+ scope: "secret",
104
+ doc: "Core only: old KEK set during an explicit rolling rewrap of stored identity signing keys, then removed.",
105
+ },
106
+ CLOUD_OAUTH_BROKER_SECRET: {
107
+ schema: z.string(),
108
+ scope: "secret",
109
+ doc: "Core and OAuth only: 64-hex secret that lets the OAuth app request token signatures from Core; generate independently with `openssl rand -hex 32`.",
110
+ },
111
+ CLOUD_CORE_INTERNAL_ORIGIN: {
112
+ schema: z.string(),
113
+ doc: "Direct Core listener origin for OAuth issuance, mandate-backed background work and internal callbacks; host-run callers use the Core port, not the gateway.",
114
+ },
115
+ CLOUD_APP_CREDENTIAL: {
116
+ schema: z.string(),
117
+ scope: "secret",
118
+ doc: "App-bound workload credential provisioned through Core's admin workload API; background apps such as Mail need one with `identity:invoke`.",
119
+ },
120
+ CLOUD_IDENTITY_JWKS_ORIGIN: {
121
+ schema: z.string(),
122
+ doc: "Optional private origin of the Core listener for session and invocation JWKS; unset fetches keys from the public issuer origin.",
123
+ },
124
+ CLOUD_OAUTH_JWKS_ORIGIN: {
125
+ schema: z.string(),
126
+ doc: "Optional private origin of the OAuth listener for OAuth JWKS; unset fetches keys from the public issuer origin.",
127
+ },
128
+
129
+ // Build and standalone development
130
+ APP_DIR: {
131
+ schema: z.string(),
132
+ scope: "development",
133
+ doc: "Application directory for standalone consumers running `@k2b/cloud/scripts/build.ts` or the SSR dev server outside the monorepo; defaults to the package directory.",
134
+ },
135
+
136
+ // Desktop
137
+ CLOUD_DESKTOP_SQLITE_PATH: {
138
+ schema: z.string(),
139
+ scope: "development",
140
+ doc: "SQLite file used by `desktop.sql` in the Bun desktop process; defaults to `.local/desktop.sqlite` in the working directory.",
141
+ },
142
+ });
143
+
144
+ type PlatformEnv = typeof registry & { readonly IS_DEVELOPMENT: boolean };
145
+
146
+ /** Platform environment; `IS_DEVELOPMENT` derives from `NODE_ENV === "development"`. */
147
+ export const env: PlatformEnv = Object.defineProperty(registry as PlatformEnv, "IS_DEVELOPMENT", {
148
+ enumerable: true,
149
+ get: () => registry.NODE_ENV === "development",
150
+ });
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Main config export.
3
- * Re-exports env vars. SSR config is per-app via defineApp().
3
+ * Re-exports the platform environment registry and the registry factory used
4
+ * by application `src/env.ts` files. SSR config is per-app via defineApp().
4
5
  */
5
6
 
7
+ export { defineEnv, type EnvRegistry, type EnvScope, type EnvSpec, type EnvSpecs, envBoolean, envList, envString } from "./define-env";
6
8
  export { env } from "./env";
@@ -1,5 +1,5 @@
1
1
  import type { Sync } from "@k2b/sync";
2
- import type { HelpManifest } from "../shared/help";
2
+ import type { AppRegistryHelpSummary } from "./registry";
3
3
  import type { Role } from "./shared";
4
4
  import type { DashboardWidgetPresentation } from "./widgets";
5
5
 
@@ -14,6 +14,18 @@ export type LegalLink = {
14
14
  icon?: string;
15
15
  };
16
16
 
17
+ /** Static page links for the global search UI, ranked after resource results.
18
+ * These declare navigation only; they do not create capabilities or readers.
19
+ */
20
+ export type AppSearchLink = {
21
+ label: string;
22
+ /** Optional plain-text context shown with the result and its preview. */
23
+ description?: string;
24
+ href: string;
25
+ icon?: string;
26
+ keywords?: readonly string[];
27
+ };
28
+
17
29
  export type AppAppearanceColor = `#${string}`;
18
30
 
19
31
  export type AppAppearance = {
@@ -35,6 +47,8 @@ export type AppAdminNavigationLink = {
35
47
  };
36
48
 
37
49
  export type AppAdminNavigationGroup = {
50
+ /** Contribute links to the platform AI section instead of a separate group. */
51
+ section?: "ai";
38
52
  /** Stable key used only to localize this presentation group. */
39
53
  id?: string;
40
54
  label: string;
@@ -50,10 +64,14 @@ export type AppPresentationTranslation = {
50
64
  adminLinks?: Readonly<Record<string, string>>;
51
65
  /** Legal-link labels keyed by their stable same-origin href. */
52
66
  legalLinks?: Readonly<Record<string, string>>;
67
+ /** Search-link labels keyed by their stable same-origin href. */
68
+ searchLinks?: Readonly<Record<string, string>>;
69
+ /** Search-link descriptions keyed by their stable same-origin href. */
70
+ searchLinkDescriptions?: Readonly<Record<string, string>>;
53
71
  };
54
72
 
55
73
  export type AppPresentationCatalog = {
56
- /** Locale of the complete `name`, `description`, `adminNav`, and `legalLinks` declaration. */
74
+ /** Locale of the complete `name`, `description`, `adminNav`, `legalLinks`, and `searchLinks` declaration. */
57
75
  baseLocale: string;
58
76
  /** Partial presentation overlays with exact -> ancestor -> base fallback. */
59
77
  translations: Readonly<Record<string, AppPresentationTranslation>>;
@@ -87,6 +105,8 @@ export type AppMeta = {
87
105
  * contributes its own (e.g. settings → terms/privacy/imprint, faq → FAQ).
88
106
  */
89
107
  legalLinks?: LegalLink[];
108
+ /** Static search destinations, visible when the app is in the user's navigation catalog. */
109
+ searchLinks?: readonly AppSearchLink[];
90
110
  /**
91
111
  * Dashboard widget endpoints this app exposes. Each entry references an
92
112
  * HTTP endpoint that returns a `WidgetResponse` (see `contracts/widgets.ts`).
@@ -111,7 +131,7 @@ export type WidgetEndpoint = {
111
131
  };
112
132
 
113
133
  export type RuntimeAppMeta = AppMeta & {
114
- help?: HelpManifest;
134
+ help?: AppRegistryHelpSummary;
115
135
  searchTags?: string[];
116
136
  searchHelp?: string;
117
137
  searchTagHelp?: AppSearchTagHelpEntry[];
@@ -1,7 +1,17 @@
1
+ import {
2
+ type CapabilityStream,
3
+ type CapabilityStreamDefinition,
4
+ CapabilityStreamPolicySchema,
5
+ CapabilityStreamSchema,
6
+ } from "./capability-streams";
7
+
8
+ export * from "./capability-streams";
9
+
1
10
  import { z } from "zod";
11
+ import { CommandPathSchema } from "./commands";
2
12
  import type { AccessSubject, RequestActor, User } from "./shared";
3
13
 
4
- export const CAPABILITY_PROTOCOL_VERSION = 1 as const;
14
+ export const CAPABILITY_PROTOCOL_VERSION = 2 as const;
5
15
  export const CAPABILITY_MAX_REQUEST_BYTES = 256 * 1024;
6
16
  export const CAPABILITY_MAX_RESULT_BYTES = 256 * 1024;
7
17
  export const CAPABILITY_MAX_CATALOG_BYTES = 2 * 1024 * 1024;
@@ -14,6 +24,8 @@ export const CAPABILITY_FRAMEWORK_ERROR_CODES = {
14
24
  idempotencyKeyRequired: "IDEMPOTENCY_KEY_REQUIRED",
15
25
  idempotencyKeyNotAllowed: "IDEMPOTENCY_KEY_NOT_ALLOWED",
16
26
  idempotencyConflict: "IDEMPOTENCY_CONFLICT",
27
+ idempotencyInProgress: "IDEMPOTENCY_IN_PROGRESS",
28
+ idempotencyUncertain: "IDEMPOTENCY_UNCERTAIN",
17
29
  deadlineExceeded: "DEADLINE_EXCEEDED",
18
30
  actionOutcomeUnknown: "ACTION_OUTCOME_UNKNOWN",
19
31
  requestCancelled: "REQUEST_CANCELLED",
@@ -32,6 +44,17 @@ export const capabilityIdempotencyConflict = (
32
44
  ...(details ? { details } : {}),
33
45
  });
34
46
 
47
+ /**
48
+ * Cloud surface a capability invocation came from. The dispatcher resolves it
49
+ * once, records it on the execution row, and forwards it to the owning app.
50
+ */
51
+ export const CAPABILITY_ORIGINS = ["assistant", "mcp", "http", "app"] as const;
52
+ export type CapabilityOrigin = (typeof CAPABILITY_ORIGINS)[number];
53
+ export const CapabilityOriginSchema = z.enum(CAPABILITY_ORIGINS);
54
+
55
+ /** Transports the resolved origin from the Cloud dispatcher to the app endpoint. */
56
+ export const CAPABILITY_ORIGIN_HEADER = "x-cloud-capability-origin";
57
+
35
58
  export const CapabilityIdempotencyKeySchema = z
36
59
  .string()
37
60
  .trim()
@@ -94,10 +117,13 @@ const CapabilityActionReviewDetailSchema = z
94
117
  label: z.string().min(1).max(120),
95
118
  value: z.string().max(10_000),
96
119
  display: z.enum(["inline", "block"]).optional(),
97
- format: z.enum(["date", "date-time"]).optional(),
120
+ format: z.enum(["date", "date-time", "markdown"]).optional(),
98
121
  })
99
122
  .strict()
100
123
  .superRefine((detail, context) => {
124
+ if (detail.format === "markdown" && detail.display !== "block") {
125
+ context.addIssue({ code: "custom", path: ["display"], message: "Markdown review details require block display" });
126
+ }
101
127
  const schema =
102
128
  detail.format === "date" ? CapabilityReviewDateSchema : detail.format === "date-time" ? CapabilityReviewDateTimeSchema : null;
103
129
  if (schema?.safeParse(detail.value).success === false) {
@@ -175,6 +201,7 @@ export const capabilityDataAtPath = (data: unknown, path: readonly string[]): un
175
201
 
176
202
  export type CapabilityResult<T> = {
177
203
  data: T;
204
+ stream?: CapabilityStream;
178
205
  /** Provider-authored, user-facing summary of the successful result. Render as escaped plain text. */
179
206
  summary?: string;
180
207
  refs?: CloudResourceReference[];
@@ -190,6 +217,7 @@ export const capabilityResultSchema = <T extends z.ZodType>(
190
217
  z
191
218
  .object({
192
219
  data,
220
+ stream: CapabilityStreamSchema.optional(),
193
221
  summary: z.string().trim().min(1).max(500).optional(),
194
222
  refs: z.array(CloudResourceReferenceSchema).max(100).optional(),
195
223
  page: CapabilityPageSchema.optional(),
@@ -264,6 +292,9 @@ export const UniversalSearchInputSchema = z
264
292
  .object({
265
293
  query: z.string().max(500).describe("User-entered search text. Empty text is allowed when a facet narrows the query."),
266
294
  tags: z.array(z.string().min(1).max(64)).max(20).describe("Canonical search facets supported by this query."),
295
+ scope: CloudResourceRefSchema.optional().describe(
296
+ "Optional resource containing the search results; must be an explicitly supported scope type.",
297
+ ),
267
298
  limit: z.number().int().min(1).max(100).describe("Maximum number of results to return."),
268
299
  })
269
300
  .strict();
@@ -285,6 +316,14 @@ export type CapabilityExecutionContext = {
285
316
  * error codes and data must not depend on it.
286
317
  */
287
318
  locale: string;
319
+ /**
320
+ * Correlation id of the originating Cloud request. Write it into the app's
321
+ * own audit rows so a capability execution record and the app's domain trail
322
+ * can be joined without sharing payloads.
323
+ */
324
+ requestId: string;
325
+ /** Cloud surface that invoked this capability. */
326
+ origin: CapabilityOrigin;
288
327
  signal: AbortSignal;
289
328
  };
290
329
 
@@ -304,6 +343,8 @@ export type CapabilitySearchTagDefinition = {
304
343
 
305
344
  export type CapabilityUniversalSearchDefinition = {
306
345
  tags: readonly CapabilitySearchTagDefinition[];
346
+ /** Local resource types whose contents this Query can search. */
347
+ scopeTypes?: readonly string[];
307
348
  };
308
349
 
309
350
  export type CapabilitySchemaPresentation = Readonly<Record<string, string>>;
@@ -323,6 +364,7 @@ export type CapabilityPresentationTranslation = {
323
364
  types?: Readonly<Record<string, { title?: string; description?: string }>>;
324
365
  queries?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
325
366
  actions?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
367
+ commands?: Readonly<Record<string, Pick<CapabilityOperationPresentationTranslation, "title" | "description" | "input">>>;
326
368
  };
327
369
 
328
370
  export type CapabilityPresentationCatalog = {
@@ -338,6 +380,7 @@ export type CapabilityQueryDefinition<Input extends z.ZodType = z.ZodType<any>,
338
380
  input: Input;
339
381
  data: Data;
340
382
  openWorld: boolean;
383
+ stream?: CapabilityStreamDefinition;
341
384
  universalSearch?: CapabilityUniversalSearchDefinition;
342
385
  run: (
343
386
  input: z.output<Input>,
@@ -346,7 +389,7 @@ export type CapabilityQueryDefinition<Input extends z.ZodType = z.ZodType<any>,
346
389
  };
347
390
 
348
391
  export type CapabilityIdempotencyPolicy = "none" | "required";
349
- export type CapabilityActionApprovalPolicy = "rememberable";
392
+ export type CapabilityActionApprovalPolicy = "rememberable" | "none";
350
393
 
351
394
  export type CapabilityActionDefinition<Input extends z.ZodType = z.ZodType<any>, Data extends z.ZodType = z.ZodType<any>> = {
352
395
  title: string;
@@ -355,6 +398,7 @@ export type CapabilityActionDefinition<Input extends z.ZodType = z.ZodType<any>,
355
398
  data: Data;
356
399
  destructive: boolean;
357
400
  openWorld: boolean;
401
+ stream?: CapabilityStreamDefinition;
358
402
  idempotency: CapabilityIdempotencyPolicy;
359
403
  approval?: CapabilityActionApprovalPolicy;
360
404
  review?: (
@@ -367,6 +411,29 @@ export type CapabilityActionDefinition<Input extends z.ZodType = z.ZodType<any>,
367
411
  ) => CapabilityInvocationResult<z.output<Data>> | Promise<CapabilityInvocationResult<z.output<Data>>>;
368
412
  };
369
413
 
414
+ /** A declarative UI entry. Resolution never executes application code or writes domain data. */
415
+ export type CapabilityCommandDefinition = {
416
+ title: string;
417
+ description: string;
418
+ icon?: string;
419
+ keywords?: readonly string[];
420
+ input: z.ZodType;
421
+ path: string;
422
+ };
423
+
424
+ export const CapabilityCommandManifestSchema = z
425
+ .object({
426
+ localId: CapabilityLocalIdSchema,
427
+ title: z.string().min(1).max(120),
428
+ description: z.string().min(1).max(1000),
429
+ icon: z.string().min(1).max(120).optional(),
430
+ keywords: z.array(z.string().min(1).max(120)).max(30),
431
+ inputSchema: z.record(z.string(), z.unknown()),
432
+ path: CommandPathSchema,
433
+ })
434
+ .strict();
435
+ export type CapabilityCommandManifest = z.infer<typeof CapabilityCommandManifestSchema>;
436
+
370
437
  type CapabilityDefinitionCatalog<T> = Readonly<Record<string, T>>;
371
438
 
372
439
  export type CapabilityDefinitions = {
@@ -375,6 +442,7 @@ export type CapabilityDefinitions = {
375
442
  types?: CapabilityDefinitionCatalog<CapabilityResourceTypeDefinition>;
376
443
  queries?: CapabilityDefinitionCatalog<CapabilityQueryDefinition>;
377
444
  actions?: CapabilityDefinitionCatalog<CapabilityActionDefinition>;
445
+ commands?: CapabilityDefinitionCatalog<CapabilityCommandDefinition>;
378
446
  };
379
447
 
380
448
  /**
@@ -409,6 +477,7 @@ const CapabilityOperationManifestBaseSchema = z
409
477
  description: z.string().min(1).max(1000),
410
478
  inputSchema: z.record(z.string(), z.unknown()),
411
479
  dataSchema: z.record(z.string(), z.unknown()),
480
+ stream: CapabilityStreamPolicySchema.optional(),
412
481
  schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
413
482
  })
414
483
  .strict();
@@ -416,7 +485,7 @@ const CapabilityOperationManifestBaseSchema = z
416
485
  export const CapabilityQueryManifestSchema = CapabilityOperationManifestBaseSchema.extend({
417
486
  openWorld: z.boolean(),
418
487
  universalSearch: z
419
- .object({ tags: z.array(CapabilitySearchTagManifestSchema).max(100) })
488
+ .object({ tags: z.array(CapabilitySearchTagManifestSchema).max(100), scopeTypes: z.array(CapabilityLocalIdSchema).max(100).optional() })
420
489
  .strict()
421
490
  .optional(),
422
491
  }).strict();
@@ -425,7 +494,7 @@ export const CapabilityActionManifestSchema = CapabilityOperationManifestBaseSch
425
494
  destructive: z.boolean(),
426
495
  openWorld: z.boolean(),
427
496
  idempotency: z.enum(["none", "required"]),
428
- approval: z.literal("rememberable").optional(),
497
+ approval: z.enum(["rememberable", "none"]).optional(),
429
498
  review: z.literal(true).optional(),
430
499
  }).strict();
431
500
 
@@ -443,6 +512,7 @@ export const CapabilityManifestSchema = z
443
512
  types: z.array(CapabilityResourceTypeManifestSchema).max(200),
444
513
  queries: z.array(CapabilityQueryManifestSchema).max(200),
445
514
  actions: z.array(CapabilityActionManifestSchema).max(200),
515
+ commands: z.array(CapabilityCommandManifestSchema).max(200),
446
516
  })
447
517
  .strict();
448
518
 
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import type { CapabilityExecutionContext, CapabilityResult } from "./capabilities";
3
+
4
+ /** One binary payload. Provider IDs are limited to 2 KiB UTF-8; Core seals them into opaque references, never URLs. */
5
+ export const CapabilityStreamSchema = z
6
+ .object({
7
+ id: z.string().min(1).max(8192),
8
+ direction: z.enum(["read", "write"]),
9
+ name: z.string().min(1).max(255).optional(),
10
+ mediaType: z
11
+ .string()
12
+ .min(1)
13
+ .max(255)
14
+ .regex(/^[\x20-\x7e]+$/),
15
+ size: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
16
+ expiresAt: z.iso.datetime(),
17
+ })
18
+ .strict();
19
+ export type CapabilityStream = z.infer<typeof CapabilityStreamSchema>;
20
+ export const CapabilityStreamPolicySchema = z
21
+ .object({
22
+ direction: z.enum(["read", "write"]),
23
+ maxBytes: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
24
+ })
25
+ .strict();
26
+ export const CapabilityStreamStatusSchema = z.discriminatedUnion("state", [
27
+ z.object({ state: z.literal("open") }).strict(),
28
+ z.object({ state: z.literal("aborted") }).strict(),
29
+ z.object({ state: z.literal("completed"), result: z.unknown() }).strict(),
30
+ ]);
31
+ export type CapabilityStreamStatus = z.infer<typeof CapabilityStreamStatusSchema>;
32
+ export type CapabilityStreamDefinition = z.infer<typeof CapabilityStreamPolicySchema> &
33
+ (
34
+ | { direction: "read"; read: (stream: CapabilityStream, context: CapabilityExecutionContext) => Promise<Response> }
35
+ | {
36
+ direction: "write";
37
+ /** Consume completely, then publish. Must reconcile retries using stream.id. */
38
+ write: (
39
+ stream: CapabilityStream,
40
+ body: ReadableStream<Uint8Array>,
41
+ context: CapabilityExecutionContext,
42
+ ) => Promise<CapabilityResult<unknown>>;
43
+ status: (stream: CapabilityStream, context: CapabilityExecutionContext) => Promise<CapabilityStreamStatus>;
44
+ abort: (stream: CapabilityStream, context: CapabilityExecutionContext) => Promise<void>;
45
+ }
46
+ );