@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,605 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { type CloudResourceRef, CloudResourceRefSchema } from "../contracts/capabilities";
|
|
3
|
+
import { logger } from "../services/logging";
|
|
4
|
+
import { withAiShortId } from "./short-id";
|
|
5
|
+
|
|
6
|
+
export const AI_MEMORY_CONTENT_MAX_CHARS = 500;
|
|
7
|
+
export const AI_MEMORY_HOT_MAX_ITEMS = 20;
|
|
8
|
+
export const AI_MEMORY_HOT_MAX_CHARS = 6_000;
|
|
9
|
+
|
|
10
|
+
export type AiMemoryKind = "fact" | "preference" | "workflow";
|
|
11
|
+
export type AiMemoryPriority = "normal" | "pinned";
|
|
12
|
+
export type AiMemorySource = "user" | "agent" | "background";
|
|
13
|
+
|
|
14
|
+
export type AiBackgroundMemoryProposal = {
|
|
15
|
+
action: "add" | "replace" | "merge" | "retire";
|
|
16
|
+
kind: AiMemoryKind;
|
|
17
|
+
content: string;
|
|
18
|
+
memoryIds: string[];
|
|
19
|
+
resourceRef: CloudResourceRef | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AiBackgroundMemoryChange = {
|
|
23
|
+
action: "added" | "updated" | "merged" | "retired";
|
|
24
|
+
kind: AiMemoryKind;
|
|
25
|
+
content: string;
|
|
26
|
+
previousContent?: string;
|
|
27
|
+
resourceRef?: CloudResourceRef;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type AiMemory = {
|
|
31
|
+
id: string;
|
|
32
|
+
shortId: string;
|
|
33
|
+
userId: string;
|
|
34
|
+
kind: AiMemoryKind;
|
|
35
|
+
content: string;
|
|
36
|
+
priority: AiMemoryPriority;
|
|
37
|
+
source: AiMemorySource;
|
|
38
|
+
sourceConversationId: string | null;
|
|
39
|
+
sourceMessageId: string | null;
|
|
40
|
+
resourceRef: CloudResourceRef | null;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type MemoryRow = {
|
|
46
|
+
id: string;
|
|
47
|
+
short_id: string;
|
|
48
|
+
user_id: string;
|
|
49
|
+
kind: AiMemoryKind;
|
|
50
|
+
content: string;
|
|
51
|
+
priority: AiMemoryPriority;
|
|
52
|
+
source: AiMemorySource;
|
|
53
|
+
source_conversation_id: string | null;
|
|
54
|
+
source_message_id: string | null;
|
|
55
|
+
resource_type: string | null;
|
|
56
|
+
resource_id: string | null;
|
|
57
|
+
created_at: string | Date;
|
|
58
|
+
updated_at: string | Date;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type SearchBackend = "native" | "bm25";
|
|
62
|
+
|
|
63
|
+
const BM25_INDEX = "ai.memories_search_bm25_idx";
|
|
64
|
+
const BM25_CAPABILITY_ERROR_CODES = new Set(["0A000", "42704", "42883", "55000"]);
|
|
65
|
+
const log = logger("ai:memories");
|
|
66
|
+
|
|
67
|
+
let backendPromise: Promise<SearchBackend> | null = null;
|
|
68
|
+
|
|
69
|
+
const toMemory = (row: MemoryRow): AiMemory => ({
|
|
70
|
+
id: row.id,
|
|
71
|
+
shortId: row.short_id,
|
|
72
|
+
userId: row.user_id,
|
|
73
|
+
kind: row.kind,
|
|
74
|
+
content: row.content,
|
|
75
|
+
priority: row.priority,
|
|
76
|
+
source: row.source,
|
|
77
|
+
sourceConversationId: row.source_conversation_id,
|
|
78
|
+
sourceMessageId: row.source_message_id,
|
|
79
|
+
resourceRef: row.resource_type && row.resource_id ? { type: row.resource_type, id: row.resource_id } : null,
|
|
80
|
+
createdAt: new Date(row.created_at).toISOString(),
|
|
81
|
+
updatedAt: new Date(row.updated_at).toISOString(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const normalizeContent = (content: string): string => content.replace(/\s+/g, " ").trim().slice(0, AI_MEMORY_CONTENT_MAX_CHARS);
|
|
85
|
+
|
|
86
|
+
const detectSearchBackend = async (): Promise<SearchBackend> => {
|
|
87
|
+
const [row] = await sql<{ available: boolean }[]>`
|
|
88
|
+
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_textsearch')
|
|
89
|
+
AND EXISTS (
|
|
90
|
+
SELECT 1
|
|
91
|
+
FROM pg_class index_class
|
|
92
|
+
JOIN pg_am access_method ON access_method.oid = index_class.relam
|
|
93
|
+
WHERE index_class.oid = to_regclass(${BM25_INDEX})
|
|
94
|
+
AND access_method.amname = 'bm25'
|
|
95
|
+
) AS available
|
|
96
|
+
`;
|
|
97
|
+
const backend = row?.available ? "bm25" : "native";
|
|
98
|
+
log.info("Memory search backend active", { backend });
|
|
99
|
+
return backend;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const getAiMemorySearchBackend = (): Promise<SearchBackend> => {
|
|
103
|
+
backendPromise ??= detectSearchBackend().catch((error) => {
|
|
104
|
+
log.warn("Memory search backend detection failed; using native PostgreSQL FTS", {
|
|
105
|
+
error: error instanceof Error ? error.message : String(error),
|
|
106
|
+
});
|
|
107
|
+
return "native";
|
|
108
|
+
});
|
|
109
|
+
return backendPromise;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Test and migration hook. Runtime callers should use getAiMemorySearchBackend(). */
|
|
113
|
+
export const resetAiMemorySearchBackend = (): void => {
|
|
114
|
+
backendPromise = null;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const isAiMemoryBm25CapabilityError = (error: unknown): boolean => {
|
|
118
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return false;
|
|
119
|
+
return BM25_CAPABILITY_ERROR_CODES.has(String(error.code));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const listActive = async (userId: string, limit: number): Promise<AiMemory[]> => {
|
|
123
|
+
const rows = await sql<MemoryRow[]>`
|
|
124
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
125
|
+
resource_type, resource_id, created_at, updated_at
|
|
126
|
+
FROM ai.memories
|
|
127
|
+
WHERE user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
128
|
+
ORDER BY (priority = 'pinned') DESC, updated_at DESC, id ASC
|
|
129
|
+
LIMIT ${limit}
|
|
130
|
+
`;
|
|
131
|
+
return rows.map(toMemory);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const searchRows = async (input: { userId: string; query: string; limit: number; backend: SearchBackend }): Promise<AiMemory[]> => {
|
|
135
|
+
const query = input.query.trim();
|
|
136
|
+
if (!query) return listActive(input.userId, input.limit);
|
|
137
|
+
|
|
138
|
+
const rows =
|
|
139
|
+
input.backend === "bm25"
|
|
140
|
+
? await sql<MemoryRow[]>`
|
|
141
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
142
|
+
resource_type, resource_id, created_at, updated_at
|
|
143
|
+
FROM ai.memories
|
|
144
|
+
WHERE user_id = ${input.userId}::uuid
|
|
145
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
146
|
+
AND search_document @@ websearch_to_tsquery('simple', ${query})
|
|
147
|
+
ORDER BY
|
|
148
|
+
(priority = 'pinned') DESC,
|
|
149
|
+
content <@> to_bm25query(${query}, ${BM25_INDEX}),
|
|
150
|
+
updated_at DESC,
|
|
151
|
+
id ASC
|
|
152
|
+
LIMIT ${input.limit}
|
|
153
|
+
`
|
|
154
|
+
: await sql<MemoryRow[]>`
|
|
155
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
156
|
+
resource_type, resource_id, created_at, updated_at
|
|
157
|
+
FROM ai.memories
|
|
158
|
+
WHERE user_id = ${input.userId}::uuid
|
|
159
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
160
|
+
AND search_document @@ websearch_to_tsquery('simple', ${query})
|
|
161
|
+
ORDER BY
|
|
162
|
+
(priority = 'pinned') DESC,
|
|
163
|
+
ts_rank_cd(search_document, websearch_to_tsquery('simple', ${query})) DESC,
|
|
164
|
+
updated_at DESC,
|
|
165
|
+
id ASC
|
|
166
|
+
LIMIT ${input.limit}
|
|
167
|
+
`;
|
|
168
|
+
return rows.map(toMemory);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const search = async (input: { userId: string; query?: string; limit?: number }): Promise<AiMemory[]> => {
|
|
172
|
+
const query = input.query?.trim() ?? "";
|
|
173
|
+
const limit = Math.min(Math.max(input.limit ?? 20, 1), 50);
|
|
174
|
+
const backend = query ? await getAiMemorySearchBackend() : "native";
|
|
175
|
+
try {
|
|
176
|
+
return await searchRows({ userId: input.userId, query, limit, backend });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (backend !== "bm25" || !isAiMemoryBm25CapabilityError(error)) throw error;
|
|
179
|
+
log.warn("Memory BM25 query failed; falling back to native PostgreSQL FTS", {
|
|
180
|
+
error: error instanceof Error ? error.message : String(error),
|
|
181
|
+
});
|
|
182
|
+
backendPromise = Promise.resolve("native");
|
|
183
|
+
return searchRows({ userId: input.userId, query, limit, backend: "native" });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const formatAiMemories = (
|
|
188
|
+
memories: AiMemory[],
|
|
189
|
+
maxChars = AI_MEMORY_HOT_MAX_CHARS,
|
|
190
|
+
): { text: string; included: AiMemory[]; truncated: boolean } => {
|
|
191
|
+
const lines: string[] = [];
|
|
192
|
+
const included: AiMemory[] = [];
|
|
193
|
+
let used = 0;
|
|
194
|
+
for (const memory of memories) {
|
|
195
|
+
const date = memory.updatedAt.slice(0, 10);
|
|
196
|
+
const resource = memory.resourceRef ? ` [Cloud resource ${memory.resourceRef.type}:${memory.resourceRef.id}]` : "";
|
|
197
|
+
const line = `- [${date}] ${memory.kind}: ${memory.content}${resource}`;
|
|
198
|
+
if (used + line.length + (lines.length > 0 ? 1 : 0) > maxChars) break;
|
|
199
|
+
lines.push(line);
|
|
200
|
+
included.push(memory);
|
|
201
|
+
used += line.length + (lines.length > 1 ? 1 : 0);
|
|
202
|
+
}
|
|
203
|
+
return { text: lines.join("\n"), included, truncated: included.length < memories.length };
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const aiMemories = {
|
|
207
|
+
list: search,
|
|
208
|
+
|
|
209
|
+
async resolveSourceConversationShortIds(userId: string, conversationIds: string[]): Promise<Map<string, string>> {
|
|
210
|
+
if (conversationIds.length === 0) return new Map();
|
|
211
|
+
const rows = await sql<{ id: string; short_id: string }[]>`
|
|
212
|
+
SELECT id, short_id
|
|
213
|
+
FROM ai.conversations
|
|
214
|
+
WHERE created_by_user_id = ${userId}::uuid AND id IN ${sql(conversationIds)}
|
|
215
|
+
`;
|
|
216
|
+
return new Map(rows.map((row) => [row.id, row.short_id]));
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
async wasDeleted(userId: string, content: string): Promise<boolean> {
|
|
220
|
+
const normalized = normalizeContent(content);
|
|
221
|
+
if (!normalized) return false;
|
|
222
|
+
const [row] = await sql<{ exists: boolean }[]>`
|
|
223
|
+
SELECT EXISTS (
|
|
224
|
+
SELECT 1 FROM ai.memories
|
|
225
|
+
WHERE user_id = ${userId}::uuid AND lower(content) = lower(${normalized}) AND deleted_at IS NOT NULL
|
|
226
|
+
) AS exists
|
|
227
|
+
`;
|
|
228
|
+
return Boolean(row?.exists);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async get(userId: string, memoryId: string): Promise<AiMemory | null> {
|
|
232
|
+
const rows = await sql<MemoryRow[]>`
|
|
233
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
234
|
+
resource_type, resource_id, created_at, updated_at
|
|
235
|
+
FROM ai.memories
|
|
236
|
+
WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
237
|
+
`;
|
|
238
|
+
return rows[0] ? toMemory(rows[0]) : null;
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async getByShortId(userId: string, shortId: string): Promise<AiMemory | null> {
|
|
242
|
+
const rows = await sql<MemoryRow[]>`
|
|
243
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
244
|
+
resource_type, resource_id, created_at, updated_at
|
|
245
|
+
FROM ai.memories
|
|
246
|
+
WHERE short_id = ${shortId} AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
247
|
+
`;
|
|
248
|
+
return rows[0] ? toMemory(rows[0]) : null;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
async create(input: {
|
|
252
|
+
userId: string;
|
|
253
|
+
kind: AiMemoryKind;
|
|
254
|
+
content: string;
|
|
255
|
+
priority?: AiMemoryPriority;
|
|
256
|
+
source?: AiMemorySource;
|
|
257
|
+
sourceConversationId?: string;
|
|
258
|
+
sourceMessageId?: string;
|
|
259
|
+
resourceRef?: CloudResourceRef;
|
|
260
|
+
}): Promise<AiMemory> {
|
|
261
|
+
const content = normalizeContent(input.content);
|
|
262
|
+
if (!content) throw new Error("Memory content is required.");
|
|
263
|
+
if (input.sourceMessageId && !input.sourceConversationId) {
|
|
264
|
+
throw new Error("A memory source message requires its source conversation.");
|
|
265
|
+
}
|
|
266
|
+
const resourceRef = input.resourceRef ? CloudResourceRefSchema.parse(input.resourceRef) : null;
|
|
267
|
+
if ((input.kind === "workflow") !== Boolean(resourceRef)) throw new Error("Workflow memories require exactly one Cloud resource.");
|
|
268
|
+
const rows = await withAiShortId(
|
|
269
|
+
"idx_ai_memories_short_id",
|
|
270
|
+
(shortId) => sql<MemoryRow[]>`
|
|
271
|
+
INSERT INTO ai.memories (
|
|
272
|
+
short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id, resource_type, resource_id
|
|
273
|
+
)
|
|
274
|
+
SELECT
|
|
275
|
+
${shortId},
|
|
276
|
+
${input.userId}::uuid,
|
|
277
|
+
${input.kind},
|
|
278
|
+
${content},
|
|
279
|
+
${input.priority ?? "normal"},
|
|
280
|
+
${input.source ?? "user"},
|
|
281
|
+
${input.sourceConversationId ?? null}::uuid,
|
|
282
|
+
${input.sourceMessageId ?? null}::uuid,
|
|
283
|
+
${resourceRef?.type ?? null},
|
|
284
|
+
${resourceRef?.id ?? null}
|
|
285
|
+
WHERE ${input.sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
|
|
286
|
+
SELECT 1 FROM ai.conversations c
|
|
287
|
+
WHERE c.id = ${input.sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${input.userId}::uuid
|
|
288
|
+
AND (${input.sourceMessageId ?? null}::uuid IS NULL OR EXISTS (
|
|
289
|
+
SELECT 1 FROM ai.messages m
|
|
290
|
+
WHERE m.id = ${input.sourceMessageId ?? null}::uuid AND m.conversation_id = c.id
|
|
291
|
+
))
|
|
292
|
+
)
|
|
293
|
+
ON CONFLICT (user_id, lower(content)) WHERE deleted_at IS NULL AND superseded_by_id IS NULL
|
|
294
|
+
DO UPDATE SET updated_at = ai.memories.updated_at
|
|
295
|
+
RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
296
|
+
resource_type, resource_id, created_at, updated_at
|
|
297
|
+
`,
|
|
298
|
+
);
|
|
299
|
+
if (!rows[0]) throw new Error("Memory source conversation is unavailable.");
|
|
300
|
+
return toMemory(rows[0]!);
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
async update(
|
|
304
|
+
userId: string,
|
|
305
|
+
memoryId: string,
|
|
306
|
+
patch: {
|
|
307
|
+
kind?: AiMemoryKind;
|
|
308
|
+
content?: string;
|
|
309
|
+
priority?: AiMemoryPriority;
|
|
310
|
+
source?: AiMemorySource;
|
|
311
|
+
sourceConversationId?: string;
|
|
312
|
+
resourceRef?: CloudResourceRef | null;
|
|
313
|
+
},
|
|
314
|
+
): Promise<AiMemory | null> {
|
|
315
|
+
const content = patch.content === undefined ? null : normalizeContent(patch.content);
|
|
316
|
+
if (patch.content !== undefined && !content) throw new Error("Memory content is required.");
|
|
317
|
+
const resourceRef = patch.resourceRef ? CloudResourceRefSchema.parse(patch.resourceRef) : null;
|
|
318
|
+
const rows = await sql<MemoryRow[]>`
|
|
319
|
+
UPDATE ai.memories
|
|
320
|
+
SET kind = COALESCE(${patch.kind ?? null}, kind),
|
|
321
|
+
content = COALESCE(${content}, content),
|
|
322
|
+
priority = COALESCE(${patch.priority ?? null}, priority),
|
|
323
|
+
source = COALESCE(${patch.source ?? null}, source),
|
|
324
|
+
source_conversation_id = COALESCE(${patch.sourceConversationId ?? null}::uuid, source_conversation_id),
|
|
325
|
+
resource_type = CASE WHEN ${patch.resourceRef === undefined} THEN resource_type ELSE ${resourceRef?.type ?? null} END,
|
|
326
|
+
resource_id = CASE WHEN ${patch.resourceRef === undefined} THEN resource_id ELSE ${resourceRef?.id ?? null} END,
|
|
327
|
+
updated_at = now()
|
|
328
|
+
WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
329
|
+
AND (${patch.sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
|
|
330
|
+
SELECT 1 FROM ai.conversations c
|
|
331
|
+
WHERE c.id = ${patch.sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${userId}::uuid
|
|
332
|
+
))
|
|
333
|
+
RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
334
|
+
resource_type, resource_id, created_at, updated_at
|
|
335
|
+
`;
|
|
336
|
+
return rows[0] ? toMemory(rows[0]) : null;
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
async updateByShortId(
|
|
340
|
+
userId: string,
|
|
341
|
+
shortId: string,
|
|
342
|
+
patch: {
|
|
343
|
+
kind?: AiMemoryKind;
|
|
344
|
+
content?: string;
|
|
345
|
+
priority?: AiMemoryPriority;
|
|
346
|
+
source?: AiMemorySource;
|
|
347
|
+
sourceConversationId?: string;
|
|
348
|
+
resourceRef?: CloudResourceRef | null;
|
|
349
|
+
},
|
|
350
|
+
): Promise<AiMemory | null> {
|
|
351
|
+
const memory = await this.getByShortId(userId, shortId);
|
|
352
|
+
return memory ? this.update(userId, memory.id, patch) : null;
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
async delete(userId: string, memoryId: string): Promise<boolean> {
|
|
356
|
+
const rows = await sql<{ id: string }[]>`
|
|
357
|
+
UPDATE ai.memories SET deleted_at = now(), updated_at = now()
|
|
358
|
+
WHERE id = ${memoryId}::uuid AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
359
|
+
RETURNING id
|
|
360
|
+
`;
|
|
361
|
+
return Boolean(rows[0]);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
async deleteByShortId(userId: string, shortId: string): Promise<boolean> {
|
|
365
|
+
const rows = await sql<{ id: string }[]>`
|
|
366
|
+
UPDATE ai.memories SET deleted_at = now(), updated_at = now()
|
|
367
|
+
WHERE short_id = ${shortId} AND user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
368
|
+
RETURNING id
|
|
369
|
+
`;
|
|
370
|
+
return Boolean(rows[0]);
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async supersede(userId: string, memoryId: string, supersededById: string, sourceConversationId?: string): Promise<boolean> {
|
|
374
|
+
if (memoryId === supersededById) return false;
|
|
375
|
+
const rows = await sql<{ id: string }[]>`
|
|
376
|
+
UPDATE ai.memories old
|
|
377
|
+
SET superseded_by_id = newer.id,
|
|
378
|
+
source = 'background',
|
|
379
|
+
source_conversation_id = COALESCE(${sourceConversationId ?? null}::uuid, old.source_conversation_id),
|
|
380
|
+
updated_at = now()
|
|
381
|
+
FROM ai.memories newer
|
|
382
|
+
WHERE old.id = ${memoryId}::uuid
|
|
383
|
+
AND old.user_id = ${userId}::uuid
|
|
384
|
+
AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
|
|
385
|
+
AND newer.id = ${supersededById}::uuid
|
|
386
|
+
AND newer.user_id = old.user_id
|
|
387
|
+
AND newer.deleted_at IS NULL AND newer.superseded_by_id IS NULL
|
|
388
|
+
AND (${sourceConversationId ?? null}::uuid IS NULL OR EXISTS (
|
|
389
|
+
SELECT 1 FROM ai.conversations c
|
|
390
|
+
WHERE c.id = ${sourceConversationId ?? null}::uuid AND c.created_by_user_id = ${userId}::uuid
|
|
391
|
+
))
|
|
392
|
+
RETURNING old.id
|
|
393
|
+
`;
|
|
394
|
+
return Boolean(rows[0]);
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Apply one model proposal as an ownership-checked transaction. Background
|
|
399
|
+
* learning may only mutate normal background memories; user, agent, and
|
|
400
|
+
* pinned entries remain authoritative.
|
|
401
|
+
*/
|
|
402
|
+
async applyBackgroundProposal(input: {
|
|
403
|
+
userId: string;
|
|
404
|
+
sourceConversationId: string;
|
|
405
|
+
sourceMessageId?: string;
|
|
406
|
+
proposal: AiBackgroundMemoryProposal;
|
|
407
|
+
}): Promise<AiBackgroundMemoryChange[]> {
|
|
408
|
+
const proposal = input.proposal;
|
|
409
|
+
const content = normalizeContent(proposal.content);
|
|
410
|
+
const memoryIds = [...new Set(proposal.memoryIds.map((id) => id.trim()).filter(Boolean))].slice(0, 5);
|
|
411
|
+
const resourceRef = proposal.resourceRef ? CloudResourceRefSchema.parse(proposal.resourceRef) : null;
|
|
412
|
+
const needsContent = proposal.action !== "retire";
|
|
413
|
+
if (needsContent && !content) return [];
|
|
414
|
+
if ((proposal.kind === "workflow") !== Boolean(resourceRef)) return [];
|
|
415
|
+
if (proposal.action === "add" && memoryIds.length > 0) return [];
|
|
416
|
+
if (proposal.action === "replace" && memoryIds.length !== 1) return [];
|
|
417
|
+
if (proposal.action === "merge" && memoryIds.length < 2) return [];
|
|
418
|
+
if (proposal.action === "retire" && memoryIds.length < 1) return [];
|
|
419
|
+
|
|
420
|
+
return sql.begin(async (tx) => {
|
|
421
|
+
const [source] = await tx<{ id: string }[]>`
|
|
422
|
+
SELECT c.id
|
|
423
|
+
FROM ai.conversations c
|
|
424
|
+
WHERE c.id = ${input.sourceConversationId}::uuid
|
|
425
|
+
AND c.created_by_user_id = ${input.userId}::uuid
|
|
426
|
+
AND (${input.sourceMessageId ?? null}::uuid IS NULL OR EXISTS (
|
|
427
|
+
SELECT 1 FROM ai.messages m
|
|
428
|
+
WHERE m.id = ${input.sourceMessageId ?? null}::uuid AND m.conversation_id = c.id
|
|
429
|
+
))
|
|
430
|
+
FOR UPDATE
|
|
431
|
+
`;
|
|
432
|
+
if (!source) return [];
|
|
433
|
+
|
|
434
|
+
const targets = memoryIds.length
|
|
435
|
+
? await tx<MemoryRow[]>`
|
|
436
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
437
|
+
resource_type, resource_id, created_at, updated_at
|
|
438
|
+
FROM ai.memories
|
|
439
|
+
WHERE user_id = ${input.userId}::uuid
|
|
440
|
+
AND short_id IN ${tx(memoryIds)}
|
|
441
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
442
|
+
FOR UPDATE
|
|
443
|
+
`
|
|
444
|
+
: [];
|
|
445
|
+
if (targets.length !== memoryIds.length || targets.some((memory) => memory.source !== "background" || memory.priority !== "normal")) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
if (targets.some((memory) => memory.kind !== proposal.kind)) return [];
|
|
449
|
+
|
|
450
|
+
if (proposal.action === "retire") {
|
|
451
|
+
await tx`
|
|
452
|
+
UPDATE ai.memories
|
|
453
|
+
SET deleted_at = now(), source_conversation_id = ${input.sourceConversationId}::uuid, updated_at = now()
|
|
454
|
+
WHERE user_id = ${input.userId}::uuid AND id IN ${tx(targets.map((memory) => memory.id))}
|
|
455
|
+
AND source = 'background' AND priority = 'normal'
|
|
456
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
457
|
+
`;
|
|
458
|
+
return targets.map((memory) => ({
|
|
459
|
+
action: "retired" as const,
|
|
460
|
+
kind: memory.kind,
|
|
461
|
+
content: memory.content,
|
|
462
|
+
...(memory.resource_type && memory.resource_id
|
|
463
|
+
? { resourceRef: { type: memory.resource_type, id: memory.resource_id } }
|
|
464
|
+
: {}),
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const [duplicate] = await tx<MemoryRow[]>`
|
|
469
|
+
SELECT id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
470
|
+
resource_type, resource_id, created_at, updated_at
|
|
471
|
+
FROM ai.memories
|
|
472
|
+
WHERE user_id = ${input.userId}::uuid AND lower(content) = lower(${content})
|
|
473
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
474
|
+
FOR UPDATE
|
|
475
|
+
`;
|
|
476
|
+
|
|
477
|
+
if (proposal.action === "add") {
|
|
478
|
+
if (duplicate) {
|
|
479
|
+
if (duplicate.source === "background" && duplicate.priority === "normal") {
|
|
480
|
+
await tx`
|
|
481
|
+
UPDATE ai.memories
|
|
482
|
+
SET updated_at = now()
|
|
483
|
+
WHERE id = ${duplicate.id}::uuid AND user_id = ${input.userId}::uuid
|
|
484
|
+
`;
|
|
485
|
+
}
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
const [deleted] = await tx<{ exists: boolean }[]>`
|
|
489
|
+
SELECT EXISTS (
|
|
490
|
+
SELECT 1 FROM ai.memories
|
|
491
|
+
WHERE user_id = ${input.userId}::uuid AND lower(content) = lower(${content}) AND deleted_at IS NOT NULL
|
|
492
|
+
) AS exists
|
|
493
|
+
`;
|
|
494
|
+
if (deleted?.exists) return [];
|
|
495
|
+
const rows = await withAiShortId(
|
|
496
|
+
"idx_ai_memories_short_id",
|
|
497
|
+
(shortId) => tx<MemoryRow[]>`
|
|
498
|
+
INSERT INTO ai.memories (
|
|
499
|
+
short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
500
|
+
resource_type, resource_id
|
|
501
|
+
) VALUES (
|
|
502
|
+
${shortId}, ${input.userId}::uuid, ${proposal.kind}, ${content}, 'normal', 'background',
|
|
503
|
+
${input.sourceConversationId}::uuid, ${input.sourceMessageId ?? null}::uuid,
|
|
504
|
+
${resourceRef?.type ?? null}, ${resourceRef?.id ?? null}
|
|
505
|
+
)
|
|
506
|
+
RETURNING id, short_id, user_id, kind, content, priority, source, source_conversation_id, source_message_id,
|
|
507
|
+
resource_type, resource_id, created_at, updated_at
|
|
508
|
+
`,
|
|
509
|
+
);
|
|
510
|
+
const created = rows[0];
|
|
511
|
+
return created
|
|
512
|
+
? [{
|
|
513
|
+
action: "added",
|
|
514
|
+
kind: created.kind,
|
|
515
|
+
content: created.content,
|
|
516
|
+
...(resourceRef ? { resourceRef } : {}),
|
|
517
|
+
}]
|
|
518
|
+
: [];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (duplicate && !targets.some((memory) => memory.id === duplicate.id)) {
|
|
522
|
+
const duplicateResourceMatches =
|
|
523
|
+
proposal.kind !== "workflow" ||
|
|
524
|
+
(duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
|
|
525
|
+
if (duplicate.kind !== proposal.kind || !duplicateResourceMatches) return [];
|
|
526
|
+
await tx`
|
|
527
|
+
UPDATE ai.memories old
|
|
528
|
+
SET superseded_by_id = ${duplicate.id}::uuid,
|
|
529
|
+
source_conversation_id = ${input.sourceConversationId}::uuid,
|
|
530
|
+
updated_at = now()
|
|
531
|
+
WHERE old.user_id = ${input.userId}::uuid AND old.id IN ${tx(targets.map((memory) => memory.id))}
|
|
532
|
+
AND old.source = 'background' AND old.priority = 'normal'
|
|
533
|
+
AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
|
|
534
|
+
`;
|
|
535
|
+
return [{
|
|
536
|
+
action: "merged",
|
|
537
|
+
kind: duplicate.kind,
|
|
538
|
+
content: duplicate.content,
|
|
539
|
+
previousContent: targets.map((memory) => memory.content).join(" · "),
|
|
540
|
+
...(duplicate.resource_type && duplicate.resource_id
|
|
541
|
+
? { resourceRef: { type: duplicate.resource_type, id: duplicate.resource_id } }
|
|
542
|
+
: {}),
|
|
543
|
+
}];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const destination = targets[0]!;
|
|
547
|
+
await tx`
|
|
548
|
+
UPDATE ai.memories
|
|
549
|
+
SET kind = ${proposal.kind}, content = ${content}, resource_type = ${resourceRef?.type ?? null},
|
|
550
|
+
resource_id = ${resourceRef?.id ?? null}, source_conversation_id = ${input.sourceConversationId}::uuid,
|
|
551
|
+
source_message_id = COALESCE(${input.sourceMessageId ?? null}::uuid, source_message_id),
|
|
552
|
+
updated_at = now()
|
|
553
|
+
WHERE id = ${destination.id}::uuid AND user_id = ${input.userId}::uuid
|
|
554
|
+
AND source = 'background' AND priority = 'normal'
|
|
555
|
+
`;
|
|
556
|
+
const superseded = targets.slice(1);
|
|
557
|
+
if (superseded.length > 0) {
|
|
558
|
+
await tx`
|
|
559
|
+
UPDATE ai.memories
|
|
560
|
+
SET superseded_by_id = ${destination.id}::uuid,
|
|
561
|
+
source_conversation_id = ${input.sourceConversationId}::uuid,
|
|
562
|
+
updated_at = now()
|
|
563
|
+
WHERE user_id = ${input.userId}::uuid AND id IN ${tx(superseded.map((memory) => memory.id))}
|
|
564
|
+
AND source = 'background' AND priority = 'normal'
|
|
565
|
+
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
566
|
+
`;
|
|
567
|
+
}
|
|
568
|
+
return [{
|
|
569
|
+
action: proposal.action === "merge" ? "merged" : "updated",
|
|
570
|
+
kind: proposal.kind,
|
|
571
|
+
content,
|
|
572
|
+
previousContent: targets.map((memory) => memory.content).join(" · "),
|
|
573
|
+
...(resourceRef ? { resourceRef } : {}),
|
|
574
|
+
}];
|
|
575
|
+
});
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
async selectHot(userId: string, query: string): Promise<{ text: string; memories: AiMemory[]; truncated: boolean }> {
|
|
579
|
+
const [countRow] = await sql<{ count: number }[]>`
|
|
580
|
+
SELECT COUNT(*)::int AS count
|
|
581
|
+
FROM ai.memories
|
|
582
|
+
WHERE user_id = ${userId}::uuid AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
583
|
+
`;
|
|
584
|
+
const count = countRow?.count ?? 0;
|
|
585
|
+
if (count <= AI_MEMORY_HOT_MAX_ITEMS) {
|
|
586
|
+
const memories = await listActive(userId, AI_MEMORY_HOT_MAX_ITEMS);
|
|
587
|
+
const formatted = formatAiMemories(memories);
|
|
588
|
+
return { text: formatted.text, memories: formatted.included, truncated: formatted.truncated };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const [pinned, relevant, recent] = await Promise.all([
|
|
592
|
+
listActive(userId, 50).then((items) => items.filter((item) => item.priority === "pinned")),
|
|
593
|
+
search({ userId, query, limit: 40 }),
|
|
594
|
+
listActive(userId, 40),
|
|
595
|
+
]);
|
|
596
|
+
const seen = new Set<string>();
|
|
597
|
+
const ranked = [...pinned, ...relevant, ...recent].filter((memory) => {
|
|
598
|
+
if (seen.has(memory.id)) return false;
|
|
599
|
+
seen.add(memory.id);
|
|
600
|
+
return true;
|
|
601
|
+
});
|
|
602
|
+
const formatted = formatAiMemories(ranked.slice(0, AI_MEMORY_HOT_MAX_ITEMS));
|
|
603
|
+
return { text: formatted.text, memories: formatted.included, truncated: count > formatted.included.length };
|
|
604
|
+
},
|
|
605
|
+
};
|