@k2b/cloud 0.6.0-rc.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 +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Cross-app ambient module declarations. Every app's tsconfig includes this
|
|
2
|
+
// so apps can import non-TS assets (e.g. `import md from './x.md' with { type: "text" }`)
|
|
3
|
+
// without re-declaring the stub per-package.
|
|
4
|
+
|
|
5
|
+
declare module "*.md" {
|
|
6
|
+
const content: string;
|
|
7
|
+
export default content;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module "*.sh" {
|
|
11
|
+
const content: string;
|
|
12
|
+
export default content;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module "*service-worker.js" {
|
|
16
|
+
const content: string;
|
|
17
|
+
export default content;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare module "@babel/core" {
|
|
21
|
+
export const types: typeof import("@babel/types");
|
|
22
|
+
|
|
23
|
+
export function transformAsync(source: string, options: Record<string, unknown>): Promise<{ code?: string } | null>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
WORKFLOW_AI_DEPENDENCY_KIND,
|
|
3
|
+
createWorkflowAiTask,
|
|
4
|
+
getWorkflowAiTask,
|
|
5
|
+
getWorkflowAiTaskByEffectKey,
|
|
6
|
+
migrateWorkflowAi,
|
|
7
|
+
wakeWorkflowAiTask,
|
|
8
|
+
workflowAiTaskExists,
|
|
9
|
+
} from "./store";
|
|
10
|
+
export { startWorkflowAiRuntime, stopWorkflowAiRuntime, submitWorkflowAiTask, type WorkflowAiRuntimeOptions } from "./runtime";
|
|
11
|
+
export {
|
|
12
|
+
type WorkflowAiRequest,
|
|
13
|
+
type WorkflowAiRequestInput,
|
|
14
|
+
type WorkflowAiTask,
|
|
15
|
+
type WorkflowAiTaskStatus,
|
|
16
|
+
workflowAiRequestSchema,
|
|
17
|
+
} from "./types";
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { StructuredOutputError } from "@k2b/nessi";
|
|
2
|
+
import type { Job, Worker } from "@k2b/sync";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { lazySync } from "../../_internal/process-sync";
|
|
5
|
+
import { isAiSettingsError, type RunAiStructuredInput, runAiStructured } from "../../ai";
|
|
6
|
+
|
|
7
|
+
import type { WorkflowJsonValue } from "../contracts";
|
|
8
|
+
import {
|
|
9
|
+
claimWorkflowAiTask,
|
|
10
|
+
completeWorkflowAiTask,
|
|
11
|
+
failWorkflowAiTask,
|
|
12
|
+
getWorkflowAiTask,
|
|
13
|
+
listRecoverableWorkflowAiTaskIds,
|
|
14
|
+
markWorkflowAiTaskCanceledIfRequested,
|
|
15
|
+
requeueWorkflowAiTask,
|
|
16
|
+
wakeWorkflowAiTask,
|
|
17
|
+
workflowAiTaskCancellationRequested,
|
|
18
|
+
} from "./store";
|
|
19
|
+
import type { WorkflowAiRequest, WorkflowAiTask } from "./types";
|
|
20
|
+
|
|
21
|
+
const JOB_ID = "cloud.workflow-ai";
|
|
22
|
+
const MAX_ATTEMPTS = 3;
|
|
23
|
+
const DEFAULT_CANCEL_POLL_MS = 500;
|
|
24
|
+
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
25
|
+
|
|
26
|
+
type StructuredRunner = typeof runAiStructured;
|
|
27
|
+
|
|
28
|
+
export type WorkflowAiRuntimeOptions = {
|
|
29
|
+
runStructured?: StructuredRunner;
|
|
30
|
+
cancelPollMs?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Attempt budget shared by the job declaration and the Postgres-side settle logic. */
|
|
34
|
+
export type WorkflowAiProcessOptions = Required<WorkflowAiRuntimeOptions> & { maxAttempts: number };
|
|
35
|
+
|
|
36
|
+
const workflowAiJob = lazySync((sync) =>
|
|
37
|
+
sync.job<{ taskId: string }>({
|
|
38
|
+
id: JOB_ID,
|
|
39
|
+
owner: "cloud",
|
|
40
|
+
delivery: { ackWaitMs: 30_000, maxAttempts: MAX_ATTEMPTS, backoffMs: [1_000, 2_000] },
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
class WorkflowAiAttemptError extends Error {
|
|
45
|
+
constructor(
|
|
46
|
+
readonly code: string,
|
|
47
|
+
message: string,
|
|
48
|
+
readonly retryable: boolean,
|
|
49
|
+
) {
|
|
50
|
+
super(message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type WorkflowAiAttemptFailure = Pick<WorkflowAiAttemptError, "code" | "message" | "retryable">;
|
|
55
|
+
|
|
56
|
+
export const settleWorkflowAiAttemptFailure = async (
|
|
57
|
+
taskId: string,
|
|
58
|
+
error: WorkflowAiAttemptFailure,
|
|
59
|
+
failureCount: number,
|
|
60
|
+
maxAttempts: number,
|
|
61
|
+
): Promise<"canceled" | "retry" | "failed"> => {
|
|
62
|
+
if (await workflowAiTaskCancellationRequested(taskId)) {
|
|
63
|
+
await markWorkflowAiTaskCanceledIfRequested(taskId);
|
|
64
|
+
return "canceled";
|
|
65
|
+
}
|
|
66
|
+
if (error.retryable && failureCount + 1 < maxAttempts) {
|
|
67
|
+
await requeueWorkflowAiTask(taskId);
|
|
68
|
+
return "retry";
|
|
69
|
+
}
|
|
70
|
+
await failWorkflowAiTask(taskId, { code: error.code, message: error.message });
|
|
71
|
+
return "failed";
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const asJson = (value: unknown): WorkflowJsonValue => JSON.parse(JSON.stringify(value)) as WorkflowJsonValue;
|
|
75
|
+
const choiceEnum = (values: string[]) => z.enum(values as [string, ...string[]]);
|
|
76
|
+
const structuredOutputSchema = (fields: Extract<WorkflowAiRequest, { kind: "extract_data" }>["fields"]) => {
|
|
77
|
+
const shape: Record<string, z.ZodType> = {};
|
|
78
|
+
for (const field of fields) {
|
|
79
|
+
let schema: z.ZodType =
|
|
80
|
+
field.type === "text"
|
|
81
|
+
? z.string().max(field.maxLength ?? 20_000)
|
|
82
|
+
: field.type === "number"
|
|
83
|
+
? z.number()
|
|
84
|
+
: field.type === "boolean"
|
|
85
|
+
? z.boolean()
|
|
86
|
+
: field.type === "date_time"
|
|
87
|
+
? z.iso.datetime({ offset: true })
|
|
88
|
+
: choiceEnum(field.choices!);
|
|
89
|
+
if (!field.required) schema = schema.optional();
|
|
90
|
+
shape[field.name] = schema.describe(field.description);
|
|
91
|
+
}
|
|
92
|
+
return z.object(shape).strict();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const executeWorkflowAiRequest = async (task: WorkflowAiTask, runStructured: StructuredRunner, signal: AbortSignal) => {
|
|
96
|
+
const request = task.request;
|
|
97
|
+
const common = {
|
|
98
|
+
appId: task.appId,
|
|
99
|
+
attribution: { workflowRunId: task.runId, userId: task.usageUserId },
|
|
100
|
+
requestedModelId: task.modelProfileId,
|
|
101
|
+
signal,
|
|
102
|
+
temperature: 0,
|
|
103
|
+
} satisfies Partial<RunAiStructuredInput<z.ZodType>>;
|
|
104
|
+
|
|
105
|
+
if (request.kind === "generate_text") {
|
|
106
|
+
const result = await runStructured({
|
|
107
|
+
...common,
|
|
108
|
+
task: "workflow-generate-text",
|
|
109
|
+
systemPrompt: request.prompt,
|
|
110
|
+
input: request.input === undefined ? "Create the requested text." : JSON.stringify(request.input),
|
|
111
|
+
outputName: "generated_text",
|
|
112
|
+
output: z.object({ text: z.string().max(request.maxOutputChars) }),
|
|
113
|
+
maxOutputTokens: Math.min(8_192, Math.max(64, Math.ceil(request.maxOutputChars / 2))),
|
|
114
|
+
});
|
|
115
|
+
return { output: result.output.text as WorkflowJsonValue, usage: result.usage ? asJson(result.usage) : null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (request.kind === "classify") {
|
|
119
|
+
const result = await runStructured({
|
|
120
|
+
...common,
|
|
121
|
+
task: "workflow-classify",
|
|
122
|
+
systemPrompt: `${request.prompt}\nReturn exactly one of the declared choices.`,
|
|
123
|
+
input: JSON.stringify(request.input),
|
|
124
|
+
outputName: "classification",
|
|
125
|
+
output: z.object({ choice: choiceEnum(request.choices) }),
|
|
126
|
+
maxOutputTokens: 200,
|
|
127
|
+
});
|
|
128
|
+
return { output: result.output.choice as WorkflowJsonValue, usage: result.usage ? asJson(result.usage) : null };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (request.kind === "extract_data") {
|
|
132
|
+
const result = await runStructured({
|
|
133
|
+
...common,
|
|
134
|
+
task: "workflow-extract-data",
|
|
135
|
+
systemPrompt: `${request.prompt}\nReturn only the declared fields. Do not invent values that are not supported by the input.`,
|
|
136
|
+
input: JSON.stringify(request.input),
|
|
137
|
+
outputName: "structured_data",
|
|
138
|
+
output: structuredOutputSchema(request.fields),
|
|
139
|
+
maxOutputTokens: 4_096,
|
|
140
|
+
});
|
|
141
|
+
return { output: asJson(result.output), usage: result.usage ? asJson(result.usage) : null };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const maximum = request.maxChoices ?? request.choices.length;
|
|
145
|
+
const result = await runStructured({
|
|
146
|
+
...common,
|
|
147
|
+
task: "workflow-classify-many",
|
|
148
|
+
systemPrompt: `${request.prompt}\nReturn only unique values from the declared choices.`,
|
|
149
|
+
input: JSON.stringify(request.input),
|
|
150
|
+
outputName: "classifications",
|
|
151
|
+
output: z.object({ choices: z.array(choiceEnum(request.choices)).min(request.minChoices).max(maximum) }),
|
|
152
|
+
maxOutputTokens: 500,
|
|
153
|
+
});
|
|
154
|
+
const selected = new Set(result.output.choices);
|
|
155
|
+
return {
|
|
156
|
+
output: request.choices.filter((choice) => selected.has(choice)) as WorkflowJsonValue,
|
|
157
|
+
usage: result.usage ? asJson(result.usage) : null,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const retryableError = (error: unknown): boolean => {
|
|
162
|
+
if (isAiSettingsError(error)) return false;
|
|
163
|
+
if (error instanceof StructuredOutputError) return error.code === "loop_failed";
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const errorCode = (error: unknown): string => {
|
|
168
|
+
if (isAiSettingsError(error)) return error.aiError.code.toUpperCase();
|
|
169
|
+
if (error instanceof StructuredOutputError) return `WORKFLOW_AI_${error.code.toUpperCase()}`;
|
|
170
|
+
return "WORKFLOW_AI_PROVIDER_ERROR";
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const processWorkflowAiTask = async (
|
|
174
|
+
taskId: string,
|
|
175
|
+
ctx: { signal: AbortSignal; heartbeat(): Promise<void> },
|
|
176
|
+
options: WorkflowAiProcessOptions,
|
|
177
|
+
): Promise<void> => {
|
|
178
|
+
const task = await claimWorkflowAiTask(taskId);
|
|
179
|
+
if (!task) {
|
|
180
|
+
const terminal = await getWorkflowAiTask(taskId);
|
|
181
|
+
if (terminal && ["succeeded", "failed", "canceled"].includes(terminal.status)) await wakeWorkflowAiTask(terminal);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (task.status === "canceled") return;
|
|
185
|
+
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
let canceled = false;
|
|
188
|
+
let monitorError: unknown;
|
|
189
|
+
let checking = false;
|
|
190
|
+
let lastHeartbeat = Date.now();
|
|
191
|
+
const abortForShutdown = () => controller.abort(ctx.signal.reason ?? new Error("Workflow AI runtime stopped"));
|
|
192
|
+
ctx.signal.addEventListener("abort", abortForShutdown, { once: true });
|
|
193
|
+
|
|
194
|
+
const monitor = setInterval(() => {
|
|
195
|
+
if (checking || controller.signal.aborted) return;
|
|
196
|
+
checking = true;
|
|
197
|
+
void (async () => {
|
|
198
|
+
try {
|
|
199
|
+
if (await workflowAiTaskCancellationRequested(task.id)) {
|
|
200
|
+
canceled = true;
|
|
201
|
+
controller.abort(new Error("Workflow canceled"));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (Date.now() - lastHeartbeat >= HEARTBEAT_INTERVAL_MS) {
|
|
205
|
+
await ctx.heartbeat();
|
|
206
|
+
lastHeartbeat = Date.now();
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
monitorError = error;
|
|
210
|
+
controller.abort(error);
|
|
211
|
+
} finally {
|
|
212
|
+
checking = false;
|
|
213
|
+
}
|
|
214
|
+
})();
|
|
215
|
+
}, options.cancelPollMs);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const result = await executeWorkflowAiRequest(task, options.runStructured, controller.signal);
|
|
219
|
+
if (canceled || (await workflowAiTaskCancellationRequested(task.id))) {
|
|
220
|
+
await markWorkflowAiTaskCanceledIfRequested(task.id);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
await completeWorkflowAiTask(task.id, result.output, result.usage);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (canceled || (await workflowAiTaskCancellationRequested(task.id))) {
|
|
226
|
+
await markWorkflowAiTaskCanceledIfRequested(task.id);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (ctx.signal.aborted || monitorError) {
|
|
230
|
+
throw new WorkflowAiAttemptError("WORKFLOW_AI_INTERRUPTED", "Workflow AI task was interrupted.", true);
|
|
231
|
+
}
|
|
232
|
+
throw new WorkflowAiAttemptError(
|
|
233
|
+
errorCode(error),
|
|
234
|
+
error instanceof Error ? error.message : "Workflow AI provider call failed.",
|
|
235
|
+
retryableError(error),
|
|
236
|
+
);
|
|
237
|
+
} finally {
|
|
238
|
+
clearInterval(monitor);
|
|
239
|
+
ctx.signal.removeEventListener("abort", abortForShutdown);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
let activeJob: Job<{ taskId: string }> | null = null;
|
|
244
|
+
let activeWorker: Worker | null = null;
|
|
245
|
+
|
|
246
|
+
export const startWorkflowAiRuntime = async (input: WorkflowAiRuntimeOptions = {}): Promise<void> => {
|
|
247
|
+
if (activeJob) return;
|
|
248
|
+
const options: WorkflowAiProcessOptions = {
|
|
249
|
+
runStructured: input.runStructured ?? runAiStructured,
|
|
250
|
+
maxAttempts: MAX_ATTEMPTS,
|
|
251
|
+
cancelPollMs: input.cancelPollMs ?? DEFAULT_CANCEL_POLL_MS,
|
|
252
|
+
};
|
|
253
|
+
const next = workflowAiJob();
|
|
254
|
+
activeJob = next;
|
|
255
|
+
try {
|
|
256
|
+
activeWorker = await next.process({ concurrency: 1 }, async (context) => {
|
|
257
|
+
try {
|
|
258
|
+
await processWorkflowAiTask(context.input.taskId, context, options);
|
|
259
|
+
} catch (caught) {
|
|
260
|
+
const error =
|
|
261
|
+
caught instanceof WorkflowAiAttemptError
|
|
262
|
+
? caught
|
|
263
|
+
: new WorkflowAiAttemptError("WORKFLOW_AI_RUNTIME_ERROR", caught instanceof Error ? caught.message : String(caught), true);
|
|
264
|
+
const outcome = await settleWorkflowAiAttemptFailure(context.input.taskId, error, context.failureCount, options.maxAttempts);
|
|
265
|
+
if (outcome === "retry") throw error;
|
|
266
|
+
// Postgres owns the terminal result, including intentional cancellation.
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
for (const taskId of await listRecoverableWorkflowAiTaskIds()) await submitWorkflowAiTask(taskId);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
stopWorkflowAiRuntime();
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const submitWorkflowAiTask = async (taskId: string): Promise<void> => {
|
|
277
|
+
if (!activeJob) throw new Error("Workflow AI runtime is not started.");
|
|
278
|
+
await activeJob.submit({ key: `task:${taskId}`, coalesce: true, input: { taskId } });
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const stopWorkflowAiRuntime = (): void => {
|
|
282
|
+
activeWorker?.stop();
|
|
283
|
+
|
|
284
|
+
activeWorker = null;
|
|
285
|
+
activeJob = null;
|
|
286
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { type SQL, sql } from "bun";
|
|
2
|
+
import type { WorkflowJsonValue } from "../contracts";
|
|
3
|
+
import { wakeWorkflowRunsWaitingOn } from "../store/runs";
|
|
4
|
+
import {
|
|
5
|
+
type WorkflowAiRequest,
|
|
6
|
+
type WorkflowAiRequestInput,
|
|
7
|
+
type WorkflowAiTask,
|
|
8
|
+
type WorkflowAiTaskStatus,
|
|
9
|
+
workflowAiRequestSchema,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
export const WORKFLOW_AI_DEPENDENCY_KIND = "ai.workflow-task";
|
|
13
|
+
|
|
14
|
+
type WorkflowAiTaskRow = {
|
|
15
|
+
usage_user_id: string | null;
|
|
16
|
+
id: string;
|
|
17
|
+
app_id: string;
|
|
18
|
+
run_id: string;
|
|
19
|
+
step_key: string;
|
|
20
|
+
effect_key: string;
|
|
21
|
+
kind: WorkflowAiRequest["kind"];
|
|
22
|
+
request: unknown;
|
|
23
|
+
input_hash: string;
|
|
24
|
+
model_profile_id: string;
|
|
25
|
+
status: WorkflowAiTaskStatus;
|
|
26
|
+
output: unknown;
|
|
27
|
+
usage: unknown;
|
|
28
|
+
attempts: number | string;
|
|
29
|
+
error_code: string | null;
|
|
30
|
+
error_message: string | null;
|
|
31
|
+
cancel_requested_at: Date | string | null;
|
|
32
|
+
created_at: Date | string;
|
|
33
|
+
started_at: Date | string | null;
|
|
34
|
+
completed_at: Date | string | null;
|
|
35
|
+
updated_at: Date | string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const iso = (value: Date | string): string => (value instanceof Date ? value.toISOString() : new Date(value).toISOString());
|
|
39
|
+
const optionalIso = (value: Date | string | null): string | null => (value === null ? null : iso(value));
|
|
40
|
+
const json = (value: unknown): unknown => {
|
|
41
|
+
if (typeof value !== "string") return value;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(value);
|
|
44
|
+
} catch {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const taskFromRow = (row: WorkflowAiTaskRow): WorkflowAiTask => ({
|
|
50
|
+
id: row.id,
|
|
51
|
+
appId: row.app_id,
|
|
52
|
+
runId: row.run_id,
|
|
53
|
+
usageUserId: row.usage_user_id ?? undefined,
|
|
54
|
+
stepKey: row.step_key,
|
|
55
|
+
effectKey: row.effect_key,
|
|
56
|
+
kind: row.kind,
|
|
57
|
+
request: workflowAiRequestSchema.parse(json(row.request)) as WorkflowAiRequest,
|
|
58
|
+
inputHash: row.input_hash,
|
|
59
|
+
modelProfileId: row.model_profile_id,
|
|
60
|
+
status: row.status,
|
|
61
|
+
output: json(row.output) as WorkflowJsonValue | null,
|
|
62
|
+
usage: json(row.usage) as WorkflowJsonValue | null,
|
|
63
|
+
attempts: Number(row.attempts),
|
|
64
|
+
errorCode: row.error_code,
|
|
65
|
+
errorMessage: row.error_message,
|
|
66
|
+
cancelRequestedAt: optionalIso(row.cancel_requested_at),
|
|
67
|
+
createdAt: iso(row.created_at),
|
|
68
|
+
startedAt: optionalIso(row.started_at),
|
|
69
|
+
completedAt: optionalIso(row.completed_at),
|
|
70
|
+
updatedAt: iso(row.updated_at),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const TASK_COLUMNS = `
|
|
74
|
+
(SELECT u.id::text FROM workflows.run r JOIN auth.users u ON u.id::text = r.authorization_snapshot #>> '{actor,userId}' WHERE r.id = task.run_id) AS usage_user_id,
|
|
75
|
+
task.id::text,
|
|
76
|
+
task.app_id,
|
|
77
|
+
task.run_id::text,
|
|
78
|
+
task.step_key,
|
|
79
|
+
task.effect_key,
|
|
80
|
+
task.kind,
|
|
81
|
+
task.request,
|
|
82
|
+
task.input_hash,
|
|
83
|
+
task.model_profile_id,
|
|
84
|
+
task.status,
|
|
85
|
+
task.output,
|
|
86
|
+
task.usage,
|
|
87
|
+
task.attempts,
|
|
88
|
+
task.error_code,
|
|
89
|
+
task.error_message,
|
|
90
|
+
task.cancel_requested_at,
|
|
91
|
+
task.created_at,
|
|
92
|
+
task.started_at,
|
|
93
|
+
task.completed_at,
|
|
94
|
+
task.updated_at
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
export const migrateWorkflowAi = async (db: SQL = sql): Promise<void> => {
|
|
98
|
+
await db`CREATE SCHEMA IF NOT EXISTS ai`.simple();
|
|
99
|
+
await db`
|
|
100
|
+
CREATE TABLE IF NOT EXISTS ai.workflow_task (
|
|
101
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
102
|
+
app_id TEXT NOT NULL,
|
|
103
|
+
run_id UUID NOT NULL REFERENCES workflows.run(id) ON DELETE CASCADE,
|
|
104
|
+
step_key TEXT NOT NULL CHECK (char_length(step_key) BETWEEN 1 AND 1000),
|
|
105
|
+
effect_key TEXT NOT NULL UNIQUE CHECK (char_length(effect_key) BETWEEN 1 AND 500),
|
|
106
|
+
kind TEXT NOT NULL CHECK (kind IN ('generate_text', 'classify', 'classify_many', 'extract_data')),
|
|
107
|
+
request JSONB NOT NULL CHECK (jsonb_typeof(request) = 'object' AND octet_length(request::text) <= 100000),
|
|
108
|
+
input_hash TEXT NOT NULL CHECK (char_length(input_hash) = 64),
|
|
109
|
+
model_profile_id TEXT NOT NULL CHECK (char_length(model_profile_id) BETWEEN 1 AND 120),
|
|
110
|
+
status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),
|
|
111
|
+
output JSONB,
|
|
112
|
+
usage JSONB,
|
|
113
|
+
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
114
|
+
error_code TEXT,
|
|
115
|
+
error_message TEXT CHECK (error_message IS NULL OR char_length(error_message) <= 2000),
|
|
116
|
+
cancel_requested_at TIMESTAMPTZ,
|
|
117
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
118
|
+
started_at TIMESTAMPTZ,
|
|
119
|
+
completed_at TIMESTAMPTZ,
|
|
120
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
121
|
+
CONSTRAINT ai_workflow_task_terminal_check CHECK (
|
|
122
|
+
(status IN ('succeeded', 'failed', 'canceled')) = (completed_at IS NOT NULL)
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
`.simple();
|
|
126
|
+
await db`
|
|
127
|
+
ALTER TABLE ai.workflow_task DROP CONSTRAINT IF EXISTS workflow_task_kind_check;
|
|
128
|
+
ALTER TABLE ai.workflow_task ADD CONSTRAINT workflow_task_kind_check
|
|
129
|
+
CHECK (kind IN ('generate_text', 'classify', 'classify_many', 'extract_data'))
|
|
130
|
+
`.simple();
|
|
131
|
+
await db`
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_ai_workflow_task_recovery
|
|
133
|
+
ON ai.workflow_task(status, updated_at, id)
|
|
134
|
+
WHERE status IN ('queued', 'running')
|
|
135
|
+
`.simple();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const requestHash = (request: WorkflowAiRequest): string => new Bun.CryptoHasher("sha256").update(JSON.stringify(request)).digest("hex");
|
|
139
|
+
|
|
140
|
+
export const getWorkflowAiTask = async (id: string, db: SQL = sql): Promise<WorkflowAiTask | null> => {
|
|
141
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
142
|
+
SELECT ${db.unsafe(TASK_COLUMNS)}
|
|
143
|
+
FROM ai.workflow_task AS task
|
|
144
|
+
WHERE task.id = ${id}::uuid
|
|
145
|
+
`;
|
|
146
|
+
return rows[0] ? taskFromRow(rows[0]) : null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const getWorkflowAiTaskByEffectKey = async (effectKey: string, db: SQL = sql): Promise<WorkflowAiTask | null> => {
|
|
150
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
151
|
+
SELECT ${db.unsafe(TASK_COLUMNS)}
|
|
152
|
+
FROM ai.workflow_task AS task
|
|
153
|
+
WHERE task.effect_key = ${effectKey}
|
|
154
|
+
`;
|
|
155
|
+
return rows[0] ? taskFromRow(rows[0]) : null;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const workflowAiTaskExists = async (effectKey: string, db: SQL = sql): Promise<boolean> => {
|
|
159
|
+
const [row] = await db<{ exists: boolean }[]>`
|
|
160
|
+
SELECT EXISTS (SELECT 1 FROM ai.workflow_task WHERE effect_key = ${effectKey}) AS exists
|
|
161
|
+
`;
|
|
162
|
+
return Boolean(row?.exists);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const createWorkflowAiTask = async (
|
|
166
|
+
input: { runId: string; stepKey: string; effectKey: string; request: WorkflowAiRequestInput; modelProfileId: string },
|
|
167
|
+
db: SQL = sql,
|
|
168
|
+
): Promise<{ task: WorkflowAiTask; created: boolean }> => {
|
|
169
|
+
const request = workflowAiRequestSchema.parse(input.request) as WorkflowAiRequest;
|
|
170
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
171
|
+
INSERT INTO ai.workflow_task (app_id, run_id, step_key, effect_key, kind, request, input_hash, model_profile_id)
|
|
172
|
+
SELECT run.app_id, run.id, ${input.stepKey}, ${input.effectKey}, ${request.kind}, ${request}, ${requestHash(request)}, ${input.modelProfileId}
|
|
173
|
+
FROM workflows.run AS run
|
|
174
|
+
WHERE run.id = ${input.runId}::uuid
|
|
175
|
+
ON CONFLICT (effect_key) DO NOTHING
|
|
176
|
+
RETURNING ${db.unsafe(TASK_COLUMNS.replaceAll("task.", ""))}
|
|
177
|
+
`;
|
|
178
|
+
if (rows[0]) return { task: taskFromRow(rows[0]), created: true };
|
|
179
|
+
const existing = await getWorkflowAiTaskByEffectKey(input.effectKey, db);
|
|
180
|
+
if (!existing) throw new Error("Workflow AI task could not be created because its run is unavailable.");
|
|
181
|
+
return { task: existing, created: false };
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const listRecoverableWorkflowAiTaskIds = async (db: SQL = sql): Promise<string[]> => {
|
|
185
|
+
const rows = await db<{ id: string }[]>`
|
|
186
|
+
SELECT id::text
|
|
187
|
+
FROM ai.workflow_task
|
|
188
|
+
WHERE status IN ('queued', 'running')
|
|
189
|
+
ORDER BY updated_at, id
|
|
190
|
+
`;
|
|
191
|
+
return rows.map((row) => row.id);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const claimWorkflowAiTask = async (id: string, db: SQL = sql): Promise<WorkflowAiTask | null> => {
|
|
195
|
+
const canceled = await markWorkflowAiTaskCanceledIfRequested(id, db);
|
|
196
|
+
if (canceled) return canceled;
|
|
197
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
198
|
+
UPDATE ai.workflow_task AS task
|
|
199
|
+
SET status = 'running',
|
|
200
|
+
attempts = task.attempts + 1,
|
|
201
|
+
started_at = COALESCE(task.started_at, now()),
|
|
202
|
+
error_code = NULL,
|
|
203
|
+
error_message = NULL,
|
|
204
|
+
updated_at = now()
|
|
205
|
+
FROM workflows.run AS run
|
|
206
|
+
WHERE task.id = ${id}::uuid
|
|
207
|
+
AND run.id = task.run_id
|
|
208
|
+
AND run.cancel_requested_at IS NULL
|
|
209
|
+
AND run.state <> 'canceled'
|
|
210
|
+
AND task.status IN ('queued', 'running')
|
|
211
|
+
RETURNING ${db.unsafe(TASK_COLUMNS)}
|
|
212
|
+
`;
|
|
213
|
+
return rows[0] ? taskFromRow(rows[0]) : markWorkflowAiTaskCanceledIfRequested(id, db);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const workflowAiTaskCancellationRequested = async (id: string, db: SQL = sql): Promise<boolean> => {
|
|
217
|
+
const [row] = await db<{ canceled: boolean }[]>`
|
|
218
|
+
SELECT run.cancel_requested_at IS NOT NULL OR run.state = 'canceled' AS canceled
|
|
219
|
+
FROM ai.workflow_task AS task
|
|
220
|
+
JOIN workflows.run AS run ON run.id = task.run_id
|
|
221
|
+
WHERE task.id = ${id}::uuid
|
|
222
|
+
`;
|
|
223
|
+
return Boolean(row?.canceled);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const wakeWorkflowAiTask = async (task: Pick<WorkflowAiTask, "appId" | "id">): Promise<void> => {
|
|
227
|
+
await wakeWorkflowRunsWaitingOn({ appId: task.appId, kind: WORKFLOW_AI_DEPENDENCY_KIND, key: task.id });
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const markWorkflowAiTaskCanceledIfRequested = async (id: string, db: SQL = sql): Promise<WorkflowAiTask | null> => {
|
|
231
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
232
|
+
UPDATE ai.workflow_task AS task
|
|
233
|
+
SET status = 'canceled',
|
|
234
|
+
output = NULL,
|
|
235
|
+
usage = NULL,
|
|
236
|
+
error_code = 'WORKFLOW_AI_CANCELED',
|
|
237
|
+
error_message = 'Workflow AI task was canceled.',
|
|
238
|
+
cancel_requested_at = COALESCE(task.cancel_requested_at, now()),
|
|
239
|
+
completed_at = now(),
|
|
240
|
+
updated_at = now()
|
|
241
|
+
FROM workflows.run AS run
|
|
242
|
+
WHERE task.id = ${id}::uuid
|
|
243
|
+
AND run.id = task.run_id
|
|
244
|
+
AND (run.cancel_requested_at IS NOT NULL OR run.state = 'canceled')
|
|
245
|
+
AND task.status IN ('queued', 'running')
|
|
246
|
+
RETURNING ${db.unsafe(TASK_COLUMNS)}
|
|
247
|
+
`;
|
|
248
|
+
const task = rows[0] ? taskFromRow(rows[0]) : null;
|
|
249
|
+
if (task) await wakeWorkflowAiTask(task);
|
|
250
|
+
return task;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const requeueWorkflowAiTask = async (id: string, db: SQL = sql): Promise<void> => {
|
|
254
|
+
await db`
|
|
255
|
+
UPDATE ai.workflow_task
|
|
256
|
+
SET status = 'queued', updated_at = now()
|
|
257
|
+
WHERE id = ${id}::uuid AND status = 'running'
|
|
258
|
+
`;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const completeWorkflowAiTask = async (
|
|
262
|
+
id: string,
|
|
263
|
+
output: WorkflowJsonValue,
|
|
264
|
+
usage: WorkflowJsonValue | null,
|
|
265
|
+
db: SQL = sql,
|
|
266
|
+
): Promise<WorkflowAiTask | null> => {
|
|
267
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
268
|
+
UPDATE ai.workflow_task AS task
|
|
269
|
+
SET status = 'succeeded', output = ${output}, usage = ${usage}, completed_at = now(), updated_at = now()
|
|
270
|
+
FROM workflows.run AS run
|
|
271
|
+
WHERE task.id = ${id}::uuid
|
|
272
|
+
AND run.id = task.run_id
|
|
273
|
+
AND run.cancel_requested_at IS NULL
|
|
274
|
+
AND run.state <> 'canceled'
|
|
275
|
+
AND task.status = 'running'
|
|
276
|
+
RETURNING ${db.unsafe(TASK_COLUMNS)}
|
|
277
|
+
`;
|
|
278
|
+
const task = rows[0] ? taskFromRow(rows[0]) : null;
|
|
279
|
+
if (task) {
|
|
280
|
+
await wakeWorkflowAiTask(task);
|
|
281
|
+
return task;
|
|
282
|
+
}
|
|
283
|
+
return markWorkflowAiTaskCanceledIfRequested(id, db);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const failWorkflowAiTask = async (
|
|
287
|
+
id: string,
|
|
288
|
+
error: { code: string; message: string },
|
|
289
|
+
db: SQL = sql,
|
|
290
|
+
): Promise<WorkflowAiTask | null> => {
|
|
291
|
+
const rows = await db<WorkflowAiTaskRow[]>`
|
|
292
|
+
UPDATE ai.workflow_task AS task
|
|
293
|
+
SET status = 'failed',
|
|
294
|
+
output = NULL,
|
|
295
|
+
usage = NULL,
|
|
296
|
+
error_code = ${error.code},
|
|
297
|
+
error_message = ${error.message.slice(0, 2_000)},
|
|
298
|
+
completed_at = now(),
|
|
299
|
+
updated_at = now()
|
|
300
|
+
FROM workflows.run AS run
|
|
301
|
+
WHERE task.id = ${id}::uuid
|
|
302
|
+
AND run.id = task.run_id
|
|
303
|
+
AND run.cancel_requested_at IS NULL
|
|
304
|
+
AND run.state <> 'canceled'
|
|
305
|
+
AND task.status IN ('queued', 'running')
|
|
306
|
+
RETURNING ${db.unsafe(TASK_COLUMNS)}
|
|
307
|
+
`;
|
|
308
|
+
const task = rows[0] ? taskFromRow(rows[0]) : null;
|
|
309
|
+
if (task) {
|
|
310
|
+
await wakeWorkflowAiTask(task);
|
|
311
|
+
return task;
|
|
312
|
+
}
|
|
313
|
+
return markWorkflowAiTaskCanceledIfRequested(id, db);
|
|
314
|
+
};
|