@k2b/cloud 0.43.0 → 0.45.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.
- package/package.json +11 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/GroupCoverage.tsx +20 -5
- package/src/access/PermissionEditor.tsx +20 -5
- package/src/access/PrincipalAvatar.tsx +30 -21
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/access/messages.ts +4 -4
- package/src/account/EntitySearch.tsx +12 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +10 -6
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +40 -25
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +12 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/project-tool.ts +4 -4
- package/src/ai/projects-authorization.ts +53 -0
- package/src/ai/projects-routes.ts +58 -47
- package/src/ai/projects.ts +110 -88
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +9 -3
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +47 -48
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/skill-tool.ts +4 -4
- package/src/ai/skills-routes.ts +27 -26
- package/src/ai/skills.ts +120 -81
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +22 -3
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-skills.ts +4 -1
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +35 -5
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/session.ts +39 -0
- package/src/browser/shared-text-history-validation.ts +151 -0
- package/src/browser/shared-text-history.ts +464 -0
- package/src/browser/shared-text-offline-authorized.ts +29 -0
- package/src/browser/shared-text-offline.ts +351 -0
- package/src/browser/shared-text-provider.ts +714 -0
- package/src/browser/shared-text-table-contract.ts +150 -0
- package/src/browser/shared-text.ts +19 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +6 -1
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +17 -4
- package/src/server/index.ts +2 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +31 -10
- package/src/server/services/index.ts +1 -0
- package/src/services/accounts/app.ts +36 -13
- package/src/services/accounts/entities.ts +43 -19
- package/src/services/accounts/group-sql.ts +23 -0
- package/src/services/accounts/groups.ts +19 -14
- package/src/services/accounts/identities.ts +70 -11
- package/src/services/accounts/identity-reconciliation.ts +3 -1
- package/src/services/accounts/local-groups.ts +69 -4
- package/src/services/accounts/users.ts +45 -14
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/index.ts +2 -2
- package/src/services/mandates/policy.ts +20 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +26 -3
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/email-html.ts +1 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/markdown/extensions/katex.ts +8 -1
- package/src/shared/markdown/extensions/tables.ts +14 -1
- package/src/shared/markdown/index.ts +22 -11
- package/src/shared-text/anchors.ts +29 -0
- package/src/shared-text/awareness.ts +87 -0
- package/src/shared-text/changes.ts +27 -0
- package/src/shared-text/documents.ts +433 -65
- package/src/shared-text/events.ts +12 -2
- package/src/shared-text/index.ts +16 -1
- package/src/shared-text/log.ts +24 -5
- package/src/shared-text/snapshots.ts +69 -23
- package/src/shared-text/structural-history.ts +377 -0
- package/src/shared-text/table-actions.ts +432 -0
- package/src/shared-text/text-diff.ts +75 -0
- package/src/shared-text/types.ts +24 -5
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +44 -7
- package/src/ssr/Layout.tsx +9 -7
- package/src/ssr/MobileProfileActions.tsx +2 -2
- package/src/ssr/ProfilePreferences.tsx +2 -1
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -31
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-launchpad.tsx +6 -1
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +11 -1
- package/src/ssr/profile-actions.ts +3 -6
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +3 -1
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/language/expressions.ts +3 -2
- package/src/workflows/runtime/executor.ts +134 -34
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- 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 = {
|
|
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(
|
|
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 = (
|
|
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)
|
|
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 =
|
|
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 = { ...
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
79
|
-
const status = CapabilityStreamStatusSchema.parse(await definition.status(stream,
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
? {
|
|
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
|
+
};
|