@k2b/cloud 0.42.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.
- package/package.json +13 -5
- package/scripts/app-favicon.ts +7 -6
- package/scripts/build.ts +3 -0
- package/scripts/installable-assets.ts +61 -0
- package/scripts/preload.ts +3 -0
- package/src/_internal/app-access-path.ts +12 -0
- 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 +185 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +10 -5
- package/src/_internal/define-app.ts +159 -45
- package/src/_internal/installable-assets.ts +90 -0
- package/src/_internal/installable-head.ts +22 -0
- 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/page-responses.ts +34 -3
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +15 -1
- package/src/_internal/registry.ts +44 -1
- package/src/_internal/runtime-context.ts +9 -1
- package/src/_internal/runtime-watcher.ts +8 -0
- package/src/_internal/status-preserving-ssr.ts +12 -1
- package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
- package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
- package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
- package/src/access/AppAccessEditor.tsx +380 -0
- package/src/access/PermissionEditor.browser-harness.tsx +22 -2
- package/src/access/PermissionEditor.tsx +386 -77
- package/src/access/PrincipalAvatar.tsx +63 -0
- package/src/access/PrincipalPicker.tsx +29 -8
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-adapter.ts +51 -0
- package/src/access/app-access-messages.ts +172 -0
- package/src/access/messages.ts +14 -0
- package/src/access/ui.ts +4 -1
- package/src/account/Avatar.tsx +21 -3
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +8 -1
- 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/capability-execution.ts +5 -2
- 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/code-capability-routes.ts +1 -0
- 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 +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +38 -27
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +6 -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-slots.ts +10 -2
- package/src/ai/openai-api.ts +83 -16
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +138 -0
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider-retry.ts +1 -1
- package/src/ai/provider.ts +2 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +11 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas-migrate.ts +1 -0
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- 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 +29 -46
- 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 +37 -21
- package/src/ai/turn-failure.ts +24 -2
- 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 +40 -16
- package/src/api/admin-ai-quotas.ts +34 -56
- 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/admin-directory-shares.ts +55 -0
- package/src/api/admin-identity.ts +81 -2
- package/src/api/app-access.ts +85 -0
- 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 +26 -3
- package/src/api/capabilities.ts +127 -15
- package/src/api/capability-streams.ts +2 -5
- package/src/api/identity-child-invocation.ts +145 -0
- package/src/api/index.ts +11 -2
- package/src/api/mcp.ts +180 -5
- package/src/api/me.ts +38 -17
- package/src/api/pwa-phone.ts +87 -48
- 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/api/widgets.ts +1 -0
- package/src/app-pwa/authority.ts +75 -0
- package/src/app-pwa/contracts.ts +177 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +23 -0
- package/src/app-pwa/protocol.ts +27 -0
- package/src/app-pwa/schema.ts +59 -0
- package/src/app-pwa/service.ts +861 -0
- package/src/app-pwa/transaction.ts +338 -0
- package/src/app-pwa/transport-input.ts +57 -0
- package/src/app-pwa/transport.ts +235 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/app-access-recovery.ts +44 -0
- package/src/browser/app-session.ts +14 -3
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/live-websocket.ts +5 -0
- package/src/browser/pwa-drafts.ts +57 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/resource-picker.tsx +3 -2
- package/src/capabilities/calendar-sources.ts +302 -0
- package/src/capabilities/executions.ts +10 -3
- package/src/capabilities/migrate.ts +4 -0
- package/src/capabilities/nested.ts +43 -0
- package/src/capabilities/server-invoke.ts +202 -0
- package/src/capabilities/server.ts +26 -187
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/account.ts +57 -0
- package/src/cli/admin/agents.ts +66 -0
- 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 +7 -3
- package/src/cli/app-access.ts +60 -0
- package/src/cli/apps.ts +89 -26
- package/src/cli/avatar.ts +64 -0
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/cli/index.ts +1 -1
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +52 -5
- package/src/contracts/app-approval.ts +8 -1
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +24 -5
- package/src/contracts/calendar-source.ts +274 -0
- package/src/contracts/calendar-view.ts +35 -0
- package/src/contracts/capabilities.ts +164 -3
- package/src/contracts/geo-admin.ts +2 -0
- package/src/contracts/geo-route.ts +0 -11
- package/src/contracts/geo.ts +14 -2
- package/src/contracts/index.ts +6 -0
- package/src/contracts/installable.ts +40 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/profile.ts +32 -0
- package/src/contracts/pwa-builds.ts +14 -0
- package/src/contracts/pwa-location.ts +38 -0
- package/src/contracts/pwa.ts +24 -3
- package/src/contracts/registry.ts +8 -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 +4 -0
- 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 +2 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +7 -6
- package/src/server/middleware/auth.ts +168 -66
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +4 -2
- package/src/server/middleware/workload.ts +3 -3
- package/src/server/oauth-scope.ts +10 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +35 -3
- package/src/services/accounts/avatar.ts +342 -35
- package/src/services/accounts/base-group.ts +4 -5
- package/src/services/accounts/directory-shares.ts +157 -0
- package/src/services/accounts/entities.ts +50 -6
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +64 -12
- package/src/services/accounts/local-groups.ts +1 -1
- package/src/services/accounts/users.ts +23 -15
- package/src/services/app-access-management.ts +519 -0
- package/src/services/app-access.ts +25 -7
- package/src/services/app-approval.ts +15 -4
- 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/admin.ts +4 -1
- package/src/services/geo/ledger.ts +47 -14
- package/src/services/geo/route-chain.ts +40 -18
- package/src/services/geo/route-estimate.ts +19 -0
- package/src/services/geo/route-plan.ts +0 -2
- package/src/services/geo/route-service.ts +57 -39
- package/src/services/geo/service.ts +51 -25
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/index.ts +2 -1
- package/src/services/identity/invocation-actor.ts +50 -2
- package/src/services/identity/invocation-authority.ts +38 -1
- package/src/services/identity/invocation-token.ts +255 -50
- package/src/services/identity/issue-invocation.ts +9 -1
- package/src/services/identity/models-only-auth.ts +13 -4
- package/src/services/identity/nested-invocation.ts +128 -0
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +24 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/policy.ts +19 -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/service-account-credentials.ts +5 -2
- package/src/services/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +91 -47
- 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/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +11 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +114 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/AppPwaRuntime.island.tsx +82 -0
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +15 -7
- package/src/ssr/PwaLayout.tsx +73 -8
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/PwaRuntime.island.tsx +85 -6
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/ShellRuntime.island.tsx +5 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-pwa-document.tsx +16 -0
- package/src/ssr/app-pwa-messages.ts +17 -0
- package/src/ssr/index.ts +8 -0
- package/src/ssr/layout-context.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +10 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +2 -0
- 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/runtime/executor.ts +132 -33
- 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
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
CapabilityStreamPolicySchema,
|
|
5
5
|
CapabilityStreamSchema,
|
|
6
6
|
} from "./capability-streams";
|
|
7
|
+
import { RESOURCE_PAGE_MAX_LENGTH, RESOURCE_PAGES_PER_TYPE, resourcePageIssue } from "./resource-pages";
|
|
7
8
|
|
|
8
9
|
export * from "./capability-streams";
|
|
9
10
|
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import type { RequestAuthority } from "../server/middleware/auth";
|
|
13
|
+
import type { AppEventDefinition, AppEventSubscriptionDefinition } from "./app-events";
|
|
12
14
|
import { CommandPathSchema } from "./commands";
|
|
13
15
|
import type { AccessSubject, RequestActor, User } from "./shared";
|
|
14
16
|
|
|
@@ -56,6 +58,10 @@ export const CAPABILITY_ORIGINS = ["assistant", "mcp", "http", "app"] as const;
|
|
|
56
58
|
export type CapabilityOrigin = (typeof CAPABILITY_ORIGINS)[number];
|
|
57
59
|
export const CapabilityOriginSchema = z.enum(CAPABILITY_ORIGINS);
|
|
58
60
|
|
|
61
|
+
/** Visible content provenance. It is metadata, never an authorization input. */
|
|
62
|
+
export const CapabilityViaSchema = z.enum(["assistant", "mcp"]).nullable();
|
|
63
|
+
export type CapabilityVia = z.infer<typeof CapabilityViaSchema>;
|
|
64
|
+
|
|
59
65
|
/** Transports the resolved origin from the Cloud dispatcher to the app endpoint. */
|
|
60
66
|
export const CAPABILITY_ORIGIN_HEADER = "x-cloud-capability-origin";
|
|
61
67
|
|
|
@@ -97,6 +103,20 @@ export const CloudResourceReferenceSchema = CloudResourceRefSchema.extend({
|
|
|
97
103
|
title: z.string().min(1).max(500).optional().describe("Optional current user-facing resource label."),
|
|
98
104
|
preview: z.string().max(2000).optional().describe("Optional short plain-text context for the resource."),
|
|
99
105
|
icon: z.string().min(1).max(120).optional().describe("Optional presentation icon for the resource."),
|
|
106
|
+
context: z
|
|
107
|
+
.string()
|
|
108
|
+
.min(1)
|
|
109
|
+
.max(200)
|
|
110
|
+
.optional()
|
|
111
|
+
.describe(
|
|
112
|
+
"One line in the caller's locale that says what and where the resource is and its most useful fact, for example 'Task in IT planning · due 30 Oct'.",
|
|
113
|
+
),
|
|
114
|
+
status: z
|
|
115
|
+
.string()
|
|
116
|
+
.min(1)
|
|
117
|
+
.max(60)
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("The resource's current state as a short label in the caller's locale, for example 'Open' or 'Done'."),
|
|
100
120
|
}).strict();
|
|
101
121
|
|
|
102
122
|
export type CloudResourceReference = z.infer<typeof CloudResourceReferenceSchema>;
|
|
@@ -660,7 +680,7 @@ const resultSchema = <T extends z.ZodType>(
|
|
|
660
680
|
stream: CapabilityStreamSchema.optional(),
|
|
661
681
|
summary: z.string().trim().min(1).max(500).optional(),
|
|
662
682
|
refs: z
|
|
663
|
-
.array(options.consumer ? LinkedCloudResourceReferenceSchema : CloudResourceReferenceSchema)
|
|
683
|
+
.array(options.consumer ? LinkedCloudResourceReferenceSchema.strip() : CloudResourceReferenceSchema)
|
|
664
684
|
.max(100)
|
|
665
685
|
.optional(),
|
|
666
686
|
page: CapabilityPageSchema.optional(),
|
|
@@ -727,6 +747,8 @@ export const CloudResourceViewSchema = z
|
|
|
727
747
|
title: z.string().min(1).max(500).describe("Primary result label."),
|
|
728
748
|
preview: z.string().max(2000).optional().describe("Short plain-text result preview."),
|
|
729
749
|
icon: z.string().min(1).max(120).optional().describe("Optional presentation icon."),
|
|
750
|
+
context: CloudResourceReferenceSchema.shape.context,
|
|
751
|
+
status: CloudResourceReferenceSchema.shape.status,
|
|
730
752
|
priority: z.number().int().min(0).max(9).optional().describe("App-local relevance hint from zero to nine."),
|
|
731
753
|
metadata: z
|
|
732
754
|
.array(
|
|
@@ -763,6 +785,11 @@ export const UniversalSearchDataSchema = z.array(CloudResourceViewSchema).max(10
|
|
|
763
785
|
export type UniversalSearchData = z.infer<typeof UniversalSearchDataSchema>;
|
|
764
786
|
|
|
765
787
|
export type CapabilityExecutionContext = {
|
|
788
|
+
/** One read-only cross-app dependency under this invocation, through Core. */
|
|
789
|
+
query?: <T extends z.ZodType>(
|
|
790
|
+
invocation: { appId: string; capabilityId: string; input: unknown; signal?: AbortSignal },
|
|
791
|
+
dataSchema: T,
|
|
792
|
+
) => Promise<import("../capabilities/types").CapabilityClientResult<z.output<T>>>;
|
|
766
793
|
/**
|
|
767
794
|
* Resolved request authority, supplied by the framework for execution, review
|
|
768
795
|
* and streams. Optional for existing in-process callers; authority-aware
|
|
@@ -788,15 +815,25 @@ export type CapabilityExecutionContext = {
|
|
|
788
815
|
requestId: string;
|
|
789
816
|
/** Cloud surface that invoked this capability. */
|
|
790
817
|
origin: CapabilityOrigin;
|
|
818
|
+
/** Framework-derived visible provenance; absent only in manually constructed contexts. */
|
|
819
|
+
via?: CapabilityVia;
|
|
791
820
|
/** Caller cancellation or shared dispatch deadline; forward it to expensive work. */
|
|
792
821
|
signal: AbortSignal;
|
|
793
822
|
};
|
|
794
823
|
|
|
824
|
+
/** Colours a consumer may give a resource type's icon, the same palette as `@k2b/ui` cards. */
|
|
825
|
+
export const CAPABILITY_RESOURCE_COLORS = ["blue", "emerald", "violet", "orange", "red", "amber", "zinc", "cyan", "rose"] as const;
|
|
826
|
+
export type CapabilityResourceColor = (typeof CAPABILITY_RESOURCE_COLORS)[number];
|
|
827
|
+
|
|
795
828
|
export type CapabilityResourceTypeDefinition = {
|
|
796
829
|
title: string;
|
|
797
830
|
description: string;
|
|
798
831
|
icon?: string;
|
|
832
|
+
/** One colour per kind of resource, so a card or list shows at a glance what it stands for. */
|
|
833
|
+
color?: CapabilityResourceColor;
|
|
799
834
|
reader?: string;
|
|
835
|
+
/** Root-relative pages recognized when a person pastes a Cloud link. Requires a reader. */
|
|
836
|
+
pages?: readonly string[];
|
|
800
837
|
};
|
|
801
838
|
|
|
802
839
|
export type CapabilitySearchTagDefinition = {
|
|
@@ -850,6 +887,7 @@ export type CapabilityOperationPresentationTranslation = {
|
|
|
850
887
|
};
|
|
851
888
|
|
|
852
889
|
export type CapabilityPresentationTranslation = {
|
|
890
|
+
events?: Readonly<Record<string, { title?: string; description?: string }>>;
|
|
853
891
|
types?: Readonly<Record<string, { title?: string; description?: string }>>;
|
|
854
892
|
queries?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
|
|
855
893
|
actions?: Readonly<Record<string, CapabilityOperationPresentationTranslation>>;
|
|
@@ -940,6 +978,8 @@ type CapabilityDefinitionCatalog<T> = Readonly<Record<string, T>>;
|
|
|
940
978
|
/** Offers files to other apps: local IDs of the list Query, the read Query, and the optional save Action. */
|
|
941
979
|
export type CapabilityFileProviderDeclaration = { list: string; read: string; save?: string };
|
|
942
980
|
|
|
981
|
+
export type CapabilityCalendarSourceDeclaration = { calendars: string; events: string; update?: string; move?: string; delete?: string };
|
|
982
|
+
|
|
943
983
|
export type CapabilityDefinitions = {
|
|
944
984
|
protocolVersion: typeof CAPABILITY_PROTOCOL_VERSION;
|
|
945
985
|
presentation?: CapabilityPresentationCatalog;
|
|
@@ -948,13 +988,28 @@ export type CapabilityDefinitions = {
|
|
|
948
988
|
actions?: CapabilityDefinitionCatalog<CapabilityActionDefinition>;
|
|
949
989
|
commands?: CapabilityDefinitionCatalog<CapabilityCommandDefinition>;
|
|
950
990
|
fileProvider?: CapabilityFileProviderDeclaration;
|
|
991
|
+
calendarSource?: CapabilityCalendarSourceDeclaration;
|
|
992
|
+
events?: CapabilityDefinitionCatalog<AppEventDefinition>;
|
|
993
|
+
subscribes?: CapabilityDefinitionCatalog<AppEventSubscriptionDefinition>;
|
|
951
994
|
};
|
|
952
995
|
|
|
953
996
|
/**
|
|
954
997
|
* Declares one app's public capability surface. Runtime compilation happens
|
|
955
998
|
* in defineApp.start(), where the app id is available for namespacing.
|
|
956
999
|
*/
|
|
957
|
-
export
|
|
1000
|
+
export function defineCapabilities<
|
|
1001
|
+
const Schemas extends Readonly<Record<string, z.ZodType>>,
|
|
1002
|
+
const Definitions extends Omit<CapabilityDefinitions, "subscribes">,
|
|
1003
|
+
>(
|
|
1004
|
+
definitions: Definitions & { subscribes: { readonly [Key in keyof Schemas]: AppEventSubscriptionDefinition<Schemas[Key]> } },
|
|
1005
|
+
): Definitions & { subscribes: { readonly [Key in keyof Schemas]: AppEventSubscriptionDefinition<Schemas[Key]> } };
|
|
1006
|
+
export function defineCapabilities<const Definitions extends Omit<CapabilityDefinitions, "subscribes">>(
|
|
1007
|
+
definitions: Definitions & { subscribes?: never },
|
|
1008
|
+
): Definitions;
|
|
1009
|
+
export function defineCapabilities<const Definitions extends CapabilityDefinitions>(definitions: Definitions): Definitions;
|
|
1010
|
+
export function defineCapabilities(definitions: CapabilityDefinitions): CapabilityDefinitions {
|
|
1011
|
+
return definitions;
|
|
1012
|
+
}
|
|
958
1013
|
|
|
959
1014
|
export const CapabilityResourceTypeManifestSchema = z
|
|
960
1015
|
.object({
|
|
@@ -962,6 +1017,7 @@ export const CapabilityResourceTypeManifestSchema = z
|
|
|
962
1017
|
title: z.string().min(1).max(120),
|
|
963
1018
|
description: z.string().min(1).max(500),
|
|
964
1019
|
icon: z.string().min(1).max(120).optional(),
|
|
1020
|
+
color: z.enum(CAPABILITY_RESOURCE_COLORS).optional(),
|
|
965
1021
|
reader: CapabilityLocalIdSchema.optional(),
|
|
966
1022
|
})
|
|
967
1023
|
.strict();
|
|
@@ -1015,6 +1071,51 @@ export const CapabilityFileProviderManifestSchema = z
|
|
|
1015
1071
|
.object({ list: CapabilityLocalIdSchema, read: CapabilityLocalIdSchema, save: CapabilityLocalIdSchema.optional() })
|
|
1016
1072
|
.strict();
|
|
1017
1073
|
|
|
1074
|
+
export const CapabilityCalendarSourceManifestSchema = z
|
|
1075
|
+
.object({
|
|
1076
|
+
calendars: CapabilityLocalIdSchema,
|
|
1077
|
+
events: CapabilityLocalIdSchema,
|
|
1078
|
+
update: CapabilityLocalIdSchema.optional(),
|
|
1079
|
+
move: CapabilityLocalIdSchema.optional(),
|
|
1080
|
+
delete: CapabilityLocalIdSchema.optional(),
|
|
1081
|
+
})
|
|
1082
|
+
.strict();
|
|
1083
|
+
|
|
1084
|
+
export const CapabilityEventManifestSchema = z
|
|
1085
|
+
.object({
|
|
1086
|
+
localId: CapabilityLocalIdSchema,
|
|
1087
|
+
version: z.number().int().positive(),
|
|
1088
|
+
title: z.string().trim().min(1).max(120),
|
|
1089
|
+
description: z.string().trim().min(1).max(1000),
|
|
1090
|
+
subject: CapabilityLocalIdSchema,
|
|
1091
|
+
place: CapabilityLocalIdSchema.optional(),
|
|
1092
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1093
|
+
schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
1094
|
+
automation: z.literal(false).optional(),
|
|
1095
|
+
})
|
|
1096
|
+
.strict();
|
|
1097
|
+
export type CapabilityEventManifest = z.infer<typeof CapabilityEventManifestSchema>;
|
|
1098
|
+
|
|
1099
|
+
export const CapabilitySubscriptionManifestSchema = z
|
|
1100
|
+
.object({
|
|
1101
|
+
type: CapabilityQualifiedIdSchema,
|
|
1102
|
+
version: z.number().int().positive(),
|
|
1103
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1104
|
+
schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
1105
|
+
})
|
|
1106
|
+
.strict();
|
|
1107
|
+
export type CapabilitySubscriptionManifest = z.infer<typeof CapabilitySubscriptionManifestSchema>;
|
|
1108
|
+
export const CapabilityResourcePageManifestSchema = z
|
|
1109
|
+
.object({
|
|
1110
|
+
type: CapabilityLocalIdSchema,
|
|
1111
|
+
path: z
|
|
1112
|
+
.string()
|
|
1113
|
+
.min(1)
|
|
1114
|
+
.max(RESOURCE_PAGE_MAX_LENGTH)
|
|
1115
|
+
.refine((path) => !resourcePageIssue(path), "Invalid resource page pattern"),
|
|
1116
|
+
})
|
|
1117
|
+
.strict();
|
|
1118
|
+
|
|
1018
1119
|
/** Keeps the sent entries this release reads completely. */
|
|
1019
1120
|
const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T>[] | null => {
|
|
1020
1121
|
// Not a bounded list: leave the value to the manifest schema, which rejects it.
|
|
@@ -1041,6 +1142,8 @@ const readEntries = <T extends z.ZodType>(values: unknown, entry: T): z.output<T
|
|
|
1041
1142
|
* - An unreadable policy or one inconsistent with derived approval, review, or effect flags leaves
|
|
1042
1143
|
* out its Action. Policies for unknown Actions are dropped.
|
|
1043
1144
|
*
|
|
1145
|
+
* Resource pages live at the top level so older releases keep the Type and reader. Unreadable
|
|
1146
|
+
* pages and pages without a readable Type/reader are omitted; live prefix ownership is checked separately.
|
|
1044
1147
|
* Every other entry stays available.
|
|
1045
1148
|
*/
|
|
1046
1149
|
const readableManifest = (value: unknown): unknown => {
|
|
@@ -1051,6 +1154,9 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1051
1154
|
const actions = readEntries(sent.actions, CapabilityActionManifestSchema);
|
|
1052
1155
|
const commands = readEntries(sent.commands, CapabilityCommandManifestSchema);
|
|
1053
1156
|
if (!types || !queries || !actions || !commands) return value;
|
|
1157
|
+
const events = sent.events === undefined ? undefined : readEntries(sent.events, CapabilityEventManifestSchema);
|
|
1158
|
+
const subscribes = sent.subscribes === undefined ? undefined : readEntries(sent.subscribes, CapabilitySubscriptionManifestSchema);
|
|
1159
|
+
if (events === null || subscribes === null) return value;
|
|
1054
1160
|
const typeIds = new Set(types.map((type) => type.localId));
|
|
1055
1161
|
const readableQueries = queries.filter((query) => query.universalSearch?.scopeTypes?.every((type) => typeIds.has(type)) ?? true);
|
|
1056
1162
|
const queryIds = new Set(readableQueries.map((query) => query.localId));
|
|
@@ -1074,11 +1180,27 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1074
1180
|
policies[action.localId] = policy;
|
|
1075
1181
|
return true;
|
|
1076
1182
|
});
|
|
1077
|
-
const { fileProvider: sentProvider, policies: _policies, ...rest } = sent;
|
|
1183
|
+
const { calendarSource: sentCalendarSource, fileProvider: sentProvider, policies: _policies, resourcePages: sentPages, ...rest } = sent;
|
|
1078
1184
|
const provider = CapabilityFileProviderManifestSchema.safeParse(sentProvider);
|
|
1185
|
+
const calendar = CapabilityCalendarSourceManifestSchema.safeParse(sentCalendarSource);
|
|
1186
|
+
const pageCounts = new Map<string, number>();
|
|
1187
|
+
const resourcePages =
|
|
1188
|
+
Array.isArray(sentPages) && sentPages.length <= MAX_MANIFEST_ENTRIES * RESOURCE_PAGES_PER_TYPE
|
|
1189
|
+
? sentPages.flatMap((value) => {
|
|
1190
|
+
const page = CapabilityResourcePageManifestSchema.safeParse(value);
|
|
1191
|
+
if (!page.success) return [];
|
|
1192
|
+
const type = types.find((type) => type.localId === page.data.type);
|
|
1193
|
+
const count = pageCounts.get(page.data.type) ?? 0;
|
|
1194
|
+
if (!type?.reader || !queryIds.has(type.reader) || count >= RESOURCE_PAGES_PER_TYPE) return [];
|
|
1195
|
+
pageCounts.set(page.data.type, count + 1);
|
|
1196
|
+
return [page.data];
|
|
1197
|
+
})
|
|
1198
|
+
: [];
|
|
1079
1199
|
return {
|
|
1080
1200
|
...rest,
|
|
1201
|
+
...(sentPages !== undefined ? { resourcePages } : {}),
|
|
1081
1202
|
...(provider.success ? { fileProvider: provider.data } : {}),
|
|
1203
|
+
...(calendar.success ? { calendarSource: calendar.data } : {}),
|
|
1082
1204
|
types: types.map((type) => {
|
|
1083
1205
|
if (!type.reader || queryIds.has(type.reader)) return type;
|
|
1084
1206
|
const { reader: _missing, ...withoutReader } = type;
|
|
@@ -1088,6 +1210,16 @@ const readableManifest = (value: unknown): unknown => {
|
|
|
1088
1210
|
actions: readableActions,
|
|
1089
1211
|
...(sentPolicies !== undefined ? { policies } : {}),
|
|
1090
1212
|
commands,
|
|
1213
|
+
...(events
|
|
1214
|
+
? {
|
|
1215
|
+
events: events.filter(
|
|
1216
|
+
(event) =>
|
|
1217
|
+
typeIds.has(event.subject) &&
|
|
1218
|
+
(!event.place || types.some((type) => type.localId === event.place && type.reader && queryIds.has(type.reader))),
|
|
1219
|
+
),
|
|
1220
|
+
}
|
|
1221
|
+
: {}),
|
|
1222
|
+
...(subscribes ? { subscribes } : {}),
|
|
1091
1223
|
};
|
|
1092
1224
|
};
|
|
1093
1225
|
|
|
@@ -1107,7 +1239,14 @@ export const CapabilityManifestSchema = z.preprocess(
|
|
|
1107
1239
|
commands: z.array(CapabilityCommandManifestSchema).max(MAX_MANIFEST_ENTRIES),
|
|
1108
1240
|
// Optional without a default: a manifest without a provider keeps the shape and hash of earlier releases.
|
|
1109
1241
|
fileProvider: CapabilityFileProviderManifestSchema.optional(),
|
|
1242
|
+
calendarSource: CapabilityCalendarSourceManifestSchema.optional(),
|
|
1110
1243
|
policies: z.record(CapabilityLocalIdSchema, CapabilityActionPolicySchema).optional(),
|
|
1244
|
+
events: z.array(CapabilityEventManifestSchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1245
|
+
subscribes: z.array(CapabilitySubscriptionManifestSchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1246
|
+
resourcePages: z
|
|
1247
|
+
.array(CapabilityResourcePageManifestSchema)
|
|
1248
|
+
.max(MAX_MANIFEST_ENTRIES * RESOURCE_PAGES_PER_TYPE)
|
|
1249
|
+
.optional(),
|
|
1111
1250
|
}),
|
|
1112
1251
|
);
|
|
1113
1252
|
|
|
@@ -1132,12 +1271,34 @@ export const resolveCapabilityResourceReader = (manifest: CapabilityManifest, re
|
|
|
1132
1271
|
};
|
|
1133
1272
|
|
|
1134
1273
|
/** Like the manifest, catalog entries, pages, and their pagination ignore fields that a newer Cloud release added. */
|
|
1274
|
+
export const CapabilityEventCatalogEntrySchema = z.object({
|
|
1275
|
+
type: CapabilityQualifiedIdSchema,
|
|
1276
|
+
version: z.number().int().positive(),
|
|
1277
|
+
title: z.string().min(1).max(120),
|
|
1278
|
+
description: z.string().min(1).max(1000),
|
|
1279
|
+
subject: CapabilityQualifiedIdSchema,
|
|
1280
|
+
place: CapabilityQualifiedIdSchema.nullable(),
|
|
1281
|
+
automation: z.boolean(),
|
|
1282
|
+
dataSchema: z.record(z.string(), z.unknown()),
|
|
1283
|
+
subscribers: z.array(
|
|
1284
|
+
z.object({
|
|
1285
|
+
appId: CapabilityAppIdSchema,
|
|
1286
|
+
subscriptionId: z.string(),
|
|
1287
|
+
state: z.enum(["active", "blocked", "cancelled"]),
|
|
1288
|
+
reason: z.string().nullable(),
|
|
1289
|
+
firstSeenAt: z.iso.datetime({ offset: true }).optional(),
|
|
1290
|
+
}),
|
|
1291
|
+
),
|
|
1292
|
+
});
|
|
1293
|
+
export type CapabilityEventCatalogEntry = z.infer<typeof CapabilityEventCatalogEntrySchema>;
|
|
1294
|
+
|
|
1135
1295
|
export const CapabilityCatalogAppSchema = z.object({
|
|
1136
1296
|
appId: CapabilityAppIdSchema,
|
|
1137
1297
|
appName: z.string().min(1).max(200),
|
|
1138
1298
|
appIcon: z.string().min(1).max(120),
|
|
1139
1299
|
appDescription: z.string().max(1000),
|
|
1140
1300
|
manifest: CapabilityManifestSchema,
|
|
1301
|
+
events: z.array(CapabilityEventCatalogEntrySchema).max(MAX_MANIFEST_ENTRIES).optional(),
|
|
1141
1302
|
});
|
|
1142
1303
|
|
|
1143
1304
|
export const CapabilityCatalogSchema = z.object({
|
|
@@ -55,6 +55,8 @@ export type GeoAdminUsage = {
|
|
|
55
55
|
shares: Array<{ group: GeoUsageGroup; credits: number }>;
|
|
56
56
|
/** Access subjects whose use today reached the per-person daily limit. */
|
|
57
57
|
peopleAtLimit: number;
|
|
58
|
+
/** Routing estimates exceeded this month; details are in the audit view. */
|
|
59
|
+
routeCostOverruns: number;
|
|
58
60
|
timezone: string;
|
|
59
61
|
};
|
|
60
62
|
|
|
@@ -2,14 +2,6 @@ import { z } from "zod";
|
|
|
2
2
|
import { GEO_MAX_POLYLINE_CHARS, GeoAddressSchema, GeoGeocodeInputSchema, GeoPointSchema, GeoProvenanceSchema } from "./geo";
|
|
3
3
|
|
|
4
4
|
export const GEO_ROUTE_MAX_STOPS = 26;
|
|
5
|
-
export const GeoRouteDistanceAllowanceSchema = z
|
|
6
|
-
.number()
|
|
7
|
-
.int()
|
|
8
|
-
.min(1)
|
|
9
|
-
.max(1_000_000_000)
|
|
10
|
-
.describe(
|
|
11
|
-
"Reference distance allowance in meters per leg; defaults to 500000. The whole chain reserves at this allowance. This is not a guaranteed provider bill ceiling. A larger actual route is charged and stops the chain.",
|
|
12
|
-
);
|
|
13
5
|
const stopId = z.string().min(1).max(100).describe("Stable stop ID, unique within this route.");
|
|
14
6
|
export const GeoRouteOptionsSchema = z
|
|
15
7
|
.object({
|
|
@@ -46,7 +38,6 @@ export const GeoRouteLegOptionsSchema = z
|
|
|
46
38
|
.strict();
|
|
47
39
|
export const GeoRoutePlanSchema = z
|
|
48
40
|
.object({
|
|
49
|
-
distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
|
|
50
41
|
stops: z.array(GeoRouteStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("2–26 stops in travel order, with unique IDs."),
|
|
51
42
|
options: GeoRouteOptionsSchema.optional().describe("Default options for the route."),
|
|
52
43
|
legOptions: z
|
|
@@ -72,7 +63,6 @@ export const GeoRouteLegSchema = z
|
|
|
72
63
|
.strict();
|
|
73
64
|
export const GeoRouteSchema = z
|
|
74
65
|
.object({
|
|
75
|
-
distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema,
|
|
76
66
|
stops: z.array(GeoRouteResolvedStopSchema).min(2).max(GEO_ROUTE_MAX_STOPS).describe("Resolved stops in current travel order."),
|
|
77
67
|
options: GeoRouteOptionsSchema.describe("Travel options; mode and avoid override only supplied fields."),
|
|
78
68
|
legs: z.array(GeoRouteLegSchema).min(1).max(25).describe("Independent stopover legs in travel order."),
|
|
@@ -122,7 +112,6 @@ export const GeoRouteUpdateInputSchema = z
|
|
|
122
112
|
.object({
|
|
123
113
|
route: GeoRouteSchema,
|
|
124
114
|
changes: GeoRouteChangesSchema.describe("One stateless route edit to preview."),
|
|
125
|
-
distanceAllowanceMeters: GeoRouteDistanceAllowanceSchema.optional(),
|
|
126
115
|
})
|
|
127
116
|
.strict();
|
|
128
117
|
export const GeoRouteUpdateDataSchema = z
|
package/src/contracts/geo.ts
CHANGED
|
@@ -188,12 +188,24 @@ export const GeoErrorDetailsSchema = z.discriminatedUnion("code", [
|
|
|
188
188
|
z
|
|
189
189
|
.object({
|
|
190
190
|
code: z.literal("GEO_COST_OVERRUN"),
|
|
191
|
-
|
|
191
|
+
adminHint: z.string().optional(),
|
|
192
|
+
estimatedDistanceMeters: z.number().nonnegative(),
|
|
193
|
+
reservedCredits: z.number().nonnegative(),
|
|
194
|
+
actualCredits: z.number().nonnegative(),
|
|
192
195
|
actualDistanceMeters: z.number().nonnegative(),
|
|
193
196
|
costs: GeoCostsSchema,
|
|
194
197
|
})
|
|
195
198
|
.strict(),
|
|
196
|
-
z
|
|
199
|
+
z
|
|
200
|
+
.object({
|
|
201
|
+
code: z.literal("GEO_BUDGET_EXHAUSTED"),
|
|
202
|
+
scope: z.enum(["person", "cloud"]),
|
|
203
|
+
resetsAt: z.iso.datetime(),
|
|
204
|
+
requiredCredits: z.number().nonnegative().optional(),
|
|
205
|
+
availableCredits: z.number().optional(),
|
|
206
|
+
shortfallCredits: z.number().nonnegative().optional(),
|
|
207
|
+
})
|
|
208
|
+
.strict(),
|
|
197
209
|
z.object({ code: z.literal("GEO_BUSY"), retryAfterSeconds: z.number().positive(), costs: GeoCostsSchema.optional() }).strict(),
|
|
198
210
|
z
|
|
199
211
|
.object({
|
package/src/contracts/index.ts
CHANGED
|
@@ -2,6 +2,10 @@ export * from "./account-categories";
|
|
|
2
2
|
export * from "./announcements";
|
|
3
3
|
export * from "./app";
|
|
4
4
|
export * from "./app-approval";
|
|
5
|
+
export * from "./app-details";
|
|
6
|
+
export * from "./avatar";
|
|
7
|
+
export * from "./calendar-source";
|
|
8
|
+
export * from "./calendar-view";
|
|
5
9
|
export * from "./capabilities";
|
|
6
10
|
export * from "./capability-compatibility";
|
|
7
11
|
export * from "./commands";
|
|
@@ -12,6 +16,7 @@ export * from "./geo-admin";
|
|
|
12
16
|
export * from "./geo-map";
|
|
13
17
|
export * from "./geo-route";
|
|
14
18
|
export * from "./geoip";
|
|
19
|
+
export * from "./installable";
|
|
15
20
|
export * from "./notification-types";
|
|
16
21
|
export * from "./outgoing-mail";
|
|
17
22
|
export * from "./panel-context";
|
|
@@ -23,6 +28,7 @@ export { RailAccessSchema, type RailAdminEntry, RailAdminInputSchema, RailAdminS
|
|
|
23
28
|
export type { RailShortcut } from "./rail-preferences";
|
|
24
29
|
export * from "./registry";
|
|
25
30
|
export * from "./resource-clipboard";
|
|
31
|
+
export { type ResolvedCloudResourceUrl, ResolvedCloudResourceUrlSchema } from "./resource-resolution";
|
|
26
32
|
export * from "./seen-hints";
|
|
27
33
|
export * from "./shared";
|
|
28
34
|
export * from "./user-notifications";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/** One independently installable surface, separate from the Cloud mobile app's `pwa` parts. */
|
|
4
|
+
export const AppInstallableSchema = z.strictObject({
|
|
5
|
+
path: z.string(),
|
|
6
|
+
name: z.string().trim().min(1),
|
|
7
|
+
shortName: z.string().trim().min(1),
|
|
8
|
+
icon: z.string().regex(/^ti ti-[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
9
|
+
themeColor: z.strictObject({
|
|
10
|
+
light: z.string().regex(/^#[0-9a-f]{6}$/i),
|
|
11
|
+
dark: z.string().regex(/^#[0-9a-f]{6}$/i),
|
|
12
|
+
}),
|
|
13
|
+
display: z.literal("standalone"),
|
|
14
|
+
});
|
|
15
|
+
export type AppInstallable = z.infer<typeof AppInstallableSchema>;
|
|
16
|
+
|
|
17
|
+
/** No decoding or URL normalization: declarations must already be canonical. */
|
|
18
|
+
export const isInstallablePath = (appId: string, path: string): boolean =>
|
|
19
|
+
/^[a-z][a-z0-9-]*$/.test(appId) &&
|
|
20
|
+
path.startsWith(`/app/${appId}/`) &&
|
|
21
|
+
/^\/app\/[a-z][a-z0-9-]*\/(?:[a-z0-9][a-z0-9-]*\/)+$/.test(path) &&
|
|
22
|
+
!path.split("/").slice(3).includes("help");
|
|
23
|
+
|
|
24
|
+
export const installableContains = (scope: string, path: string): boolean => path === scope.slice(0, -1) || path.startsWith(scope);
|
|
25
|
+
|
|
26
|
+
/** Root and /app are routing fallbacks, not evidence that an app owns a surface. */
|
|
27
|
+
export const validateInstallable = (appId: string, routes: readonly string[], value: unknown): AppInstallable | null => {
|
|
28
|
+
const parsed = AppInstallableSchema.safeParse(value);
|
|
29
|
+
if (!parsed.success || !isInstallablePath(appId, parsed.data.path)) return null;
|
|
30
|
+
const ownRoot = `/app/${appId}`;
|
|
31
|
+
const covered = routes.some((route) => {
|
|
32
|
+
const prefix = route.replace(/\/$/, "");
|
|
33
|
+
return (
|
|
34
|
+
/^\/app\/[a-z][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/.test(prefix) &&
|
|
35
|
+
(prefix === ownRoot || prefix.startsWith(`${ownRoot}/`)) &&
|
|
36
|
+
installableContains(`${prefix}/`, parsed.data.path)
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
return covered ? parsed.data : null;
|
|
40
|
+
};
|
|
@@ -37,6 +37,8 @@ export type NotificationPresentation = {
|
|
|
37
37
|
group?: string;
|
|
38
38
|
/** Non-negative safe integer for the app badge; zero clears it. Browser-only. */
|
|
39
39
|
badge?: number;
|
|
40
|
+
/** `false` shows or replaces the browser notification without sound or vibration. Defaults to `true`. Browser-only. */
|
|
41
|
+
alert?: boolean;
|
|
40
42
|
/** Same-origin absolute path. Validated again before persistence and navigation. */
|
|
41
43
|
targetHref?: `/${string}`;
|
|
42
44
|
};
|
|
@@ -55,15 +57,18 @@ export type NotificationRenderContext = {
|
|
|
55
57
|
export type NotificationDeliveryPolicy = {
|
|
56
58
|
recommended?: readonly NotificationChannelId[];
|
|
57
59
|
required?: readonly NotificationChannelId[];
|
|
60
|
+
/** Channels this kind never prepares for delivery, even when a saved preference selects them. */
|
|
61
|
+
excluded?: readonly NotificationChannelId[];
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
export type NotificationDefinitionPresentationTranslation = {
|
|
61
65
|
label?: string;
|
|
62
66
|
description?: string;
|
|
67
|
+
excludedReason?: string;
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
export type NotificationDefinitionPresentationCatalog = {
|
|
66
|
-
/** Locale of the
|
|
71
|
+
/** Locale of the base label, description, and optional excluded reason. */
|
|
67
72
|
baseLocale: string;
|
|
68
73
|
/** Partial presentation overlays with exact -> ancestor -> base fallback. */
|
|
69
74
|
translations: Readonly<Record<string, NotificationDefinitionPresentationTranslation>>;
|
|
@@ -73,6 +78,8 @@ export type NotificationDefinitionInput<R extends NotificationRecipientKind, S e
|
|
|
73
78
|
recipient: R;
|
|
74
79
|
label: string;
|
|
75
80
|
description: string;
|
|
81
|
+
/** Explanation shown beside excluded channels; required when delivery.excluded is non-empty. */
|
|
82
|
+
excludedReason?: string;
|
|
76
83
|
presentation?: NotificationDefinitionPresentationCatalog;
|
|
77
84
|
data: S;
|
|
78
85
|
delivery?: NotificationDeliveryPolicy;
|
|
@@ -92,6 +99,7 @@ type AnyNotificationDefinition = Readonly<{
|
|
|
92
99
|
recipient: NotificationRecipientKind;
|
|
93
100
|
label: string;
|
|
94
101
|
description: string;
|
|
102
|
+
excludedReason?: string;
|
|
95
103
|
presentation?: NotificationDefinitionPresentationCatalog;
|
|
96
104
|
data: ZodType;
|
|
97
105
|
delivery?: NotificationDeliveryPolicy;
|
|
@@ -165,6 +173,9 @@ const compileDefinitionPresentation = (
|
|
|
165
173
|
...(translation.description !== undefined
|
|
166
174
|
? { description: requiredPresentationText(translation.description, `${canonical}.description`) }
|
|
167
175
|
: {}),
|
|
176
|
+
...(translation.excludedReason !== undefined
|
|
177
|
+
? { excludedReason: requiredPresentationText(translation.excludedReason, `${canonical}.excludedReason`) }
|
|
178
|
+
: {}),
|
|
168
179
|
});
|
|
169
180
|
}
|
|
170
181
|
return Object.freeze({ baseLocale, translations: Object.freeze(translations) });
|
|
@@ -174,20 +185,23 @@ export const resolveNotificationDefinitionPresentation = (
|
|
|
174
185
|
definition: {
|
|
175
186
|
label: string;
|
|
176
187
|
description: string;
|
|
188
|
+
excludedReason?: string;
|
|
177
189
|
presentation?: NotificationDefinitionPresentationCatalog | null;
|
|
178
190
|
},
|
|
179
191
|
requestedLocale?: string | null,
|
|
180
|
-
): { label: string; description: string } => {
|
|
192
|
+
): { label: string; description: string; excludedReason?: string } => {
|
|
181
193
|
const catalog = definition.presentation;
|
|
182
|
-
|
|
194
|
+
const base = {
|
|
195
|
+
label: definition.label,
|
|
196
|
+
description: definition.description,
|
|
197
|
+
...(definition.excludedReason !== undefined ? { excludedReason: definition.excludedReason } : {}),
|
|
198
|
+
};
|
|
199
|
+
if (!catalog || !requestedLocale) return base;
|
|
183
200
|
const translations = new Map(Object.entries(catalog.translations));
|
|
184
201
|
return localeFallbackChain(requestedLocale, catalog.baseLocale)
|
|
185
202
|
.filter((locale) => locale !== catalog.baseLocale)
|
|
186
203
|
.reverse()
|
|
187
|
-
.reduce((resolved, locale) => ({ ...resolved, ...translations.get(locale) }),
|
|
188
|
-
label: definition.label,
|
|
189
|
-
description: definition.description,
|
|
190
|
-
});
|
|
204
|
+
.reduce((resolved, locale) => ({ ...resolved, ...translations.get(locale) }), base);
|
|
191
205
|
};
|
|
192
206
|
|
|
193
207
|
export const notification = <const R extends NotificationRecipientKind, const S extends ZodType>(
|
|
@@ -200,13 +214,20 @@ export const notification = <const R extends NotificationRecipientKind, const S
|
|
|
200
214
|
|
|
201
215
|
const recommended = [...(input.delivery?.recommended ?? [])];
|
|
202
216
|
const required = [...(input.delivery?.required ?? [])];
|
|
203
|
-
|
|
217
|
+
const excluded = [...(input.delivery?.excluded ?? [])];
|
|
218
|
+
const excludedReason = input.excludedReason === undefined ? undefined : requiredPresentationText(input.excludedReason, "excludedReason");
|
|
219
|
+
if (excluded.length > 0 && !excludedReason) throw new Error(`Notification "${label}" excludedReason is required for excluded channels`);
|
|
220
|
+
if (hasDuplicates(recommended) || hasDuplicates(required) || hasDuplicates(excluded)) {
|
|
204
221
|
throw new Error(`Notification "${label}" contains duplicate delivery channels`);
|
|
205
222
|
}
|
|
206
223
|
const overlappingChannel = recommended.find((channel) => required.includes(channel));
|
|
207
224
|
if (overlappingChannel) {
|
|
208
225
|
throw new Error(`Notification "${label}" cannot recommend and require the "${overlappingChannel}" channel`);
|
|
209
226
|
}
|
|
227
|
+
const excludedOverlap = excluded.find((channel) => recommended.includes(channel) || required.includes(channel));
|
|
228
|
+
if (excludedOverlap) {
|
|
229
|
+
throw new Error(`Notification "${label}" cannot exclude the recommended or required "${excludedOverlap}" channel`);
|
|
230
|
+
}
|
|
210
231
|
if (input.recipient === "email" && !required.includes("email")) {
|
|
211
232
|
throw new Error(`Email-recipient notification "${label}" must require the email channel`);
|
|
212
233
|
}
|
|
@@ -216,8 +237,14 @@ export const notification = <const R extends NotificationRecipientKind, const S
|
|
|
216
237
|
...input,
|
|
217
238
|
label,
|
|
218
239
|
description,
|
|
240
|
+
...(excludedReason !== undefined ? { excludedReason } : {}),
|
|
219
241
|
...(presentation ? { presentation } : {}),
|
|
220
|
-
delivery: Object.freeze({
|
|
242
|
+
delivery: Object.freeze({
|
|
243
|
+
recommended: Object.freeze(recommended),
|
|
244
|
+
required: Object.freeze(required),
|
|
245
|
+
// Only kinds that exclude a channel carry the field, so every other definition keeps its shape.
|
|
246
|
+
...(excluded.length > 0 ? { excluded: Object.freeze(excluded) } : {}),
|
|
247
|
+
}),
|
|
221
248
|
[notificationDefinitionMarker]: true,
|
|
222
249
|
};
|
|
223
250
|
return Object.freeze(definition);
|
package/src/contracts/profile.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { AVATAR_KINDS, PersonAvatarSchema, RobotAvatarSchema } from "./avatar";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Schemas for account profile / password / request inputs. Live in core
|
|
@@ -55,6 +56,37 @@ export const UpdateAvatarResponseSchema = z.object({
|
|
|
55
56
|
});
|
|
56
57
|
export type UpdateAvatarResponse = z.infer<typeof UpdateAvatarResponseSchema>;
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* One save of the profile picture dialog: store and show a photo or a built avatar, or switch to a stored picture.
|
|
61
|
+
* The photo stays stored while another kind shows; only the active picture is served.
|
|
62
|
+
*/
|
|
63
|
+
export const SaveAvatarSchema = z.union([
|
|
64
|
+
z.object({ dataUrl: AvatarDataUrlSchema }).strict(),
|
|
65
|
+
z.object({ built: PersonAvatarSchema }).strict(),
|
|
66
|
+
z.object({ use: z.enum(AVATAR_KINDS) }).strict(),
|
|
67
|
+
]);
|
|
68
|
+
export type SaveAvatar = z.infer<typeof SaveAvatarSchema>;
|
|
69
|
+
|
|
70
|
+
/** A service account always shows a picture, so it switches only between its photo and its robot. */
|
|
71
|
+
export const SaveServiceAccountAvatarSchema = z.union([
|
|
72
|
+
z.object({ dataUrl: AvatarDataUrlSchema }).strict(),
|
|
73
|
+
z.object({ built: RobotAvatarSchema }).strict(),
|
|
74
|
+
z.object({ use: z.enum(["photo", "built"]) }).strict(),
|
|
75
|
+
]);
|
|
76
|
+
export type SaveServiceAccountAvatar = z.infer<typeof SaveServiceAccountAvatarSchema>;
|
|
77
|
+
|
|
78
|
+
export const AvatarStateSchema = z.object({
|
|
79
|
+
kind: z.enum(AVATAR_KINDS),
|
|
80
|
+
built: z.union([PersonAvatarSchema, RobotAvatarSchema]).nullable(),
|
|
81
|
+
hasPhoto: z.boolean(),
|
|
82
|
+
/** The stored photo as a data URL, also while another kind shows; only the account holder or an administrator reads it. */
|
|
83
|
+
photo: AvatarDataUrlSchema.nullable(),
|
|
84
|
+
avatarHash: z.string().nullable(),
|
|
85
|
+
});
|
|
86
|
+
export type AvatarStateResponse = z.infer<typeof AvatarStateSchema>;
|
|
87
|
+
|
|
88
|
+
export const SaveAvatarResponseSchema = AvatarStateSchema.extend({ message: z.string() });
|
|
89
|
+
|
|
58
90
|
export const ChangePasswordSchema = z
|
|
59
91
|
.object({
|
|
60
92
|
currentPassword: z.string().min(1),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AppRegistryEntry } from "./registry";
|
|
2
|
+
|
|
3
|
+
/** Unnamed development builds do not identify a release. */
|
|
4
|
+
export const pwaBuild = (runtime: { release: string; version?: string } | undefined): string | undefined =>
|
|
5
|
+
runtime && !["", "development", "local"].includes(runtime.release) ? `${runtime.version ?? ""}:${runtime.release}` : undefined;
|
|
6
|
+
|
|
7
|
+
/** Only page owners and Core's shared assets can change a mobile document. */
|
|
8
|
+
export const pwaBuilds = (apps: readonly AppRegistryEntry[]): Record<string, string> =>
|
|
9
|
+
Object.fromEntries(
|
|
10
|
+
apps.flatMap((app) => {
|
|
11
|
+
const build = pwaBuild(app.runtime);
|
|
12
|
+
return build && (app.id === "core" || app.id === "pwa" || app.pwa) ? [[app.id, build]] : [];
|
|
13
|
+
}),
|
|
14
|
+
);
|