@k2b/cloud 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +67 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +6 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +116 -35
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/app-approval.ts +11 -10
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +131 -72
- package/src/services/pdf/index.ts +11 -8
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +11 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +4 -2
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
type CapabilityActionManifest,
|
|
11
11
|
type CapabilityActionReviewResult,
|
|
12
12
|
CapabilityActionReviewSchema,
|
|
13
|
+
type CapabilityCommandManifest,
|
|
14
|
+
CapabilityCommandManifestSchema,
|
|
13
15
|
type CapabilityDefinitions,
|
|
14
16
|
type CapabilityError,
|
|
15
17
|
CapabilityErrorSchema,
|
|
@@ -190,12 +192,14 @@ const compileSchemaPresentation = (
|
|
|
190
192
|
|
|
191
193
|
const compileOperationPresentation = (
|
|
192
194
|
value: unknown,
|
|
193
|
-
operation: CapabilityQueryManifest | CapabilityActionManifest,
|
|
195
|
+
operation: CapabilityQueryManifest | CapabilityActionManifest | CapabilityCommandManifest,
|
|
194
196
|
label: string,
|
|
195
197
|
): CapabilityOperationPresentationTranslation => {
|
|
196
198
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
197
199
|
const translation = value as Record<string, unknown>;
|
|
198
|
-
const allowed = new Set(
|
|
200
|
+
const allowed = new Set(
|
|
201
|
+
"path" in operation ? ["title", "description", "input"] : ["title", "description", "input", "data", "searchTags"],
|
|
202
|
+
);
|
|
199
203
|
const extra = Object.keys(translation).find((key) => !allowed.has(key));
|
|
200
204
|
if (extra) throw new Error(`${label} contains unsupported field "${extra}"`);
|
|
201
205
|
if (translation.title !== undefined) {
|
|
@@ -238,7 +242,7 @@ const compileOperationPresentation = (
|
|
|
238
242
|
);
|
|
239
243
|
}
|
|
240
244
|
const input = compileSchemaPresentation(translation.input, operation.inputSchema, `${label}.input`);
|
|
241
|
-
const data = compileSchemaPresentation(translation.data, operation.dataSchema, `${label}.data`);
|
|
245
|
+
const data = "dataSchema" in operation ? compileSchemaPresentation(translation.data, operation.dataSchema, `${label}.data`) : undefined;
|
|
242
246
|
return {
|
|
243
247
|
...(typeof translation.title === "string" ? { title: translation.title.trim() } : {}),
|
|
244
248
|
...(typeof translation.description === "string" ? { description: translation.description.trim() } : {}),
|
|
@@ -263,6 +267,7 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
|
|
|
263
267
|
const types = new Map(manifest.types.map((type) => [type.localId, type]));
|
|
264
268
|
const queries = new Map(manifest.queries.map((operation) => [operation.localId, operation]));
|
|
265
269
|
const actions = new Map(manifest.actions.map((operation) => [operation.localId, operation]));
|
|
270
|
+
const commands = new Map(manifest.commands.map((operation) => [operation.localId, operation]));
|
|
266
271
|
const translations: Record<string, CapabilityPresentationTranslation> = {};
|
|
267
272
|
for (const [locale, rawTranslation] of Object.entries(catalog.translations as Record<string, unknown>)) {
|
|
268
273
|
const canonical = canonicalLocale(locale);
|
|
@@ -273,7 +278,7 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
|
|
|
273
278
|
throw new Error(`Capability presentation translation ${canonical} must be an object`);
|
|
274
279
|
}
|
|
275
280
|
const raw = rawTranslation as Record<string, unknown>;
|
|
276
|
-
const extra = Object.keys(raw).find((key) => key !== "types" && key !== "queries" && key !== "actions");
|
|
281
|
+
const extra = Object.keys(raw).find((key) => key !== "types" && key !== "queries" && key !== "actions" && key !== "commands");
|
|
277
282
|
if (extra) throw new Error(`Capability presentation translation ${canonical} contains unsupported field "${extra}"`);
|
|
278
283
|
const compileGroup = <T>(
|
|
279
284
|
group: unknown,
|
|
@@ -311,10 +316,12 @@ export const compileCapabilityPresentation = (manifest: CapabilityManifest, valu
|
|
|
311
316
|
});
|
|
312
317
|
const translatedQueries = compileGroup(raw.queries, queries, "Query", compileOperationPresentation);
|
|
313
318
|
const translatedActions = compileGroup(raw.actions, actions, "Action", compileOperationPresentation);
|
|
319
|
+
const translatedCommands = compileGroup(raw.commands, commands, "Command", compileOperationPresentation);
|
|
314
320
|
translations[canonical] = {
|
|
315
321
|
...(translatedTypes ? { types: translatedTypes } : {}),
|
|
316
322
|
...(translatedQueries ? { queries: translatedQueries } : {}),
|
|
317
323
|
...(translatedActions ? { actions: translatedActions } : {}),
|
|
324
|
+
...(translatedCommands ? { commands: translatedCommands } : {}),
|
|
318
325
|
};
|
|
319
326
|
}
|
|
320
327
|
return { baseLocale, translations };
|
|
@@ -406,7 +413,7 @@ const normalizeSearchTags = (definition: CapabilityQueryDefinition, label: strin
|
|
|
406
413
|
...(aliases.length > 0 ? { aliases } : {}),
|
|
407
414
|
};
|
|
408
415
|
});
|
|
409
|
-
return { tags };
|
|
416
|
+
return { tags, ...(definition.universalSearch.scopeTypes ? { scopeTypes: [...new Set(definition.universalSearch.scopeTypes)] } : {}) };
|
|
410
417
|
};
|
|
411
418
|
|
|
412
419
|
const compileOperationSchemas = (definition: CapabilityQueryDefinition | CapabilityActionDefinition, label: string) => {
|
|
@@ -426,7 +433,11 @@ const compileOperationSchemas = (definition: CapabilityQueryDefinition | Capabil
|
|
|
426
433
|
inputSchema,
|
|
427
434
|
dataSchema,
|
|
428
435
|
resultZodSchema,
|
|
429
|
-
schemaHash: capabilityHash({
|
|
436
|
+
schemaHash: capabilityHash({
|
|
437
|
+
inputSchema,
|
|
438
|
+
dataSchema,
|
|
439
|
+
...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
|
|
440
|
+
}),
|
|
430
441
|
};
|
|
431
442
|
};
|
|
432
443
|
|
|
@@ -462,6 +473,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
462
473
|
for (const [localId, definition] of Object.entries(definitions.queries ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
463
474
|
registerLocalId(localId, "Query");
|
|
464
475
|
const label = `Query ${localId}`;
|
|
476
|
+
if (
|
|
477
|
+
definition.stream &&
|
|
478
|
+
((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
|
|
479
|
+
)
|
|
480
|
+
throw new Error(`${label}: write streams require an Action and a positive byte budget`);
|
|
481
|
+
if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
|
|
482
|
+
throw new Error(`${label}: write streams require idempotency`);
|
|
465
483
|
const schemas = compileOperationSchemas(definition, label);
|
|
466
484
|
if (definition.universalSearch) {
|
|
467
485
|
const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
|
|
@@ -480,9 +498,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
480
498
|
inputSchema: schemas.inputSchema,
|
|
481
499
|
dataSchema: schemas.dataSchema,
|
|
482
500
|
schemaHash: schemas.schemaHash,
|
|
501
|
+
...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
|
|
483
502
|
openWorld: definition.openWorld,
|
|
484
503
|
universalSearch: normalizeSearchTags(definition, label),
|
|
485
504
|
} satisfies CapabilityQueryManifest;
|
|
505
|
+
for (const type of manifest.universalSearch?.scopeTypes ?? []) {
|
|
506
|
+
if (!typeIds.has(qualifiedId(appId, type))) throw new Error(`${label} declares an unknown search scope type ${type}`);
|
|
507
|
+
}
|
|
486
508
|
queries.set(localId, {
|
|
487
509
|
definition,
|
|
488
510
|
manifest,
|
|
@@ -499,12 +521,41 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
499
521
|
for (const [localId, definition] of Object.entries(definitions.actions ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
500
522
|
registerLocalId(localId, "Action");
|
|
501
523
|
const label = `Action ${localId}`;
|
|
524
|
+
// Declaration rule, documented in docs/platform/capabilities.md:
|
|
525
|
+
// destructive means irreversible or externally visible. An irreversible or
|
|
526
|
+
// open-world effect must always be reviewable and safely repeatable, and
|
|
527
|
+
// must never be approved once and then replayed silently.
|
|
528
|
+
if (definition.destructive && !definition.review) {
|
|
529
|
+
throw new Error(`${label} is destructive and must declare review()`);
|
|
530
|
+
}
|
|
531
|
+
if (definition.destructive && definition.idempotency !== "required") {
|
|
532
|
+
throw new Error(`${label} is destructive and must declare idempotency: "required"`);
|
|
533
|
+
}
|
|
534
|
+
if (definition.openWorld && !definition.review) {
|
|
535
|
+
throw new Error(`${label} is open-world and must declare review()`);
|
|
536
|
+
}
|
|
537
|
+
if (definition.openWorld && definition.idempotency !== "required") {
|
|
538
|
+
throw new Error(`${label} is open-world and must declare idempotency: "required"`);
|
|
539
|
+
}
|
|
502
540
|
if (definition.approval === "rememberable" && definition.openWorld) {
|
|
503
541
|
throw new Error(`${label} cannot remember approval for an open-world effect`);
|
|
504
542
|
}
|
|
543
|
+
if (definition.approval === "rememberable" && definition.destructive) {
|
|
544
|
+
throw new Error(`${label} cannot remember approval for a destructive effect`);
|
|
545
|
+
}
|
|
546
|
+
if (definition.approval === "none" && (definition.destructive || definition.openWorld)) {
|
|
547
|
+
throw new Error(`${label} cannot skip approval for destructive or open-world effects`);
|
|
548
|
+
}
|
|
505
549
|
if (definition.approval === "rememberable" && !definition.review) {
|
|
506
550
|
throw new Error(`${label} must provide a review before approval can be remembered`);
|
|
507
551
|
}
|
|
552
|
+
if (
|
|
553
|
+
definition.stream &&
|
|
554
|
+
((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
|
|
555
|
+
)
|
|
556
|
+
throw new Error(`${label}: write streams require an Action and a positive byte budget`);
|
|
557
|
+
if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
|
|
558
|
+
throw new Error(`${label}: write streams require idempotency`);
|
|
508
559
|
const schemas = compileOperationSchemas(definition, label);
|
|
509
560
|
const manifest = {
|
|
510
561
|
localId,
|
|
@@ -513,6 +564,7 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
513
564
|
inputSchema: schemas.inputSchema,
|
|
514
565
|
dataSchema: schemas.dataSchema,
|
|
515
566
|
schemaHash: schemas.schemaHash,
|
|
567
|
+
...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
|
|
516
568
|
destructive: definition.destructive,
|
|
517
569
|
openWorld: definition.openWorld,
|
|
518
570
|
idempotency: definition.idempotency,
|
|
@@ -526,12 +578,30 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
526
578
|
});
|
|
527
579
|
}
|
|
528
580
|
|
|
581
|
+
const commands = Object.entries(definitions.commands ?? {})
|
|
582
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
583
|
+
.map(([localId, definition]) => {
|
|
584
|
+
registerLocalId(localId, "Command");
|
|
585
|
+
const inputSchema = projectSchema(definition.input, `Command ${localId} input`, "input");
|
|
586
|
+
assertClosedObjectInput(inputSchema, `Command ${localId} input`);
|
|
587
|
+
return CapabilityCommandManifestSchema.parse({
|
|
588
|
+
localId,
|
|
589
|
+
title: definition.title.trim(),
|
|
590
|
+
description: definition.description.trim(),
|
|
591
|
+
icon: definition.icon,
|
|
592
|
+
keywords: [...(definition.keywords ?? [])],
|
|
593
|
+
inputSchema,
|
|
594
|
+
path: definition.path,
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
529
598
|
const manifestBase = {
|
|
530
599
|
protocolVersion: CAPABILITY_PROTOCOL_VERSION,
|
|
531
600
|
appId,
|
|
532
601
|
types,
|
|
533
602
|
queries: [...queries.values()].map((entry) => entry.manifest),
|
|
534
603
|
actions: [...actions.values()].map((entry) => entry.manifest),
|
|
604
|
+
commands,
|
|
535
605
|
};
|
|
536
606
|
const manifest = CapabilityManifestSchema.parse({
|
|
537
607
|
...manifestBase,
|
|
@@ -614,6 +684,7 @@ export const resolveCapabilityManifestPresentation = (
|
|
|
614
684
|
...(operation.universalSearch
|
|
615
685
|
? {
|
|
616
686
|
universalSearch: {
|
|
687
|
+
...operation.universalSearch,
|
|
617
688
|
tags: operation.universalSearch.tags.map((tag) => {
|
|
618
689
|
const tagCopy = copy.searchTags?.[tag.tag];
|
|
619
690
|
return tagCopy
|
|
@@ -625,6 +696,17 @@ export const resolveCapabilityManifestPresentation = (
|
|
|
625
696
|
: {}),
|
|
626
697
|
};
|
|
627
698
|
}),
|
|
699
|
+
commands: current.commands.map((operation) => {
|
|
700
|
+
const copy = translation.commands?.[operation.localId];
|
|
701
|
+
return copy
|
|
702
|
+
? {
|
|
703
|
+
...operation,
|
|
704
|
+
title: copy.title ?? operation.title,
|
|
705
|
+
description: copy.description ?? operation.description,
|
|
706
|
+
inputSchema: applySchemaPresentation(operation.inputSchema, copy.input),
|
|
707
|
+
}
|
|
708
|
+
: operation;
|
|
709
|
+
}),
|
|
628
710
|
actions: current.actions.map((operation) => {
|
|
629
711
|
const copy = translation.actions?.[operation.localId];
|
|
630
712
|
return copy
|
|
@@ -668,10 +750,20 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
|
|
|
668
750
|
);
|
|
669
751
|
}
|
|
670
752
|
assertClosedObjectInput(inputSchema, `Operation ${operation.localId} input`);
|
|
671
|
-
|
|
753
|
+
if (operation.stream?.direction === "write" && (!("idempotency" in operation) || operation.idempotency !== "required")) {
|
|
754
|
+
throw new Error(`Operation ${operation.localId} write streams require an idempotent Action`);
|
|
755
|
+
}
|
|
756
|
+
const expectedSchemaHash = capabilityHash({
|
|
757
|
+
inputSchema: operation.inputSchema,
|
|
758
|
+
dataSchema: operation.dataSchema,
|
|
759
|
+
...(operation.stream ? { stream: operation.stream } : {}),
|
|
760
|
+
});
|
|
672
761
|
if (operation.schemaHash !== expectedSchemaHash)
|
|
673
762
|
throw new Error(`Operation ${operation.localId} schemaHash does not match its schemas`);
|
|
674
763
|
if ("universalSearch" in operation && operation.universalSearch) {
|
|
764
|
+
for (const type of operation.universalSearch.scopeTypes ?? []) {
|
|
765
|
+
if (!manifest.types.some((entry) => entry.localId === type)) throw new Error(`Unknown search scope type ${type}`);
|
|
766
|
+
}
|
|
675
767
|
const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
|
|
676
768
|
const expectedData = projectSchema(UniversalSearchDataSchema, "Universal Search data", "output");
|
|
677
769
|
if (
|
|
@@ -682,6 +774,11 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
|
|
|
682
774
|
}
|
|
683
775
|
}
|
|
684
776
|
}
|
|
777
|
+
for (const command of manifest.commands) {
|
|
778
|
+
registerLocalId(command.localId, "Command");
|
|
779
|
+
z.fromJSONSchema(structuredClone(command.inputSchema));
|
|
780
|
+
assertClosedObjectInput(command.inputSchema, `Command ${command.localId} input`);
|
|
781
|
+
}
|
|
685
782
|
const queries = new Map(manifest.queries.map((query) => [query.localId, query]));
|
|
686
783
|
for (const type of manifest.types) {
|
|
687
784
|
if (!type.reader) continue;
|
|
@@ -787,6 +884,12 @@ export const capabilityManifestEvolutionIssues = (previous: CapabilityManifest,
|
|
|
787
884
|
else if (type.reader && current.reader !== type.reader) issues.push(`Type ${type.localId} reader changed`);
|
|
788
885
|
}
|
|
789
886
|
|
|
887
|
+
const nextCommands = new Map(next.commands.map((command) => [command.localId, command]));
|
|
888
|
+
for (const command of previous.commands) {
|
|
889
|
+
const current = nextCommands.get(command.localId);
|
|
890
|
+
if (!current) issues.push(`Command ${command.localId} was removed`);
|
|
891
|
+
else issues.push(...schemaEvolutionIssues(command.inputSchema, current.inputSchema, `Command ${command.localId} input`, "input"));
|
|
892
|
+
}
|
|
790
893
|
const nextQueries = new Map(next.queries.map((operation) => [operation.localId, operation]));
|
|
791
894
|
const nextActions = new Map(next.actions.map((operation) => [operation.localId, operation]));
|
|
792
895
|
for (const operation of previous.queries) {
|
|
@@ -982,11 +1085,32 @@ export const invokeCompiledCapability = async (params: {
|
|
|
982
1085
|
},
|
|
983
1086
|
};
|
|
984
1087
|
}
|
|
1088
|
+
if ("universalSearch" in operation.manifest && operation.manifest.universalSearch) {
|
|
1089
|
+
const search = UniversalSearchInputSchema.parse(input.data);
|
|
1090
|
+
if (
|
|
1091
|
+
search.scope &&
|
|
1092
|
+
!(operation.manifest.universalSearch.scopeTypes ?? []).some(
|
|
1093
|
+
(type) => qualifiedId(params.compiled.manifest.appId, type) === search.scope!.type,
|
|
1094
|
+
)
|
|
1095
|
+
)
|
|
1096
|
+
return { ok: false, error: { code: "VALIDATION_FAILED", message: "Unsupported search scope", status: 400 } };
|
|
1097
|
+
}
|
|
985
1098
|
try {
|
|
986
1099
|
const invoked = await operation.definition.run(input.data, context);
|
|
987
1100
|
if (!invoked.ok) {
|
|
988
1101
|
return normalizeProviderError(invoked.error, "Capability returned an invalid error", params.onUnexpectedError);
|
|
989
1102
|
}
|
|
1103
|
+
const stream = invoked.data.stream;
|
|
1104
|
+
if (
|
|
1105
|
+
stream &&
|
|
1106
|
+
(Buffer.byteLength(stream.id) > 2048 ||
|
|
1107
|
+
!operation.definition.stream ||
|
|
1108
|
+
stream.direction !== operation.definition.stream.direction ||
|
|
1109
|
+
stream.size > operation.definition.stream.maxBytes ||
|
|
1110
|
+
Date.parse(stream.expiresAt) <= Date.now() ||
|
|
1111
|
+
Date.parse(stream.expiresAt) > Date.now() + 86_400_000)
|
|
1112
|
+
)
|
|
1113
|
+
return invalidProviderError("Stream is outside its declared policy or 24 hour lifetime");
|
|
990
1114
|
const validated = validateCapabilityResult(params.compiled, operation, invoked.data, params.onUnexpectedError);
|
|
991
1115
|
return validated.ok
|
|
992
1116
|
? ({
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isServiceError } from "@k2b/stdlib";
|
|
2
|
+
import { exactStream } from "../capabilities/stream-body";
|
|
3
|
+
import type { CapabilityExecutionContext } from "../contracts/capabilities";
|
|
4
|
+
import {
|
|
5
|
+
CAPABILITY_MAX_RESULT_BYTES,
|
|
6
|
+
CapabilityStreamSchema,
|
|
7
|
+
CapabilityStreamStatusSchema,
|
|
8
|
+
capabilityResultSchema,
|
|
9
|
+
} from "../contracts/capabilities";
|
|
10
|
+
import type { CompiledCapabilities } from "./capabilities";
|
|
11
|
+
|
|
12
|
+
const failure = (status: number, code: string, message: string) => Response.json({ code, message }, { status });
|
|
13
|
+
export async function invokeCapabilityStream(params: {
|
|
14
|
+
compiled: CompiledCapabilities;
|
|
15
|
+
kind: "queries" | "actions";
|
|
16
|
+
localId: string;
|
|
17
|
+
verb: string;
|
|
18
|
+
request: Request;
|
|
19
|
+
context: CapabilityExecutionContext;
|
|
20
|
+
}): Promise<Response> {
|
|
21
|
+
const operation = params.compiled[params.kind].get(params.localId);
|
|
22
|
+
const definition = operation?.definition.stream;
|
|
23
|
+
if (!operation || !definition) return failure(404, "STREAM_NOT_FOUND", "Stream operation not found");
|
|
24
|
+
const offer = params.request.headers.get("x-cloud-stream-offer");
|
|
25
|
+
if (!offer || offer.length > 24000) return failure(400, "STREAM_INVALID", "Invalid stream offer");
|
|
26
|
+
const receipt = (value: unknown) => {
|
|
27
|
+
const parsed = capabilityResultSchema(operation.definition.data).parse(value);
|
|
28
|
+
if (parsed.stream) throw new Error("Stream receipts cannot issue another stream");
|
|
29
|
+
return parsed;
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
const stream = CapabilityStreamSchema.parse(JSON.parse(Buffer.from(offer, "base64url").toString()));
|
|
33
|
+
if (stream.direction !== definition.direction || stream.size > definition.maxBytes || Date.parse(stream.expiresAt) <= Date.now())
|
|
34
|
+
return failure(410, "STREAM_EXPIRED", "Stream expired or outside its byte budget");
|
|
35
|
+
if (definition.direction === "read") {
|
|
36
|
+
if (params.verb !== "read") return failure(403, "STREAM_DENIED", "Read-only stream");
|
|
37
|
+
const response = await definition.read(stream, params.context);
|
|
38
|
+
if (!response.ok || !response.body) {
|
|
39
|
+
await response.body?.cancel();
|
|
40
|
+
return failure(502, "STREAM_FAILED", "Source could not be read");
|
|
41
|
+
}
|
|
42
|
+
return new Response(exactStream(response.body, stream.size), {
|
|
43
|
+
headers: { "content-type": stream.mediaType, "cache-control": "no-store" },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
let result: unknown;
|
|
47
|
+
if (params.verb === "write") {
|
|
48
|
+
const body = exactStream(
|
|
49
|
+
params.request.body ??
|
|
50
|
+
new ReadableStream({
|
|
51
|
+
start(c) {
|
|
52
|
+
c.close();
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
stream.size,
|
|
56
|
+
);
|
|
57
|
+
try {
|
|
58
|
+
const value = await definition.write(stream, body, params.context);
|
|
59
|
+
result = receipt(value);
|
|
60
|
+
} finally {
|
|
61
|
+
if (!body.locked) await body.cancel().catch(() => undefined);
|
|
62
|
+
}
|
|
63
|
+
} else if (params.verb === "status") {
|
|
64
|
+
const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
|
|
65
|
+
if (status.state === "completed") receipt(status.result);
|
|
66
|
+
result = status;
|
|
67
|
+
} else if (params.verb === "abort") {
|
|
68
|
+
await definition.abort(stream, params.context);
|
|
69
|
+
const status = CapabilityStreamStatusSchema.parse(await definition.status(stream, params.context));
|
|
70
|
+
if (status.state === "completed") receipt(status.result);
|
|
71
|
+
result = status;
|
|
72
|
+
} else return failure(400, "STREAM_INVALID", "Unknown stream operation");
|
|
73
|
+
const json = JSON.stringify(result);
|
|
74
|
+
if (Buffer.byteLength(json) > CAPABILITY_MAX_RESULT_BYTES)
|
|
75
|
+
return failure(502, "STREAM_FAILED", "Stream receipt is too large; inspect status before retrying");
|
|
76
|
+
return new Response(json, { headers: { "content-type": "application/json", "cache-control": "no-store" } });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (isServiceError(error)) return failure(error.status, error.code, error.message);
|
|
79
|
+
return failure(409, "STREAM_FAILED", "Transfer failed; inspect status before retrying a write");
|
|
80
|
+
}
|
|
81
|
+
}
|