@k2b/cloud 0.43.0 → 0.44.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 (267) hide show
  1. package/package.json +7 -2
  2. package/src/_internal/app-access-request.ts +13 -0
  3. package/src/_internal/app-access-resources.ts +141 -0
  4. package/src/_internal/app-event-catalog.ts +40 -0
  5. package/src/_internal/app-event-schemas.ts +127 -0
  6. package/src/_internal/application-package.ts +40 -0
  7. package/src/_internal/built-in-app-ids.json +27 -0
  8. package/src/_internal/capabilities.ts +158 -12
  9. package/src/_internal/capability-origin.ts +4 -0
  10. package/src/_internal/capability-streams.ts +7 -5
  11. package/src/_internal/define-app.ts +57 -6
  12. package/src/_internal/object-store-area.ts +9 -0
  13. package/src/_internal/object-store-config.ts +101 -0
  14. package/src/_internal/object-store-key.ts +14 -0
  15. package/src/_internal/process-identity.ts +7 -2
  16. package/src/_internal/registry-validation.ts +11 -1
  17. package/src/_internal/registry.ts +8 -1
  18. package/src/_internal/runtime-context.ts +8 -1
  19. package/src/access/AppAccessEditor.tsx +2 -0
  20. package/src/access/PermissionEditor.tsx +9 -3
  21. package/src/access/PrincipalPicker.tsx +14 -4
  22. package/src/access/ResourceApiKeys.tsx +3 -4
  23. package/src/access/app-access-messages.ts +6 -0
  24. package/src/account/ProfilePictureDialog.tsx +699 -0
  25. package/src/account/profile-picture-messages.ts +269 -0
  26. package/src/account/ui.ts +7 -0
  27. package/src/ai/admin.ts +3 -0
  28. package/src/ai/app-frame.ts +12 -0
  29. package/src/ai/application-tasks.ts +101 -0
  30. package/src/ai/assistant-models.ts +126 -64
  31. package/src/ai/capabilities.ts +19 -4
  32. package/src/ai/chart-block.ts +63 -30
  33. package/src/ai/chat/chart-block.tsx +12 -3
  34. package/src/ai/chat/composer-adapter.ts +8 -7
  35. package/src/ai/chat/message-actions.tsx +1 -1
  36. package/src/ai/chat/message-utils.ts +2 -2
  37. package/src/ai/chat/presentation.tsx +22 -1
  38. package/src/ai/chat-quotas.ts +5 -4
  39. package/src/ai/chat-tasks.ts +2 -2
  40. package/src/ai/client/controller.ts +11 -10
  41. package/src/ai/client/projection.ts +3 -3
  42. package/src/ai/compaction.ts +3 -3
  43. package/src/ai/credentials.ts +13 -114
  44. package/src/ai/default-tool-contracts.ts +4 -3
  45. package/src/ai/dictation-runtime.ts +4 -4
  46. package/src/ai/enrich.ts +2 -2
  47. package/src/ai/executor.ts +31 -22
  48. package/src/ai/http.ts +17 -29
  49. package/src/ai/index.ts +11 -5
  50. package/src/ai/inference-calls.ts +5 -4
  51. package/src/ai/maintenance.ts +1 -1
  52. package/src/ai/memory-learning-runs.ts +5 -5
  53. package/src/ai/memory-learning.ts +3 -3
  54. package/src/ai/message-queue.ts +1 -2
  55. package/src/ai/migrate.ts +75 -42
  56. package/src/ai/model-access-migrate.ts +3 -17
  57. package/src/ai/model-access.ts +44 -85
  58. package/src/ai/model-card.tsx +134 -0
  59. package/src/ai/model-catalog.ts +359 -0
  60. package/src/ai/model-chooser-messages.ts +88 -0
  61. package/src/ai/model-chooser.tsx +577 -0
  62. package/src/ai/model-delete.ts +234 -0
  63. package/src/ai/model-fixture.ts +39 -0
  64. package/src/ai/model-icon.tsx +36 -0
  65. package/src/ai/model-overview.ts +53 -0
  66. package/src/ai/model-usage.ts +45 -0
  67. package/src/ai/openai-api-messages.ts +0 -4
  68. package/src/ai/openai-api.ts +26 -9
  69. package/src/ai/personal-agent.ts +1 -1
  70. package/src/ai/prefs.ts +12 -10
  71. package/src/ai/projects-routes.ts +2 -1
  72. package/src/ai/projects.ts +13 -12
  73. package/src/ai/protocol.ts +2 -2
  74. package/src/ai/provider-connections.ts +161 -0
  75. package/src/ai/provider-errors.ts +55 -6
  76. package/src/ai/provider-fetch.ts +4 -0
  77. package/src/ai/provider.ts +1 -1
  78. package/src/ai/quota-limits.ts +36 -0
  79. package/src/ai/quota-provider.ts +4 -2
  80. package/src/ai/quota-report.ts +7 -4
  81. package/src/ai/quotas.ts +19 -8
  82. package/src/ai/recent-resources.ts +6 -0
  83. package/src/ai/request-headers.ts +14 -87
  84. package/src/ai/resource-refs.ts +6 -2
  85. package/src/ai/routes.ts +17 -18
  86. package/src/ai/runtime.ts +50 -22
  87. package/src/ai/settings.ts +93 -580
  88. package/src/ai/store.ts +45 -23
  89. package/src/ai/stream.ts +1 -1
  90. package/src/ai/structured-runs.ts +2 -2
  91. package/src/ai/structured.ts +21 -44
  92. package/src/ai/task-contracts.ts +25 -5
  93. package/src/ai/task-execution.ts +15 -6
  94. package/src/ai/task-model.ts +9 -0
  95. package/src/ai/transcription.ts +27 -22
  96. package/src/ai/types.ts +50 -62
  97. package/src/ai/ui.tsx +13 -0
  98. package/src/ai/usage.ts +8 -8
  99. package/src/ai/validate.ts +8 -7
  100. package/src/ai/view-block.ts +2 -2
  101. package/src/api/accounts-entities.ts +16 -0
  102. package/src/api/admin-ai-quotas.ts +34 -56
  103. package/src/api/admin-ai-usage.ts +1 -1
  104. package/src/api/admin-ai.ts +173 -0
  105. package/src/api/admin-core-settings.ts +68 -244
  106. package/src/api/app-approval.ts +3 -1
  107. package/src/api/app-event-delivery.ts +51 -0
  108. package/src/api/app-events.ts +123 -0
  109. package/src/api/app-resource-access.ts +48 -0
  110. package/src/api/auth/account-mismatch.ts +24 -0
  111. package/src/api/auth.ts +26 -3
  112. package/src/api/capabilities.ts +28 -4
  113. package/src/api/index.ts +7 -1
  114. package/src/api/mcp.ts +175 -2
  115. package/src/api/me.ts +6 -4
  116. package/src/api/pwa-phone.ts +75 -26
  117. package/src/api/pwa.ts +34 -8
  118. package/src/api/resource-links.ts +83 -6
  119. package/src/api/resource-resolve.ts +50 -0
  120. package/src/api/search/schemas.ts +3 -1
  121. package/src/api/search.ts +9 -7
  122. package/src/app-pwa/contracts.ts +4 -0
  123. package/src/app-pwa/core-query-error.ts +20 -0
  124. package/src/app-pwa/index.ts +1 -0
  125. package/src/app-pwa/service.ts +91 -0
  126. package/src/app-pwa/transport.ts +22 -0
  127. package/src/approvals/messages.ts +2 -2
  128. package/src/browser/cloud-id.ts +86 -0
  129. package/src/browser/resource-clipboard.ts +4 -0
  130. package/src/browser/resource-links.ts +52 -0
  131. package/src/capabilities/server-invoke.ts +2 -11
  132. package/src/capabilities/testing.ts +5 -2
  133. package/src/cli/access.ts +6 -5
  134. package/src/cli/admin/ai-models.ts +199 -0
  135. package/src/cli/admin/ai-quotas.ts +6 -102
  136. package/src/cli/admin/ai-usage.ts +4 -4
  137. package/src/cli/admin/app-credentials.ts +5 -3
  138. package/src/cli/admin/index.ts +5 -2
  139. package/src/cli/apps.ts +89 -26
  140. package/src/cli/capabilities.ts +22 -1
  141. package/src/cli/events.ts +320 -0
  142. package/src/config/env.ts +40 -0
  143. package/src/contracts/app-access.ts +26 -2
  144. package/src/contracts/app-details.ts +89 -0
  145. package/src/contracts/app-events.ts +51 -0
  146. package/src/contracts/app.ts +21 -5
  147. package/src/contracts/capabilities.ts +143 -3
  148. package/src/contracts/index.ts +2 -0
  149. package/src/contracts/notification-types.ts +36 -9
  150. package/src/contracts/pwa.ts +17 -1
  151. package/src/contracts/registry.ts +6 -0
  152. package/src/contracts/resource-clipboard.ts +35 -0
  153. package/src/contracts/resource-pages.ts +140 -0
  154. package/src/contracts/resource-resolution.ts +13 -0
  155. package/src/contracts/shared.ts +4 -0
  156. package/src/contracts/user-notifications.ts +2 -0
  157. package/src/events/app-event-delivery.ts +100 -0
  158. package/src/events/app-events.testing.ts +65 -0
  159. package/src/events/app-events.ts +134 -0
  160. package/src/events/core-user-created.ts +24 -0
  161. package/src/events/index.ts +9 -0
  162. package/src/events/testing.ts +2 -0
  163. package/src/index.ts +1 -1
  164. package/src/server/api/respond.ts +8 -3
  165. package/src/server/index.ts +1 -0
  166. package/src/server/middleware/app-access.ts +1 -1
  167. package/src/server/middleware/auth.ts +21 -7
  168. package/src/server/middleware/index.ts +1 -0
  169. package/src/server/middleware/invocation.ts +2 -0
  170. package/src/server/public-storage.ts +106 -0
  171. package/src/server/services/access.ts +8 -0
  172. package/src/services/accounts/app.ts +6 -0
  173. package/src/services/accounts/entities.ts +14 -3
  174. package/src/services/accounts/groups.ts +3 -1
  175. package/src/services/accounts/identities.ts +62 -10
  176. package/src/services/accounts/users.ts +7 -6
  177. package/src/services/app-access.ts +25 -7
  178. package/src/services/app-details.ts +224 -0
  179. package/src/services/app-discovery.ts +2 -1
  180. package/src/services/app-event-shapes.ts +139 -0
  181. package/src/services/app-events/accept.ts +116 -0
  182. package/src/services/app-events/admin.ts +250 -0
  183. package/src/services/app-events/declarations.ts +187 -0
  184. package/src/services/app-events/deliver.ts +218 -0
  185. package/src/services/app-events.ts +92 -0
  186. package/src/services/config-conflict.ts +11 -0
  187. package/src/services/core-user-events.ts +1 -0
  188. package/src/services/freeipa-config.ts +9 -0
  189. package/src/services/geo/ledger.ts +28 -10
  190. package/src/services/geo/service.ts +43 -24
  191. package/src/services/identity/core-origin.ts +12 -0
  192. package/src/services/identity/invocation-actor.ts +20 -2
  193. package/src/services/identity/invocation-authority.ts +4 -0
  194. package/src/services/identity/invocation-token.ts +238 -56
  195. package/src/services/identity/issue-invocation.ts +6 -1
  196. package/src/services/image-metadata.ts +65 -1
  197. package/src/services/index.ts +13 -1
  198. package/src/services/ipa/sync.ts +6 -2
  199. package/src/services/ipa/users.ts +4 -1
  200. package/src/services/mandates/policy.ts +19 -12
  201. package/src/services/notifications/browser.ts +4 -0
  202. package/src/services/notifications/catalog.ts +5 -2
  203. package/src/services/notifications/platform.ts +7 -2
  204. package/src/services/notifications/user.ts +18 -7
  205. package/src/services/oauth-tokens.ts +28 -2
  206. package/src/services/object-store.ts +217 -0
  207. package/src/services/providers/local/users.ts +5 -0
  208. package/src/services/pwa-devices.ts +147 -37
  209. package/src/services/session/index.ts +14 -0
  210. package/src/services/settings/core-settings.ts +71 -43
  211. package/src/services/settings/index.ts +8 -0
  212. package/src/services/settings/versions.ts +26 -0
  213. package/src/shared/ai-models.ts +274 -0
  214. package/src/shared/ai-quotas.ts +2 -2
  215. package/src/shared/ai-usage.ts +2 -2
  216. package/src/shared/index.ts +2 -0
  217. package/src/shared-text/documents.ts +25 -8
  218. package/src/shared-text/log.ts +1 -1
  219. package/src/shared-text/text-diff.ts +75 -0
  220. package/src/ssr/AdminArea.tsx +131 -0
  221. package/src/ssr/AdminLayout.tsx +9 -3
  222. package/src/ssr/AdminSidebar.tsx +7 -7
  223. package/src/ssr/AppAccessDenied.tsx +2 -0
  224. package/src/ssr/AppBadge.tsx +14 -0
  225. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  226. package/src/ssr/CloudShell.island.tsx +25 -4
  227. package/src/ssr/Layout.tsx +8 -6
  228. package/src/ssr/PwaLayout.tsx +16 -1
  229. package/src/ssr/PwaPartCount.island.tsx +23 -0
  230. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  231. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  232. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  233. package/src/ssr/admin-active-link.ts +16 -0
  234. package/src/ssr/admin-navigation.ts +25 -20
  235. package/src/ssr/app-badges.ts +31 -7
  236. package/src/ssr/index.ts +8 -0
  237. package/src/ssr/page-trail-runtime.ts +3 -1
  238. package/src/ssr/platform-messages.ts +9 -1
  239. package/src/ssr/pwa-messages.ts +2 -2
  240. package/src/ssr/rail-navigation.ts +5 -1
  241. package/src/ssr/settings-draft.ts +153 -0
  242. package/src/ssr/shell-data.tsx +2 -0
  243. package/src/styles/global.css +1 -0
  244. package/src/styles/profile-picture.css +270 -0
  245. package/src/styles/utilities-navigation.css +6 -0
  246. package/src/workflows/ai/policy.ts +49 -0
  247. package/src/workflows/ai/runtime.ts +131 -21
  248. package/src/workflows/ai/store.ts +283 -35
  249. package/src/workflows/ai/types.ts +8 -2
  250. package/src/workflows/ai-actions.ts +148 -120
  251. package/src/workflows/authoring.ts +31 -0
  252. package/src/workflows/contracts.ts +1 -0
  253. package/src/workflows/definition.ts +2 -0
  254. package/src/workflows/editor.ts +15 -0
  255. package/src/workflows/index.ts +2 -1
  256. package/src/workflows/language/compiler.ts +34 -3
  257. package/src/workflows/runtime/executor.ts +132 -33
  258. package/src/workflows/runtime/index.ts +2 -0
  259. package/src/workflows/runtime/ports.ts +5 -1
  260. package/src/workflows/store/actions.ts +1 -1
  261. package/src/workflows/store/definitions.ts +5 -0
  262. package/src/workflows/store/runs.ts +5 -3
  263. package/src/workflows/store/worker.ts +7 -2
  264. package/src/ai/model-pricing.ts +0 -43
  265. package/src/ai/model-request-settings.ts +0 -98
  266. package/src/ai/settings-messages.ts +0 -129
  267. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -1,5 +1,16 @@
1
+ import type { AppEventDefinition, AppEventSubscriptionDefinition } from "../contracts/app-events";
2
+ import {
3
+ type CapabilityEventManifest,
4
+ CapabilityEventManifestSchema,
5
+ CapabilityQualifiedIdSchema,
6
+ type CapabilitySubscriptionManifest,
7
+ CapabilitySubscriptionManifestSchema,
8
+ } from "../contracts/capabilities";
9
+ import { RESOURCE_PAGES_PER_TYPE, resourcePageIssue } from "../contracts/resource-pages";
1
10
  import { hasOAuthScope } from "../server/oauth-scope";
2
11
  import { capabilityMessages } from "../shared/capability-messages";
12
+ import { assertAppEventDeclarationSize, assertBoundedAppEventSchema, compileAppEventSchema } from "./app-event-schemas";
13
+ import { capabilityVia } from "./capability-origin";
3
14
  import { capabilityHash, projectSchema, stableJsonValue } from "./capability-schemas";
4
15
 
5
16
  export { capabilityHash } from "./capability-schemas";
@@ -13,6 +24,7 @@ import {
13
24
  CAPABILITY_MAX_RESULT_BYTES,
14
25
  CAPABILITY_MAX_REVIEW_RESPONSE_BYTES,
15
26
  CAPABILITY_PROTOCOL_VERSION,
27
+ CAPABILITY_RESOURCE_COLORS,
16
28
  type CapabilityActionDefinition,
17
29
  type CapabilityActionManifest,
18
30
  CapabilityActionPolicySchema,
@@ -42,6 +54,7 @@ import {
42
54
  type CapabilitySearchTagDefinition,
43
55
  type CapabilityStreamDefinition,
44
56
  type CapabilityUniversalSearchDefinition,
57
+ CloudResourceViewSchema,
45
58
  capabilityActionPolicy,
46
59
  capabilityResultSchema,
47
60
  UniversalSearchDataSchema,
@@ -83,6 +96,8 @@ const DECLARATION_FIELDS = {
83
96
  commands: true,
84
97
  fileProvider: true,
85
98
  calendarSource: true,
99
+ events: true,
100
+ subscribes: true,
86
101
  } satisfies DefinedFields<CapabilityDefinitions>;
87
102
  const CALENDAR_SOURCE_FIELDS = {
88
103
  calendars: true,
@@ -92,7 +107,14 @@ const CALENDAR_SOURCE_FIELDS = {
92
107
  delete: true,
93
108
  } satisfies DefinedFields<CapabilityCalendarSourceDeclaration>;
94
109
  const FILE_PROVIDER_FIELDS = { list: true, read: true, save: true } satisfies DefinedFields<CapabilityFileProviderDeclaration>;
95
- const TYPE_FIELDS = { title: true, description: true, icon: true, reader: true } satisfies DefinedFields<CapabilityResourceTypeDefinition>;
110
+ const TYPE_FIELDS = {
111
+ title: true,
112
+ description: true,
113
+ icon: true,
114
+ color: true,
115
+ reader: true,
116
+ pages: true,
117
+ } satisfies DefinedFields<CapabilityResourceTypeDefinition>;
96
118
  const QUERY_FIELDS = {
97
119
  title: true,
98
120
  description: true,
@@ -160,7 +182,12 @@ export type CompiledCapabilityAction = {
160
182
  resultSchema: z.ZodType;
161
183
  };
162
184
 
185
+ export type CompiledAppEvent = { definition: AppEventDefinition; manifest: CapabilityEventManifest };
186
+ export type CompiledAppEventSubscription = { definition: AppEventSubscriptionDefinition; manifest: CapabilitySubscriptionManifest };
187
+
163
188
  export type CompiledCapabilities = {
189
+ events: ReadonlyMap<string, CompiledAppEvent>;
190
+ subscribes: ReadonlyMap<string, CompiledAppEventSubscription>;
164
191
  manifest: CapabilityManifest;
165
192
  presentation?: CapabilityPresentationCatalog;
166
193
  typeIds: ReadonlySet<string>;
@@ -443,6 +470,7 @@ export const compileCapabilityPresentation = (
443
470
  const queries = new Map(manifest.queries.map((operation) => [operation.localId, operation]));
444
471
  const actions = new Map(manifest.actions.map((operation) => [operation.localId, operation]));
445
472
  const commands = new Map(manifest.commands.map((operation) => [operation.localId, operation]));
473
+ const events = new Map((manifest.events ?? []).map((event) => [event.localId, event]));
446
474
  let sentences: Record<string, CapabilityActionSentences> | undefined;
447
475
  if (catalog.sentences !== undefined) {
448
476
  if (!catalog.sentences || typeof catalog.sentences !== "object" || Array.isArray(catalog.sentences)) {
@@ -470,7 +498,9 @@ export const compileCapabilityPresentation = (
470
498
  throw new Error(`Capability presentation translation ${canonical} must be an object`);
471
499
  }
472
500
  const raw = rawTranslation as Record<string, unknown>;
473
- const extra = Object.keys(raw).find((key) => key !== "types" && key !== "queries" && key !== "actions" && key !== "commands");
501
+ const extra = Object.keys(raw).find(
502
+ (key) => key !== "types" && key !== "queries" && key !== "actions" && key !== "commands" && key !== "events",
503
+ );
474
504
  if (strict && extra) throw new Error(`Capability presentation translation ${canonical} contains unsupported field "${extra}"`);
475
505
  const compileGroup = <T>(
476
506
  group: unknown,
@@ -514,12 +544,20 @@ export const compileCapabilityPresentation = (
514
544
  ...(copy.description ? { description: String(copy.description).trim() } : {}),
515
545
  };
516
546
  });
547
+ const translatedEvents = compileGroup(raw.events, events, "Event", (entry, _definition, label) => {
548
+ const parsed = z.object({
549
+ title: z.string().trim().min(1).max(120).optional(),
550
+ description: z.string().trim().min(1).max(1000).optional(),
551
+ });
552
+ return (strict ? parsed.strict() : parsed).parse(entry);
553
+ });
517
554
  const operationPresentation = (entry: unknown, operation: Parameters<typeof compileOperationPresentation>[1], label: string) =>
518
555
  compileOperationPresentation(entry, operation, label, strict);
519
556
  const translatedQueries = compileGroup(raw.queries, queries, "Query", operationPresentation);
520
557
  const translatedActions = compileGroup(raw.actions, actions, "Action", operationPresentation);
521
558
  const translatedCommands = compileGroup(raw.commands, commands, "Command", operationPresentation);
522
559
  translations[canonical] = {
560
+ ...(translatedEvents ? { events: translatedEvents } : {}),
523
561
  ...(translatedTypes ? { types: translatedTypes } : {}),
524
562
  ...(translatedQueries ? { queries: translatedQueries } : {}),
525
563
  ...(translatedActions ? { actions: translatedActions } : {}),
@@ -634,7 +672,11 @@ const compileOperationSchemas = (definition: CapabilityQueryDefinition | Capabil
634
672
  };
635
673
  };
636
674
 
637
- export const compileCapabilities = (appId: string, definitions: CapabilityDefinitions): CompiledCapabilities => {
675
+ export const compileCapabilities = (
676
+ appId: string,
677
+ definitions: CapabilityDefinitions,
678
+ routes?: readonly string[],
679
+ ): CompiledCapabilities => {
638
680
  if (definitions.protocolVersion !== CAPABILITY_PROTOCOL_VERSION) {
639
681
  throw new Error(`Unsupported capability protocol version ${String(definitions.protocolVersion)}`);
640
682
  }
@@ -658,6 +700,15 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
658
700
  .map(([localId, definition]) => {
659
701
  registerLocalId(localId, "Resource type");
660
702
  assertDefinedFields(definition, TYPE_FIELDS, `Resource type ${localId}`);
703
+ if (definition.pages !== undefined) {
704
+ if (!definition.reader) throw new Error(`Resource type ${localId}: pages require a reader`);
705
+ if (!Array.isArray(definition.pages) || definition.pages.length > RESOURCE_PAGES_PER_TYPE)
706
+ throw new Error(`Resource type ${localId}: at most ${RESOURCE_PAGES_PER_TYPE} pages are allowed`);
707
+ for (const path of definition.pages) {
708
+ const issue = typeof path === "string" ? resourcePageIssue(path, routes, appId) : "Expected a page pattern string";
709
+ if (issue) throw new Error(`Resource type ${localId} pages: ${issue}`);
710
+ }
711
+ }
661
712
  assertText(definition.title, `Resource type ${localId} title`, 120);
662
713
  assertText(definition.description, `Resource type ${localId} description`, 500);
663
714
  return {
@@ -665,6 +716,7 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
665
716
  title: definition.title.trim(),
666
717
  description: definition.description.trim(),
667
718
  ...(definition.icon ? { icon: definition.icon } : {}),
719
+ ...(definition.color !== undefined ? { color: z.enum(CAPABILITY_RESOURCE_COLORS).parse(definition.color) } : {}),
668
720
  ...(definition.reader !== undefined ? { reader: CapabilityLocalIdSchema.parse(definition.reader) } : {}),
669
721
  };
670
722
  });
@@ -821,18 +873,74 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
821
873
  });
822
874
  });
823
875
 
876
+ const events = new Map<string, CompiledAppEvent>();
877
+ for (const [localId, definition] of Object.entries(definitions.events ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
878
+ registerLocalId(localId, "Event");
879
+ assertDefinedFields(
880
+ definition,
881
+ {
882
+ version: true,
883
+ title: true,
884
+ description: true,
885
+ subject: true,
886
+ place: true,
887
+ schema: true,
888
+ automation: true,
889
+ } satisfies DefinedFields<AppEventDefinition>,
890
+ `Event ${localId}`,
891
+ );
892
+ if (!types.some((type) => type.localId === definition.subject)) throw new Error(`Event ${localId} subject must reference a local Type`);
893
+ if (definition.place && !types.some((type) => type.localId === definition.place && type.reader))
894
+ throw new Error(`Event ${localId} place must reference a local Type with a reader`);
895
+ const schemas = compileAppEventSchema(definition.schema, `Event ${localId} data`);
896
+ assertAppEventDeclarationSize(appId, localId, definition, schemas.dataSchema);
897
+ const manifest = CapabilityEventManifestSchema.parse({
898
+ localId,
899
+ version: definition.version,
900
+ title: definition.title,
901
+ description: definition.description,
902
+ subject: definition.subject,
903
+ ...(definition.place !== undefined ? { place: definition.place } : {}),
904
+ ...schemas,
905
+ ...(definition.automation !== undefined ? { automation: definition.automation } : {}),
906
+ });
907
+ events.set(localId, { definition, manifest });
908
+ }
909
+ const subscribes = new Map<string, CompiledAppEventSubscription>();
910
+ for (const [type, definition] of Object.entries(definitions.subscribes ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
911
+ CapabilityQualifiedIdSchema.parse(type);
912
+ assertDefinedFields(
913
+ definition,
914
+ { version: true, schema: true, run: true } satisfies DefinedFields<AppEventSubscriptionDefinition>,
915
+ `Subscription ${type}`,
916
+ );
917
+ if (typeof definition.run !== "function") throw new Error(`Subscription ${type} needs run`);
918
+ const manifest = CapabilitySubscriptionManifestSchema.parse({
919
+ type,
920
+ version: definition.version,
921
+ ...compileAppEventSchema(definition.schema, `Subscription ${type} data`, "reader"),
922
+ });
923
+ subscribes.set(type, { definition, manifest });
924
+ }
925
+
824
926
  const policies = Object.fromEntries(
825
927
  [...actions].flatMap(([localId, action]) => (action.definition.policy ? [[localId, action.definition.policy]] : [])),
826
928
  );
827
929
  const fileProvider = definitions.fileProvider;
828
930
  const calendarSource = definitions.calendarSource;
931
+ const resourcePages = Object.entries(definitions.types ?? {})
932
+ .sort(([a], [b]) => a.localeCompare(b))
933
+ .flatMap(([type, definition]) => (definition.pages ?? []).map((path) => ({ type, path })));
829
934
  const manifestBase = {
935
+ ...(resourcePages.length ? { resourcePages } : {}),
830
936
  protocolVersion: CAPABILITY_PROTOCOL_VERSION,
831
937
  appId,
832
938
  types,
833
939
  queries: [...queries.values()].map((entry) => entry.manifest),
834
940
  actions: [...actions.values()].map((entry) => entry.manifest),
835
941
  commands,
942
+ ...(definitions.events !== undefined ? { events: [...events.values()].map((event) => event.manifest) } : {}),
943
+ ...(definitions.subscribes !== undefined ? { subscribes: [...subscribes.values()].map((subscription) => subscription.manifest) } : {}),
836
944
  ...(Object.keys(policies).length ? { policies } : {}),
837
945
  // Omitted when unused so manifests without a provider keep their earlier hash.
838
946
  ...(calendarSource ? { calendarSource: { ...calendarSource } } : {}),
@@ -862,7 +970,7 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
862
970
  throw new Error(`Capability manifest exceeds the ${MAX_CAPABILITY_MANIFEST_BYTES}-byte registry limit`);
863
971
  }
864
972
  const presentation = compileCapabilityPresentation(manifest, definitions.presentation);
865
- return { manifest, presentation, typeIds, queries, actions };
973
+ return { manifest, presentation, typeIds, queries, actions, events, subscribes };
866
974
  };
867
975
 
868
976
  const applySchemaPresentation = (schema: Record<string, unknown>, descriptions: Readonly<Record<string, string>> | undefined) => {
@@ -1007,6 +1115,16 @@ export const resolveCapabilityManifestPresentation = (
1007
1115
  }
1008
1116
  : operation;
1009
1117
  }),
1118
+ ...(current.events
1119
+ ? {
1120
+ events: current.events.map((event) => {
1121
+ const copy = translation.events?.[event.localId];
1122
+ return copy ? { ...event, title: copy.title ?? event.title, description: copy.description ?? event.description } : event;
1123
+ }),
1124
+ }
1125
+ : {}),
1126
+ ...(current.subscribes ? { subscribes: current.subscribes } : {}),
1127
+ ...(current.resourcePages ? { resourcePages: current.resourcePages } : {}),
1010
1128
  ...(current.calendarSource ? { calendarSource: current.calendarSource } : {}),
1011
1129
  ...(current.fileProvider ? { fileProvider: current.fileProvider } : {}),
1012
1130
  ...(current.policies ? { policies: current.policies } : {}),
@@ -1047,16 +1165,26 @@ const assertUsableOperation = (operation: CapabilityQueryManifest | CapabilityAc
1047
1165
  if (operation.schemaHash !== expectedSchemaHash) throw new Error(`Operation ${operation.localId} schemaHash does not match its schemas`);
1048
1166
  if ("universalSearch" in operation && operation.universalSearch) {
1049
1167
  const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
1050
- const expectedData = projectSchema(UniversalSearchDataSchema, "Universal Search data", "output");
1051
1168
  if (
1052
1169
  capabilityHash(operation.inputSchema) !== capabilityHash(expectedInput) ||
1053
- capabilityHash(operation.dataSchema) !== capabilityHash(expectedData)
1170
+ !universalSearchDataHashes().includes(capabilityHash(operation.dataSchema))
1054
1171
  ) {
1055
1172
  throw new Error(`Operation ${operation.localId} advertises Universal Search with non-canonical schemas`);
1056
1173
  }
1057
1174
  }
1058
1175
  };
1059
1176
 
1177
+ /**
1178
+ * The Universal Search data schemas a live manifest may declare: the current one, and the one before results gained
1179
+ * `context` and `status`, so an app built by the previous release keeps its search while Core is already newer. Its
1180
+ * results still parse, because both fields are optional.
1181
+ */
1182
+ let searchDataHashes: string[] | undefined;
1183
+ const universalSearchDataHashes = () =>
1184
+ (searchDataHashes ??= [UniversalSearchDataSchema, z.array(CloudResourceViewSchema.omit({ context: true, status: true })).max(100)].map(
1185
+ (schema) => capabilityHash(projectSchema(schema, "Universal Search data", "output")),
1186
+ ));
1187
+
1060
1188
  const assertUsableCommand = (command: CapabilityCommandManifest): void => {
1061
1189
  z.fromJSONSchema(structuredClone(command.inputSchema));
1062
1190
  assertClosedObjectInput(command.inputSchema, `Command ${command.localId} input`);
@@ -1079,7 +1207,7 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
1079
1207
  const sent = CapabilityManifestSchema.parse(value);
1080
1208
  if (sent.appId !== expectedAppId) throw new Error(`manifest appId must be ${expectedAppId}`);
1081
1209
  const localIds = new Set<string>();
1082
- for (const { localId } of [...sent.types, ...sent.queries, ...sent.actions, ...sent.commands]) {
1210
+ for (const { localId } of [...sent.types, ...sent.queries, ...sent.actions, ...sent.commands, ...(sent.events ?? [])]) {
1083
1211
  if (localIds.has(localId)) throw new Error(`localId ${localId} is declared more than once across Types, Queries, and Actions`);
1084
1212
  localIds.add(localId);
1085
1213
  }
@@ -1087,15 +1215,27 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
1087
1215
  const { manifestHash: _sentHash, ...sentBase } = value as Record<string, unknown>;
1088
1216
  if (sent.manifestHash !== capabilityHash(sentBase)) throw new Error("manifestHash does not match the manifest");
1089
1217
 
1218
+ const usableEventSchema = (entry: CapabilityEventManifest | CapabilitySubscriptionManifest) => {
1219
+ assertBoundedAppEventSchema(entry.dataSchema);
1220
+ if (entry.schemaHash !== capabilityHash({ dataSchema: entry.dataSchema }))
1221
+ throw new Error("Event schemaHash does not match its schema");
1222
+ };
1223
+ const subscriptionTypes = new Set<string>();
1224
+ for (const subscription of sent.subscribes ?? []) {
1225
+ if (subscriptionTypes.has(subscription.type)) throw new Error(`Duplicate subscription ${subscription.type}`);
1226
+ subscriptionTypes.add(subscription.type);
1227
+ }
1090
1228
  // Reading the usable entries again leaves out what depends on an entry left out here.
1091
1229
  const manifest = CapabilityManifestSchema.parse({
1092
1230
  ...sent,
1231
+ ...(sent.events ? { events: sent.events.filter((event) => passes(() => usableEventSchema(event))) } : {}),
1232
+ ...(sent.subscribes ? { subscribes: sent.subscribes.filter((subscription) => passes(() => usableEventSchema(subscription))) } : {}),
1093
1233
  queries: sent.queries.filter((query) => passes(() => assertUsableOperation(query))),
1094
1234
  actions: sent.actions.filter((action) => passes(() => assertUsableOperation(action))),
1095
1235
  commands: sent.commands.filter((command) => passes(() => assertUsableCommand(command))),
1096
1236
  });
1097
1237
  const queries = new Map(manifest.queries.map((query) => [query.localId, query]));
1098
- return {
1238
+ return CapabilityManifestSchema.parse({
1099
1239
  ...manifest,
1100
1240
  types: manifest.types.map((type) => {
1101
1241
  const reader = type.reader === undefined ? undefined : queries.get(type.reader);
@@ -1104,7 +1244,7 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
1104
1244
  const { reader: _unusable, ...withoutReader } = type;
1105
1245
  return withoutReader;
1106
1246
  }),
1107
- };
1247
+ });
1108
1248
  };
1109
1249
 
1110
1250
  const schemaSemantics = (value: unknown): unknown => {
@@ -1435,7 +1575,10 @@ export const invokeCompiledCapability = async (params: {
1435
1575
  },
1436
1576
  };
1437
1577
  }
1438
- let context = params.context;
1578
+ let context: CapabilityExecutionContext = {
1579
+ ...params.context,
1580
+ via: capabilityVia(params.context.origin),
1581
+ };
1439
1582
  if (params.kind === "action") {
1440
1583
  const action = operation as CompiledCapabilityAction;
1441
1584
  if (action.manifest.idempotency === "required" && !params.context.idempotencyKey) {
@@ -1481,7 +1624,7 @@ export const invokeCompiledCapability = async (params: {
1481
1624
  },
1482
1625
  };
1483
1626
  }
1484
- context = { ...params.context, idempotencyKey: idempotencyKey.data };
1627
+ context = { ...context, idempotencyKey: idempotencyKey.data };
1485
1628
  }
1486
1629
  const input = operation.definition.input.safeParse(params.input);
1487
1630
  if (!input.success) {
@@ -1603,7 +1746,10 @@ export const reviewCompiledCapability = async (params: {
1603
1746
  }
1604
1747
  if (params.context.signal.aborted) return cancelledInvocation(params.context.locale);
1605
1748
  try {
1606
- const reviewed = await operation.definition.review(input.data, params.context);
1749
+ const reviewed = await operation.definition.review(input.data, {
1750
+ ...params.context,
1751
+ via: capabilityVia(params.context.origin),
1752
+ });
1607
1753
  if (!reviewed.ok) {
1608
1754
  return normalizeProviderError(reviewed.error, "Capability review returned an invalid error", params.onUnexpectedError);
1609
1755
  }
@@ -0,0 +1,4 @@
1
+ import type { CapabilityOrigin, CapabilityVia } from "../contracts/capabilities";
2
+
3
+ /** Visible provenance never changes the authority of an invocation. */
4
+ export const capabilityVia = (origin: CapabilityOrigin): CapabilityVia => (origin === "assistant" || origin === "mcp" ? origin : null);
@@ -9,6 +9,7 @@ import {
9
9
  } from "../contracts/capabilities";
10
10
  import { hasOAuthScope } from "../server/oauth-scope";
11
11
  import type { CompiledCapabilities } from "./capabilities";
12
+ import { capabilityVia } from "./capability-origin";
12
13
  import { capabilityActionReceiptIssues } from "./capability-reviews";
13
14
 
14
15
  const failure = (status: number, code: string, message: string) => Response.json({ code, message }, { status });
@@ -38,13 +39,14 @@ export async function invokeCapabilityStream(params: {
38
39
  }
39
40
  return parsed;
40
41
  };
42
+ const context = { ...params.context, via: capabilityVia(params.context.origin) };
41
43
  try {
42
44
  const stream = CapabilityStreamSchema.parse(JSON.parse(Buffer.from(offer, "base64url").toString()));
43
45
  if (stream.direction !== definition.direction || stream.size > definition.maxBytes || Date.parse(stream.expiresAt) <= Date.now())
44
46
  return failure(410, "STREAM_EXPIRED", "Stream expired or outside its byte budget");
45
47
  if (definition.direction === "read") {
46
48
  if (params.verb !== "read") return failure(403, "STREAM_DENIED", "Read-only stream");
47
- const response = await definition.read(stream, params.context);
49
+ const response = await definition.read(stream, context);
48
50
  if (!response.ok || !response.body) {
49
51
  await response.body?.cancel();
50
52
  return failure(502, "STREAM_FAILED", "Source could not be read");
@@ -65,18 +67,18 @@ export async function invokeCapabilityStream(params: {
65
67
  stream.size,
66
68
  );
67
69
  try {
68
- const value = await definition.write(stream, body, params.context);
70
+ const value = await definition.write(stream, body, context);
69
71
  result = receipt(value);
70
72
  } finally {
71
73
  if (!body.locked) await body.cancel().catch(() => undefined);
72
74
  }
73
75
  } else if (params.verb === "status") {
74
- const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
76
+ const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, context));
75
77
  if (status.state === "completed") receipt(status.result);
76
78
  result = status;
77
79
  } else if (params.verb === "abort") {
78
- await definition.abort(stream, params.context);
79
- const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
80
+ await definition.abort(stream, context);
81
+ const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, context));
80
82
  if (status.state === "completed") receipt(status.result);
81
83
  result = status;
82
84
  } else return failure(400, "STREAM_INVALID", "Unknown stream operation");
@@ -1,12 +1,18 @@
1
1
  import { type AppSkillDefinition, appSkillManifestHash, registerAppSkills, validateAppSkillCatalog } from "../ai/app-skills";
2
2
  import { createOwnedAppAccessRoutes } from "../api/app-access";
3
+ import { createAppEventDeliveryRoutes } from "../api/app-event-delivery";
4
+ import { createResourceAppAccessRoutes } from "../api/app-resource-access";
3
5
  import { type AppAccessDeclaration, compileAppAccess } from "../contracts/app-access";
6
+ import { startAppEventDelivery } from "../events/app-event-delivery";
7
+ import { bindAppEvents } from "../events/app-events";
4
8
  import { startLiveOutbox } from "../events/live";
5
9
  import { registerHelp } from "../services/help";
6
10
  import { preloadLayoutHelp } from "../ssr/help";
7
11
  import { registerPageSwap } from "../ssr/page-swap";
8
12
  import { preloadPanelConsent } from "../ssr/panel-consent";
9
13
  import { appAccessManagementPath } from "./app-access-path";
14
+ import { bindResourceAppAccess, RESOURCE_ACCESS_PATH } from "./app-access-resources";
15
+ import { registerApplicationPackage, resolveApplicationPackage } from "./application-package";
10
16
  import { invokeCapabilityStream } from "./capability-streams";
11
17
  import { withCapabilityRequestTimeout } from "./capability-timeout";
12
18
  import { bindProcessApplicationId, clearProcessApplicationId } from "./process-identity";
@@ -28,6 +34,7 @@ import { generateSpecs } from "hono-openapi";
28
34
  import { CLOUD_CLI_PLUGIN_ROUTE } from "../cli/plugin";
29
35
  import { env } from "../config/env";
30
36
  import type {
37
+ AppAdminArea,
31
38
  AppAdminNavigationGroup,
32
39
  AppAppearance,
33
40
  AppCliModules,
@@ -151,6 +158,8 @@ type ResolvedPageOptions = PageOptions & {
151
158
  */
152
159
  export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
153
160
  id: AppId;
161
+ /** Shared object-store package. Defaults to the app ID. */
162
+ package?: string;
154
163
  access?: AppAccessDeclaration;
155
164
  name: string;
156
165
  icon: string;
@@ -167,7 +176,7 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
167
176
  adminHref?: string;
168
177
  /** Multi-link admin navigation contributed by this app. */
169
178
  adminNav?: ReadonlyArray<{
170
- section?: "ai";
179
+ section?: AppAdminArea;
171
180
  id?: string;
172
181
  label: string;
173
182
  links: ReadonlyArray<{ label: string; href: string; icon: string }>;
@@ -183,7 +192,7 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
183
192
  * Same-origin path of a `GET` route this app serves below one of its
184
193
  * `routes`. After the page loads, the shell reads it with the person's
185
194
  * session and shows the returned `AppNavBadge` count on the app's icon in
186
- * the rail and the app grid. Protect it like any other route.
195
+ * the rail, the phone bar, and the app grid. Protect it like any other route.
187
196
  */
188
197
  badge?: string;
189
198
  };
@@ -224,6 +233,17 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
224
233
  pwa?: {
225
234
  /** Coarse visibility in the mobile app, like `nav.requiresRoles`. Routes and services still authorize. */
226
235
  requiresRoles?: Role[];
236
+ /**
237
+ * Same-origin path of a `GET` route this app serves that returns an `AppNavBadge`: the count beside the part in
238
+ * the app's sidebar, such as unread messages. Like `nav.badge`, it must be one of the app's own paths.
239
+ */
240
+ badge?: string;
241
+ /**
242
+ * Same-origin path of a `GET` route this app serves that returns a `PwaPartSummary`: one line under the part on
243
+ * Start, such as "5 tasks for you", in the reader's locale, and its count beside the part in the sidebar when the
244
+ * part has no `badge`. It may be the badge's route. It must be one of the app's own paths.
245
+ */
246
+ summary?: string;
227
247
  };
228
248
  /**
229
249
  * Dashboard widget endpoints this app exposes. Each entry references an
@@ -396,7 +416,21 @@ const validateAppPwaPart = (opts: Pick<AppOptions, "id" | "routes" | "pwa">): Ap
396
416
  if (!isPwaPartId(opts.id)) {
397
417
  throw new Error(`App "${opts.id}" cannot declare pwa; a part needs a plain id other than pwa, settings and offline`);
398
418
  }
399
- return { href: `/pwa/${opts.id}`, requiresRoles: opts.pwa.requiresRoles ? [...opts.pwa.requiresRoles] : undefined };
419
+ const { badge, summary } = opts.pwa;
420
+ for (const [field, path] of [
421
+ ["badge", badge],
422
+ ["summary", summary],
423
+ ] as const) {
424
+ if (path !== undefined && !isOwnAppPath(path, [...opts.routes, `/pwa/${opts.id}`])) {
425
+ throw new Error(`App "${opts.id}" declares pwa.${field} "${path}"; it must be a same-origin path below one of its routes`);
426
+ }
427
+ }
428
+ return {
429
+ href: `/pwa/${opts.id}`,
430
+ requiresRoles: opts.pwa.requiresRoles ? [...opts.pwa.requiresRoles] : undefined,
431
+ ...(badge ? { badge } : {}),
432
+ ...(summary ? { summary } : {}),
433
+ };
400
434
  };
401
435
 
402
436
  /** The badge route is read by the browser through the gateway, so it must be one of this app's own paths. */
@@ -448,6 +482,7 @@ export const defineApp = <
448
482
  const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
449
483
  const cliModules = validateAppCliModules(opts.id, opts.cli);
450
484
  const pwaPart = validateAppPwaPart(opts);
485
+ const packageName = resolveApplicationPackage(opts.id, opts.package);
451
486
  const installable = opts.installable === undefined ? undefined : validateInstallable(opts.id, opts.routes, opts.installable);
452
487
  if (installable === null)
453
488
  throw new Error(`App "${opts.id}" declares invalid installable; use a canonical own /app/<id>/<surface>/ path covered by routes`);
@@ -616,6 +651,7 @@ export const defineApp = <
616
651
 
617
652
  // ── 2. Meta ───────────────────────────────────────────────────────────
618
653
  const baseMeta: AppMeta = {
654
+ package: packageName,
619
655
  id: opts.id,
620
656
  platformPermissions: opts.platformPermissions
621
657
  ? opts.platformPermissions.map((permission) => PlatformPermissionSchema.parse(permission))
@@ -685,7 +721,8 @@ export const defineApp = <
685
721
  }
686
722
  })());
687
723
  try {
688
- bindProcessApplicationId(meta.id, meta.platformPermissions);
724
+ await registerApplicationPackage(meta.id, packageName);
725
+ bindProcessApplicationId(meta.id, meta.platformPermissions, packageName);
689
726
  cleanup.unshift(clearProcessApplicationId);
690
727
  const startedAt = Date.now();
691
728
 
@@ -693,7 +730,8 @@ export const defineApp = <
693
730
  // derive the spec from. The mount block lower down uses the same
694
731
  // flag so the registry never points at a URL that 404s.
695
732
  const advertiseOpenapi = !!(opts.openapi && startOpts.openapi);
696
- const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities) : undefined;
733
+ const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities, meta.routes) : undefined;
734
+ if (startOpts.capabilities) bindAppEvents(meta.id, startOpts.capabilities, compiledCapabilities);
697
735
  const compiledHelp = startOpts.help
698
736
  ? compileHelp({
699
737
  appId: meta.id,
@@ -704,6 +742,7 @@ export const defineApp = <
704
742
 
705
743
  // Registry entry
706
744
  const entry: AppRegistryEntry = {
745
+ package: packageName,
707
746
  id: meta.id,
708
747
  access: meta.access,
709
748
  name: meta.name,
@@ -744,7 +783,12 @@ export const defineApp = <
744
783
  legalLinks: meta.legalLinks ? meta.legalLinks.map((l) => ({ ...l })) : undefined,
745
784
  searchLinks: meta.searchLinks,
746
785
  pwa: meta.pwa
747
- ? { href: meta.pwa.href, requiresRoles: meta.pwa.requiresRoles ? [...meta.pwa.requiresRoles] : undefined }
786
+ ? {
787
+ href: meta.pwa.href,
788
+ requiresRoles: meta.pwa.requiresRoles ? [...meta.pwa.requiresRoles] : undefined,
789
+ ...(meta.pwa.badge ? { badge: meta.pwa.badge } : {}),
790
+ ...(meta.pwa.summary ? { summary: meta.pwa.summary } : {}),
791
+ }
748
792
  : undefined,
749
793
  installable: meta.installable,
750
794
  panel: meta.panel
@@ -868,6 +912,9 @@ export const defineApp = <
868
912
 
869
913
  if (installable) server.route("/", createInstallableRoutes(meta.id, installable));
870
914
 
915
+ if (compiledCapabilities?.subscribes.size)
916
+ server.route("/api/_internal/events/v1", createAppEventDeliveryRoutes(compiledCapabilities));
917
+
871
918
  if (compiledCapabilities) {
872
919
  const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") =>
873
920
  withCapabilityRequestTimeout(c, kind, async () => {
@@ -1030,6 +1077,8 @@ export const defineApp = <
1030
1077
  );
1031
1078
  }
1032
1079
 
1080
+ cleanup.push(bindResourceAppAccess(meta.id, opts.access));
1081
+ server.route(RESOURCE_ACCESS_PATH, createResourceAppAccessRoutes(meta));
1033
1082
  cleanup.push(() => stopAppAccessConnections());
1034
1083
  if (meta.access?.kind === "grants") {
1035
1084
  server.route(appAccessManagementPath(meta), createOwnedAppAccessRoutes(meta, opts.access?.canManage));
@@ -1100,6 +1149,8 @@ export const defineApp = <
1100
1149
  const startOutbox = async () => {
1101
1150
  const stopLiveOutbox = await startLiveOutbox(meta.id);
1102
1151
  if (stopLiveOutbox) cleanup.push(stopLiveOutbox);
1152
+ const stopAppEvents = await startAppEventDelivery(meta.id);
1153
+ if (stopAppEvents) cleanup.push(stopAppEvents);
1103
1154
  };
1104
1155
  // An application fails before any setup when Core has not created the platform outbox yet.
1105
1156
  // Core creates it in its own setup, so it starts publishing after that.
@@ -0,0 +1,9 @@
1
+ import { getProcessApplicationPackage } from "./process-identity";
2
+
3
+ /** The single application-to-area mapping. No caller-supplied application or area ID. */
4
+ export const objectStoreArea = (visibility: "private" | "public" = "private"): string => {
5
+ const packageName = getProcessApplicationPackage();
6
+ if (!packageName) throw new Error("Start the application before using the object store.");
7
+ if (!/^[a-z][a-z0-9-]{0,79}$/.test(packageName)) throw new Error("Invalid object-store application package.");
8
+ return `${visibility}/${packageName}/`;
9
+ };