@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,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading runs, for operators.
|
|
3
|
+
*
|
|
4
|
+
* Once the kernel owns runs, "is any workflow broken right now" is one query
|
|
5
|
+
* across every app rather than an aggregation protocol between them. The
|
|
6
|
+
* previous arrangement needed a per-app health port purely because storage was
|
|
7
|
+
* split; there is nothing left for that port to do.
|
|
8
|
+
*
|
|
9
|
+
* The four findings below are the ones that stayed invisible: an effect that
|
|
10
|
+
* escaped and was never settled, a run waiting on a human, an event that
|
|
11
|
+
* matched nothing and said nothing, and a run that started long after the
|
|
12
|
+
* occurrence that caused it.
|
|
13
|
+
*/
|
|
14
|
+
import { type SQL, sql } from "bun";
|
|
15
|
+
import type { WorkflowInvocationMode, WorkflowJsonValue, WorkflowRunState } from "../contracts";
|
|
16
|
+
import type { WorkflowEffectBudget } from "./budget";
|
|
17
|
+
|
|
18
|
+
export type WorkflowRunSummary = {
|
|
19
|
+
id: string;
|
|
20
|
+
appId: string;
|
|
21
|
+
scopeId: string;
|
|
22
|
+
workflowId: string;
|
|
23
|
+
workflowName: string;
|
|
24
|
+
revision: number;
|
|
25
|
+
mode: WorkflowInvocationMode;
|
|
26
|
+
state: WorkflowRunState;
|
|
27
|
+
attempt: number;
|
|
28
|
+
eventType: string | null;
|
|
29
|
+
parentRunId: string | null;
|
|
30
|
+
occurredAt: Date;
|
|
31
|
+
createdAt: Date;
|
|
32
|
+
startedAt: Date | null;
|
|
33
|
+
finishedAt: Date | null;
|
|
34
|
+
/** Milliseconds between the occurrence and the run starting. The drift an operator hunts. */
|
|
35
|
+
startLagMs: number | null;
|
|
36
|
+
durationMs: number | null;
|
|
37
|
+
error: WorkflowJsonValue | null;
|
|
38
|
+
/** What the run said about itself, distinct from what it produced. */
|
|
39
|
+
resultMessage: string | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type WorkflowRunFilter = {
|
|
43
|
+
appId?: string;
|
|
44
|
+
scopeId?: string;
|
|
45
|
+
workflowId?: string;
|
|
46
|
+
/** Restrict the list to the direct children of one fan-out parent. */
|
|
47
|
+
parentRunId?: string;
|
|
48
|
+
state?: WorkflowRunState;
|
|
49
|
+
mode?: WorkflowInvocationMode;
|
|
50
|
+
/** Children are noise in a list view until you are looking at their parent. */
|
|
51
|
+
includeChildren?: boolean;
|
|
52
|
+
since?: Date;
|
|
53
|
+
limit?: number;
|
|
54
|
+
offset?: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type RunRow = {
|
|
58
|
+
id: string;
|
|
59
|
+
app_id: string;
|
|
60
|
+
scope_id: string;
|
|
61
|
+
workflow_id: string;
|
|
62
|
+
workflow_name: string;
|
|
63
|
+
revision: number;
|
|
64
|
+
mode: WorkflowInvocationMode;
|
|
65
|
+
state: WorkflowRunState;
|
|
66
|
+
attempt: number;
|
|
67
|
+
event_type: string | null;
|
|
68
|
+
parent_run_id: string | null;
|
|
69
|
+
occurred_at: Date;
|
|
70
|
+
created_at: Date;
|
|
71
|
+
started_at: Date | null;
|
|
72
|
+
finished_at: Date | null;
|
|
73
|
+
error: WorkflowJsonValue | null;
|
|
74
|
+
result_message: string | null;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const millisBetween = (from: Date | null, to: Date | null): number | null =>
|
|
78
|
+
from && to ? Math.max(0, to.getTime() - from.getTime()) : null;
|
|
79
|
+
|
|
80
|
+
const toSummary = (row: RunRow): WorkflowRunSummary => ({
|
|
81
|
+
id: row.id,
|
|
82
|
+
appId: row.app_id,
|
|
83
|
+
scopeId: row.scope_id,
|
|
84
|
+
workflowId: row.workflow_id,
|
|
85
|
+
workflowName: row.workflow_name,
|
|
86
|
+
revision: row.revision,
|
|
87
|
+
mode: row.mode,
|
|
88
|
+
state: row.state,
|
|
89
|
+
attempt: row.attempt,
|
|
90
|
+
eventType: row.event_type,
|
|
91
|
+
parentRunId: row.parent_run_id,
|
|
92
|
+
occurredAt: row.occurred_at,
|
|
93
|
+
createdAt: row.created_at,
|
|
94
|
+
startedAt: row.started_at,
|
|
95
|
+
finishedAt: row.finished_at,
|
|
96
|
+
startLagMs: millisBetween(row.occurred_at, row.started_at),
|
|
97
|
+
durationMs: millisBetween(row.started_at, row.finished_at),
|
|
98
|
+
error: row.error,
|
|
99
|
+
resultMessage: row.result_message,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const RUN_SELECT = `
|
|
103
|
+
r.id, r.app_id, r.scope_id, r.workflow_id, w.name AS workflow_name, v.revision,
|
|
104
|
+
r.mode, r.state, r.attempt, e.type AS event_type, r.parent_run_id,
|
|
105
|
+
r.occurred_at, r.created_at, r.started_at, r.finished_at, r.error, r.result_message
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const RUN_FROM = `
|
|
109
|
+
FROM workflows.run AS r
|
|
110
|
+
JOIN workflows.workflow AS w ON w.id = r.workflow_id
|
|
111
|
+
JOIN workflows.version AS v ON v.id = r.workflow_version_id
|
|
112
|
+
LEFT JOIN workflows.event AS e ON e.id = r.event_id
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
export const listWorkflowRuns = async (filter: WorkflowRunFilter = {}, options: { db?: SQL } = {}): Promise<WorkflowRunSummary[]> => {
|
|
116
|
+
const db = options.db ?? sql;
|
|
117
|
+
const rows = await db<RunRow[]>`
|
|
118
|
+
SELECT ${db.unsafe(RUN_SELECT)} ${db.unsafe(RUN_FROM)}
|
|
119
|
+
WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
|
|
120
|
+
AND (${filter.scopeId ?? null}::text IS NULL OR r.scope_id = ${filter.scopeId ?? null})
|
|
121
|
+
AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
|
|
122
|
+
AND (${filter.parentRunId ?? null}::uuid IS NULL OR r.parent_run_id = ${filter.parentRunId ?? null}::uuid)
|
|
123
|
+
AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
|
|
124
|
+
AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
|
|
125
|
+
AND (${filter.parentRunId !== undefined || filter.includeChildren === true} OR r.parent_run_id IS NULL)
|
|
126
|
+
AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
|
|
127
|
+
ORDER BY r.created_at DESC, r.id DESC
|
|
128
|
+
LIMIT ${filter.limit ?? 50} OFFSET ${filter.offset ?? 0}
|
|
129
|
+
`;
|
|
130
|
+
return rows.map(toSummary);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type WorkflowFamilySummary = {
|
|
134
|
+
workflowId: string;
|
|
135
|
+
appId: string;
|
|
136
|
+
scopeId: string;
|
|
137
|
+
workflowName: string;
|
|
138
|
+
eventTypes: string[];
|
|
139
|
+
latestRunId: string;
|
|
140
|
+
latestRevision: number;
|
|
141
|
+
latestState: WorkflowRunState;
|
|
142
|
+
latestRunAt: Date;
|
|
143
|
+
runs: number;
|
|
144
|
+
failed: number;
|
|
145
|
+
active: number;
|
|
146
|
+
needsAttention: number;
|
|
147
|
+
avgDurationMs: number | null;
|
|
148
|
+
p99DurationMs: number | null;
|
|
149
|
+
oldestQueuedAt: Date | null;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type WorkflowFamilyFilter = Pick<WorkflowRunFilter, "appId" | "workflowId" | "state" | "mode" | "since" | "limit" | "offset">;
|
|
153
|
+
|
|
154
|
+
type WorkflowFamilyRow = {
|
|
155
|
+
workflow_id: string;
|
|
156
|
+
app_id: string;
|
|
157
|
+
scope_id: string;
|
|
158
|
+
workflow_name: string;
|
|
159
|
+
event_types: string[];
|
|
160
|
+
latest_run_id: string;
|
|
161
|
+
latest_revision: number;
|
|
162
|
+
latest_state: WorkflowRunState;
|
|
163
|
+
latest_run_at: Date;
|
|
164
|
+
runs: number | string;
|
|
165
|
+
failed: number | string;
|
|
166
|
+
active: number | string;
|
|
167
|
+
needs_attention: number | string;
|
|
168
|
+
avg_duration_ms: number | string | null;
|
|
169
|
+
p99_duration_ms: number | string | null;
|
|
170
|
+
oldest_queued_at: Date | null;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const numberOrNull = (value: number | string | null): number | null => (value === null ? null : Number(value));
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* One row per workflow definition, analogous to the job-family overview.
|
|
177
|
+
*
|
|
178
|
+
* The filter is applied before aggregation, so every number describes the
|
|
179
|
+
* selected URL scope. Individual runs remain a separate drill-down.
|
|
180
|
+
*/
|
|
181
|
+
export const listWorkflowFamilies = async (
|
|
182
|
+
filter: WorkflowFamilyFilter = {},
|
|
183
|
+
options: { db?: SQL } = {},
|
|
184
|
+
): Promise<WorkflowFamilySummary[]> => {
|
|
185
|
+
const db = options.db ?? sql;
|
|
186
|
+
const rows = await db<WorkflowFamilyRow[]>`
|
|
187
|
+
WITH filtered AS (
|
|
188
|
+
SELECT
|
|
189
|
+
r.id, r.app_id, r.scope_id, r.workflow_id, w.name AS workflow_name,
|
|
190
|
+
v.revision, r.state, r.created_at, r.started_at, r.finished_at,
|
|
191
|
+
e.type AS event_type,
|
|
192
|
+
EXTRACT(EPOCH FROM (r.finished_at - r.started_at)) * 1000 AS duration_ms
|
|
193
|
+
${db.unsafe(RUN_FROM)}
|
|
194
|
+
WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
|
|
195
|
+
AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
|
|
196
|
+
AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
|
|
197
|
+
AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
|
|
198
|
+
AND r.parent_run_id IS NULL
|
|
199
|
+
AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
|
|
200
|
+
),
|
|
201
|
+
latest AS (
|
|
202
|
+
SELECT DISTINCT ON (workflow_id)
|
|
203
|
+
workflow_id, id AS latest_run_id, revision AS latest_revision,
|
|
204
|
+
state AS latest_state, created_at AS latest_run_at
|
|
205
|
+
FROM filtered
|
|
206
|
+
ORDER BY workflow_id, created_at DESC, id DESC
|
|
207
|
+
),
|
|
208
|
+
families AS (
|
|
209
|
+
SELECT
|
|
210
|
+
workflow_id, app_id, scope_id, workflow_name,
|
|
211
|
+
COALESCE(array_agg(DISTINCT event_type) FILTER (WHERE event_type IS NOT NULL), ARRAY[]::text[]) AS event_types,
|
|
212
|
+
COUNT(*)::int AS runs,
|
|
213
|
+
COUNT(*) FILTER (WHERE state = 'failed')::int AS failed,
|
|
214
|
+
COUNT(*) FILTER (WHERE state IN ('queued', 'running', 'waiting'))::int AS active,
|
|
215
|
+
COUNT(*) FILTER (WHERE state = 'needs_attention')::int AS needs_attention,
|
|
216
|
+
AVG(duration_ms)::float AS avg_duration_ms,
|
|
217
|
+
(percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
|
|
218
|
+
FILTER (WHERE duration_ms IS NOT NULL))::float AS p99_duration_ms,
|
|
219
|
+
MIN(created_at) FILTER (WHERE state = 'queued') AS oldest_queued_at
|
|
220
|
+
FROM filtered
|
|
221
|
+
GROUP BY workflow_id, app_id, scope_id, workflow_name
|
|
222
|
+
)
|
|
223
|
+
SELECT families.*, latest.latest_run_id, latest.latest_revision, latest.latest_state, latest.latest_run_at
|
|
224
|
+
FROM families
|
|
225
|
+
JOIN latest USING (workflow_id)
|
|
226
|
+
ORDER BY
|
|
227
|
+
CASE latest.latest_state
|
|
228
|
+
WHEN 'needs_attention' THEN 0
|
|
229
|
+
WHEN 'failed' THEN 1
|
|
230
|
+
WHEN 'running' THEN 2
|
|
231
|
+
WHEN 'waiting' THEN 2
|
|
232
|
+
WHEN 'queued' THEN 2
|
|
233
|
+
ELSE 3
|
|
234
|
+
END,
|
|
235
|
+
latest.latest_run_at DESC,
|
|
236
|
+
families.workflow_name,
|
|
237
|
+
families.workflow_id
|
|
238
|
+
LIMIT ${filter.limit ?? 50} OFFSET ${filter.offset ?? 0}
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
return rows.map((row) => ({
|
|
242
|
+
workflowId: row.workflow_id,
|
|
243
|
+
appId: row.app_id,
|
|
244
|
+
scopeId: row.scope_id,
|
|
245
|
+
workflowName: row.workflow_name,
|
|
246
|
+
eventTypes: [...row.event_types].sort(),
|
|
247
|
+
latestRunId: row.latest_run_id,
|
|
248
|
+
latestRevision: row.latest_revision,
|
|
249
|
+
latestState: row.latest_state,
|
|
250
|
+
latestRunAt: row.latest_run_at,
|
|
251
|
+
runs: Number(row.runs),
|
|
252
|
+
failed: Number(row.failed),
|
|
253
|
+
active: Number(row.active),
|
|
254
|
+
needsAttention: Number(row.needs_attention),
|
|
255
|
+
avgDurationMs: numberOrNull(row.avg_duration_ms),
|
|
256
|
+
p99DurationMs: numberOrNull(row.p99_duration_ms),
|
|
257
|
+
oldestQueuedAt: row.oldest_queued_at,
|
|
258
|
+
}));
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export type WorkflowRunTimeline = {
|
|
262
|
+
runs: WorkflowRunSummary[];
|
|
263
|
+
total: number;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A bounded set of individual runs for charts, plus the uncapped total so the
|
|
268
|
+
* UI can say when the chart is a sample rather than the complete window.
|
|
269
|
+
*/
|
|
270
|
+
export const listWorkflowRunTimeline = async (
|
|
271
|
+
filter: Omit<WorkflowRunFilter, "limit" | "offset"> = {},
|
|
272
|
+
options: { db?: SQL; limit?: number } = {},
|
|
273
|
+
): Promise<WorkflowRunTimeline> => {
|
|
274
|
+
const db = options.db ?? sql;
|
|
275
|
+
const limit = Math.min(5_000, Math.max(1, Math.trunc(options.limit ?? 2_000)));
|
|
276
|
+
const [runs, countRows] = await Promise.all([
|
|
277
|
+
listWorkflowRuns({ ...filter, limit, offset: 0 }, { db }),
|
|
278
|
+
db<{ count: number | string }[]>`
|
|
279
|
+
SELECT COUNT(*)::int AS count FROM workflows.run AS r
|
|
280
|
+
WHERE (${filter.appId ?? null}::text IS NULL OR r.app_id = ${filter.appId ?? null})
|
|
281
|
+
AND (${filter.scopeId ?? null}::text IS NULL OR r.scope_id = ${filter.scopeId ?? null})
|
|
282
|
+
AND (${filter.workflowId ?? null}::uuid IS NULL OR r.workflow_id = ${filter.workflowId ?? null}::uuid)
|
|
283
|
+
AND (${filter.parentRunId ?? null}::uuid IS NULL OR r.parent_run_id = ${filter.parentRunId ?? null}::uuid)
|
|
284
|
+
AND (${filter.state ?? null}::text IS NULL OR r.state = ${filter.state ?? null})
|
|
285
|
+
AND (${filter.mode ?? null}::text IS NULL OR r.mode = ${filter.mode ?? null})
|
|
286
|
+
AND (${filter.parentRunId !== undefined || filter.includeChildren === true} OR r.parent_run_id IS NULL)
|
|
287
|
+
AND (${filter.since ?? null}::timestamptz IS NULL OR r.created_at >= ${filter.since ?? null}::timestamptz)
|
|
288
|
+
`,
|
|
289
|
+
]);
|
|
290
|
+
return { runs, total: Number(countRows[0]?.count ?? 0) };
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type WorkflowStepSummary = {
|
|
294
|
+
stepKey: string;
|
|
295
|
+
/** Where the step sits in the written source, so a view can point at it. */
|
|
296
|
+
sourcePath: WorkflowJsonValue;
|
|
297
|
+
/** Which iteration of an enclosing loop this was. Empty outside one. */
|
|
298
|
+
iterationPath: WorkflowJsonValue;
|
|
299
|
+
kind: string;
|
|
300
|
+
action: string | null;
|
|
301
|
+
state: string;
|
|
302
|
+
attempt: number;
|
|
303
|
+
/** What the step reported — the output a later step referenced, or why it stopped. */
|
|
304
|
+
outcome: WorkflowJsonValue | null;
|
|
305
|
+
effectKey: string | null;
|
|
306
|
+
effectState: string | null;
|
|
307
|
+
effectStartedAt: Date | null;
|
|
308
|
+
dependency: WorkflowJsonValue | null;
|
|
309
|
+
startedAt: Date;
|
|
310
|
+
finishedAt: Date | null;
|
|
311
|
+
durationMs: number | null;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/** One step's row, shaped for a run view. Shared by the detail read and the live one. */
|
|
315
|
+
type StepRow = {
|
|
316
|
+
step_key: string;
|
|
317
|
+
source_path: WorkflowJsonValue;
|
|
318
|
+
iteration_path: WorkflowJsonValue;
|
|
319
|
+
kind: string;
|
|
320
|
+
action: string | null;
|
|
321
|
+
state: string;
|
|
322
|
+
attempt: number;
|
|
323
|
+
outcome: WorkflowJsonValue | null;
|
|
324
|
+
effect_key: string | null;
|
|
325
|
+
effect_state: string | null;
|
|
326
|
+
effect_started_at: Date | null;
|
|
327
|
+
dependency: WorkflowJsonValue | null;
|
|
328
|
+
started_at: Date;
|
|
329
|
+
finished_at: Date | null;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const STEP_SELECT = `
|
|
333
|
+
step_key, source_path, iteration_path, kind, action, state, attempt, outcome,
|
|
334
|
+
effect_key, effect_state, effect_started_at, dependency, started_at, finished_at
|
|
335
|
+
`;
|
|
336
|
+
|
|
337
|
+
const toStepSummary = (step: StepRow): WorkflowStepSummary => ({
|
|
338
|
+
stepKey: step.step_key,
|
|
339
|
+
sourcePath: step.source_path,
|
|
340
|
+
iterationPath: step.iteration_path,
|
|
341
|
+
kind: step.kind,
|
|
342
|
+
action: step.action,
|
|
343
|
+
state: step.state,
|
|
344
|
+
attempt: step.attempt,
|
|
345
|
+
outcome: step.outcome,
|
|
346
|
+
effectKey: step.effect_key,
|
|
347
|
+
effectState: step.effect_state,
|
|
348
|
+
effectStartedAt: step.effect_started_at,
|
|
349
|
+
dependency: step.dependency,
|
|
350
|
+
startedAt: step.started_at,
|
|
351
|
+
finishedAt: step.finished_at,
|
|
352
|
+
durationMs: millisBetween(step.started_at, step.finished_at),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The steps of one run, or just the ones named.
|
|
357
|
+
*
|
|
358
|
+
* Naming them is what a live update needs: a step finished, and the stream
|
|
359
|
+
* carries that step rather than re-reading a plan that may have thousands.
|
|
360
|
+
*/
|
|
361
|
+
export const listWorkflowRunSteps = async (
|
|
362
|
+
runId: string,
|
|
363
|
+
options: { stepKeys?: readonly string[]; db?: SQL } = {},
|
|
364
|
+
): Promise<WorkflowStepSummary[]> => {
|
|
365
|
+
const db = options.db ?? sql;
|
|
366
|
+
const keys = options.stepKeys;
|
|
367
|
+
if (keys && keys.length === 0) return [];
|
|
368
|
+
const rows = keys
|
|
369
|
+
? await db<StepRow[]>`
|
|
370
|
+
SELECT ${db.unsafe(STEP_SELECT)} FROM workflows.step_outcome
|
|
371
|
+
WHERE run_id = ${runId}::uuid AND step_key IN ${db(keys)}
|
|
372
|
+
ORDER BY started_at, step_key
|
|
373
|
+
`
|
|
374
|
+
: await db<StepRow[]>`
|
|
375
|
+
SELECT ${db.unsafe(STEP_SELECT)} FROM workflows.step_outcome
|
|
376
|
+
WHERE run_id = ${runId}::uuid
|
|
377
|
+
ORDER BY started_at, step_key
|
|
378
|
+
`;
|
|
379
|
+
return rows.map(toStepSummary);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export type WorkflowRunDetail = WorkflowRunSummary & {
|
|
383
|
+
inputs: Record<string, WorkflowJsonValue>;
|
|
384
|
+
result: WorkflowJsonValue | null;
|
|
385
|
+
effectsUsed: WorkflowEffectBudget;
|
|
386
|
+
effectBudget: WorkflowEffectBudget;
|
|
387
|
+
source: string;
|
|
388
|
+
eventId: string | null;
|
|
389
|
+
eventData: Record<string, WorkflowJsonValue> | null;
|
|
390
|
+
steps: WorkflowStepSummary[];
|
|
391
|
+
children: Record<WorkflowRunState, number>;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const EMPTY_CHILD_COUNTS: Record<WorkflowRunState, number> = {
|
|
395
|
+
queued: 0,
|
|
396
|
+
running: 0,
|
|
397
|
+
waiting: 0,
|
|
398
|
+
succeeded: 0,
|
|
399
|
+
failed: 0,
|
|
400
|
+
canceled: 0,
|
|
401
|
+
needs_attention: 0,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export const getWorkflowRun = async (runId: string, options: { db?: SQL } = {}): Promise<WorkflowRunDetail | null> => {
|
|
405
|
+
const db = options.db ?? sql;
|
|
406
|
+
const [row] = await db<
|
|
407
|
+
(RunRow & {
|
|
408
|
+
inputs: Record<string, WorkflowJsonValue>;
|
|
409
|
+
result: WorkflowJsonValue | null;
|
|
410
|
+
effects_used: WorkflowEffectBudget;
|
|
411
|
+
effect_budget: WorkflowEffectBudget;
|
|
412
|
+
source: string;
|
|
413
|
+
event_id: string | null;
|
|
414
|
+
event_data: Record<string, WorkflowJsonValue> | null;
|
|
415
|
+
})[]
|
|
416
|
+
>`
|
|
417
|
+
SELECT ${db.unsafe(RUN_SELECT)},
|
|
418
|
+
r.inputs, r.result, r.effects_used, v.effect_budget, v.source, r.event_id, e.data AS event_data
|
|
419
|
+
${db.unsafe(RUN_FROM)}
|
|
420
|
+
WHERE r.id = ${runId}::uuid
|
|
421
|
+
`;
|
|
422
|
+
if (!row) return null;
|
|
423
|
+
|
|
424
|
+
const steps = await listWorkflowRunSteps(runId, { db });
|
|
425
|
+
|
|
426
|
+
const childCounts = await db<{ state: WorkflowRunState; count: string }[]>`
|
|
427
|
+
SELECT state, count(*) AS count FROM workflows.run WHERE parent_run_id = ${runId}::uuid GROUP BY state
|
|
428
|
+
`;
|
|
429
|
+
const children = { ...EMPTY_CHILD_COUNTS };
|
|
430
|
+
for (const child of childCounts) children[child.state] = Number(child.count);
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
...toSummary(row),
|
|
434
|
+
inputs: row.inputs,
|
|
435
|
+
result: row.result,
|
|
436
|
+
effectsUsed: row.effects_used,
|
|
437
|
+
effectBudget: row.effect_budget,
|
|
438
|
+
source: row.source,
|
|
439
|
+
eventId: row.event_id,
|
|
440
|
+
eventData: row.event_data,
|
|
441
|
+
children,
|
|
442
|
+
steps,
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
export type StrandedWorkflowEffect = {
|
|
447
|
+
runId: string;
|
|
448
|
+
appId: string;
|
|
449
|
+
workflowName: string;
|
|
450
|
+
stepKey: string;
|
|
451
|
+
action: string | null;
|
|
452
|
+
effectKey: string;
|
|
453
|
+
effectState: string;
|
|
454
|
+
effectStartedAt: Date;
|
|
455
|
+
ageMs: number;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Effects that left the process and never reported back.
|
|
460
|
+
*
|
|
461
|
+
* These are the ones a replay refuses to repeat, so each is a message that may
|
|
462
|
+
* or may not have gone out and a run that cannot proceed on its own. Nothing
|
|
463
|
+
* surfaced them before.
|
|
464
|
+
*/
|
|
465
|
+
export const listStrandedWorkflowEffects = async (
|
|
466
|
+
options: { appId?: string; olderThanMs?: number; limit?: number; offset?: number; db?: SQL } = {},
|
|
467
|
+
): Promise<StrandedWorkflowEffect[]> => {
|
|
468
|
+
const db = options.db ?? sql;
|
|
469
|
+
const rows = await db<
|
|
470
|
+
{
|
|
471
|
+
run_id: string;
|
|
472
|
+
app_id: string;
|
|
473
|
+
workflow_name: string;
|
|
474
|
+
step_key: string;
|
|
475
|
+
action: string | null;
|
|
476
|
+
effect_key: string;
|
|
477
|
+
effect_state: string;
|
|
478
|
+
effect_started_at: Date;
|
|
479
|
+
}[]
|
|
480
|
+
>`
|
|
481
|
+
SELECT s.run_id, r.app_id, w.name AS workflow_name, s.step_key, s.action, s.effect_key, s.effect_state, s.effect_started_at
|
|
482
|
+
FROM workflows.step_outcome AS s
|
|
483
|
+
JOIN workflows.run AS r ON r.id = s.run_id
|
|
484
|
+
JOIN workflows.workflow AS w ON w.id = r.workflow_id
|
|
485
|
+
WHERE s.effect_state IN ('executing', 'ambiguous')
|
|
486
|
+
AND s.effect_started_at < now() - ${`${options.olderThanMs ?? 0} milliseconds`}::interval
|
|
487
|
+
AND (${options.appId ?? null}::text IS NULL OR r.app_id = ${options.appId ?? null})
|
|
488
|
+
ORDER BY s.effect_started_at, s.run_id
|
|
489
|
+
LIMIT ${options.limit ?? 100} OFFSET ${options.offset ?? 0}
|
|
490
|
+
`;
|
|
491
|
+
const now = Date.now();
|
|
492
|
+
return rows.map((row) => ({
|
|
493
|
+
runId: row.run_id,
|
|
494
|
+
appId: row.app_id,
|
|
495
|
+
workflowName: row.workflow_name,
|
|
496
|
+
stepKey: row.step_key,
|
|
497
|
+
action: row.action,
|
|
498
|
+
effectKey: row.effect_key,
|
|
499
|
+
effectState: row.effect_state,
|
|
500
|
+
effectStartedAt: row.effect_started_at,
|
|
501
|
+
ageMs: Math.max(0, now - row.effect_started_at.getTime()),
|
|
502
|
+
}));
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export type WorkflowAppHealth = {
|
|
506
|
+
appId: string;
|
|
507
|
+
runs: Record<WorkflowRunState, number>;
|
|
508
|
+
/** Effects that escaped and never settled — each one a possible duplicate. */
|
|
509
|
+
strandedEffects: number;
|
|
510
|
+
/** Events that matched nothing or could not be dispatched. */
|
|
511
|
+
undispatchedEvents: number;
|
|
512
|
+
/** How far behind the oldest undispatched event is, in milliseconds. */
|
|
513
|
+
oldestUndispatchedMs: number | null;
|
|
514
|
+
/** Worst gap between an occurrence and the run for it actually starting. */
|
|
515
|
+
worstStartLagMs: number | null;
|
|
516
|
+
/** How long the oldest run that never started has been queued. */
|
|
517
|
+
oldestQueuedMs: number | null;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* One row per app, answering "is anything broken right now".
|
|
522
|
+
*
|
|
523
|
+
* Deliberately a single query per concern rather than a per-app port: the
|
|
524
|
+
* point of moving storage into the kernel is that this stops being a protocol.
|
|
525
|
+
*/
|
|
526
|
+
export const workflowHealth = async (options: { since?: Date; db?: SQL } = {}): Promise<WorkflowAppHealth[]> => {
|
|
527
|
+
const db = options.db ?? sql;
|
|
528
|
+
const since = options.since ?? new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
529
|
+
|
|
530
|
+
const [runs, stranded, events] = await Promise.all([
|
|
531
|
+
db<
|
|
532
|
+
{
|
|
533
|
+
app_id: string;
|
|
534
|
+
state: WorkflowRunState;
|
|
535
|
+
count: string;
|
|
536
|
+
worst_lag_ms: number | null;
|
|
537
|
+
oldest_queued_ms: number | null;
|
|
538
|
+
}[]
|
|
539
|
+
>`
|
|
540
|
+
SELECT app_id, state, count(*) AS count,
|
|
541
|
+
max(EXTRACT(EPOCH FROM (started_at - occurred_at)) * 1000)::bigint AS worst_lag_ms,
|
|
542
|
+
(max(EXTRACT(EPOCH FROM (now() - created_at)) * 1000)
|
|
543
|
+
FILTER (WHERE state = 'queued'))::bigint AS oldest_queued_ms
|
|
544
|
+
FROM workflows.run WHERE created_at >= ${since} GROUP BY app_id, state
|
|
545
|
+
`,
|
|
546
|
+
db<{ app_id: string; count: string }[]>`
|
|
547
|
+
SELECT r.app_id, count(*) AS count FROM workflows.step_outcome AS s
|
|
548
|
+
JOIN workflows.run AS r ON r.id = s.run_id
|
|
549
|
+
WHERE s.effect_state IN ('executing', 'ambiguous') GROUP BY r.app_id
|
|
550
|
+
`,
|
|
551
|
+
db<{ app_id: string; count: string; oldest: Date }[]>`
|
|
552
|
+
SELECT app_id, count(*) AS count, min(occurred_at) AS oldest
|
|
553
|
+
FROM workflows.event
|
|
554
|
+
WHERE matched_count = 0 OR dispatched_at IS NULL
|
|
555
|
+
GROUP BY app_id
|
|
556
|
+
`,
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
const byApp = new Map<string, WorkflowAppHealth>();
|
|
560
|
+
const health = (appId: string): WorkflowAppHealth => {
|
|
561
|
+
let entry = byApp.get(appId);
|
|
562
|
+
if (!entry) {
|
|
563
|
+
entry = {
|
|
564
|
+
appId,
|
|
565
|
+
runs: { ...EMPTY_CHILD_COUNTS },
|
|
566
|
+
strandedEffects: 0,
|
|
567
|
+
undispatchedEvents: 0,
|
|
568
|
+
oldestUndispatchedMs: null,
|
|
569
|
+
worstStartLagMs: null,
|
|
570
|
+
oldestQueuedMs: null,
|
|
571
|
+
};
|
|
572
|
+
byApp.set(appId, entry);
|
|
573
|
+
}
|
|
574
|
+
return entry;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const now = Date.now();
|
|
578
|
+
for (const row of runs) {
|
|
579
|
+
const entry = health(row.app_id);
|
|
580
|
+
entry.runs[row.state] = Number(row.count);
|
|
581
|
+
const lag = row.worst_lag_ms === null ? null : Number(row.worst_lag_ms);
|
|
582
|
+
if (lag !== null) entry.worstStartLagMs = Math.max(entry.worstStartLagMs ?? 0, lag);
|
|
583
|
+
const queued = row.oldest_queued_ms === null ? null : Number(row.oldest_queued_ms);
|
|
584
|
+
if (queued !== null) entry.oldestQueuedMs = Math.max(entry.oldestQueuedMs ?? 0, queued);
|
|
585
|
+
}
|
|
586
|
+
for (const row of stranded) health(row.app_id).strandedEffects = Number(row.count);
|
|
587
|
+
for (const row of events) {
|
|
588
|
+
const entry = health(row.app_id);
|
|
589
|
+
entry.undispatchedEvents = Number(row.count);
|
|
590
|
+
entry.oldestUndispatchedMs = Math.max(0, now - row.oldest.getTime());
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return [...byApp.values()].sort((left, right) => left.appId.localeCompare(right.appId));
|
|
594
|
+
};
|