@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,619 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { CloudResourceRefSchema } from "../contracts/capabilities";
|
|
5
|
+
import { coreSettings } from "../services";
|
|
6
|
+
import { logger } from "../services/logging";
|
|
7
|
+
import { normalizeLocale } from "../shared/locale";
|
|
8
|
+
import { parseAiAttachmentMarkers } from "./attachments";
|
|
9
|
+
import { type AiMemoryLearningChange, aiMemoryLearningRuns } from "./memory-learning-runs";
|
|
10
|
+
import {
|
|
11
|
+
listAiPendingWorkflowPatterns,
|
|
12
|
+
listAiTurnWorkflowEvidence,
|
|
13
|
+
markAiWorkflowPatternReviewed,
|
|
14
|
+
type AiMemoryWorkflowEvidence,
|
|
15
|
+
type AiMemoryWorkflowPattern,
|
|
16
|
+
} from "./memory-workflow-evidence";
|
|
17
|
+
import { type AiBackgroundMemoryProposal, aiMemories } from "./memories";
|
|
18
|
+
import { aiConversations } from "./store";
|
|
19
|
+
import type { RunAiStructuredInput, RunAiStructuredResult } from "./structured";
|
|
20
|
+
import { resolveAiBackgroundModel, runAiStructured } from "./structured";
|
|
21
|
+
import { AI_MEMORY_LEARNING_INSTRUCTIONS_SETTING_KEY, buildAiTaskPrompt } from "./task-prompt";
|
|
22
|
+
import type { AiResolvedModel, AiStoredMessage } from "./types";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_BATCH_LIMIT = 10;
|
|
25
|
+
const WORKFLOW_PATTERN_LIMIT = 3;
|
|
26
|
+
const USER_TEXT_MAX_CHARS = 6_000;
|
|
27
|
+
const FINAL_ASSISTANT_MAX_CHARS = 1_500;
|
|
28
|
+
const WORKFLOW_EXAMPLE_MAX_CHARS = 2_000;
|
|
29
|
+
const MAX_OUTPUT_TOKENS = 700;
|
|
30
|
+
export const AI_MEMORY_LEARNING_MONTHLY_TOKEN_BUDGET_SETTING_KEY = "ai.memory_learning_monthly_token_budget";
|
|
31
|
+
export const AI_MEMORY_LEARNING_DEFAULT_MONTHLY_TOKEN_BUDGET = 100_000;
|
|
32
|
+
|
|
33
|
+
export const AiMemoryLearningChangeSchema = z.object({
|
|
34
|
+
action: z.enum(["add", "replace", "merge", "retire"]),
|
|
35
|
+
kind: z.enum(["fact", "preference", "workflow"]),
|
|
36
|
+
content: z.string().max(500),
|
|
37
|
+
memoryIds: z.array(z.string().min(1).max(12)).max(5),
|
|
38
|
+
resourceRef: CloudResourceRefSchema.nullable(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const AiLearnedMemoriesSchema = z.object({ changes: z.array(AiMemoryLearningChangeSchema).max(5) });
|
|
42
|
+
export type AiLearnedMemories = z.infer<typeof AiLearnedMemoriesSchema>;
|
|
43
|
+
|
|
44
|
+
const AiWorkflowPatternSchema = z.object({
|
|
45
|
+
workflow: z
|
|
46
|
+
.object({
|
|
47
|
+
content: z.string().min(1).max(500),
|
|
48
|
+
memoryIds: z.array(z.string().min(1).max(12)).max(5),
|
|
49
|
+
})
|
|
50
|
+
.nullable(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
type Candidate = {
|
|
54
|
+
conversationId: string;
|
|
55
|
+
turnId: string;
|
|
56
|
+
userId: string;
|
|
57
|
+
completedAsOf: string;
|
|
58
|
+
failCount: number;
|
|
59
|
+
locale?: string | null;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type TurnEvidence = {
|
|
63
|
+
candidate: Candidate;
|
|
64
|
+
messages: AiStoredMessage[];
|
|
65
|
+
userText: string;
|
|
66
|
+
finalAssistantText: string;
|
|
67
|
+
userMessageId?: string;
|
|
68
|
+
actions: AiMemoryWorkflowEvidence[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type MemoryLearningDeps = {
|
|
72
|
+
structured?: <TOutput extends z.ZodType>(input: RunAiStructuredInput<TOutput>) => Promise<RunAiStructuredResult<TOutput>>;
|
|
73
|
+
resolveModel?: () => Promise<AiResolvedModel>;
|
|
74
|
+
listCandidates?: (limit: number, monthlyTokenBudget: number) => Promise<Candidate[]>;
|
|
75
|
+
readAdditionalInstructions?: () => Promise<string>;
|
|
76
|
+
listWorkflowPatterns?: (limit: number) => Promise<AiMemoryWorkflowPattern[]>;
|
|
77
|
+
monthlyTokenBudget?: number;
|
|
78
|
+
readMonthlyAccountedTokens?: (userId: string) => Promise<number>;
|
|
79
|
+
readDefaultLocale?: () => Promise<string>;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type AiMemoryLearningRunSummary = {
|
|
83
|
+
scanned: number;
|
|
84
|
+
learned: number;
|
|
85
|
+
updated: number;
|
|
86
|
+
retired: number;
|
|
87
|
+
skipped: number;
|
|
88
|
+
failed: number;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const log = logger("ai:memory-learning");
|
|
92
|
+
|
|
93
|
+
export const MEMORY_LEARNING_PROMPT = [
|
|
94
|
+
"Maintain a small, useful personal profile from one newly completed private chat turn.",
|
|
95
|
+
"The normal result is no changes. Keep only durable information likely to help in multiple future chats.",
|
|
96
|
+
"Facts and preferences require an explicit statement in NEW USER TEXT. Do not infer them from Assistant text or actions.",
|
|
97
|
+
"A workflow links a recurring request type to one Cloud resource. Add one from this turn only when the user explicitly establishes a lasting routing rule and the matching resource appears in SUCCESSFUL ACTIONS.",
|
|
98
|
+
"FINAL ASSISTANT MARKDOWN may clarify what happened, but it is model-written context and never evidence by itself.",
|
|
99
|
+
"Exclude secrets, credentials, sensitive inferences, temporary plans, task details, quoted or attached content, and facts already owned by Contacts, Spaces, Notebooks, or another Cloud resource.",
|
|
100
|
+
"Use replace for one clearly corrected mutable memory, merge for two or more overlapping mutable memories, and retire only when the user explicitly makes mutable background information obsolete.",
|
|
101
|
+
"Protected memories are context only. Never replace, merge, or retire them.",
|
|
102
|
+
"Examples: 'Always answer briefly in German' adds a preference. 'Use Accounting for invoice mail' plus a successful mail.mailbox ref adds a workflow. 'Find this invoice in Accounting' does not establish a lasting workflow.",
|
|
103
|
+
].join("\n");
|
|
104
|
+
|
|
105
|
+
export const MEMORY_LEARNING_OUTPUT_CONTRACT = [
|
|
106
|
+
"Return exactly {changes:[...]} with at most five changes.",
|
|
107
|
+
"add uses no memoryIds; replace uses one; merge uses two or more; retire uses one or more and empty content.",
|
|
108
|
+
"fact and preference use resourceRef null. workflow uses exactly one resourceRef from SUCCESSFUL ACTIONS.",
|
|
109
|
+
"Use only mutable memory ids shown in the input. Return an empty changes array when uncertain.",
|
|
110
|
+
].join("\n");
|
|
111
|
+
|
|
112
|
+
export const WORKFLOW_PATTERN_PROMPT = [
|
|
113
|
+
"Decide whether three successful uses of the same Cloud capability and resource reveal one reusable workflow default.",
|
|
114
|
+
"Return a workflow only when the user requests share a clear recurring category and defaulting to this resource would avoid a future search or clarification.",
|
|
115
|
+
"Do not generalize from unrelated requests, resource contents, or the Assistant wording. Uncertainty returns null.",
|
|
116
|
+
"If an existing mutable workflow is corrected, return its id. Return multiple ids only when one new workflow genuinely consolidates them. Protected memories cannot be changed.",
|
|
117
|
+
].join("\n");
|
|
118
|
+
|
|
119
|
+
export const WORKFLOW_PATTERN_OUTPUT_CONTRACT =
|
|
120
|
+
"Return exactly {workflow:null} or {workflow:{content,memoryIds}}. Content is one short reusable rule; memoryIds contains only shown mutable ids.";
|
|
121
|
+
|
|
122
|
+
const boundedText = (value: string, maxChars: number): string => {
|
|
123
|
+
const text = value.trim();
|
|
124
|
+
if (text.length <= maxChars) return text;
|
|
125
|
+
const half = Math.floor((maxChars - 30) / 2);
|
|
126
|
+
return `${text.slice(0, half).trimEnd()}\n[...truncated...]\n${text.slice(-half).trimStart()}`;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const listAiMemoryLearningCandidates = async (limit: number, monthlyTokenBudget: number): Promise<Candidate[]> => {
|
|
130
|
+
return sql<Candidate[]>`
|
|
131
|
+
WITH monthly_usage AS (
|
|
132
|
+
SELECT run.user_id, COALESCE(sum(run.accounted_tokens), 0)::bigint AS used_tokens
|
|
133
|
+
FROM ai.memory_learning_runs run
|
|
134
|
+
WHERE run.created_at >= date_trunc('month', now())
|
|
135
|
+
GROUP BY run.user_id
|
|
136
|
+
), eligible AS (
|
|
137
|
+
SELECT
|
|
138
|
+
conversation.id AS conversation_id,
|
|
139
|
+
turn.id AS turn_id,
|
|
140
|
+
conversation.created_by_user_id AS user_id,
|
|
141
|
+
turn.completed_at,
|
|
142
|
+
turn.memory_learn_fail_count,
|
|
143
|
+
turn.run_config->>'locale' AS locale,
|
|
144
|
+
row_number() OVER (
|
|
145
|
+
PARTITION BY conversation.created_by_user_id
|
|
146
|
+
ORDER BY turn.completed_at ASC, turn.id ASC
|
|
147
|
+
) AS user_rank
|
|
148
|
+
FROM ai.turns turn
|
|
149
|
+
JOIN ai.conversations conversation ON conversation.id = turn.conversation_id
|
|
150
|
+
JOIN ai.user_prefs prefs ON prefs.user_id = conversation.created_by_user_id
|
|
151
|
+
LEFT JOIN monthly_usage usage ON usage.user_id = conversation.created_by_user_id
|
|
152
|
+
WHERE conversation.created_by_user_id IS NOT NULL
|
|
153
|
+
AND conversation.archived_at IS NULL
|
|
154
|
+
AND prefs.memory_learning_enabled = TRUE
|
|
155
|
+
AND turn.status = 'completed'
|
|
156
|
+
AND COALESCE(turn.run_config->>'kind', 'chat') = 'chat'
|
|
157
|
+
AND turn.memory_learned_at IS NULL
|
|
158
|
+
AND COALESCE(usage.used_tokens, 0) < ${monthlyTokenBudget}
|
|
159
|
+
AND (
|
|
160
|
+
turn.memory_learn_failed_at IS NULL
|
|
161
|
+
OR turn.memory_learn_failed_at + (interval '5 minutes' * pow(2, LEAST(turn.memory_learn_fail_count, 7))) < now()
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
SELECT
|
|
165
|
+
conversation_id AS "conversationId",
|
|
166
|
+
turn_id AS "turnId",
|
|
167
|
+
user_id AS "userId",
|
|
168
|
+
completed_at::text AS "completedAsOf",
|
|
169
|
+
memory_learn_fail_count AS "failCount",
|
|
170
|
+
locale
|
|
171
|
+
FROM eligible
|
|
172
|
+
WHERE user_rank <= 5
|
|
173
|
+
ORDER BY completed_at ASC, turn_id ASC
|
|
174
|
+
LIMIT ${Math.min(Math.max(limit, 1), 100)}
|
|
175
|
+
`;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const markLearned = async (candidate: Candidate): Promise<void> => {
|
|
179
|
+
await sql`
|
|
180
|
+
UPDATE ai.turns
|
|
181
|
+
SET memory_learned_at = ${candidate.completedAsOf}::timestamptz,
|
|
182
|
+
memory_learn_failed_at = NULL,
|
|
183
|
+
memory_learn_fail_count = 0
|
|
184
|
+
WHERE id = ${candidate.turnId}::uuid
|
|
185
|
+
AND conversation_id = ${candidate.conversationId}::uuid
|
|
186
|
+
`;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const markFailed = async (candidate: Candidate): Promise<void> => {
|
|
190
|
+
await sql`
|
|
191
|
+
UPDATE ai.turns
|
|
192
|
+
SET memory_learn_failed_at = now(), memory_learn_fail_count = memory_learn_fail_count + 1
|
|
193
|
+
WHERE id = ${candidate.turnId}::uuid
|
|
194
|
+
AND conversation_id = ${candidate.conversationId}::uuid
|
|
195
|
+
`;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const learningEnabled = async (userId: string): Promise<boolean> => {
|
|
199
|
+
const [row] = await sql<{ enabled: boolean }[]>`
|
|
200
|
+
SELECT COALESCE(memory_learning_enabled, FALSE) AS enabled
|
|
201
|
+
FROM ai.user_prefs
|
|
202
|
+
WHERE user_id = ${userId}::uuid
|
|
203
|
+
`;
|
|
204
|
+
return Boolean(row?.enabled);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const monthlyAccountedTokens = async (userId: string): Promise<number> => {
|
|
208
|
+
const [row] = await sql<{ tokens: number }[]>`
|
|
209
|
+
SELECT COALESCE(sum(accounted_tokens), 0)::int AS tokens
|
|
210
|
+
FROM ai.memory_learning_runs
|
|
211
|
+
WHERE user_id = ${userId}::uuid AND created_at >= date_trunc('month', now())
|
|
212
|
+
`;
|
|
213
|
+
return row?.tokens ?? 0;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const buildMemoryLearningTranscript = (messages: AiStoredMessage[]): string =>
|
|
217
|
+
messages
|
|
218
|
+
.flatMap((stored) => {
|
|
219
|
+
const message = stored.message;
|
|
220
|
+
if (
|
|
221
|
+
stored.kind !== "message" ||
|
|
222
|
+
message.role !== "user" ||
|
|
223
|
+
stored.meta?.agentMessage ||
|
|
224
|
+
stored.meta?.scheduledTask
|
|
225
|
+
)
|
|
226
|
+
return [];
|
|
227
|
+
return message.content.flatMap((part) => {
|
|
228
|
+
if (typeof part === "string") return part.trim() ? [part.trim()] : [];
|
|
229
|
+
if (part.type !== "text") return [];
|
|
230
|
+
const text = parseAiAttachmentMarkers(part.text).text.trim();
|
|
231
|
+
if (!text || text.startsWith("[Attached Cloud resource ")) return [];
|
|
232
|
+
return [text];
|
|
233
|
+
});
|
|
234
|
+
})
|
|
235
|
+
.join("\n\n");
|
|
236
|
+
|
|
237
|
+
export const buildFinalAssistantMarkdown = (messages: AiStoredMessage[]): string => {
|
|
238
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
239
|
+
const message = messages[index]?.message;
|
|
240
|
+
if (message?.role !== "assistant") continue;
|
|
241
|
+
const text = message.content.flatMap((part) => (part.type === "text" && part.text.trim() ? [part.text.trim()] : [])).join("\n\n");
|
|
242
|
+
if (text) return text;
|
|
243
|
+
}
|
|
244
|
+
return "";
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const loadTurnEvidence = async (candidate: Candidate): Promise<TurnEvidence> => {
|
|
248
|
+
const [messages, actions] = await Promise.all([
|
|
249
|
+
aiConversations.listTurnMessages({ conversationId: candidate.conversationId, loopId: candidate.turnId, includeCompacted: true }),
|
|
250
|
+
listAiTurnWorkflowEvidence(candidate.userId, candidate.turnId),
|
|
251
|
+
]);
|
|
252
|
+
const firstUser = messages.find(
|
|
253
|
+
(message) => message.message.role === "user" && !message.meta?.agentMessage && !message.meta?.scheduledTask,
|
|
254
|
+
);
|
|
255
|
+
return {
|
|
256
|
+
candidate,
|
|
257
|
+
messages,
|
|
258
|
+
userText: boundedText(buildMemoryLearningTranscript(messages), USER_TEXT_MAX_CHARS),
|
|
259
|
+
finalAssistantText: boundedText(buildFinalAssistantMarkdown(messages), FINAL_ASSISTANT_MAX_CHARS),
|
|
260
|
+
userMessageId: firstUser?.id,
|
|
261
|
+
actions,
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const memoryLines = (memories: Awaited<ReturnType<typeof aiMemories.list>>): string =>
|
|
266
|
+
memories.length > 0
|
|
267
|
+
? memories
|
|
268
|
+
.map((memory) => {
|
|
269
|
+
const mutability = memory.source === "background" && memory.priority === "normal" ? "mutable" : "protected";
|
|
270
|
+
const resource = memory.resourceRef ? ` | ${memory.resourceRef.type}:${memory.resourceRef.id}` : "";
|
|
271
|
+
return `- ${memory.shortId} | ${mutability} | ${memory.kind} | ${memory.content}${resource}`;
|
|
272
|
+
})
|
|
273
|
+
.join("\n")
|
|
274
|
+
: "(none)";
|
|
275
|
+
|
|
276
|
+
const actionLines = (actions: AiMemoryWorkflowEvidence[]): string =>
|
|
277
|
+
actions.length > 0
|
|
278
|
+
? actions
|
|
279
|
+
.map(
|
|
280
|
+
(action) =>
|
|
281
|
+
`- ${action.capabilityId} | ${action.resourceRef.type}:${action.resourceRef.id}${
|
|
282
|
+
action.resourceTitle ? ` | ${action.resourceTitle}` : ""
|
|
283
|
+
}`,
|
|
284
|
+
)
|
|
285
|
+
.join("\n")
|
|
286
|
+
: "(none)";
|
|
287
|
+
|
|
288
|
+
const learningInput = (evidence: TurnEvidence, memories: Awaited<ReturnType<typeof aiMemories.list>>): string =>
|
|
289
|
+
[
|
|
290
|
+
"NEW USER TEXT:",
|
|
291
|
+
evidence.userText || "(none)",
|
|
292
|
+
"",
|
|
293
|
+
"SUCCESSFUL ACTIONS:",
|
|
294
|
+
actionLines(evidence.actions),
|
|
295
|
+
"",
|
|
296
|
+
"FINAL ASSISTANT MARKDOWN (context only):",
|
|
297
|
+
evidence.finalAssistantText || "(none)",
|
|
298
|
+
"",
|
|
299
|
+
"ACTIVE MEMORIES:",
|
|
300
|
+
memoryLines(memories),
|
|
301
|
+
].join("\n");
|
|
302
|
+
|
|
303
|
+
const estimatedTokens = (systemPrompt: string, taskInput: string): number =>
|
|
304
|
+
new TextEncoder().encode(systemPrompt + taskInput).byteLength + MAX_OUTPUT_TOKENS;
|
|
305
|
+
|
|
306
|
+
const workflowEvidenceKey = (pattern: AiMemoryWorkflowPattern): string =>
|
|
307
|
+
createHash("sha256")
|
|
308
|
+
.update(JSON.stringify([pattern.userId, pattern.capabilityId, pattern.resourceRef.type, pattern.resourceRef.id, ...[...pattern.turnIds].sort()]))
|
|
309
|
+
.digest("hex");
|
|
310
|
+
|
|
311
|
+
const toProposal = (change: z.infer<typeof AiMemoryLearningChangeSchema>): AiBackgroundMemoryProposal => ({
|
|
312
|
+
action: change.action,
|
|
313
|
+
kind: change.kind,
|
|
314
|
+
content: change.content,
|
|
315
|
+
memoryIds: change.memoryIds,
|
|
316
|
+
resourceRef: change.resourceRef,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const applyChanges = async (
|
|
320
|
+
evidence: TurnEvidence,
|
|
321
|
+
changes: z.infer<typeof AiMemoryLearningChangeSchema>[],
|
|
322
|
+
mutableMemoryIds: ReadonlySet<string>,
|
|
323
|
+
): Promise<AiMemoryLearningChange[]> => {
|
|
324
|
+
const applied: AiMemoryLearningChange[] = [];
|
|
325
|
+
for (const change of changes) {
|
|
326
|
+
if (change.memoryIds.some((memoryId) => !mutableMemoryIds.has(memoryId))) continue;
|
|
327
|
+
if (
|
|
328
|
+
change.resourceRef &&
|
|
329
|
+
!evidence.actions.some(
|
|
330
|
+
(action) => action.resourceRef.type === change.resourceRef?.type && action.resourceRef.id === change.resourceRef.id,
|
|
331
|
+
)
|
|
332
|
+
) continue;
|
|
333
|
+
const results = await aiMemories.applyBackgroundProposal({
|
|
334
|
+
userId: evidence.candidate.userId,
|
|
335
|
+
sourceConversationId: evidence.candidate.conversationId,
|
|
336
|
+
sourceMessageId: evidence.userMessageId,
|
|
337
|
+
proposal: toProposal(change),
|
|
338
|
+
});
|
|
339
|
+
applied.push(...results);
|
|
340
|
+
}
|
|
341
|
+
return applied;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const workflowPatternInput = async (
|
|
345
|
+
pattern: AiMemoryWorkflowPattern,
|
|
346
|
+
): Promise<{ input: string; source: TurnEvidence | null; mutableMemoryIds: Set<string> }> => {
|
|
347
|
+
const sources = await Promise.all(
|
|
348
|
+
pattern.turnIds.map(async (turnId) => {
|
|
349
|
+
const [row] = await sql<{ conversation_id: string; completed_as_of: string; fail_count: number }[]>`
|
|
350
|
+
SELECT turn.conversation_id, turn.completed_at::text AS completed_as_of, turn.memory_learn_fail_count AS fail_count
|
|
351
|
+
FROM ai.turns turn
|
|
352
|
+
JOIN ai.conversations conversation ON conversation.id = turn.conversation_id
|
|
353
|
+
WHERE turn.id = ${turnId}::uuid AND conversation.created_by_user_id = ${pattern.userId}::uuid
|
|
354
|
+
`;
|
|
355
|
+
if (!row) return null;
|
|
356
|
+
return loadTurnEvidence({
|
|
357
|
+
conversationId: row.conversation_id,
|
|
358
|
+
turnId,
|
|
359
|
+
userId: pattern.userId,
|
|
360
|
+
completedAsOf: row.completed_as_of,
|
|
361
|
+
failCount: row.fail_count,
|
|
362
|
+
});
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
const evidence = sources.filter((source): source is TurnEvidence => Boolean(source));
|
|
366
|
+
const query = evidence.map((source) => source.userText).join(" ");
|
|
367
|
+
const selected = await aiMemories.selectHot(pattern.userId, query);
|
|
368
|
+
const examples = evidence
|
|
369
|
+
.map(
|
|
370
|
+
(source, index) =>
|
|
371
|
+
`EXAMPLE ${index + 1}\nUser: ${boundedText(source.userText, WORKFLOW_EXAMPLE_MAX_CHARS)}\nFinal Assistant context: ${
|
|
372
|
+
boundedText(source.finalAssistantText, 500) || "(none)"
|
|
373
|
+
}`,
|
|
374
|
+
)
|
|
375
|
+
.join("\n\n");
|
|
376
|
+
return {
|
|
377
|
+
source: evidence[0] ?? null,
|
|
378
|
+
mutableMemoryIds: new Set(
|
|
379
|
+
selected.memories
|
|
380
|
+
.filter((memory) => memory.source === "background" && memory.priority === "normal")
|
|
381
|
+
.map((memory) => memory.shortId),
|
|
382
|
+
),
|
|
383
|
+
input: [
|
|
384
|
+
`CAPABILITY: ${pattern.capabilityId}`,
|
|
385
|
+
`RESOURCE: ${pattern.resourceRef.type}:${pattern.resourceRef.id}${pattern.resourceTitle ? ` | ${pattern.resourceTitle}` : ""}`,
|
|
386
|
+
"",
|
|
387
|
+
examples,
|
|
388
|
+
"",
|
|
389
|
+
"ACTIVE MEMORIES:",
|
|
390
|
+
memoryLines(selected.memories),
|
|
391
|
+
].join("\n"),
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const usageNumbers = (usage: RunAiStructuredResult<z.ZodType>["usage"], reserved: number) => {
|
|
396
|
+
const inputTokens = usage?.input;
|
|
397
|
+
const outputTokens = usage?.output;
|
|
398
|
+
const total = usage?.total ?? (inputTokens !== undefined || outputTokens !== undefined ? (inputTokens ?? 0) + (outputTokens ?? 0) : undefined);
|
|
399
|
+
return { inputTokens, outputTokens, accountedTokens: total ?? reserved };
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const learnAiMemoriesFromPrivateChats = async (
|
|
403
|
+
input: { limit?: number; signal?: AbortSignal; heartbeat?: () => Promise<void>; deps?: MemoryLearningDeps } = {},
|
|
404
|
+
): Promise<AiMemoryLearningRunSummary> => {
|
|
405
|
+
const summary: AiMemoryLearningRunSummary = { scanned: 0, learned: 0, updated: 0, retired: 0, skipped: 0, failed: 0 };
|
|
406
|
+
let resolved: AiResolvedModel;
|
|
407
|
+
try {
|
|
408
|
+
resolved = await (input.deps?.resolveModel ?? resolveAiBackgroundModel)();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
log.info("Memory learning skipped: no background model available", {
|
|
411
|
+
error: error instanceof Error ? error.message : String(error),
|
|
412
|
+
});
|
|
413
|
+
return summary;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const configuredBudget =
|
|
417
|
+
input.deps?.monthlyTokenBudget ??
|
|
418
|
+
Number(await coreSettings.get<number>(AI_MEMORY_LEARNING_MONTHLY_TOKEN_BUDGET_SETTING_KEY));
|
|
419
|
+
const monthlyTokenBudget =
|
|
420
|
+
Number.isFinite(configuredBudget) && configuredBudget > 0
|
|
421
|
+
? Math.floor(configuredBudget)
|
|
422
|
+
: AI_MEMORY_LEARNING_DEFAULT_MONTHLY_TOKEN_BUDGET;
|
|
423
|
+
const batchLimit = Math.min(Math.max(input.limit ?? DEFAULT_BATCH_LIMIT, 1), 100);
|
|
424
|
+
const candidateReadLimit = Math.min(batchLimit * 5, 100);
|
|
425
|
+
const candidates = await (input.deps?.listCandidates ?? listAiMemoryLearningCandidates)(candidateReadLimit, monthlyTokenBudget);
|
|
426
|
+
const readMonthlyUsage = input.deps?.readMonthlyAccountedTokens ?? monthlyAccountedTokens;
|
|
427
|
+
const structured = input.deps?.structured ?? runAiStructured;
|
|
428
|
+
const additionalInstructions = await (
|
|
429
|
+
input.deps?.readAdditionalInstructions ??
|
|
430
|
+
(() => coreSettings.get<string>(AI_MEMORY_LEARNING_INSTRUCTIONS_SETTING_KEY).then((value) => value ?? ""))
|
|
431
|
+
)();
|
|
432
|
+
let defaultLocale: Promise<string> | undefined;
|
|
433
|
+
const resolveLearningLocale = (locale?: string | null): Promise<string> => {
|
|
434
|
+
if (locale) return Promise.resolve(normalizeLocale(locale));
|
|
435
|
+
defaultLocale ??= (input.deps?.readDefaultLocale ?? (() => coreSettings.get<string>("app.locale")))().then(
|
|
436
|
+
normalizeLocale,
|
|
437
|
+
);
|
|
438
|
+
return defaultLocale;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
let processedCandidates = 0;
|
|
442
|
+
for (const candidate of candidates) {
|
|
443
|
+
if (processedCandidates >= batchLimit) break;
|
|
444
|
+
if (input.signal?.aborted) break;
|
|
445
|
+
summary.scanned += 1;
|
|
446
|
+
const startedAt = Date.now();
|
|
447
|
+
let runId: string | null = null;
|
|
448
|
+
let changes: AiMemoryLearningChange[] = [];
|
|
449
|
+
try {
|
|
450
|
+
if (!(await learningEnabled(candidate.userId))) continue;
|
|
451
|
+
const evidence = await loadTurnEvidence(candidate);
|
|
452
|
+
if (!evidence.userText) {
|
|
453
|
+
await markLearned(candidate);
|
|
454
|
+
summary.skipped += 1;
|
|
455
|
+
processedCandidates += 1;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const selected = await aiMemories.selectHot(candidate.userId, evidence.userText);
|
|
459
|
+
const taskInput = learningInput(evidence, selected.memories);
|
|
460
|
+
const locale = await resolveLearningLocale(candidate.locale);
|
|
461
|
+
const systemPrompt = buildAiTaskPrompt({
|
|
462
|
+
baseInstructions: `${MEMORY_LEARNING_PROMPT}\nWrite new or changed memory content in the language identified by locale ${locale}.`,
|
|
463
|
+
additionalInstructions,
|
|
464
|
+
outputContract: MEMORY_LEARNING_OUTPUT_CONTRACT,
|
|
465
|
+
});
|
|
466
|
+
const reservedTokens = estimatedTokens(systemPrompt, taskInput);
|
|
467
|
+
if ((await readMonthlyUsage(candidate.userId)) + reservedTokens > monthlyTokenBudget) continue;
|
|
468
|
+
runId = await aiMemoryLearningRuns.start({
|
|
469
|
+
userId: candidate.userId,
|
|
470
|
+
conversationId: candidate.conversationId,
|
|
471
|
+
turnId: candidate.turnId,
|
|
472
|
+
runKind: "turn",
|
|
473
|
+
modelProfileId: resolved.profile.id,
|
|
474
|
+
accountedTokens: reservedTokens,
|
|
475
|
+
});
|
|
476
|
+
if (!runId) continue;
|
|
477
|
+
const result = await structured({
|
|
478
|
+
task: "memory-learn-turn",
|
|
479
|
+
attribution: { userId: candidate.userId, conversationId: candidate.conversationId, turnId: candidate.turnId },
|
|
480
|
+
appId: "ai",
|
|
481
|
+
systemPrompt,
|
|
482
|
+
input: taskInput,
|
|
483
|
+
output: AiLearnedMemoriesSchema,
|
|
484
|
+
outputName: "personalization_changes",
|
|
485
|
+
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
486
|
+
signal: input.signal,
|
|
487
|
+
resolveModel: async () => resolved,
|
|
488
|
+
});
|
|
489
|
+
if (!(await learningEnabled(candidate.userId))) {
|
|
490
|
+
await markLearned(candidate);
|
|
491
|
+
} else {
|
|
492
|
+
changes = await applyChanges(
|
|
493
|
+
evidence,
|
|
494
|
+
result.output.changes,
|
|
495
|
+
new Set(
|
|
496
|
+
selected.memories
|
|
497
|
+
.filter((memory) => memory.source === "background" && memory.priority === "normal")
|
|
498
|
+
.map((memory) => memory.shortId),
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
await markLearned(candidate);
|
|
502
|
+
}
|
|
503
|
+
const status = changes.length > 0 ? "ok" : "skipped";
|
|
504
|
+
await aiMemoryLearningRuns.finish({
|
|
505
|
+
runId,
|
|
506
|
+
status,
|
|
507
|
+
durationMs: Date.now() - startedAt,
|
|
508
|
+
changes,
|
|
509
|
+
...usageNumbers(result.usage, reservedTokens),
|
|
510
|
+
});
|
|
511
|
+
summary.learned += changes.filter((change) => change.action === "added").length;
|
|
512
|
+
summary.updated += changes.filter((change) => change.action === "updated" || change.action === "merged").length;
|
|
513
|
+
summary.retired += changes.filter((change) => change.action === "retired").length;
|
|
514
|
+
if (status === "skipped") summary.skipped += 1;
|
|
515
|
+
processedCandidates += 1;
|
|
516
|
+
} catch (error) {
|
|
517
|
+
summary.failed += 1;
|
|
518
|
+
processedCandidates += 1;
|
|
519
|
+
await markFailed(candidate).catch(() => undefined);
|
|
520
|
+
if (runId) {
|
|
521
|
+
await aiMemoryLearningRuns
|
|
522
|
+
.fail({ runId, durationMs: Date.now() - startedAt, error: error instanceof Error ? error.message : String(error), changes })
|
|
523
|
+
.catch(() => undefined);
|
|
524
|
+
}
|
|
525
|
+
log.warn("Memory turn learning failed", {
|
|
526
|
+
conversationId: candidate.conversationId,
|
|
527
|
+
turnId: candidate.turnId,
|
|
528
|
+
failCount: candidate.failCount + 1,
|
|
529
|
+
error: error instanceof Error ? error.message : String(error),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
await input.heartbeat?.();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const patterns = await (input.deps?.listWorkflowPatterns ?? listAiPendingWorkflowPatterns)(WORKFLOW_PATTERN_LIMIT);
|
|
536
|
+
for (const pattern of patterns) {
|
|
537
|
+
if (input.signal?.aborted) break;
|
|
538
|
+
let runId: string | null = null;
|
|
539
|
+
const startedAt = Date.now();
|
|
540
|
+
try {
|
|
541
|
+
if (!(await learningEnabled(pattern.userId))) continue;
|
|
542
|
+
const context = await workflowPatternInput(pattern);
|
|
543
|
+
if (!context.source) {
|
|
544
|
+
await markAiWorkflowPatternReviewed(pattern);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const locale = await resolveLearningLocale(context.source.candidate.locale);
|
|
548
|
+
const workflowPrompt = buildAiTaskPrompt({
|
|
549
|
+
baseInstructions: `${WORKFLOW_PATTERN_PROMPT}\nWrite new or changed workflow content in the language identified by locale ${locale}.`,
|
|
550
|
+
additionalInstructions,
|
|
551
|
+
outputContract: WORKFLOW_PATTERN_OUTPUT_CONTRACT,
|
|
552
|
+
});
|
|
553
|
+
const reservedTokens = estimatedTokens(workflowPrompt, context.input);
|
|
554
|
+
if ((await readMonthlyUsage(pattern.userId)) + reservedTokens > monthlyTokenBudget) continue;
|
|
555
|
+
runId = await aiMemoryLearningRuns.start({
|
|
556
|
+
userId: pattern.userId,
|
|
557
|
+
conversationId: context.source.candidate.conversationId,
|
|
558
|
+
turnId: context.source.candidate.turnId,
|
|
559
|
+
runKind: "workflow",
|
|
560
|
+
evidenceKey: workflowEvidenceKey(pattern),
|
|
561
|
+
modelProfileId: resolved.profile.id,
|
|
562
|
+
accountedTokens: reservedTokens,
|
|
563
|
+
});
|
|
564
|
+
if (!runId) continue;
|
|
565
|
+
const result = await structured({
|
|
566
|
+
task: "memory-learn-workflow",
|
|
567
|
+
attribution: { userId: pattern.userId, conversationId: context.source.candidate.conversationId, turnId: context.source.candidate.turnId },
|
|
568
|
+
appId: "ai",
|
|
569
|
+
systemPrompt: workflowPrompt,
|
|
570
|
+
input: context.input,
|
|
571
|
+
output: AiWorkflowPatternSchema,
|
|
572
|
+
outputName: "workflow_pattern",
|
|
573
|
+
maxOutputTokens: 300,
|
|
574
|
+
signal: input.signal,
|
|
575
|
+
resolveModel: async () => resolved,
|
|
576
|
+
});
|
|
577
|
+
let changes: AiMemoryLearningChange[] = [];
|
|
578
|
+
if (result.output.workflow && (await learningEnabled(pattern.userId))) {
|
|
579
|
+
const memoryIds = result.output.workflow.memoryIds;
|
|
580
|
+
const action = memoryIds.length > 1 ? "merge" : memoryIds.length === 1 ? "replace" : "add";
|
|
581
|
+
changes = await applyChanges(context.source, [
|
|
582
|
+
{
|
|
583
|
+
action,
|
|
584
|
+
kind: "workflow",
|
|
585
|
+
content: result.output.workflow.content,
|
|
586
|
+
memoryIds,
|
|
587
|
+
resourceRef: pattern.resourceRef,
|
|
588
|
+
},
|
|
589
|
+
], context.mutableMemoryIds);
|
|
590
|
+
}
|
|
591
|
+
await markAiWorkflowPatternReviewed(pattern);
|
|
592
|
+
await aiMemoryLearningRuns.finish({
|
|
593
|
+
runId,
|
|
594
|
+
status: changes.length > 0 ? "ok" : "skipped",
|
|
595
|
+
durationMs: Date.now() - startedAt,
|
|
596
|
+
changes,
|
|
597
|
+
...usageNumbers(result.usage, reservedTokens),
|
|
598
|
+
});
|
|
599
|
+
summary.learned += changes.filter((change) => change.action === "added").length;
|
|
600
|
+
summary.updated += changes.filter((change) => change.action === "updated" || change.action === "merged").length;
|
|
601
|
+
if (changes.length === 0) summary.skipped += 1;
|
|
602
|
+
} catch (error) {
|
|
603
|
+
summary.failed += 1;
|
|
604
|
+
if (runId) {
|
|
605
|
+
await aiMemoryLearningRuns
|
|
606
|
+
.fail({ runId, durationMs: Date.now() - startedAt, error: error instanceof Error ? error.message : String(error), changes: [] })
|
|
607
|
+
.catch(() => undefined);
|
|
608
|
+
}
|
|
609
|
+
log.warn("Memory workflow learning failed", {
|
|
610
|
+
capabilityId: pattern.capabilityId,
|
|
611
|
+
resourceType: pattern.resourceRef.type,
|
|
612
|
+
error: error instanceof Error ? error.message : String(error),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
await input.heartbeat?.();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return summary;
|
|
619
|
+
};
|